@xoxoai/checkmate 0.4.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 (158) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +244 -0
  3. package/dist/ai/client.d.ts +49 -0
  4. package/dist/ai/client.d.ts.map +1 -0
  5. package/dist/ai/client.js +244 -0
  6. package/dist/ai/client.js.map +1 -0
  7. package/dist/ai/message-handler.d.ts +11 -0
  8. package/dist/ai/message-handler.d.ts.map +1 -0
  9. package/dist/ai/message-handler.js +33 -0
  10. package/dist/ai/message-handler.js.map +1 -0
  11. package/dist/ai/message-history.d.ts +19 -0
  12. package/dist/ai/message-history.d.ts.map +1 -0
  13. package/dist/ai/message-history.js +48 -0
  14. package/dist/ai/message-history.js.map +1 -0
  15. package/dist/ai/prompts.d.ts +4 -0
  16. package/dist/ai/prompts.d.ts.map +1 -0
  17. package/dist/ai/prompts.js +24 -0
  18. package/dist/ai/prompts.js.map +1 -0
  19. package/dist/ai/rate-limit-policy.d.ts +7 -0
  20. package/dist/ai/rate-limit-policy.d.ts.map +1 -0
  21. package/dist/ai/rate-limit-policy.js +17 -0
  22. package/dist/ai/rate-limit-policy.js.map +1 -0
  23. package/dist/ai/response-processor.d.ts +20 -0
  24. package/dist/ai/response-processor.d.ts.map +1 -0
  25. package/dist/ai/response-processor.js +62 -0
  26. package/dist/ai/response-processor.js.map +1 -0
  27. package/dist/ai/token-pricing.d.ts +13 -0
  28. package/dist/ai/token-pricing.d.ts.map +1 -0
  29. package/dist/ai/token-pricing.js +348 -0
  30. package/dist/ai/token-pricing.js.map +1 -0
  31. package/dist/ai/token-tracker.d.ts +33 -0
  32. package/dist/ai/token-tracker.d.ts.map +1 -0
  33. package/dist/ai/token-tracker.js +138 -0
  34. package/dist/ai/token-tracker.js.map +1 -0
  35. package/dist/ai/tool-response-handler.d.ts +21 -0
  36. package/dist/ai/tool-response-handler.d.ts.map +1 -0
  37. package/dist/ai/tool-response-handler.js +54 -0
  38. package/dist/ai/tool-response-handler.js.map +1 -0
  39. package/dist/config/runtime-config.d.ts +21 -0
  40. package/dist/config/runtime-config.d.ts.map +1 -0
  41. package/dist/config/runtime-config.js +98 -0
  42. package/dist/config/runtime-config.js.map +1 -0
  43. package/dist/core.d.ts +8 -0
  44. package/dist/core.d.ts.map +1 -0
  45. package/dist/core.js +4 -0
  46. package/dist/core.js.map +1 -0
  47. package/dist/index.d.ts +2 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +2 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/integrations/salesforce/authenticator.d.ts +27 -0
  52. package/dist/integrations/salesforce/authenticator.d.ts.map +1 -0
  53. package/dist/integrations/salesforce/authenticator.js +27 -0
  54. package/dist/integrations/salesforce/authenticator.js.map +1 -0
  55. package/dist/integrations/salesforce/cli-handler.d.ts +14 -0
  56. package/dist/integrations/salesforce/cli-handler.d.ts.map +1 -0
  57. package/dist/integrations/salesforce/cli-handler.js +50 -0
  58. package/dist/integrations/salesforce/cli-handler.js.map +1 -0
  59. package/dist/logging/index.d.ts +2 -0
  60. package/dist/logging/index.d.ts.map +1 -0
  61. package/dist/logging/index.js +4 -0
  62. package/dist/logging/index.js.map +1 -0
  63. package/dist/logging/logger.d.ts +5 -0
  64. package/dist/logging/logger.d.ts.map +1 -0
  65. package/dist/logging/logger.js +15 -0
  66. package/dist/logging/logger.js.map +1 -0
  67. package/dist/playwright.d.ts +102 -0
  68. package/dist/playwright.d.ts.map +1 -0
  69. package/dist/playwright.js +116 -0
  70. package/dist/playwright.js.map +1 -0
  71. package/dist/runtime/extension.d.ts +274 -0
  72. package/dist/runtime/extension.d.ts.map +1 -0
  73. package/dist/runtime/extension.js +171 -0
  74. package/dist/runtime/extension.js.map +1 -0
  75. package/dist/runtime/runner.d.ts +94 -0
  76. package/dist/runtime/runner.d.ts.map +1 -0
  77. package/dist/runtime/runner.js +86 -0
  78. package/dist/runtime/runner.js.map +1 -0
  79. package/dist/runtime/step-execution.d.ts +17 -0
  80. package/dist/runtime/step-execution.d.ts.map +1 -0
  81. package/dist/runtime/step-execution.js +49 -0
  82. package/dist/runtime/step-execution.js.map +1 -0
  83. package/dist/runtime/types.d.ts +79 -0
  84. package/dist/runtime/types.d.ts.map +1 -0
  85. package/dist/runtime/types.js +2 -0
  86. package/dist/runtime/types.js.map +1 -0
  87. package/dist/salesforce.d.ts +71 -0
  88. package/dist/salesforce.d.ts.map +1 -0
  89. package/dist/salesforce.js +73 -0
  90. package/dist/salesforce.js.map +1 -0
  91. package/dist/tools/browser/screenshot-service.d.ts +10 -0
  92. package/dist/tools/browser/screenshot-service.d.ts.map +1 -0
  93. package/dist/tools/browser/screenshot-service.js +23 -0
  94. package/dist/tools/browser/screenshot-service.js.map +1 -0
  95. package/dist/tools/browser/snapshot-filter/index.d.ts +4 -0
  96. package/dist/tools/browser/snapshot-filter/index.d.ts.map +1 -0
  97. package/dist/tools/browser/snapshot-filter/index.js +4 -0
  98. package/dist/tools/browser/snapshot-filter/index.js.map +1 -0
  99. package/dist/tools/browser/snapshot-filter/semantic-scorer.d.ts +15 -0
  100. package/dist/tools/browser/snapshot-filter/semantic-scorer.d.ts.map +1 -0
  101. package/dist/tools/browser/snapshot-filter/semantic-scorer.js +99 -0
  102. package/dist/tools/browser/snapshot-filter/semantic-scorer.js.map +1 -0
  103. package/dist/tools/browser/snapshot-filter/snapshot-filter.d.ts +4 -0
  104. package/dist/tools/browser/snapshot-filter/snapshot-filter.d.ts.map +1 -0
  105. package/dist/tools/browser/snapshot-filter/snapshot-filter.js +57 -0
  106. package/dist/tools/browser/snapshot-filter/snapshot-filter.js.map +1 -0
  107. package/dist/tools/browser/snapshot-filter/tree-reconstructor.d.ts +3 -0
  108. package/dist/tools/browser/snapshot-filter/tree-reconstructor.d.ts.map +1 -0
  109. package/dist/tools/browser/snapshot-filter/tree-reconstructor.js +85 -0
  110. package/dist/tools/browser/snapshot-filter/tree-reconstructor.js.map +1 -0
  111. package/dist/tools/browser/snapshot-service.d.ts +19 -0
  112. package/dist/tools/browser/snapshot-service.d.ts.map +1 -0
  113. package/dist/tools/browser/snapshot-service.js +67 -0
  114. package/dist/tools/browser/snapshot-service.js.map +1 -0
  115. package/dist/tools/browser/tool.d.ts +34 -0
  116. package/dist/tools/browser/tool.d.ts.map +1 -0
  117. package/dist/tools/browser/tool.js +226 -0
  118. package/dist/tools/browser/tool.js.map +1 -0
  119. package/dist/tools/browser/transient-state-tracker.d.ts +16 -0
  120. package/dist/tools/browser/transient-state-tracker.d.ts.map +1 -0
  121. package/dist/tools/browser/transient-state-tracker.js +266 -0
  122. package/dist/tools/browser/transient-state-tracker.js.map +1 -0
  123. package/dist/tools/define-agent-tool.d.ts +45 -0
  124. package/dist/tools/define-agent-tool.d.ts.map +1 -0
  125. package/dist/tools/define-agent-tool.js +55 -0
  126. package/dist/tools/define-agent-tool.js.map +1 -0
  127. package/dist/tools/dispatcher.d.ts +11 -0
  128. package/dist/tools/dispatcher.d.ts.map +1 -0
  129. package/dist/tools/dispatcher.js +49 -0
  130. package/dist/tools/dispatcher.js.map +1 -0
  131. package/dist/tools/loop-detector.d.ts +27 -0
  132. package/dist/tools/loop-detector.d.ts.map +1 -0
  133. package/dist/tools/loop-detector.js +76 -0
  134. package/dist/tools/loop-detector.js.map +1 -0
  135. package/dist/tools/registry.d.ts +21 -0
  136. package/dist/tools/registry.d.ts.map +1 -0
  137. package/dist/tools/registry.js +46 -0
  138. package/dist/tools/registry.js.map +1 -0
  139. package/dist/tools/salesforce/login-tool.d.ts +7 -0
  140. package/dist/tools/salesforce/login-tool.d.ts.map +1 -0
  141. package/dist/tools/salesforce/login-tool.js +25 -0
  142. package/dist/tools/salesforce/login-tool.js.map +1 -0
  143. package/dist/tools/step/result-tool.d.ts +7 -0
  144. package/dist/tools/step/result-tool.d.ts.map +1 -0
  145. package/dist/tools/step/result-tool.js +32 -0
  146. package/dist/tools/step/result-tool.js.map +1 -0
  147. package/dist/tools/tool-contract.d.ts +3 -0
  148. package/dist/tools/tool-contract.d.ts.map +1 -0
  149. package/dist/tools/tool-contract.js +2 -0
  150. package/dist/tools/tool-contract.js.map +1 -0
  151. package/dist/tools/types.d.ts +139 -0
  152. package/dist/tools/types.d.ts.map +1 -0
  153. package/dist/tools/types.js +4 -0
  154. package/dist/tools/types.js.map +1 -0
  155. package/docs/EXTENSIONS.md +233 -0
  156. package/docs/GUIDE.md +467 -0
  157. package/docs/ROADMAP.md +47 -0
  158. package/package.json +106 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Dawid Dobrowolski SoftQA
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,244 @@
1
+ # **_checkmate_**
2
+
3
+ AI test automation that actually works. Write tests in plain English, without locators, and with less code.
4
+
5
+ ![playwright](https://img.shields.io/badge/Playwright-1.59.1-blue.svg)
6
+ ![typescript](https://img.shields.io/badge/TypeScript-5.9.3-blue.svg)
7
+ ![nodejs](https://img.shields.io/badge/Node.js-LTS-green.svg)
8
+ ![openai](https://img.shields.io/badge/OpenAI-API-yellow.svg)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
10
+
11
+ ## Why?
12
+
13
+ Spending countless hours building and maintaining E2E tests that look like this?
14
+
15
+ ```
16
+ await page.goto('https://www.google.com')
17
+ const searchBox = page.getByRole('combobox', { name: 'Search', exact: true })
18
+ await searchBox.fill('playwright test automation')
19
+ await searchBox.press('Enter')
20
+ await expect(page.getByRole('link', { name: 'playwright' })
21
+ .filter({ hasText: 'playwright.dev' })
22
+ .first(), 'playwright.dev link should be visible')
23
+ .toBeVisible( { timeout: 30 * 1000 } )
24
+ ```
25
+
26
+ Try **_checkmate_**!
27
+
28
+ ```typescript
29
+ await ai.run({
30
+ action: `
31
+ Navigate to google.com
32
+ Type 'playwright test automation' in the search bar
33
+ Press Enter key`,
34
+ expect: `
35
+ Search results contain the playwright.dev link`,
36
+ })
37
+ ```
38
+
39
+ ## What You Get
40
+
41
+ ✅ **Zero Locators** - Write tests in plain English
42
+ ✅ **Self-Healing** - Tests adapt to UI changes automatically
43
+ ✅ **Any Provider** - Gemini, Claude, Groq, GPT, xAI, or local models
44
+ ✅ **Web & Salesforce** - Basic support out of the box
45
+ ✅ **Cost Optimized** - Built-in token management and budgeting
46
+ ✅ **Playwright Test** - Native reports, traces and debugging
47
+ ✅ **Fully Customizable** - Build your own [extensions](docs/EXTENSIONS.md) and tools
48
+
49
+ <img src="docs/img/gpt-oss-20b-e2e-checkout.gif" alt="example-e2e-test" width="100%"/>
50
+
51
+ ## Get Started in 5 Minutes
52
+
53
+ ### Prerequisites
54
+
55
+ - Node.js [LTS](https://nodejs.org/en/download)
56
+ - OpenAI [API key](https://platform.openai.com/api-keys) or compatible provider [Groq](https://console.groq.com/keys) [Gemini](https://aistudio.google.com/app/api-keys) [xAI](https://x.ai/api) etc.
57
+
58
+ ### 1. Install
59
+
60
+ ```bash
61
+ npm install -D @playwright/test @xoxoai/checkmate
62
+ npx playwright install
63
+ ```
64
+
65
+ ### 2. Configure `.env`
66
+
67
+ _using [OpenAI API](https://platform.openai.com/settings/organization/api-keys) key and default settings:_
68
+
69
+ ```bash
70
+ OPENAI_API_KEY=#your_api_key_here
71
+ ```
72
+
73
+ _for other providers, set the base url and model:_
74
+
75
+ ```bash
76
+ OPENAI_BASE_URL=https://api.groq.com/openai/v1
77
+ OPENAI_MODEL=openai/gpt-oss-20b
78
+ ```
79
+
80
+ ### 3. Run Tests
81
+
82
+ ```
83
+ npm run test:web:example
84
+ ```
85
+
86
+ ### 4. View Report
87
+
88
+ ```
89
+ npm run show:report
90
+ ```
91
+
92
+ ## Writing Tests
93
+
94
+ Import `test` and `expect` from `@xoxoai/checkmate/playwright` and use the `ai` fixture to run AI-driven tests alongside standard Playwright assertions.
95
+ **_checkmate_** tests are written using natural language by specifying `action` and `expect`:
96
+
97
+ ```typescript
98
+ import { expect, test } from '@xoxoai/checkmate/playwright'
99
+
100
+ test('google search', async ({ ai }) => {
101
+ await ai.run({
102
+ action: `
103
+ Open the browser and navigate to google.com.
104
+ Type 'playwright test automation' in the search bar.
105
+ Press Enter key.`,
106
+ expect: `
107
+ Search results contain the 'playwright.dev' link`,
108
+ })
109
+ })
110
+ ```
111
+
112
+ That's it. No page objects, no selectors. No locators. Peace on Earth.
113
+
114
+ Browser settings (viewport, headless mode, video recording, timeouts, etc.) are configured in [playwright.config.ts](playwright.config.ts) using Playwright's [standard](https://playwright.dev/docs/test-configuration) configuration mechanism.
115
+
116
+ See [guide](docs/GUIDE.md#best-practices) for detailed examples and best practices.
117
+
118
+ ### Programmatic API
119
+
120
+ If you use **_checkmate_** without the fixture wrapper, compose a runner from `@xoxoai/checkmate/core` and extensions:
121
+
122
+ ```typescript
123
+ import { createRunner } from '@xoxoai/checkmate/core'
124
+ import { web } from '@xoxoai/checkmate/playwright'
125
+
126
+ const ai = createRunner({
127
+ extensions: [web({ page })],
128
+ })
129
+
130
+ await ai.run({
131
+ action: 'Open the pricing page',
132
+ expect: 'Pricing details are visible',
133
+ })
134
+ ```
135
+
136
+ See [guide](docs/GUIDE.md#advanced-topics) for advanced topics and [extensions](docs/EXTENSIONS.md) for building custom tools, extensions, and runners.
137
+
138
+ Published entry points:
139
+
140
+ `@xoxoai/checkmate/core`: Build your own runner with extensions.
141
+ `@xoxoai/checkmate/playwright`: Use the built-in web extension with Playwright `test` and `expect`.
142
+ `@xoxoai/checkmate/salesforce`: Use the built-in web + Salesforce extensions with the same `ai` fixture shape.
143
+
144
+ The repository keeps runnable consumer-style examples under `test/examples/`.
145
+
146
+ ## Costs
147
+
148
+ Costs vary based on model and provider, test complexity and number of steps.
149
+ **_checkmate_** includes built-in token usage [monitoring](docs/GUIDE.md#cost-management).
150
+
151
+ Cost estimates with [gpt-oss-20b hosted on groq.com](https://console.groq.com/docs/model/openai/gpt-oss-20b) for optimal balance:
152
+
153
+ - Simple test (~5 steps): ~$0.001 - $0.01
154
+ - Complex test (~20 steps): ~$0.01 - $0.05
155
+ - Full E2E suite (~50 complex tests): ~$1.00 - $2.00
156
+
157
+ See [guide](docs/GUIDE.md#cost-management) for detailed cost control and monitoring options.
158
+
159
+ ## Common Issues
160
+
161
+ **AI makes incorrect decisions**
162
+
163
+ - Provide precise descriptions in `action` and more focused assertions in `expect`
164
+ - Reference specific element identifiers and roles (for example: text, label, button, list)
165
+ - Break complex workflows into single-action steps; use a step-by-step approach
166
+
167
+ **Tests loop during step execution**
168
+
169
+ - Increase `OPENAI_TEMPERATURE` to encourage exploration
170
+ - Use a reasoning/thinking model (if available) to improve planning and avoid repetitive loops
171
+
172
+ **High token costs**
173
+
174
+ - Enable [snapshot filtering](docs/GUIDE.md#using-snapshot-filtering-for-token-optimization) with `CHECKMATE_SNAPSHOT_FILTERING=true` to score and narrow the elements automatically from `action` and `expect`. Use `topPercent` to dial how much of the scored snapshot to keep for a step.
175
+ - Set a lower reasoning effort: `OPENAI_REASONING_EFFORT`
176
+ - Consider disabling `OPENAI_INCLUDE_SCREENSHOT_IN_SNAPSHOT`
177
+ - Use a cheaper model, lower-end models often perform well (e.g., `gpt-5.4-nano` or `gpt-oss-20b`)
178
+
179
+ See [guide](docs/GUIDE.md#openai-api-settings) for detailed configuration options and troubleshooting tips.
180
+
181
+ ## FAQ
182
+
183
+ **Which models work best?**
184
+ You can use any model that was trained for tool use. Here are the best picks based on extensive testing:
185
+
186
+ - Highly recommended: [`gpt-oss-20b` hosted on groq.com](https://console.groq.com/docs/model/openai/gpt-oss-20b). Groq's infrastructure is optimized for minimal latency and fast inference, making it ideal for E2E test automation.
187
+ - Google's `gemini-2.5-flash` offers an excellent balance of cost and performance if you prefer major cloud providers.
188
+ - OpenAI's `gpt-5-mini`, `gpt-5.4-nano` and xAI's `grok-4-1-fast-reasoning` also work well and keep costs relatively low.
189
+
190
+ **Can I use local models?**
191
+ Yes - **_checkmate_** works with any OpenAI‑compatible API, including local models via LM Studio, Ollama, or llama.cpp. I recommend [qwen3.5-4b](https://huggingface.co/Qwen/Qwen3.5-4B). It is fast (≈100 tokens/sec on an RTX 3060 Ti; ≈40 tokens/sec on Apple M3) and performs surprisingly well for E2E testing.
192
+
193
+ **Does it work with CI/CD?**
194
+ Absolutely. Use **_checkmate_** as part of your existing [Playwright Test suites in any CI/CD pipeline](https://playwright.dev/docs/best-practices#run-tests-on-ci). You can mix AI‑driven steps and traditional tests as needed.
195
+
196
+ **Is this production-ready?**
197
+ It depends. If you can accept some non‑deterministic behavior and leverage LLMs' randomness to help address the [pesticide paradox](https://medium.com/@suwekasansiluni/the-pesticide-paradox-what-farming-teaches-us-about-software-testing-ab5d625d4de1), **_checkmate_** can be production-ready. In many cases, the maintenance savings, faster development, and benefits of non‑linear execution outweigh occasional hiccups.
198
+
199
+ If you require 100% deterministic tests at all times, traditional Playwright remains the better choice.
200
+
201
+ **Best part?**
202
+ You can mix both approaches within the same test suite, combining AI‑driven and traditional tests as needed:
203
+
204
+ ```typescript
205
+ // traditional playwright actions:
206
+ await page.goto('https://www.google.com')
207
+ const searchBox = page.getByRole('combobox', { name: 'Search', exact: true })
208
+ await searchBox.fill('playwright test automation')
209
+ await searchBox.press('Enter')
210
+
211
+ // ai-driven actions and assertions:
212
+ await ai.run({
213
+ action: 'Click on the link that leads to playwright.dev',
214
+ expect: 'The playwright.dev homepage is displayed',
215
+ })
216
+ ```
217
+
218
+ ## Documentation
219
+
220
+ - [**_checkmate_**](docs/GUIDE.md)
221
+ - [Playwright](https://playwright.dev/)
222
+
223
+ ## Contributing
224
+
225
+ I'd love your help! Key areas:
226
+
227
+ - Additional tool integrations (API testing, Salesforce, etc.)
228
+ - Further cost optimization techniques
229
+ - Context and prompt engineering improvements
230
+ - Error handling and recovery
231
+
232
+ See [roadmap](docs/ROADMAP.md) for future plans and development
233
+
234
+ ## MIT License
235
+
236
+ See [license](LICENSE) file for details
237
+
238
+ ## Why I build this?
239
+
240
+ Test automation shouldn't require a PhD in XPath. This project explores how AI can make it accessible to anyone.
241
+
242
+ Less coding, more testing.
243
+
244
+ Built with ❤️ by [Dawid Dobrowolski](https://github.com/dawiddiwad)
@@ -0,0 +1,49 @@
1
+ import { ChatCompletion, ChatCompletionMessageParam } from 'openai/resources/chat/completions';
2
+ import { RuntimeConfig } from '../config/runtime-config';
3
+ import { ToolRegistry } from '../tools/registry';
4
+ import { Step, ResolveStepResult } from '../runtime/types';
5
+ import { ExtensionHost } from '../runtime/extension';
6
+ export type AiClientDependencies = {
7
+ runtimeConfig: RuntimeConfig;
8
+ toolRegistry: ToolRegistry;
9
+ extensionHost: ExtensionHost;
10
+ };
11
+ export declare class AiClient {
12
+ private client;
13
+ private messages;
14
+ private readonly responseProcessor;
15
+ private readonly retryableStatus;
16
+ private step;
17
+ private resolveStepResult;
18
+ temperature: number;
19
+ private readonly messageHistory;
20
+ constructor({ runtimeConfig, toolRegistry, extensionHost }: AiClientDependencies);
21
+ private readonly runtimeConfig;
22
+ private readonly toolRegistry;
23
+ initialize(step: Step, resolveStepResult: ResolveStepResult): Promise<void>;
24
+ getMessages(): ChatCompletionMessageParam[];
25
+ getToolRegistry(): ToolRegistry;
26
+ getRuntimeConfig(): RuntimeConfig;
27
+ sendMessage(userMessage: string | ChatCompletionMessageParam[]): Promise<void>;
28
+ addToolResponse(toolCallId: string, content: string): Promise<void>;
29
+ addUserMessage(content: string): Promise<void>;
30
+ addToolExecutionSummaryMessage(content: string): Promise<void>;
31
+ addCurrentSnapshotMessage(snapshotContent: string): Promise<void>;
32
+ addCurrentScreenshotMessage(base64Data: string, mimeType?: string): Promise<void>;
33
+ sendToolResponseWithRetry(): Promise<ChatCompletion>;
34
+ private createChatCompletion;
35
+ private appendResponseMessages;
36
+ private sanitizeAssistantMessage;
37
+ private executeWithRetry;
38
+ private getStatus;
39
+ private isRetryable;
40
+ private getRetryAfterSeconds;
41
+ private calculateBackoff;
42
+ private sleep;
43
+ countHistoryTokens(): number;
44
+ replaceHistory(history: ChatCompletionMessageParam[]): void;
45
+ private enhanceError;
46
+ private isToolError;
47
+ private countContentChars;
48
+ }
49
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/ai/client.ts"],"names":[],"mappings":"AACA,OAAO,EACN,cAAc,EAEd,0BAA0B,EAC1B,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAIxD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAG1D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEpD,MAAM,MAAM,oBAAoB,GAAG;IAClC,aAAa,EAAE,aAAa,CAAA;IAC5B,YAAY,EAAE,YAAY,CAAA;IAC1B,aAAa,EAAE,aAAa,CAAA;CAC5B,CAAA;AAED,qBAAa,QAAQ;IACpB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAmB;IACrD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAS/B;IACD,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAuB;gBAE1C,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,EAAE,oBAAoB;IAOhF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAErC,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,iBAAiB,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBjF,WAAW,IAAI,0BAA0B,EAAE;IAI3C,eAAe,IAAI,YAAY;IAI/B,gBAAgB,IAAI,aAAa;IAI3B,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,0BAA0B,EAAE;IAe9D,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAInE,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,8BAA8B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO9D,yBAAyB,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjE,2BAA2B,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9F,yBAAyB,IAAI,OAAO,CAAC,cAAc,CAAC;YAS5C,oBAAoB;IAalC,OAAO,CAAC,sBAAsB;IAQ9B,OAAO,CAAC,wBAAwB;YAgClB,gBAAgB;IAmC9B,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,oBAAoB;IAqB5B,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,KAAK;IAIb,kBAAkB,IAAI,MAAM;IAK5B,cAAc,CAAC,OAAO,EAAE,0BAA0B,EAAE,GAAG,IAAI;IAI3D,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,iBAAiB;CA0BzB"}
@@ -0,0 +1,244 @@
1
+ import OpenAI from 'openai';
2
+ import { logger } from '../logging';
3
+ import { CheckmateLogger } from '../logging/logger';
4
+ import { LoopDetectedError } from '../tools/loop-detector';
5
+ import { MessageHistory } from './message-history';
6
+ import { ResponseProcessor } from './response-processor';
7
+ export class AiClient {
8
+ client;
9
+ messages = [];
10
+ responseProcessor;
11
+ retryableStatus = [
12
+ 408,
13
+ 409,
14
+ 429,
15
+ 500,
16
+ 502,
17
+ 503,
18
+ 504,
19
+ LoopDetectedError.STATUS,
20
+ ];
21
+ step;
22
+ resolveStepResult;
23
+ temperature;
24
+ messageHistory = new MessageHistory();
25
+ constructor({ runtimeConfig, toolRegistry, extensionHost }) {
26
+ this.runtimeConfig = runtimeConfig;
27
+ this.toolRegistry = toolRegistry;
28
+ this.responseProcessor = new ResponseProcessor({ aiClient: this, extensionHost });
29
+ this.temperature = this.runtimeConfig.getTemperature();
30
+ }
31
+ runtimeConfig;
32
+ toolRegistry;
33
+ async initialize(step, resolveStepResult) {
34
+ this.client = new OpenAI({
35
+ apiKey: this.runtimeConfig.getApiKey(),
36
+ baseURL: this.runtimeConfig.getBaseURL(),
37
+ timeout: this.runtimeConfig.getTimeout(),
38
+ maxRetries: 0,
39
+ logLevel: this.runtimeConfig.getLogLevel(),
40
+ logger: CheckmateLogger.create('ai_client', this.runtimeConfig.getLogLevel()),
41
+ });
42
+ this.messages = [];
43
+ this.step = step;
44
+ this.resolveStepResult = resolveStepResult;
45
+ this.responseProcessor.resetStepTokens();
46
+ this.temperature = this.runtimeConfig.getTemperature();
47
+ }
48
+ getMessages() {
49
+ return this.messages;
50
+ }
51
+ getToolRegistry() {
52
+ return this.toolRegistry;
53
+ }
54
+ getRuntimeConfig() {
55
+ return this.runtimeConfig;
56
+ }
57
+ async sendMessage(userMessage) {
58
+ if (typeof userMessage === 'string') {
59
+ this.messages.push({ role: 'user', content: userMessage });
60
+ }
61
+ else {
62
+ this.messages.push(...userMessage);
63
+ }
64
+ return this.executeWithRetry(async () => {
65
+ const tools = await this.toolRegistry.getTools();
66
+ const response = await this.createChatCompletion(tools, true);
67
+ this.appendResponseMessages(response);
68
+ await this.responseProcessor.handleResponse(response, this.step, this.resolveStepResult);
69
+ });
70
+ }
71
+ async addToolResponse(toolCallId, content) {
72
+ this.messages.push({ role: 'tool', tool_call_id: toolCallId, content });
73
+ }
74
+ async addUserMessage(content) {
75
+ this.messages.push({ role: 'user', content });
76
+ }
77
+ async addToolExecutionSummaryMessage(content) {
78
+ this.messages.push({
79
+ role: 'user',
80
+ content: `${MessageHistory.TOOL_EXECUTION_SUMMARY_IDENTIFIER}:\n${content}`,
81
+ });
82
+ }
83
+ async addCurrentSnapshotMessage(snapshotContent) {
84
+ this.messages.push(this.messageHistory.createSnapshotMessage(snapshotContent));
85
+ }
86
+ async addCurrentScreenshotMessage(base64Data, mimeType = 'image/png') {
87
+ this.messages.push(this.messageHistory.createScreenshotMessage(base64Data, mimeType));
88
+ }
89
+ async sendToolResponseWithRetry() {
90
+ return this.executeWithRetry(async () => {
91
+ const tools = await this.toolRegistry.getTools();
92
+ const response = await this.createChatCompletion(tools, false);
93
+ this.appendResponseMessages(response);
94
+ return response;
95
+ });
96
+ }
97
+ async createChatCompletion(tools, includeN) {
98
+ return this.client.chat.completions.create({
99
+ model: this.runtimeConfig.getModel(),
100
+ messages: this.messages,
101
+ tools,
102
+ tool_choice: this.runtimeConfig.getToolChoice(),
103
+ parallel_tool_calls: false,
104
+ temperature: this.temperature,
105
+ reasoning_effort: this.runtimeConfig.getReasoningEffort(),
106
+ ...(includeN ? { n: 1 } : {}),
107
+ });
108
+ }
109
+ appendResponseMessages(response) {
110
+ response.choices.forEach((choice) => {
111
+ if (choice.message) {
112
+ this.messages.push(this.sanitizeAssistantMessage(choice.message));
113
+ }
114
+ });
115
+ }
116
+ sanitizeAssistantMessage(message) {
117
+ const sanitizedMessage = { role: 'assistant' };
118
+ if (message.content !== undefined) {
119
+ sanitizedMessage.content = message.content;
120
+ }
121
+ if ('refusal' in message && message.refusal !== undefined) {
122
+ sanitizedMessage.refusal = message.refusal;
123
+ }
124
+ if ('tool_calls' in message && message.tool_calls !== undefined) {
125
+ sanitizedMessage.tool_calls = message.tool_calls;
126
+ }
127
+ if ('function_call' in message && message.function_call !== undefined) {
128
+ sanitizedMessage.function_call = message.function_call;
129
+ }
130
+ if ('name' in message && typeof message.name === 'string') {
131
+ sanitizedMessage.name = message.name;
132
+ }
133
+ if ('audio' in message && message.audio !== undefined) {
134
+ sanitizedMessage.audio = message.audio;
135
+ }
136
+ return sanitizedMessage;
137
+ }
138
+ async executeWithRetry(operation) {
139
+ const maxRetries = this.runtimeConfig.getMaxRetries();
140
+ let lastError = null;
141
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
142
+ try {
143
+ return await operation();
144
+ }
145
+ catch (error) {
146
+ lastError = error instanceof Error ? error : new Error(String(error));
147
+ if ((!this.isRetryable(error) && !this.isToolError(error)) || attempt === maxRetries) {
148
+ throw this.enhanceError(error);
149
+ }
150
+ if (error instanceof LoopDetectedError) {
151
+ const antiLoopTempChange = Math.round(Math.random() * 10) / 10;
152
+ logger.warn(`repeated tool calls detected: adjusting temperature from ${this.temperature} to ${antiLoopTempChange} to mitigate looping`);
153
+ this.temperature = antiLoopTempChange;
154
+ }
155
+ const retryAfter = this.getRetryAfterSeconds(error);
156
+ const delay = retryAfter ? retryAfter * 1000 : this.calculateBackoff(attempt);
157
+ logger.warn(`status: ${this.getStatus(error)} retry attempt: ${attempt + 1}/${maxRetries} starting in: ${delay}ms ...`);
158
+ logger.debug(`retryable error details:\n${JSON.stringify(error, null, 2)}`);
159
+ await this.sleep(delay);
160
+ }
161
+ }
162
+ throw lastError || new Error('Unexpected error in retry loop');
163
+ }
164
+ getStatus(error) {
165
+ if (typeof error !== 'object' || error === null) {
166
+ return null;
167
+ }
168
+ const status = error.status ??
169
+ error.statusCode ??
170
+ error.code;
171
+ return typeof status === 'number' || typeof status === 'string' ? status : null;
172
+ }
173
+ isRetryable(error) {
174
+ const statusCode = this.getStatus(error);
175
+ return statusCode !== null && this.retryableStatus.includes(statusCode);
176
+ }
177
+ getRetryAfterSeconds(error) {
178
+ if (typeof error !== 'object' || error === null) {
179
+ return null;
180
+ }
181
+ const headers = error.headers;
182
+ if (!headers) {
183
+ return null;
184
+ }
185
+ const retryAfter = headers.get?.('retry-after') ??
186
+ headers['retry-after'];
187
+ if (!retryAfter) {
188
+ return null;
189
+ }
190
+ const seconds = parseInt(retryAfter, 10);
191
+ return Number.isNaN(seconds) ? null : seconds;
192
+ }
193
+ calculateBackoff(attempt) {
194
+ const delays = [1_000, 10_000, 60_000];
195
+ return delays[Math.min(attempt, delays.length - 1)];
196
+ }
197
+ sleep(ms) {
198
+ return new Promise((resolve) => setTimeout(resolve, ms));
199
+ }
200
+ countHistoryTokens() {
201
+ const totalChars = this.messages.reduce((sum, msg) => sum + this.countContentChars(msg.content), 0);
202
+ return Math.ceil(totalChars / 4);
203
+ }
204
+ replaceHistory(history) {
205
+ this.messages = [...history];
206
+ }
207
+ enhanceError(error) {
208
+ const status = this.getStatus(error) ?? 'unknown';
209
+ const message = error instanceof Error ? error.message : null;
210
+ return new Error(`OpenAI API error [${status}]: ${message ?? JSON.stringify(error, null, 2)}`);
211
+ }
212
+ isToolError(error) {
213
+ const errorAsString = JSON.stringify(error, null, 2).toLowerCase();
214
+ if (this.getStatus(error) === 400 && errorAsString.includes('tool')) {
215
+ logger.warn('tool call error detected [400]');
216
+ void this.addUserMessage('you did not call a tool or called it incorrectly, try again and always only call a tool with correct parameters to proceed with the step.');
217
+ return true;
218
+ }
219
+ return false;
220
+ }
221
+ countContentChars(content) {
222
+ if (typeof content === 'string') {
223
+ return content.length;
224
+ }
225
+ if (!Array.isArray(content)) {
226
+ return 0;
227
+ }
228
+ let totalChars = 0;
229
+ for (const part of content) {
230
+ if ('text' in part && typeof part.text === 'string') {
231
+ totalChars += part.text.length;
232
+ continue;
233
+ }
234
+ if ('image_url' in part) {
235
+ const imageUrl = part.image_url;
236
+ if (typeof imageUrl.url === 'string') {
237
+ totalChars += imageUrl.url.length;
238
+ }
239
+ }
240
+ }
241
+ return totalChars;
242
+ }
243
+ }
244
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/ai/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { ChatCompletion } from 'openai/resources/chat/completions';
2
+ import { Step, ResolveStepResult } from '../runtime/types';
3
+ import { AiClient } from './client';
4
+ import { ResponseProcessor } from './response-processor';
5
+ export declare class MessageHandler {
6
+ private readonly aiClient;
7
+ private readonly responseProcessor;
8
+ constructor(aiClient: AiClient, responseProcessor: ResponseProcessor);
9
+ handle(choice: ChatCompletion.Choice, step: Step, resolveStepResult: ResolveStepResult): Promise<void>;
10
+ }
11
+ //# sourceMappingURL=message-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-handler.d.ts","sourceRoot":"","sources":["../../src/ai/message-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAA;AAElE,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAE1D,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AACnC,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAExD,qBAAa,cAAc;IAEzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,iBAAiB;gBADjB,QAAQ,EAAE,QAAQ,EAClB,iBAAiB,EAAE,iBAAiB;IAGhD,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,iBAAiB,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;CA8B5G"}
@@ -0,0 +1,33 @@
1
+ import { logger } from '../logging';
2
+ import { StepResultTool } from '../tools/step/result-tool';
3
+ export class MessageHandler {
4
+ aiClient;
5
+ responseProcessor;
6
+ constructor(aiClient, responseProcessor) {
7
+ this.aiClient = aiClient;
8
+ this.responseProcessor = responseProcessor;
9
+ }
10
+ async handle(choice, step, resolveStepResult) {
11
+ const { message } = choice;
12
+ if (choice.finish_reason === 'stop' || message.content) {
13
+ logger.warn(`text: ${message.content}`);
14
+ logger.warn(`warning: model responded with text but no tool call. Prompting to use ${StepResultTool.TOOL_PASS_TEST_STEP} or ${StepResultTool.TOOL_FAIL_TEST_STEP}.`);
15
+ await this.aiClient.addUserMessage(`You provided a text response but did not call a tool. Based on your analysis, call either '${StepResultTool.TOOL_PASS_TEST_STEP}' or '${StepResultTool.TOOL_FAIL_TEST_STEP}' with the actual result. Do not respond with text. Only use the tool.`);
16
+ const followUpResponse = await this.aiClient.sendToolResponseWithRetry();
17
+ await this.responseProcessor.handleResponse(followUpResponse, step, resolveStepResult);
18
+ return;
19
+ }
20
+ if (choice.finish_reason && choice.finish_reason !== 'tool_calls') {
21
+ resolveStepResult({
22
+ passed: false,
23
+ actual: `OpenAI API finished unexpectedly with reason: ${choice.finish_reason}`,
24
+ });
25
+ return;
26
+ }
27
+ if (!choice.message.content && (!choice.message.tool_calls || choice.message.tool_calls.length === 0)) {
28
+ throw new Error(`No content or tool calls found in message:\n${JSON.stringify(choice.message, null, 2)}`);
29
+ }
30
+ throw new Error(`Unhandled choice on response from model:\n${JSON.stringify(choice, null, 2)}`);
31
+ }
32
+ }
33
+ //# sourceMappingURL=message-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-handler.js","sourceRoot":"","sources":["../../src/ai/message-handler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAEnC,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAI1D,MAAM,OAAO,cAAc;IAER;IACA;IAFlB,YACkB,QAAkB,EAClB,iBAAoC;QADpC,aAAQ,GAAR,QAAQ,CAAU;QAClB,sBAAiB,GAAjB,iBAAiB,CAAmB;IACnD,CAAC;IAEJ,KAAK,CAAC,MAAM,CAAC,MAA6B,EAAE,IAAU,EAAE,iBAAoC;QAC3F,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAA;QAE1B,IAAI,MAAM,CAAC,aAAa,KAAK,MAAM,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACxD,MAAM,CAAC,IAAI,CAAC,SAAS,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;YACvC,MAAM,CAAC,IAAI,CACV,yEAAyE,cAAc,CAAC,mBAAmB,OAAO,cAAc,CAAC,mBAAmB,GAAG,CACvJ,CAAA;YACD,MAAM,IAAI,CAAC,QAAQ,CAAC,cAAc,CACjC,8FAA8F,cAAc,CAAC,mBAAmB,SAAS,cAAc,CAAC,mBAAmB,wEAAwE,CACnP,CAAA;YACD,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,yBAAyB,EAAE,CAAA;YACxE,MAAM,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,gBAAgB,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAA;YACtF,OAAM;QACP,CAAC;QAED,IAAI,MAAM,CAAC,aAAa,IAAI,MAAM,CAAC,aAAa,KAAK,YAAY,EAAE,CAAC;YACnE,iBAAiB,CAAC;gBACjB,MAAM,EAAE,KAAK;gBACb,MAAM,EAAE,iDAAiD,MAAM,CAAC,aAAa,EAAE;aAC/E,CAAC,CAAA;YACF,OAAM;QACP,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;YACvG,MAAM,IAAI,KAAK,CAAC,+CAA+C,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAA;QAC1G,CAAC;QAED,MAAM,IAAI,KAAK,CAAC,6CAA6C,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAA;IAChG,CAAC;CACD"}
@@ -0,0 +1,19 @@
1
+ import { ChatCompletionMessageParam } from 'openai/resources/chat/completions';
2
+ import { AiClient } from './client';
3
+ type InitialMessageParameters = {
4
+ systemPrompt: string;
5
+ userPrompt: string;
6
+ contextMessages?: ChatCompletionMessageParam[];
7
+ };
8
+ export declare class MessageHistory {
9
+ static readonly SNAPSHOT_IDENTIFIER = "this is a current page snapshot";
10
+ static readonly SCREENSHOT_IDENTIFIER = "this is a current screenshot of the page";
11
+ static readonly TOOL_EXECUTION_SUMMARY_IDENTIFIER = "tool execution summary";
12
+ buildInitialMessages(config: InitialMessageParameters): ChatCompletionMessageParam[];
13
+ createSnapshotMessage(snapshotContent: string | null): ChatCompletionMessageParam;
14
+ createScreenshotMessage(base64Data: string, mimeType?: string): ChatCompletionMessageParam;
15
+ removeEphemeralStateMessages(aiClient: AiClient): void;
16
+ private isEphemeralStateMessage;
17
+ }
18
+ export {};
19
+ //# sourceMappingURL=message-history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-history.d.ts","sourceRoot":"","sources":["../../src/ai/message-history.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,mCAAmC,CAAA;AAC9E,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAEnC,KAAK,wBAAwB,GAAG;IAC/B,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,0BAA0B,EAAE,CAAA;CAC9C,CAAA;AAED,qBAAa,cAAc;IAC1B,MAAM,CAAC,QAAQ,CAAC,mBAAmB,qCAAoC;IACvE,MAAM,CAAC,QAAQ,CAAC,qBAAqB,8CAA6C;IAClF,MAAM,CAAC,QAAQ,CAAC,iCAAiC,4BAA2B;IAE5E,oBAAoB,CAAC,MAAM,EAAE,wBAAwB,GAAG,0BAA0B,EAAE;IAcpF,qBAAqB,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,GAAG,0BAA0B;IAOjF,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAoB,GAAG,0BAA0B;IAUvG,4BAA4B,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAItD,OAAO,CAAC,uBAAuB;CAe/B"}