@jini-ai/agent-runtime 0.2.1 → 0.3.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.
Files changed (163) hide show
  1. package/README.md +2 -2
  2. package/dist/agent-protocol/acp/session.d.ts +206 -1
  3. package/dist/agent-protocol/acp/session.d.ts.map +1 -1
  4. package/dist/agent-protocol/acp/session.js +505 -350
  5. package/dist/agent-protocol/acp/session.js.map +1 -1
  6. package/dist/agent-protocol/core/json-line-stream.d.ts +53 -0
  7. package/dist/agent-protocol/core/json-line-stream.d.ts.map +1 -1
  8. package/dist/agent-protocol/core/json-line-stream.js +307 -255
  9. package/dist/agent-protocol/core/json-line-stream.js.map +1 -1
  10. package/dist/agent-protocol/pi-rpc/events.d.ts +22 -1
  11. package/dist/agent-protocol/pi-rpc/events.d.ts.map +1 -1
  12. package/dist/agent-protocol/pi-rpc/events.js +189 -153
  13. package/dist/agent-protocol/pi-rpc/events.js.map +1 -1
  14. package/dist/agent-protocol/pi-rpc/session.d.ts +39 -0
  15. package/dist/agent-protocol/pi-rpc/session.d.ts.map +1 -1
  16. package/dist/agent-protocol/pi-rpc/session.js +143 -107
  17. package/dist/agent-protocol/pi-rpc/session.js.map +1 -1
  18. package/dist/claude-stream.d.ts +64 -1
  19. package/dist/claude-stream.d.ts.map +1 -1
  20. package/dist/claude-stream.js +568 -441
  21. package/dist/claude-stream.js.map +1 -1
  22. package/dist/defs/aider.d.ts.map +1 -1
  23. package/dist/defs/aider.js +13 -1
  24. package/dist/defs/aider.js.map +1 -1
  25. package/dist/defs/amp.d.ts.map +1 -1
  26. package/dist/defs/amp.js +16 -1
  27. package/dist/defs/amp.js.map +1 -1
  28. package/dist/defs/amr.d.ts +2 -1
  29. package/dist/defs/amr.d.ts.map +1 -1
  30. package/dist/defs/amr.js +12 -1
  31. package/dist/defs/amr.js.map +1 -1
  32. package/dist/defs/antigravity.d.ts +41 -2
  33. package/dist/defs/antigravity.d.ts.map +1 -1
  34. package/dist/defs/antigravity.js +201 -22
  35. package/dist/defs/antigravity.js.map +1 -1
  36. package/dist/defs/claude.d.ts +2 -0
  37. package/dist/defs/claude.d.ts.map +1 -1
  38. package/dist/defs/claude.js +58 -3
  39. package/dist/defs/claude.js.map +1 -1
  40. package/dist/defs/codebuddy.d.ts.map +1 -1
  41. package/dist/defs/codebuddy.js +9 -1
  42. package/dist/defs/codebuddy.js.map +1 -1
  43. package/dist/defs/codex.d.ts +1 -0
  44. package/dist/defs/codex.d.ts.map +1 -1
  45. package/dist/defs/codex.js +34 -0
  46. package/dist/defs/codex.js.map +1 -1
  47. package/dist/defs/copilot.d.ts.map +1 -1
  48. package/dist/defs/copilot.js +15 -1
  49. package/dist/defs/copilot.js.map +1 -1
  50. package/dist/defs/cursor-agent.d.ts.map +1 -1
  51. package/dist/defs/cursor-agent.js +24 -2
  52. package/dist/defs/cursor-agent.js.map +1 -1
  53. package/dist/defs/deepseek.d.ts.map +1 -1
  54. package/dist/defs/deepseek.js +12 -0
  55. package/dist/defs/deepseek.js.map +1 -1
  56. package/dist/defs/devin.d.ts +2 -1
  57. package/dist/defs/devin.d.ts.map +1 -1
  58. package/dist/defs/devin.js +12 -7
  59. package/dist/defs/devin.js.map +1 -1
  60. package/dist/defs/grok-build.d.ts.map +1 -1
  61. package/dist/defs/grok-build.js +10 -0
  62. package/dist/defs/grok-build.js.map +1 -1
  63. package/dist/defs/hermes.d.ts +1 -0
  64. package/dist/defs/hermes.d.ts.map +1 -1
  65. package/dist/defs/hermes.js +3 -0
  66. package/dist/defs/hermes.js.map +1 -1
  67. package/dist/defs/kilo.d.ts +1 -0
  68. package/dist/defs/kilo.d.ts.map +1 -1
  69. package/dist/defs/kilo.js +3 -0
  70. package/dist/defs/kilo.js.map +1 -1
  71. package/dist/defs/kimi.d.ts +1 -0
  72. package/dist/defs/kimi.d.ts.map +1 -1
  73. package/dist/defs/kimi.js +3 -0
  74. package/dist/defs/kimi.js.map +1 -1
  75. package/dist/defs/kiro.d.ts +1 -0
  76. package/dist/defs/kiro.d.ts.map +1 -1
  77. package/dist/defs/kiro.js +3 -0
  78. package/dist/defs/kiro.js.map +1 -1
  79. package/dist/defs/pi.d.ts +1 -1
  80. package/dist/defs/pi.d.ts.map +1 -1
  81. package/dist/defs/pi.js +6 -1
  82. package/dist/defs/pi.js.map +1 -1
  83. package/dist/defs/qoder.d.ts +1 -0
  84. package/dist/defs/qoder.d.ts.map +1 -1
  85. package/dist/defs/qoder.js +15 -0
  86. package/dist/defs/qoder.js.map +1 -1
  87. package/dist/defs/qwen.d.ts.map +1 -1
  88. package/dist/defs/qwen.js +9 -0
  89. package/dist/defs/qwen.js.map +1 -1
  90. package/dist/defs/reasonix.d.ts +1 -0
  91. package/dist/defs/reasonix.d.ts.map +1 -1
  92. package/dist/defs/reasonix.js +3 -0
  93. package/dist/defs/reasonix.js.map +1 -1
  94. package/dist/defs/shared.d.ts +31 -1
  95. package/dist/defs/shared.d.ts.map +1 -1
  96. package/dist/defs/shared.js +35 -0
  97. package/dist/defs/shared.js.map +1 -1
  98. package/dist/defs/trae-cli.d.ts +1 -0
  99. package/dist/defs/trae-cli.d.ts.map +1 -1
  100. package/dist/defs/trae-cli.js +3 -0
  101. package/dist/defs/trae-cli.js.map +1 -1
  102. package/dist/defs/vibe.d.ts +1 -0
  103. package/dist/defs/vibe.d.ts.map +1 -1
  104. package/dist/defs/vibe.js +3 -0
  105. package/dist/defs/vibe.js.map +1 -1
  106. package/dist/detection.d.ts +1 -1
  107. package/dist/detection.d.ts.map +1 -1
  108. package/dist/detection.js +7 -2
  109. package/dist/detection.js.map +1 -1
  110. package/dist/index.d.ts +2 -1
  111. package/dist/index.d.ts.map +1 -1
  112. package/dist/index.js +2 -1
  113. package/dist/index.js.map +1 -1
  114. package/dist/log-file.d.ts +23 -0
  115. package/dist/log-file.d.ts.map +1 -0
  116. package/dist/log-file.js +56 -0
  117. package/dist/log-file.js.map +1 -0
  118. package/dist/model-registry.d.ts +7 -45
  119. package/dist/model-registry.d.ts.map +1 -1
  120. package/dist/model-registry.js.map +1 -1
  121. package/dist/providers/anthropic-messages.d.ts +198 -6
  122. package/dist/providers/anthropic-messages.d.ts.map +1 -1
  123. package/dist/providers/anthropic-messages.js +317 -140
  124. package/dist/providers/anthropic-messages.js.map +1 -1
  125. package/dist/providers/azure-chat.d.ts +113 -3
  126. package/dist/providers/azure-chat.d.ts.map +1 -1
  127. package/dist/providers/azure-chat.js +114 -21
  128. package/dist/providers/azure-chat.js.map +1 -1
  129. package/dist/providers/connection-guard.d.ts +121 -1
  130. package/dist/providers/connection-guard.d.ts.map +1 -1
  131. package/dist/providers/connection-guard.js +205 -3
  132. package/dist/providers/connection-guard.js.map +1 -1
  133. package/dist/providers/connection-test.d.ts +62 -0
  134. package/dist/providers/connection-test.d.ts.map +1 -0
  135. package/dist/providers/connection-test.js +404 -0
  136. package/dist/providers/connection-test.js.map +1 -0
  137. package/dist/providers/elevenlabs.d.ts.map +1 -1
  138. package/dist/providers/elevenlabs.js +3 -2
  139. package/dist/providers/elevenlabs.js.map +1 -1
  140. package/dist/providers/google-messages.d.ts +208 -7
  141. package/dist/providers/google-messages.d.ts.map +1 -1
  142. package/dist/providers/google-messages.js +331 -105
  143. package/dist/providers/google-messages.js.map +1 -1
  144. package/dist/providers/index.d.ts +1 -0
  145. package/dist/providers/index.d.ts.map +1 -1
  146. package/dist/providers/index.js +1 -0
  147. package/dist/providers/index.js.map +1 -1
  148. package/dist/providers/model-catalog.d.ts.map +1 -1
  149. package/dist/providers/model-catalog.js +21 -0
  150. package/dist/providers/model-catalog.js.map +1 -1
  151. package/dist/providers/ollama-chat.d.ts +190 -1
  152. package/dist/providers/ollama-chat.d.ts.map +1 -1
  153. package/dist/providers/ollama-chat.js +319 -110
  154. package/dist/providers/ollama-chat.js.map +1 -1
  155. package/dist/providers/openai-chat.d.ts +222 -14
  156. package/dist/providers/openai-chat.d.ts.map +1 -1
  157. package/dist/providers/openai-chat.js +377 -128
  158. package/dist/providers/openai-chat.js.map +1 -1
  159. package/dist/providers/sse-decode.d.ts +2 -2
  160. package/dist/providers/sse-decode.js +2 -2
  161. package/dist/types.d.ts +169 -87
  162. package/dist/types.d.ts.map +1 -1
  163. package/package.json +6 -4
@@ -1,3 +1,78 @@
1
+ /**
2
+ * @module providers/azure-chat
3
+ *
4
+ * Azure OpenAI Chat Completions wire adapter + tool-loop turn-runner.
5
+ * Sibling of `anthropic-messages.ts`/`openai-chat.ts` — see those modules'
6
+ * headers for the shared design rationale. Azure OpenAI's chat-completions
7
+ * JSON request/response body is byte-identical to plain OpenAI's (same
8
+ * `messages`/`tools`/streaming-delta shape) — only the URL and the auth
9
+ * header differ — so this module reuses `openai-chat.ts`'s extracted
10
+ * `runOpenAiCompatibleRequest` SSE-reduction loop rather than duplicating
11
+ * it; see that function's doc for why it was pulled out.
12
+ *
13
+ * **URL**: `{baseUrl}/openai/deployments/{model}/chat/completions
14
+ * ?api-version={apiVersion}` — `model` here is the Azure *deployment name*,
15
+ * not a model id (Azure OpenAI resources map a deployment name to a
16
+ * specific model version at resource-creation time). `apiVersion` defaults
17
+ * to `'2024-10-21'`, the default this repo's real OD predecessor used for
18
+ * its own azure chat-completions proxy route (`apps/daemon/src/routes/
19
+ * chat.ts`'s `[proxy:azure]` handler, confirmed by reading that file
20
+ * directly in a sibling checkout — see `source-map.md`'s dated entry).
21
+ * That source additionally branches on whether `baseUrl` already contains
22
+ * a versioned `/openai/v1` path (a newer Azure OpenAI API preview); that
23
+ * branch is deliberately not carried forward here — this adapter targets
24
+ * the one documented, stable URL/body shape per this task's approved scope
25
+ * ("byte-identical to OpenAI, only URL/auth differ"). Its 400-retry
26
+ * behavior IS carried forward (see "Token-limit fix" below) — round-4
27
+ * external audit (`AUD-R4-001`) found the first port had dropped it,
28
+ * which fails every request against a GPT-5/o-series Azure deployment.
29
+ *
30
+ * **Auth**: `api-key: {apiKey}` header — NOT `Authorization: Bearer`,
31
+ * Azure OpenAI's own convention (also confirmed against the same OD
32
+ * source above).
33
+ *
34
+ * **No default `baseUrl`**: every Azure OpenAI resource has its own
35
+ * endpoint (`https://{resource}.openai.azure.com`), so unlike Anthropic/
36
+ * OpenAI/Google/Ollama, `baseUrl` is a required field on
37
+ * {@link AzureTurnOptions}, and `apiVersion` is validated as
38
+ * non-empty-if-supplied by the caller (`@jini-ai/http-kit`'s
39
+ * `parseAzureProxyRequest` — see that module).
40
+ *
41
+ * **Token-limit fix**: an earlier version of this module sent no token-limit
42
+ * field at all — a live comparison against a running Open Design daemon
43
+ * found OD's real `azure` proxy handler always sends `max_tokens` (defaulting
44
+ * to 8192), unconditionally the legacy field name — never the newer
45
+ * `max_completion_tokens` `openai-chat.ts` picks for GPT-5/o-series models,
46
+ * since Azure deployment names are caller-defined strings, not necessarily
47
+ * matching OpenAI's own model-naming scheme. Wired via
48
+ * `./token-params.js#buildLegacyMaxTokensParam`. Because deployment names
49
+ * are opaque, a legacy-field request can still be rejected by a
50
+ * GPT-5/o-series deployment with a 400 — OD's real handler retries exactly
51
+ * once with `max_completion_tokens` on that specific error
52
+ * (`isUnsupportedMaxTokensError`); this module does the same via
53
+ * `runOpenAiCompatibleRequest`'s `retryableBody` hook.
54
+ *
55
+ * **Image support inherits from plain OpenAI, confirmed by doc AND by code.** Microsoft's own
56
+ * vision how-to (`learn.microsoft.com/azure/ai-foundry/openai/how-to/gpt-with-vision`) states
57
+ * verbatim: "The format is the same as the chat completions API for GPT-4o, except that the
58
+ * message content can be an array containing text and images" — identical `type:'text'`/
59
+ * `type:'image_url'` shape and `detail` values as `openai-chat.ts`. That inheritance is genuine at
60
+ * the code level too, not just assumed from the docs matching: `azureRequestBody` below spreads
61
+ * `messages` straight into the JSON body with no per-field transform, so once `AzureMessageParam`
62
+ * (this module's own copy of `OpenAiMessageParam`, per this file's existing full-duplication
63
+ * convention — see every other type in this file) is widened the same way as OpenAI's, an image
64
+ * survives untouched all the way to the wire. `azure-chat.test.ts`'s new image tests assert the
65
+ * request body directly to prove this, rather than trusting the doc claim alone.
66
+ *
67
+ * **The synthetic follow-up message and its ordering/attribution rules are also inherited
68
+ * unchanged from `openai-chat.ts`** — same workaround for the same text-only `tool` message
69
+ * constraint, same requirement that every `tool` message in a batch be emitted before the single
70
+ * follow-up (never interleaved — a real 400 otherwise), same per-image attribution label naming
71
+ * the tool call it answers. See that module's doc for the full rationale; it is not repeated here
72
+ * beyond this pointer to avoid the two copies drifting.
73
+ */
74
+ import { type DnsLookupFn, type PinnedFetch } from './connection-guard.js';
75
+ import { type OpenAiCompatibleRequestOutcome } from './openai-chat.js';
1
76
  import { type TurnEndReason } from './turn-end-guard.js';
2
77
  export interface AzureFunctionToolDef {
3
78
  readonly type: 'function';
@@ -15,9 +90,23 @@ export interface AzureToolCallParam {
15
90
  readonly arguments: string;
16
91
  };
17
92
  }
93
+ export interface AzureTextPart {
94
+ readonly type: 'text';
95
+ readonly text: string;
96
+ }
97
+ /** `detail` defaults to `'auto'` server-side when omitted — never sent unless the caller supplies it. Same shape as `openai-chat.ts#OpenAiImageUrlPart`; see module doc's "Image support inherits from plain OpenAI" note. */
98
+ export interface AzureImageUrlPart {
99
+ readonly type: 'image_url';
100
+ readonly image_url: {
101
+ readonly url: string;
102
+ readonly detail?: 'auto' | 'low' | 'high';
103
+ };
104
+ }
105
+ /** A user/system/assistant message's `content` array item. Not legal inside a `role: 'tool'` message — same constraint as OpenAI's (see `openai-chat.ts` module doc's "Tool messages cannot carry an image" section, which applies here identically). */
106
+ export type AzureContentPart = AzureTextPart | AzureImageUrlPart;
18
107
  export interface AzureMessageParam {
19
108
  readonly role: 'system' | 'user' | 'assistant' | 'tool';
20
- readonly content: string | null;
109
+ readonly content: string | null | readonly AzureContentPart[];
21
110
  readonly tool_calls?: readonly AzureToolCallParam[];
22
111
  readonly tool_call_id?: string;
23
112
  readonly name?: string;
@@ -27,8 +116,9 @@ export interface AzureToolCall {
27
116
  readonly name: string;
28
117
  readonly input: unknown;
29
118
  }
119
+ /** `content` may include `AzureImageUrlPart`s (e.g. a vision self-check's screenshot) even though the wire `tool` message can't carry them directly — `runAzureToolTurn` splits them onto a follow-up `user` message; see `openai-chat.ts`'s identical design. */
30
120
  export interface AzureToolResult {
31
- readonly content: string;
121
+ readonly content: string | readonly AzureContentPart[];
32
122
  }
33
123
  /** Host-owned tool execution — same "the collaborator is always supplied" convention as `anthropic-messages.ts#AnthropicToolExecutor`. */
34
124
  export type AzureToolExecutor = (call: AzureToolCall) => Promise<AzureToolResult>;
@@ -47,7 +137,7 @@ export type AzureTurnEvent = {
47
137
  } | {
48
138
  readonly type: 'tool_result';
49
139
  readonly toolUseId: string;
50
- readonly content: string;
140
+ readonly content: string | readonly AzureContentPart[];
51
141
  readonly isError: boolean;
52
142
  } | {
53
143
  readonly type: 'usage';
@@ -84,11 +174,31 @@ export interface AzureTurnOptions {
84
174
  readonly signal?: AbortSignal;
85
175
  /** Caller-supplied extra headers — see `anthropic-messages.ts#AnthropicTurnOptions.extraHeaders`'s doc for why this exists and what it fixes. */
86
176
  readonly extraHeaders?: Record<string, string>;
177
+ /** Injectable HTTP transport, defaulting to `connection-guard.ts`'s `pinnedFetch` (SSRF-guarded, DNS-pinned). See `anthropic-messages.ts#AnthropicTurnOptions.fetchImpl`'s doc for the seam this exists for and why production wiring must never override it. Forwarded to `openai-chat.ts#runOpenAiCompatibleRequest` — this provider delegates its actual request to that shared reducer (see module doc). */
178
+ readonly fetchImpl?: PinnedFetch;
179
+ /** Injectable DNS resolver for `validateBaseUrlResolved`'s DNS-pinning check, defaulting to `defaultDnsLookup`. Same test-only override rationale as `fetchImpl`. */
180
+ readonly dnsLookup?: DnsLookupFn;
87
181
  }
88
182
  export interface AzureTurnResult {
89
183
  readonly finishReason: string | null;
90
184
  readonly toolTurns: number;
91
185
  }
186
+ /**
187
+ * Decides why the tool loop should stop after one request, or returns `null` when it should
188
+ * instead proceed to execute the pending tool calls — mirrors
189
+ * `anthropic-messages.ts#anthropicLoopExitReason`'s pure decision/effect split.
190
+ */
191
+ export declare function azureLoopExitReason(outcome: OpenAiCompatibleRequestOutcome, toolTurns: number, maxToolTurns: number): AzureTurnEndReason | null;
192
+ /** Builds the assistant continuation's `tool_calls` field — same shape as `openai-chat.ts#buildOpenAiAssistantToolCallMessage`'s `tool_calls`, since Azure's wire body is byte-identical to plain OpenAI's (see module doc). */
193
+ export declare function buildAzureAssistantToolCalls(toolCalls: readonly AzureToolCall[]): AzureToolCallParam[];
194
+ export interface AzureToolExecutionOutcome {
195
+ readonly toolResultMessages: AzureMessageParam[];
196
+ readonly followUpParts: AzureContentPart[];
197
+ }
198
+ /** Azure counterpart of `openai-chat.ts#executeOpenAiToolCalls` — same split (text-only `tool` message plus one labeled-image follow-up per batch) and same per-batch assembly discipline; see that function's doc and module doc's "synthetic follow-up message" note for why. */
199
+ export declare function executeAzureToolCalls(executeTool: AzureToolExecutor, calls: readonly AzureToolCall[], onEvent: (event: AzureTurnEvent) => void): Promise<AzureToolExecutionOutcome>;
200
+ /** Azure counterpart of `openai-chat.ts#buildOpenAiToolExchangeMessages` — appends the batch's `tool` messages plus, when present, the single labeled-image follow-up message. */
201
+ export declare function buildAzureToolExchangeMessages(toolResultMessages: readonly AzureMessageParam[], followUpParts: readonly AzureContentPart[]): AzureMessageParam[];
92
202
  /**
93
203
  * Runs a full Azure OpenAI Chat Completions turn, including the
94
204
  * tool-execution loop when `options.executeTool` is supplied and the model
@@ -1 +1 @@
1
- {"version":3,"file":"azure-chat.d.ts","sourceRoot":"","sources":["../../src/providers/azure-chat.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"azure-chat.d.ts","sourceRoot":"","sources":["../../src/providers/azure-chat.ts"],"names":[],"mappings":"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"}
@@ -35,7 +35,7 @@
35
35
  * endpoint (`https://{resource}.openai.azure.com`), so unlike Anthropic/
36
36
  * OpenAI/Google/Ollama, `baseUrl` is a required field on
37
37
  * {@link AzureTurnOptions}, and `apiVersion` is validated as
38
- * non-empty-if-supplied by the caller (`@jini-ai/http`'s
38
+ * non-empty-if-supplied by the caller (`@jini-ai/http-kit`'s
39
39
  * `parseAzureProxyRequest` — see that module).
40
40
  *
41
41
  * **Token-limit fix**: an earlier version of this module sent no token-limit
@@ -51,6 +51,25 @@
51
51
  * once with `max_completion_tokens` on that specific error
52
52
  * (`isUnsupportedMaxTokensError`); this module does the same via
53
53
  * `runOpenAiCompatibleRequest`'s `retryableBody` hook.
54
+ *
55
+ * **Image support inherits from plain OpenAI, confirmed by doc AND by code.** Microsoft's own
56
+ * vision how-to (`learn.microsoft.com/azure/ai-foundry/openai/how-to/gpt-with-vision`) states
57
+ * verbatim: "The format is the same as the chat completions API for GPT-4o, except that the
58
+ * message content can be an array containing text and images" — identical `type:'text'`/
59
+ * `type:'image_url'` shape and `detail` values as `openai-chat.ts`. That inheritance is genuine at
60
+ * the code level too, not just assumed from the docs matching: `azureRequestBody` below spreads
61
+ * `messages` straight into the JSON body with no per-field transform, so once `AzureMessageParam`
62
+ * (this module's own copy of `OpenAiMessageParam`, per this file's existing full-duplication
63
+ * convention — see every other type in this file) is widened the same way as OpenAI's, an image
64
+ * survives untouched all the way to the wire. `azure-chat.test.ts`'s new image tests assert the
65
+ * request body directly to prove this, rather than trusting the doc claim alone.
66
+ *
67
+ * **The synthetic follow-up message and its ordering/attribution rules are also inherited
68
+ * unchanged from `openai-chat.ts`** — same workaround for the same text-only `tool` message
69
+ * constraint, same requirement that every `tool` message in a batch be emitted before the single
70
+ * follow-up (never interleaved — a real 400 otherwise), same per-image attribution label naming
71
+ * the tool call it answers. See that module's doc for the full rationale; it is not repeated here
72
+ * beyond this pointer to avoid the two copies drifting.
54
73
  */
55
74
  import { defaultDnsLookup, validateBaseUrlResolved } from './connection-guard.js';
56
75
  import { runOpenAiCompatibleRequest } from './openai-chat.js';
@@ -88,7 +107,7 @@ function azureRequestBody(options, messages, tokenParam) {
88
107
  }
89
108
  /** Validates `options.baseUrl`, then delegates to `openai-chat.ts#runOpenAiCompatibleRequest` with Azure's own URL/header/body builders. */
90
109
  async function runSingleAzureRequest(options, messages, emitEnd, hasEnded) {
91
- const baseUrlCheck = await validateBaseUrlResolved(options.baseUrl, defaultDnsLookup);
110
+ const baseUrlCheck = await validateBaseUrlResolved(options.baseUrl, options.dnsLookup ?? defaultDnsLookup);
92
111
  if (baseUrlCheck.error) {
93
112
  options.onEvent({ type: 'error', message: baseUrlCheck.error });
94
113
  emitEnd('error');
@@ -102,7 +121,9 @@ async function runSingleAzureRequest(options, messages, emitEnd, hasEnded) {
102
121
  retryableBody: (status, rawErrorText) => status === 400 && isUnsupportedMaxTokensError(rawErrorText)
103
122
  ? azureRequestBody(options, messages, buildMaxCompletionTokensParam(maxTokens))
104
123
  : null,
124
+ ...(baseUrlCheck.pinnedAddress ? { pinnedAddress: baseUrlCheck.pinnedAddress } : {}),
105
125
  ...(options.signal ? { signal: options.signal } : {}),
126
+ ...(options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}),
106
127
  redactSecretsList: [options.apiKey],
107
128
  guardMessageId: 'azure-turn',
108
129
  providerLabel: 'Azure OpenAI',
@@ -111,6 +132,89 @@ async function runSingleAzureRequest(options, messages, emitEnd, hasEnded) {
111
132
  hasEnded,
112
133
  });
113
134
  }
135
+ const AZURE_ALLOWED_IMAGE_MEDIA_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp', 'image/gif']);
136
+ /** Same 20 MB conservative single-image guard as `openai-chat.ts#MAX_IMAGE_DATA_URI_BASE64_CHARS` — see that constant's doc for the full rationale (Azure inherits OpenAI's image support, so it inherits this module's own defensive posture around it too). */
137
+ const MAX_IMAGE_DATA_URI_BASE64_CHARS = Math.ceil((20 * 1024 * 1024 * 4) / 3);
138
+ /** Same conservative per-tool-result image-count guard as `openai-chat.ts#MAX_IMAGES_PER_OPENAI_TOOL_RESULT`. */
139
+ const MAX_IMAGES_PER_AZURE_TOOL_RESULT = 1500;
140
+ const AZURE_DATA_URI_PATTERN = /^data:([^;,]+)(?:;charset=[^;,]+)?;base64,(.+)$/su;
141
+ /** Azure counterpart of `openai-chat.ts#invalidOpenAiContentPartReason` — same validation, same URL-sourced-image skip rationale (Azure's own servers fetch a plain `https://` `image_url`, not this adapter). @complexity O(1). */
142
+ function invalidAzureContentPartReason(part) {
143
+ if (part.type === 'text')
144
+ return null;
145
+ const dataUriMatch = AZURE_DATA_URI_PATTERN.exec(part.image_url.url);
146
+ if (!dataUriMatch)
147
+ return null;
148
+ const [, mediaType, base64Data] = dataUriMatch;
149
+ if (!mediaType || !AZURE_ALLOWED_IMAGE_MEDIA_TYPES.has(mediaType.toLowerCase())) {
150
+ return `unsupported image media type ${JSON.stringify(mediaType)} (Azure OpenAI's chat completions API supports image/jpeg, image/png, image/webp, and non-animated image/gif — same as plain OpenAI)`;
151
+ }
152
+ if (base64Data && base64Data.length > MAX_IMAGE_DATA_URI_BASE64_CHARS) {
153
+ return `image exceeds this adapter's 20 MB base64 size guard (${base64Data.length} base64 chars)`;
154
+ }
155
+ return null;
156
+ }
157
+ /** Azure counterpart of `openai-chat.ts#sanitizeOpenAiToolResult` — same runtime guard, same rationale (an `AzureToolResult` is host-owned; the TS union only constrains a well-behaved host at compile time). @complexity O(n) in content parts. */
158
+ function sanitizeAzureToolResult(result) {
159
+ if (typeof result.content === 'string')
160
+ return { content: result.content, isError: false };
161
+ if (result.content.length > MAX_IMAGES_PER_AZURE_TOOL_RESULT) {
162
+ return { content: `tool result rejected: exceeds the ${MAX_IMAGES_PER_AZURE_TOOL_RESULT}-image-per-request guard (${result.content.length} parts)`, isError: true };
163
+ }
164
+ for (const part of result.content) {
165
+ const reason = invalidAzureContentPartReason(part);
166
+ if (reason)
167
+ return { content: `tool result rejected: ${reason}`, isError: true };
168
+ }
169
+ return { content: result.content, isError: false };
170
+ }
171
+ /** Azure counterpart of `openai-chat.ts#splitOpenAiToolResultContent` — same text/image split, same placeholder-when-image-only rule, same reason (a `role: 'tool'` message on Azure is the identical wire shape as plain OpenAI's, so it has the identical text-only constraint). @complexity O(n) in content parts. */
172
+ function splitAzureToolResultContent(content) {
173
+ if (typeof content === 'string')
174
+ return { toolMessageContent: content, imageParts: [] };
175
+ const textParts = content.filter((part) => part.type === 'text');
176
+ const imageParts = content.filter((part) => part.type === 'image_url');
177
+ const toolMessageContent = textParts.length > 0 ? textParts : [{ type: 'text', text: '(tool result included only non-text content; see the following message)' }];
178
+ return { toolMessageContent, imageParts };
179
+ }
180
+ /**
181
+ * Decides why the tool loop should stop after one request, or returns `null` when it should
182
+ * instead proceed to execute the pending tool calls — mirrors
183
+ * `anthropic-messages.ts#anthropicLoopExitReason`'s pure decision/effect split.
184
+ */
185
+ export function azureLoopExitReason(outcome, toolTurns, maxToolTurns) {
186
+ if (outcome.finishReason !== 'tool_calls' || outcome.toolCalls.length === 0)
187
+ return 'stop';
188
+ if (toolTurns >= maxToolTurns)
189
+ return 'max_tool_turns';
190
+ return null;
191
+ }
192
+ /** Builds the assistant continuation's `tool_calls` field — same shape as `openai-chat.ts#buildOpenAiAssistantToolCallMessage`'s `tool_calls`, since Azure's wire body is byte-identical to plain OpenAI's (see module doc). */
193
+ export function buildAzureAssistantToolCalls(toolCalls) {
194
+ return toolCalls.map((call) => ({ id: call.id, type: 'function', function: { name: call.name, arguments: JSON.stringify(call.input) } }));
195
+ }
196
+ /** Azure counterpart of `openai-chat.ts#executeOpenAiToolCalls` — same split (text-only `tool` message plus one labeled-image follow-up per batch) and same per-batch assembly discipline; see that function's doc and module doc's "synthetic follow-up message" note for why. */
197
+ export async function executeAzureToolCalls(executeTool, calls, onEvent) {
198
+ const toolResultMessages = [];
199
+ const followUpParts = [];
200
+ for (const call of calls) {
201
+ const result = await executeTool(call);
202
+ const sanitized = sanitizeAzureToolResult(result);
203
+ onEvent({ type: 'tool_result', toolUseId: call.id, content: sanitized.content, isError: sanitized.isError });
204
+ const split = splitAzureToolResultContent(sanitized.content);
205
+ toolResultMessages.push({ role: 'tool', content: split.toolMessageContent, tool_call_id: call.id });
206
+ if (split.imageParts.length > 0) {
207
+ // Attribution label — see `openai-chat.ts`'s identical note for why this matters.
208
+ followUpParts.push({ type: 'text', text: `Image output from tool \`${call.name}\` (tool_call_id: ${call.id}):` });
209
+ followUpParts.push(...split.imageParts);
210
+ }
211
+ }
212
+ return { toolResultMessages, followUpParts };
213
+ }
214
+ /** Azure counterpart of `openai-chat.ts#buildOpenAiToolExchangeMessages` — appends the batch's `tool` messages plus, when present, the single labeled-image follow-up message. */
215
+ export function buildAzureToolExchangeMessages(toolResultMessages, followUpParts) {
216
+ return [...toolResultMessages, ...(followUpParts.length > 0 ? [{ role: 'user', content: followUpParts }] : [])];
217
+ }
114
218
  /**
115
219
  * Runs a full Azure OpenAI Chat Completions turn, including the
116
220
  * tool-execution loop when `options.executeTool` is supplied and the model
@@ -119,6 +223,7 @@ async function runSingleAzureRequest(options, messages, emitEnd, hasEnded) {
119
223
  */
120
224
  export async function runAzureToolTurn(options) {
121
225
  const maxToolTurns = options.maxToolTurns ?? DEFAULT_MAX_TOOL_TURNS;
226
+ const executeTool = options.executeTool;
122
227
  const endGuard = createTurnEndGuard(options.onEvent, (reason) => ({ type: 'end', reason }));
123
228
  const emitEnd = endGuard.emitEnd;
124
229
  let messages = options.messages.slice();
@@ -129,34 +234,22 @@ export async function runAzureToolTurn(options) {
129
234
  lastFinishReason = outcome.finishReason;
130
235
  if (endGuard.hasEnded())
131
236
  break;
132
- if (outcome.finishReason !== 'tool_calls' || outcome.toolCalls.length === 0) {
133
- emitEnd('stop');
237
+ const exitReason = azureLoopExitReason(outcome, toolTurns, maxToolTurns);
238
+ if (exitReason) {
239
+ emitEnd(exitReason);
134
240
  break;
135
241
  }
136
- if (!options.executeTool) {
242
+ if (!executeTool) {
137
243
  emitEnd('stop');
138
244
  break;
139
245
  }
140
- if (toolTurns >= maxToolTurns) {
141
- emitEnd('max_tool_turns');
142
- break;
143
- }
144
246
  toolTurns += 1;
145
- const assistantToolCalls = outcome.toolCalls.map((call) => ({
146
- id: call.id,
147
- type: 'function',
148
- function: { name: call.name, arguments: JSON.stringify(call.input) },
149
- }));
150
- const toolResultMessages = [];
151
- for (const call of outcome.toolCalls) {
152
- const result = await options.executeTool(call);
153
- options.onEvent({ type: 'tool_result', toolUseId: call.id, content: result.content, isError: false });
154
- toolResultMessages.push({ role: 'tool', content: result.content, tool_call_id: call.id });
155
- }
247
+ const assistantToolCalls = buildAzureAssistantToolCalls(outcome.toolCalls);
248
+ const { toolResultMessages, followUpParts } = await executeAzureToolCalls(executeTool, outcome.toolCalls, options.onEvent);
156
249
  messages = [
157
250
  ...messages,
158
251
  { role: 'assistant', content: outcome.text || null, tool_calls: assistantToolCalls },
159
- ...toolResultMessages,
252
+ ...buildAzureToolExchangeMessages(toolResultMessages, followUpParts),
160
253
  ];
161
254
  }
162
255
  return { finishReason: lastFinishReason, toolTurns };
@@ -1 +1 @@
1
- {"version":3,"file":"azure-chat.js","sourceRoot":"","sources":["../../src/providers/azure-chat.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"azure-chat.js","sourceRoot":"","sources":["../../src/providers/azure-chat.ts"],"names":[],"mappings":"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"}
@@ -16,17 +16,63 @@
16
16
  * or RFC1918/link-local/CGNAT/metadata-service addresses, and strip bearer
17
17
  * tokens / API-key headers / `?key=` query values out of free-form text
18
18
  * before it is logged or surfaced to a caller.
19
+ *
20
+ * Also exports `pinnedFetch` — a `node:https`/`node:http`-based POST that dials the exact address
21
+ * `validateBaseUrlResolved` already validated, instead of leaving the transport to re-resolve DNS
22
+ * independently when it connects. Added later than the four functions above, for the same
23
+ * dependency-free package; see `pinnedFetch`'s own doc for why `fetch()` itself cannot do this.
24
+ *
25
+ * ## Paired with `@jini-ai/ui`'s `utils/endpoint-policy.ts`
26
+ *
27
+ * That module carries a browser-safe copy of the SYNCHRONOUS half below —
28
+ * `isLoopbackApiHost`/`isBlockedExternalApiHostname`/`validateBaseUrl` — so the
29
+ * settings tabs can reject a bad endpoint as the operator types it. It is a
30
+ * copy rather than an import because `endpoint-policy.ts` itself has zero
31
+ * imports by design and this package is Node-only (`validateBaseUrlResolved`
32
+ * needs `node:dns`); see that file's header for the full reasoning.
33
+ *
34
+ * **If the block-list here changes, change it there too.** A divergence means
35
+ * the UI accepts an endpoint this guard then refuses — or, in the direction
36
+ * that actually matters, the UI accepts one this guard would have refused.
19
37
  */
20
38
  export interface BaseUrlValidationResult {
21
39
  parsed?: URL;
22
40
  error?: string;
23
41
  forbidden?: boolean;
42
+ /**
43
+ * The validated address to pin the outbound connection to, present exactly when
44
+ * {@link validateBaseUrlResolved} performed a DNS lookup and passed it (i.e. never set by
45
+ * {@link validateBaseUrl} alone, and never set for the loopback-literal / IP-literal hosts that
46
+ * skip resolution — see that function's doc for why those two cases need no pinning). Feed this
47
+ * straight into {@link pinnedFetch} so the connection dials the exact address the guard approved
48
+ * instead of re-resolving DNS when it dials.
49
+ */
50
+ pinnedAddress?: DnsLookupAddress;
24
51
  }
25
52
  /** True for `localhost`, `::1`, `127.0.0.0/8`, and their IPv4-mapped-IPv6 forms. */
26
53
  export declare function isLoopbackApiHost(hostname: string): boolean;
27
54
  /** True for RFC1918/link-local/CGNAT/multicast/unspecified/unique-local-IPv6 addresses — private network space a public caller should never be steered into. */
28
55
  export declare function isBlockedExternalApiHostname(hostname: string): boolean;
29
- /** Synchronous base-URL check: scheme allow-list + literal-hostname block-list. Does not resolve DNS — see {@link validateBaseUrlResolved}. */
56
+ /**
57
+ * Synchronous base-URL check: scheme allow-list + literal-hostname block-list.
58
+ * Does not resolve DNS — see {@link validateBaseUrlResolved}.
59
+ *
60
+ * **Trims before parsing, and must keep doing so.** `@jini-ai/ui`'s
61
+ * `isAllowedEndpointUrl` is a deliberate browser-safe copy of this function and
62
+ * trims its input; this one did not. WHATWG URL parsing strips leading/trailing
63
+ * ASCII space itself, so the two agreed on `" https://x "` and diverged only on
64
+ * whitespace `String.prototype.trim` removes but the URL parser does not — NBSP,
65
+ * BOM, thin space and friends. A URL pasted from a rich-text source therefore
66
+ * passed the UI and was refused at connection time.
67
+ * `endpoint-policy.parity.test.ts` now holds the two in agreement mechanically.
68
+ *
69
+ * Trimming cannot weaken the block-list. Checked across every whitespace
70
+ * character `trim` strips, against blocked/loopback/public hosts in all three
71
+ * padding positions: an untrimmed parse either fails outright or yields a
72
+ * hostname IDENTICAL to the trimmed one. It never yields a DIFFERENT host, which
73
+ * is the only shape that would let the block-list inspect one string while the
74
+ * network used another.
75
+ */
30
76
  export declare function validateBaseUrl(baseUrl: string): BaseUrlValidationResult;
31
77
  export type DnsLookupAddress = {
32
78
  address: string;
@@ -45,6 +91,25 @@ export type DnsLookupFn = (hostname: string) => Promise<DnsLookupAddress[]>;
45
91
  * hostname that resolves to a loopback address (including `*.localhost` per
46
92
  * RFC 6761 and IPv4-mapped IPv6 loopback) follows the same carve-out.
47
93
  *
94
+ * ## The TOCTOU this closes, and how
95
+ *
96
+ * A validator that only checks and returns pass/fail has a gap: `fetch` (or any transport) then
97
+ * resolves the hostname AGAIN, independently, when it dials. Between those two resolutions the
98
+ * answer can change — a DNS rebinding attacker returns a public address to this check and a
99
+ * private one to the connection. No amount of re-checking closes that on its own; the request has
100
+ * to dial the exact address that was approved. So this function does the resolution exactly ONCE
101
+ * and hands the first validated address back as `pinnedAddress` — feed it to {@link pinnedFetch},
102
+ * which dials that address directly instead of letting the transport re-resolve. `pinnedAddress` is
103
+ * only set when a lookup actually happened (i.e. never for the loopback-literal / IP-literal hosts
104
+ * below, which need no pinning: an IP literal never re-resolves to anything else, and loopback
105
+ * literals are outside this threat model — see the carve-out above).
106
+ *
107
+ * That said, `baseUrl` here is operator-configured provider config, not attacker-supplied input —
108
+ * pinning is defence-in-depth rather than the primary trust boundary. Callers should additionally
109
+ * refuse redirects (`redirect: 'error'`), which closes the OTHER way a validated origin reaches an
110
+ * unvalidated address; `pinnedFetch` never follows one regardless (`node:https`/`node:http` don't,
111
+ * unlike `fetch`'s default), but callers still pass `redirect: 'error'` for self-documentation.
112
+ *
48
113
  * DNS lookup failures are not treated as a security signal — the caller is
49
114
  * going to surface a connection error from `fetch` anyway, and turning a
50
115
  * transient resolver hiccup into a rejection would just confuse callers. The
@@ -54,6 +119,61 @@ export type DnsLookupFn = (hostname: string) => Promise<DnsLookupAddress[]>;
54
119
  * hosts that already own a resolver.
55
120
  */
56
121
  export declare function validateBaseUrlResolved(baseUrl: string, lookup: DnsLookupFn): Promise<BaseUrlValidationResult>;
122
+ /** Request options accepted by {@link pinnedFetch} — the subset of `fetch`'s `init` this package's provider adapters actually pass. `redirect` is accepted only as documentation of intent: `pinnedFetch` never follows a redirect regardless of this field, so a caller cannot opt back into `fetch`'s default follow-redirects behavior. */
123
+ export interface PinnedFetchInit {
124
+ readonly method: string;
125
+ readonly headers: Record<string, string>;
126
+ readonly body: string;
127
+ readonly redirect?: 'error';
128
+ readonly signal?: AbortSignal;
129
+ }
130
+ /** The subset of `fetch`'s `Response` this package's provider adapters actually consume. */
131
+ export interface PinnedFetchResponse {
132
+ readonly ok: boolean;
133
+ readonly status: number;
134
+ readonly body: AsyncIterable<Uint8Array> | null;
135
+ text(): Promise<string>;
136
+ }
137
+ /**
138
+ * The call signature of {@link pinnedFetch} — exported so provider adapters
139
+ * (`AnthropicTurnOptions.fetchImpl`, `OpenAiTurnOptions.fetchImpl`, etc.) can accept it as an
140
+ * injectable dependency instead of hardcoding the import, and so a test can pass a fake directly
141
+ * through that seam rather than module-mocking this file (or, worse, its compiled `dist/` output —
142
+ * see `@jini-ai/http-kit`'s `model-proxy.test.ts` for the fragility that motivated this). Every
143
+ * adapter still defaults to the real {@link pinnedFetch} when the option is omitted — production
144
+ * behavior, including the SSRF guard and DNS pinning, never changes unless a caller explicitly
145
+ * overrides it.
146
+ */
147
+ export type PinnedFetch = (url: string, init: PinnedFetchInit, pinnedAddress: DnsLookupAddress | undefined) => Promise<PinnedFetchResponse>;
148
+ /**
149
+ * `fetch()`-shaped POST that dials `pinnedAddress` directly instead of letting the transport
150
+ * re-resolve DNS when it connects — see {@link validateBaseUrlResolved}'s doc for the TOCTOU this
151
+ * closes and why `pinnedAddress` is the exact address that function already validated.
152
+ *
153
+ * Built on `node:https`/`node:http`, not `fetch`, because Node's global `fetch` is backed by an
154
+ * internal, non-importable copy of undici and exposes no `lookup`/dispatcher hook without adding
155
+ * `undici` (or an equivalent) as a runtime dependency — confirmed empirically against this
156
+ * package's pinned Node version (`require('node:undici')` throws `No such built-in module`, i.e.
157
+ * it is not a stable built-in here even though Node's own `fetch` uses it internally). This
158
+ * package carries no external runtime dependencies by design (module doc), so `node:https`/
159
+ * `node:http`'s own `lookup` option — which `net.connect` already accepts for exactly this purpose
160
+ * — is the only built-in path.
161
+ *
162
+ * `hostname`/`servername` passed to the transport stay the ORIGINAL host from `url`, not
163
+ * `pinnedAddress`; only the `lookup` override changes which address the socket actually dials.
164
+ * That is what keeps this a pin rather than a redirect to a different origin: the Host header and,
165
+ * for https, the TLS SNI + certificate hostname validation are unaffected, so a certificate for the
166
+ * real hostname still validates normally against a connection that happens to land on the address
167
+ * the guard already approved. Never follows a redirect — `http(s).request` doesn't, unlike
168
+ * `fetch`'s default — which is the other half of the rebinding surface `redirect: 'error'`
169
+ * documents at each call site.
170
+ *
171
+ * When `pinnedAddress` is `undefined` — the loopback-literal / IP-literal hosts
172
+ * `validateBaseUrlResolved` never runs a lookup for — this issues a normal, unpinned request
173
+ * (`lookup` option simply omitted). That is intentional, not a gap: an IP literal has nothing left
174
+ * to re-resolve, and loopback literals are the documented carve-out, outside this threat model.
175
+ */
176
+ export declare function pinnedFetch(url: string, init: PinnedFetchInit, pinnedAddress: DnsLookupAddress | undefined): Promise<PinnedFetchResponse>;
57
177
  /**
58
178
  * Redacts bearer tokens, `x-api-key`/`api-key`/`x-goog-api-key` headers,
59
179
  * `?key=` query values, and any exact secret strings supplied via
@@ -1 +1 @@
1
- {"version":3,"file":"connection-guard.d.ts","sourceRoot":"","sources":["../../src/providers/connection-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAsED,oFAAoF;AACpF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAM3D;AAED,gKAAgK;AAChK,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAQtE;AAED,+IAA+I;AAC/I,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,uBAAuB,CAexE;AAED,MAAM,MAAM,gBAAgB,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AACnE,MAAM,MAAM,WAAW,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;AAU5E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,uBAAuB,CAAC,CAwBlC;AAMD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,YAAY,GAAE,aAAa,CAAC,MAAM,GAAG,SAAS,GAAG,IAAI,CAAM,GAC1D,MAAM,CAWR;AAED,kIAAkI;AAClI,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAIpF"}
1
+ {"version":3,"file":"connection-guard.d.ts","sourceRoot":"","sources":["../../src/providers/connection-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,gBAAgB,CAAC;CAClC;AAsED,oFAAoF;AACpF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAM3D;AAED,gKAAgK;AAChK,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAQtE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,uBAAuB,CAiBxE;AAED,MAAM,MAAM,gBAAgB,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AACnE,MAAM,MAAM,WAAW,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;AAU5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,uBAAuB,CAAC,CA+BlC;AAED,8UAA8U;AAC9U,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,4FAA4F;AAC5F,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;IAChD,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CACzB;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,WAAW,GAAG,CACxB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,eAAe,EACrB,aAAa,EAAE,gBAAgB,GAAG,SAAS,KACxC,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAYlC;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,eAAe,EACrB,aAAa,EAAE,gBAAgB,GAAG,SAAS,GAC1C,OAAO,CAAC,mBAAmB,CAAC,CAsH9B;AAMD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,YAAY,GAAE,aAAa,CAAC,MAAM,GAAG,SAAS,GAAG,IAAI,CAAM,GAC1D,MAAM,CAWR;AAED,kIAAkI;AAClI,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAIpF"}