@sayer/agent-step 0.1.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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -0
  3. package/dist/agent/action-executor.d.ts +20 -0
  4. package/dist/agent/action-executor.d.ts.map +1 -0
  5. package/dist/agent/action-executor.js +112 -0
  6. package/dist/agent/action-executor.js.map +1 -0
  7. package/dist/agent/agent-runtime.d.ts +11 -0
  8. package/dist/agent/agent-runtime.d.ts.map +1 -0
  9. package/dist/agent/agent-runtime.js +103 -0
  10. package/dist/agent/agent-runtime.js.map +1 -0
  11. package/dist/agent/browser-tools.d.ts +28 -0
  12. package/dist/agent/browser-tools.d.ts.map +1 -0
  13. package/dist/agent/browser-tools.js +259 -0
  14. package/dist/agent/browser-tools.js.map +1 -0
  15. package/dist/agent/model.d.ts +6 -0
  16. package/dist/agent/model.d.ts.map +1 -0
  17. package/dist/agent/model.js +161 -0
  18. package/dist/agent/model.js.map +1 -0
  19. package/dist/agent/redaction.d.ts +4 -0
  20. package/dist/agent/redaction.d.ts.map +1 -0
  21. package/dist/agent/redaction.js +37 -0
  22. package/dist/agent/redaction.js.map +1 -0
  23. package/dist/agent/types.d.ts +89 -0
  24. package/dist/agent/types.d.ts.map +1 -0
  25. package/dist/agent/types.js +2 -0
  26. package/dist/agent/types.js.map +1 -0
  27. package/dist/agent/verifier.d.ts +22 -0
  28. package/dist/agent/verifier.d.ts.map +1 -0
  29. package/dist/agent/verifier.js +209 -0
  30. package/dist/agent/verifier.js.map +1 -0
  31. package/dist/agent-step.d.ts +10 -0
  32. package/dist/agent-step.d.ts.map +1 -0
  33. package/dist/agent-step.js +68 -0
  34. package/dist/agent-step.js.map +1 -0
  35. package/dist/agent-test.d.ts +10 -0
  36. package/dist/agent-test.d.ts.map +1 -0
  37. package/dist/agent-test.js +7 -0
  38. package/dist/agent-test.js.map +1 -0
  39. package/dist/index.d.ts +4 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +3 -0
  42. package/dist/index.js.map +1 -0
  43. package/package.json +68 -0
@@ -0,0 +1,161 @@
1
+ import OpenAI from 'openai';
2
+ export function normalizeOpenAICompatibleBaseURL(baseURL) {
3
+ let normalized = baseURL.trim().replace(/\/+$/, '');
4
+ normalized = normalized.replace(/\/chat\/completions$/i, '');
5
+ normalized = normalized.replace(/\/+$/, '');
6
+ return normalized;
7
+ }
8
+ export function loadAgentConfig() {
9
+ const baseURL = process.env.AGENT_LLM_BASE_URL;
10
+ const apiKey = process.env.AGENT_LLM_API_KEY;
11
+ const model = process.env.AGENT_LLM_MODEL;
12
+ if (!baseURL || !apiKey || !model) {
13
+ throw new Error('Missing required environment variables: AGENT_LLM_BASE_URL, AGENT_LLM_API_KEY, AGENT_LLM_MODEL');
14
+ }
15
+ return {
16
+ baseURL: normalizeOpenAICompatibleBaseURL(baseURL),
17
+ apiKey,
18
+ model,
19
+ };
20
+ }
21
+ export function createModelClient(config) {
22
+ const baseURL = normalizeOpenAICompatibleBaseURL(config.baseURL);
23
+ const client = new OpenAI({
24
+ apiKey: config.apiKey,
25
+ baseURL,
26
+ defaultHeaders: {
27
+ 'HTTP-Referer': process.env.AGENT_LLM_SITE_URL ?? 'https://github.com/Sayer122/agent-step',
28
+ 'X-OpenRouter-Title': process.env.AGENT_LLM_SITE_NAME ?? 'agent-step',
29
+ },
30
+ });
31
+ return {
32
+ async chat(params) {
33
+ let response;
34
+ try {
35
+ response = await client.chat.completions.create({
36
+ model: config.model,
37
+ messages: params.messages.map(toOpenAIMessage),
38
+ tools: params.tools?.map((tool) => ({
39
+ type: 'function',
40
+ function: tool.function,
41
+ })),
42
+ tool_choice: params.toolChoice ?? 'auto',
43
+ response_format: params.responseFormat,
44
+ }, { signal: params.signal });
45
+ }
46
+ catch (error) {
47
+ throw wrapModelRequestError(error, baseURL, config.model);
48
+ }
49
+ return parseOpenAIChatResponse(response, baseURL, config.model);
50
+ },
51
+ };
52
+ }
53
+ export function parseOpenAIChatResponse(response, baseURL, model) {
54
+ const record = asRecord(response);
55
+ if (record?.error) {
56
+ throw wrapModelRequestError(embeddedProviderError(record.error), baseURL, model);
57
+ }
58
+ const choices = record?.choices;
59
+ const choice = Array.isArray(choices) ? asRecord(choices[0]) : undefined;
60
+ const message = asRecord(choice?.message);
61
+ if (!choice || !message) {
62
+ throw wrapModelRequestError(new Error(`LLM returned no chat choices. body=${truncateJson(response)}`), baseURL, model);
63
+ }
64
+ const usageRecord = asRecord(record?.usage);
65
+ const usage = {
66
+ promptTokens: numberOrZero(usageRecord?.prompt_tokens),
67
+ completionTokens: numberOrZero(usageRecord?.completion_tokens),
68
+ totalTokens: numberOrZero(usageRecord?.total_tokens),
69
+ };
70
+ return {
71
+ content: typeof message.content === 'string' ? message.content : null,
72
+ toolCalls: readToolCalls(message.tool_calls),
73
+ usage,
74
+ };
75
+ }
76
+ function wrapModelRequestError(error, baseURL, model) {
77
+ const record = asRecord(error);
78
+ const status = numberOrUndefined(record?.status);
79
+ const message = error instanceof Error ? error.message : String(error);
80
+ const hint = status === 404
81
+ ? ` Check AGENT_LLM_BASE_URL. The OpenAI SDK posts to ${baseURL}/chat/completions; for OpenRouter use https://openrouter.ai/api/v1, not .../chat/completions.`
82
+ : message.includes('no chat choices')
83
+ ? ' The provider accepted the request but returned no completion. Check AGENT_LLM_MODEL supports tool calling on this endpoint.'
84
+ : '';
85
+ return new Error(`LLM request failed (${status ?? 'unknown'}): ${message}. model=${model} baseURL=${baseURL}.${hint}`, { cause: error instanceof Error ? error : undefined });
86
+ }
87
+ function readToolCalls(value) {
88
+ if (!Array.isArray(value))
89
+ return [];
90
+ return value.flatMap((item, index) => {
91
+ const call = asRecord(item);
92
+ const fn = asRecord(call?.function);
93
+ const name = typeof fn?.name === 'string' ? fn.name : undefined;
94
+ if (!name)
95
+ return [];
96
+ return [
97
+ {
98
+ id: typeof call?.id === 'string' ? call.id : `call_${index}`,
99
+ name,
100
+ arguments: typeof fn?.arguments === 'string' ? fn.arguments : '{}',
101
+ },
102
+ ];
103
+ });
104
+ }
105
+ function embeddedProviderError(error) {
106
+ const record = asRecord(error);
107
+ const message = typeof record?.message === 'string'
108
+ ? record.message
109
+ : truncateJson(error);
110
+ const code = record?.code;
111
+ return new Error(code === undefined ? message : `${message} (code ${String(code)})`);
112
+ }
113
+ function asRecord(value) {
114
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
115
+ return undefined;
116
+ }
117
+ return value;
118
+ }
119
+ function numberOrZero(value) {
120
+ return typeof value === 'number' && Number.isFinite(value) ? value : 0;
121
+ }
122
+ function numberOrUndefined(value) {
123
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
124
+ }
125
+ function truncateJson(value, max = 800) {
126
+ try {
127
+ const text = JSON.stringify(value) ?? String(value);
128
+ return text.length > max ? `${text.slice(0, max)}…` : text;
129
+ }
130
+ catch {
131
+ return String(value);
132
+ }
133
+ }
134
+ function toOpenAIMessage(message) {
135
+ if (message.role === 'tool') {
136
+ return {
137
+ role: 'tool',
138
+ tool_call_id: message.toolCallId,
139
+ content: message.content ?? '',
140
+ };
141
+ }
142
+ if (message.role === 'assistant') {
143
+ return {
144
+ role: 'assistant',
145
+ content: message.content,
146
+ tool_calls: message.toolCalls?.map((call) => ({
147
+ id: call.id,
148
+ type: 'function',
149
+ function: {
150
+ name: call.name,
151
+ arguments: call.arguments,
152
+ },
153
+ })),
154
+ };
155
+ }
156
+ return {
157
+ role: message.role,
158
+ content: message.content ?? '',
159
+ };
160
+ }
161
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/agent/model.ts"],"names":[],"mappings":"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","sourcesContent":["import OpenAI from 'openai';\nimport type {\n AgentConfig,\n ChatParams,\n ChatResponse,\n ModelClient,\n TokenUsage,\n} from './types.js';\n\nexport function normalizeOpenAICompatibleBaseURL(baseURL: string): string {\n let normalized = baseURL.trim().replace(/\\/+$/, '');\n normalized = normalized.replace(/\\/chat\\/completions$/i, '');\n normalized = normalized.replace(/\\/+$/, '');\n return normalized;\n}\n\nexport function loadAgentConfig(): AgentConfig {\n const baseURL = process.env.AGENT_LLM_BASE_URL;\n const apiKey = process.env.AGENT_LLM_API_KEY;\n const model = process.env.AGENT_LLM_MODEL;\n\n if (!baseURL || !apiKey || !model) {\n throw new Error(\n 'Missing required environment variables: AGENT_LLM_BASE_URL, AGENT_LLM_API_KEY, AGENT_LLM_MODEL',\n );\n }\n\n return {\n baseURL: normalizeOpenAICompatibleBaseURL(baseURL),\n apiKey,\n model,\n };\n}\n\nexport function createModelClient(config: AgentConfig): ModelClient {\n const baseURL = normalizeOpenAICompatibleBaseURL(config.baseURL);\n const client = new OpenAI({\n apiKey: config.apiKey,\n baseURL,\n defaultHeaders: {\n 'HTTP-Referer': process.env.AGENT_LLM_SITE_URL ?? 'https://github.com/Sayer122/agent-step',\n 'X-OpenRouter-Title':\n process.env.AGENT_LLM_SITE_NAME ?? 'agent-step',\n },\n });\n\n return {\n async chat(params: ChatParams): Promise<ChatResponse> {\n let response;\n try {\n response = await client.chat.completions.create(\n {\n model: config.model,\n messages: params.messages.map(toOpenAIMessage),\n tools: params.tools?.map((tool) => ({\n type: 'function' as const,\n function: tool.function,\n })),\n tool_choice: params.toolChoice ?? 'auto',\n response_format: params.responseFormat,\n },\n { signal: params.signal },\n );\n } catch (error) {\n throw wrapModelRequestError(error, baseURL, config.model);\n }\n\n return parseOpenAIChatResponse(response, baseURL, config.model);\n },\n };\n}\n\nexport function parseOpenAIChatResponse(\n response: unknown,\n baseURL: string,\n model: string,\n): ChatResponse {\n const record = asRecord(response);\n if (record?.error) {\n throw wrapModelRequestError(\n embeddedProviderError(record.error),\n baseURL,\n model,\n );\n }\n\n const choices = record?.choices;\n const choice = Array.isArray(choices) ? asRecord(choices[0]) : undefined;\n const message = asRecord(choice?.message);\n\n if (!choice || !message) {\n throw wrapModelRequestError(\n new Error(\n `LLM returned no chat choices. body=${truncateJson(response)}`,\n ),\n baseURL,\n model,\n );\n }\n\n const usageRecord = asRecord(record?.usage);\n const usage: TokenUsage = {\n promptTokens: numberOrZero(usageRecord?.prompt_tokens),\n completionTokens: numberOrZero(usageRecord?.completion_tokens),\n totalTokens: numberOrZero(usageRecord?.total_tokens),\n };\n\n return {\n content: typeof message.content === 'string' ? message.content : null,\n toolCalls: readToolCalls(message.tool_calls),\n usage,\n };\n}\n\nfunction wrapModelRequestError(\n error: unknown,\n baseURL: string,\n model: string,\n): Error {\n const record = asRecord(error);\n const status = numberOrUndefined(record?.status);\n const message = error instanceof Error ? error.message : String(error);\n const hint =\n status === 404\n ? ` Check AGENT_LLM_BASE_URL. The OpenAI SDK posts to ${baseURL}/chat/completions; for OpenRouter use https://openrouter.ai/api/v1, not .../chat/completions.`\n : message.includes('no chat choices')\n ? ' The provider accepted the request but returned no completion. Check AGENT_LLM_MODEL supports tool calling on this endpoint.'\n : '';\n\n return new Error(\n `LLM request failed (${status ?? 'unknown'}): ${message}. model=${model} baseURL=${baseURL}.${hint}`,\n { cause: error instanceof Error ? error : undefined },\n );\n}\n\nfunction readToolCalls(value: unknown): ChatResponse['toolCalls'] {\n if (!Array.isArray(value)) return [];\n return value.flatMap((item, index) => {\n const call = asRecord(item);\n const fn = asRecord(call?.function);\n const name = typeof fn?.name === 'string' ? fn.name : undefined;\n if (!name) return [];\n return [\n {\n id: typeof call?.id === 'string' ? call.id : `call_${index}`,\n name,\n arguments: typeof fn?.arguments === 'string' ? fn.arguments : '{}',\n },\n ];\n });\n}\n\nfunction embeddedProviderError(error: unknown): Error {\n const record = asRecord(error);\n const message =\n typeof record?.message === 'string'\n ? record.message\n : truncateJson(error);\n const code = record?.code;\n return new Error(\n code === undefined ? message : `${message} (code ${String(code)})`,\n );\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return undefined;\n }\n return value as Record<string, unknown>;\n}\n\nfunction numberOrZero(value: unknown): number {\n return typeof value === 'number' && Number.isFinite(value) ? value : 0;\n}\n\nfunction numberOrUndefined(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction truncateJson(value: unknown, max = 800): string {\n try {\n const text = JSON.stringify(value) ?? String(value);\n return text.length > max ? `${text.slice(0, max)}…` : text;\n } catch {\n return String(value);\n }\n}\n\nfunction toOpenAIMessage(\n message: ChatParams['messages'][number],\n): OpenAI.Chat.Completions.ChatCompletionMessageParam {\n if (message.role === 'tool') {\n return {\n role: 'tool',\n tool_call_id: message.toolCallId!,\n content: message.content ?? '',\n };\n }\n\n if (message.role === 'assistant') {\n return {\n role: 'assistant',\n content: message.content,\n tool_calls: message.toolCalls?.map((call) => ({\n id: call.id,\n type: 'function' as const,\n function: {\n name: call.name,\n arguments: call.arguments,\n },\n })),\n };\n }\n\n return {\n role: message.role,\n content: message.content ?? '',\n };\n}\n"]}
@@ -0,0 +1,4 @@
1
+ export declare function resolveSecretValue(value: string, secrets: Record<string, string>): string;
2
+ export declare function redactSecrets(text: string, secrets: Record<string, string>): string;
3
+ export declare function redactObject(value: unknown, secrets: Record<string, string>): unknown;
4
+ //# sourceMappingURL=redaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../../src/agent/redaction.ts"],"names":[],"mappings":"AAEA,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,MAAM,CAQR;AAED,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,MAAM,CAOR;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,OAAO,CAeT"}
@@ -0,0 +1,37 @@
1
+ const SECRET_PLACEHOLDER = /^%([A-Za-z_][A-Za-z0-9_]*)%$/;
2
+ export function resolveSecretValue(value, secrets) {
3
+ const match = value.match(SECRET_PLACEHOLDER);
4
+ if (!match)
5
+ return value;
6
+ const key = match[1];
7
+ if (!(key in secrets)) {
8
+ throw new Error(`Missing secret placeholder value for %${key}%`);
9
+ }
10
+ return secrets[key];
11
+ }
12
+ export function redactSecrets(text, secrets) {
13
+ let result = text;
14
+ for (const [key, value] of Object.entries(secrets)) {
15
+ if (!value)
16
+ continue;
17
+ result = result.split(value).join(`%${key}%`);
18
+ }
19
+ return result;
20
+ }
21
+ export function redactObject(value, secrets) {
22
+ if (typeof value === 'string') {
23
+ return redactSecrets(value, secrets);
24
+ }
25
+ if (Array.isArray(value)) {
26
+ return value.map((item) => redactObject(item, secrets));
27
+ }
28
+ if (value && typeof value === 'object') {
29
+ const result = {};
30
+ for (const [key, nested] of Object.entries(value)) {
31
+ result[key] = redactObject(nested, secrets);
32
+ }
33
+ return result;
34
+ }
35
+ return value;
36
+ }
37
+ //# sourceMappingURL=redaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redaction.js","sourceRoot":"","sources":["../../src/agent/redaction.ts"],"names":[],"mappings":"AAAA,MAAM,kBAAkB,GAAG,8BAA8B,CAAC;AAE1D,MAAM,UAAU,kBAAkB,CAChC,KAAa,EACb,OAA+B;IAE/B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;IAC9C,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACrB,IAAI,CAAC,CAAC,GAAG,IAAI,OAAO,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,yCAAyC,GAAG,GAAG,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,IAAY,EACZ,OAA+B;IAE/B,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,KAAc,EACd,OAA+B;IAE/B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACvC,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IAC1D,CAAC;IACD,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvC,MAAM,MAAM,GAA4B,EAAE,CAAC;QAC3C,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAClD,MAAM,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC9C,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["const SECRET_PLACEHOLDER = /^%([A-Za-z_][A-Za-z0-9_]*)%$/;\n\nexport function resolveSecretValue(\n value: string,\n secrets: Record<string, string>,\n): string {\n const match = value.match(SECRET_PLACEHOLDER);\n if (!match) return value;\n const key = match[1];\n if (!(key in secrets)) {\n throw new Error(`Missing secret placeholder value for %${key}%`);\n }\n return secrets[key];\n}\n\nexport function redactSecrets(\n text: string,\n secrets: Record<string, string>,\n): string {\n let result = text;\n for (const [key, value] of Object.entries(secrets)) {\n if (!value) continue;\n result = result.split(value).join(`%${key}%`);\n }\n return result;\n}\n\nexport function redactObject(\n value: unknown,\n secrets: Record<string, string>,\n): unknown {\n if (typeof value === 'string') {\n return redactSecrets(value, secrets);\n }\n if (Array.isArray(value)) {\n return value.map((item) => redactObject(item, secrets));\n }\n if (value && typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, nested] of Object.entries(value)) {\n result[key] = redactObject(nested, secrets);\n }\n return result;\n }\n return value;\n}\n"]}
@@ -0,0 +1,89 @@
1
+ import type { Page, TestInfo } from '@playwright/test';
2
+ export interface AgentStepInput {
3
+ action: string;
4
+ expect: string[];
5
+ timeout?: number;
6
+ secrets?: Record<string, string>;
7
+ }
8
+ export interface AgentStepResult {
9
+ action: string;
10
+ expect: string[];
11
+ actionTranscript: TranscriptEntry[];
12
+ verification: VerificationResult;
13
+ tokenUsage: TokenUsage;
14
+ rawVerifierContent?: string;
15
+ }
16
+ export interface TranscriptEntry {
17
+ turn: number;
18
+ type: 'tool_call' | 'tool_result' | 'message';
19
+ name?: string;
20
+ input?: unknown;
21
+ output?: unknown;
22
+ content?: string;
23
+ }
24
+ export interface TokenUsage {
25
+ promptTokens: number;
26
+ completionTokens: number;
27
+ totalTokens: number;
28
+ }
29
+ export interface CriterionVerdict {
30
+ criterion: string;
31
+ passed: boolean;
32
+ evidence: string;
33
+ }
34
+ export interface VerificationResult {
35
+ passed: boolean;
36
+ criteria: CriterionVerdict[];
37
+ inconclusive: boolean;
38
+ }
39
+ export interface AgentRuntimeOptions {
40
+ page: Page;
41
+ testInfo: TestInfo;
42
+ allowedOrigins?: string[];
43
+ maxTurns?: number;
44
+ defaultTimeout?: number;
45
+ /** Override model client for deterministic framework tests. */
46
+ modelClient?: ModelClient;
47
+ }
48
+ export interface ModelClient {
49
+ chat(params: ChatParams): Promise<ChatResponse>;
50
+ }
51
+ export interface ChatParams {
52
+ messages: ChatMessage[];
53
+ tools?: ToolDefinition[];
54
+ toolChoice?: 'auto' | 'none' | 'required';
55
+ responseFormat?: {
56
+ type: 'json_object';
57
+ };
58
+ signal?: AbortSignal;
59
+ }
60
+ export interface ChatMessage {
61
+ role: 'system' | 'user' | 'assistant' | 'tool';
62
+ content?: string | null;
63
+ toolCallId?: string;
64
+ toolCalls?: ToolCall[];
65
+ }
66
+ export interface ToolCall {
67
+ id: string;
68
+ name: string;
69
+ arguments: string;
70
+ }
71
+ export interface ToolDefinition {
72
+ type: 'function';
73
+ function: {
74
+ name: string;
75
+ description: string;
76
+ parameters: Record<string, unknown>;
77
+ };
78
+ }
79
+ export interface ChatResponse {
80
+ content: string | null;
81
+ toolCalls: ToolCall[];
82
+ usage: TokenUsage;
83
+ }
84
+ export interface AgentConfig {
85
+ baseURL: string;
86
+ apiKey: string;
87
+ model: string;
88
+ }
89
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/agent/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/agent/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Page, TestInfo } from '@playwright/test';\n\nexport interface AgentStepInput {\n action: string;\n expect: string[];\n timeout?: number;\n secrets?: Record<string, string>;\n}\n\nexport interface AgentStepResult {\n action: string;\n expect: string[];\n actionTranscript: TranscriptEntry[];\n verification: VerificationResult;\n tokenUsage: TokenUsage;\n rawVerifierContent?: string;\n}\n\nexport interface TranscriptEntry {\n turn: number;\n type: 'tool_call' | 'tool_result' | 'message';\n name?: string;\n input?: unknown;\n output?: unknown;\n content?: string;\n}\n\nexport interface TokenUsage {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n}\n\nexport interface CriterionVerdict {\n criterion: string;\n passed: boolean;\n evidence: string;\n}\n\nexport interface VerificationResult {\n passed: boolean;\n criteria: CriterionVerdict[];\n inconclusive: boolean;\n}\n\nexport interface AgentRuntimeOptions {\n page: Page;\n testInfo: TestInfo;\n allowedOrigins?: string[];\n maxTurns?: number;\n defaultTimeout?: number;\n /** Override model client for deterministic framework tests. */\n modelClient?: ModelClient;\n}\n\nexport interface ModelClient {\n chat(params: ChatParams): Promise<ChatResponse>;\n}\n\nexport interface ChatParams {\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n toolChoice?: 'auto' | 'none' | 'required';\n responseFormat?: { type: 'json_object' };\n signal?: AbortSignal;\n}\n\nexport interface ChatMessage {\n role: 'system' | 'user' | 'assistant' | 'tool';\n content?: string | null;\n toolCallId?: string;\n toolCalls?: ToolCall[];\n}\n\nexport interface ToolCall {\n id: string;\n name: string;\n arguments: string;\n}\n\nexport interface ToolDefinition {\n type: 'function';\n function: {\n name: string;\n description: string;\n parameters: Record<string, unknown>;\n };\n}\n\nexport interface ChatResponse {\n content: string | null;\n toolCalls: ToolCall[];\n usage: TokenUsage;\n}\n\nexport interface AgentConfig {\n baseURL: string;\n apiKey: string;\n model: string;\n}\n"]}
@@ -0,0 +1,22 @@
1
+ import type { ModelClient, TokenUsage, VerificationResult } from './types.js';
2
+ export interface VerifierOptions {
3
+ modelClient: ModelClient;
4
+ expect: string[];
5
+ snapshot: unknown;
6
+ url: string;
7
+ signal: AbortSignal;
8
+ }
9
+ export interface VerifierResult {
10
+ verification: VerificationResult;
11
+ tokenUsage: TokenUsage;
12
+ rawContent: string;
13
+ }
14
+ export declare class Verifier {
15
+ private readonly options;
16
+ constructor(options: VerifierOptions);
17
+ run(): Promise<VerifierResult>;
18
+ private requestVerdict;
19
+ }
20
+ export declare function parseJsonObject(content: string): Record<string, unknown>;
21
+ export declare function normalizeVerification(parsed: Record<string, unknown>, expected: string[]): VerificationResult;
22
+ //# sourceMappingURL=verifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verifier.d.ts","sourceRoot":"","sources":["../../src/agent/verifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,WAAW,EACX,UAAU,EACV,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,WAAW,CAAC;IACzB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,kBAAkB,CAAC;IACjC,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB;AAcD,qBAAa,QAAQ;IACP,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,eAAe;IAE/C,GAAG,IAAI,OAAO,CAAC,cAAc,CAAC;YAkCtB,cAAc;CAsD7B;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAcxE;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,QAAQ,EAAE,MAAM,EAAE,GACjB,kBAAkB,CA4CpB"}
@@ -0,0 +1,209 @@
1
+ const VERIFIER_SYSTEM_PROMPT = [
2
+ 'You are a read-only test verifier.',
3
+ 'Given an accessibility snapshot and pass criteria, decide whether each criterion is satisfied.',
4
+ 'Use only evidence visible in the snapshot or URL.',
5
+ 'If evidence is insufficient, set passed=false for that criterion and inconclusive=true.',
6
+ 'Respond with a JSON object only. Do not wrap it in markdown.',
7
+ 'Required shape:',
8
+ '{"inconclusive":false,"criteria":[{"criterion":"<exact expected string>","passed":true,"evidence":"<short visible evidence>"}]}',
9
+ 'Every criteria item MUST include boolean passed and string evidence.',
10
+ 'Include one criteria object for every expected string, using the expected text as criterion.',
11
+ ].join(' ');
12
+ export class Verifier {
13
+ options;
14
+ constructor(options) {
15
+ this.options = options;
16
+ }
17
+ async run() {
18
+ const first = await this.requestVerdict(undefined);
19
+ const firstVerification = normalizeVerification(first.parsed, this.options.expect);
20
+ if (!needsRepair(firstVerification)) {
21
+ return {
22
+ verification: firstVerification,
23
+ tokenUsage: first.usage,
24
+ rawContent: first.content,
25
+ };
26
+ }
27
+ const repaired = await this.requestVerdict(first.content);
28
+ const repairedVerification = normalizeVerification(repaired.parsed, this.options.expect);
29
+ const tokenUsage = {
30
+ promptTokens: first.usage.promptTokens + repaired.usage.promptTokens,
31
+ completionTokens: first.usage.completionTokens + repaired.usage.completionTokens,
32
+ totalTokens: first.usage.totalTokens + repaired.usage.totalTokens,
33
+ };
34
+ return {
35
+ verification: repairedVerification,
36
+ tokenUsage,
37
+ rawContent: repaired.content,
38
+ };
39
+ }
40
+ async requestVerdict(previousContent) {
41
+ const response = await this.options.modelClient.chat({
42
+ messages: [
43
+ {
44
+ role: 'system',
45
+ content: VERIFIER_SYSTEM_PROMPT,
46
+ },
47
+ {
48
+ role: 'user',
49
+ content: JSON.stringify({
50
+ url: this.options.url,
51
+ snapshot: this.options.snapshot,
52
+ expect: this.options.expect,
53
+ }, null, 2),
54
+ },
55
+ ...(previousContent
56
+ ? [
57
+ {
58
+ role: 'assistant',
59
+ content: previousContent,
60
+ },
61
+ {
62
+ role: 'user',
63
+ content: 'That JSON omitted required fields. Reply with JSON only in this exact shape: {"inconclusive":false,"criteria":[{"criterion":"<exact expected string>","passed":true,"evidence":"<short visible evidence>"}]}. Include every expected criterion, with boolean passed and string evidence on each item.',
64
+ },
65
+ ]
66
+ : []),
67
+ ],
68
+ toolChoice: 'none',
69
+ responseFormat: { type: 'json_object' },
70
+ signal: this.options.signal,
71
+ });
72
+ if (!response.content) {
73
+ throw new Error('Verifier returned empty content');
74
+ }
75
+ return {
76
+ content: response.content,
77
+ parsed: parseJsonObject(response.content),
78
+ usage: response.usage,
79
+ };
80
+ }
81
+ }
82
+ export function parseJsonObject(content) {
83
+ const trimmed = content.trim();
84
+ const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
85
+ const candidate = fenced ? fenced[1].trim() : trimmed;
86
+ try {
87
+ const parsed = JSON.parse(candidate);
88
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
89
+ throw new Error('Verifier JSON was not an object');
90
+ }
91
+ return parsed;
92
+ }
93
+ catch {
94
+ throw new Error(`Verifier returned malformed JSON: ${truncate(content)}`);
95
+ }
96
+ }
97
+ export function normalizeVerification(parsed, expected) {
98
+ const rawCriteria = Array.isArray(parsed.criteria)
99
+ ? parsed.criteria
100
+ : Array.isArray(parsed.results)
101
+ ? parsed.results
102
+ : [];
103
+ const normalized = rawCriteria.map((item, index) => normalizeCriterion(item, expected[index]));
104
+ const criteria = expected.map((criterion, index) => {
105
+ const exact = normalized.find((item) => item.criterion === criterion);
106
+ if (exact)
107
+ return { ...exact, criterion };
108
+ const loose = normalized.find((item) => item.criterion.trim().toLowerCase() === criterion.trim().toLowerCase());
109
+ if (loose)
110
+ return { ...loose, criterion };
111
+ if (normalized.length === expected.length) {
112
+ return { ...normalized[index], criterion };
113
+ }
114
+ return {
115
+ criterion,
116
+ passed: false,
117
+ evidence: 'Verifier did not return a verdict for this criterion',
118
+ };
119
+ });
120
+ const omittedVerdict = criteria.some((item) => item.evidence.startsWith('Verifier omitted') ||
121
+ item.evidence.startsWith('Verifier did not return') ||
122
+ item.evidence.startsWith('Verifier returned a non-object'));
123
+ const inconclusive = coerceBoolean(parsed.inconclusive) === true || omittedVerdict;
124
+ return {
125
+ passed: !inconclusive && criteria.every((item) => item.passed),
126
+ criteria,
127
+ inconclusive,
128
+ };
129
+ }
130
+ function needsRepair(verification) {
131
+ return verification.criteria.some((item) => item.evidence.startsWith('Verifier omitted') ||
132
+ item.evidence.startsWith('Verifier did not return') ||
133
+ item.evidence.startsWith('Verifier returned a non-object'));
134
+ }
135
+ function normalizeCriterion(item, fallbackCriterion) {
136
+ if (typeof item === 'string') {
137
+ return {
138
+ criterion: item,
139
+ passed: false,
140
+ evidence: 'Verifier omitted boolean passed and string evidence',
141
+ };
142
+ }
143
+ if (!item || typeof item !== 'object') {
144
+ return {
145
+ criterion: fallbackCriterion ?? 'unknown',
146
+ passed: false,
147
+ evidence: 'Verifier returned a non-object criterion',
148
+ };
149
+ }
150
+ const record = item;
151
+ const criterion = readString(record.criterion) ??
152
+ readString(record.expected) ??
153
+ readString(record.text) ??
154
+ fallbackCriterion ??
155
+ 'unknown';
156
+ const passed = coerceBoolean(record.passed ??
157
+ record.pass ??
158
+ record.ok ??
159
+ record.met ??
160
+ record.satisfied ??
161
+ record.success ??
162
+ record.status);
163
+ const evidence = readString(record.evidence) ??
164
+ readString(record.reason) ??
165
+ readString(record.explanation) ??
166
+ readString(record.details) ??
167
+ readString(record.notes);
168
+ if (passed === undefined) {
169
+ return {
170
+ criterion,
171
+ passed: false,
172
+ evidence: evidence
173
+ ? `Verifier omitted boolean passed. ${evidence}`
174
+ : 'Verifier omitted boolean passed and string evidence',
175
+ };
176
+ }
177
+ return {
178
+ criterion,
179
+ passed,
180
+ evidence: evidence ?? 'Verifier omitted evidence',
181
+ };
182
+ }
183
+ function coerceBoolean(value) {
184
+ if (typeof value === 'boolean')
185
+ return value;
186
+ if (typeof value === 'number') {
187
+ if (value === 1)
188
+ return true;
189
+ if (value === 0)
190
+ return false;
191
+ }
192
+ if (typeof value === 'string') {
193
+ const normalized = value.trim().toLowerCase();
194
+ if (['true', 'pass', 'passed', 'yes', 'ok', 'success'].includes(normalized)) {
195
+ return true;
196
+ }
197
+ if (['false', 'fail', 'failed', 'no', 'not_passed'].includes(normalized)) {
198
+ return false;
199
+ }
200
+ }
201
+ return undefined;
202
+ }
203
+ function readString(value) {
204
+ return typeof value === 'string' && value.trim() ? value : undefined;
205
+ }
206
+ function truncate(value, max = 500) {
207
+ return value.length > max ? `${value.slice(0, max)}…` : value;
208
+ }
209
+ //# sourceMappingURL=verifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verifier.js","sourceRoot":"","sources":["../../src/agent/verifier.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CriterionVerdict,\n ModelClient,\n TokenUsage,\n VerificationResult,\n} from './types.js';\n\nexport interface VerifierOptions {\n modelClient: ModelClient;\n expect: string[];\n snapshot: unknown;\n url: string;\n signal: AbortSignal;\n}\n\nexport interface VerifierResult {\n verification: VerificationResult;\n tokenUsage: TokenUsage;\n rawContent: string;\n}\n\nconst VERIFIER_SYSTEM_PROMPT = [\n 'You are a read-only test verifier.',\n 'Given an accessibility snapshot and pass criteria, decide whether each criterion is satisfied.',\n 'Use only evidence visible in the snapshot or URL.',\n 'If evidence is insufficient, set passed=false for that criterion and inconclusive=true.',\n 'Respond with a JSON object only. Do not wrap it in markdown.',\n 'Required shape:',\n '{\"inconclusive\":false,\"criteria\":[{\"criterion\":\"<exact expected string>\",\"passed\":true,\"evidence\":\"<short visible evidence>\"}]}',\n 'Every criteria item MUST include boolean passed and string evidence.',\n 'Include one criteria object for every expected string, using the expected text as criterion.',\n].join(' ');\n\nexport class Verifier {\n constructor(private readonly options: VerifierOptions) {}\n\n async run(): Promise<VerifierResult> {\n const first = await this.requestVerdict(undefined);\n const firstVerification = normalizeVerification(\n first.parsed,\n this.options.expect,\n );\n\n if (!needsRepair(firstVerification)) {\n return {\n verification: firstVerification,\n tokenUsage: first.usage,\n rawContent: first.content,\n };\n }\n\n const repaired = await this.requestVerdict(first.content);\n const repairedVerification = normalizeVerification(\n repaired.parsed,\n this.options.expect,\n );\n const tokenUsage = {\n promptTokens: first.usage.promptTokens + repaired.usage.promptTokens,\n completionTokens:\n first.usage.completionTokens + repaired.usage.completionTokens,\n totalTokens: first.usage.totalTokens + repaired.usage.totalTokens,\n };\n\n return {\n verification: repairedVerification,\n tokenUsage,\n rawContent: repaired.content,\n };\n }\n\n private async requestVerdict(\n previousContent: string | undefined,\n ): Promise<{\n content: string;\n parsed: Record<string, unknown>;\n usage: TokenUsage;\n }> {\n const response = await this.options.modelClient.chat({\n messages: [\n {\n role: 'system',\n content: VERIFIER_SYSTEM_PROMPT,\n },\n {\n role: 'user',\n content: JSON.stringify(\n {\n url: this.options.url,\n snapshot: this.options.snapshot,\n expect: this.options.expect,\n },\n null,\n 2,\n ),\n },\n ...(previousContent\n ? [\n {\n role: 'assistant' as const,\n content: previousContent,\n },\n {\n role: 'user' as const,\n content:\n 'That JSON omitted required fields. Reply with JSON only in this exact shape: {\"inconclusive\":false,\"criteria\":[{\"criterion\":\"<exact expected string>\",\"passed\":true,\"evidence\":\"<short visible evidence>\"}]}. Include every expected criterion, with boolean passed and string evidence on each item.',\n },\n ]\n : []),\n ],\n toolChoice: 'none',\n responseFormat: { type: 'json_object' },\n signal: this.options.signal,\n });\n\n if (!response.content) {\n throw new Error('Verifier returned empty content');\n }\n\n return {\n content: response.content,\n parsed: parseJsonObject(response.content),\n usage: response.usage,\n };\n }\n}\n\nexport function parseJsonObject(content: string): Record<string, unknown> {\n const trimmed = content.trim();\n const fenced = trimmed.match(/```(?:json)?\\s*([\\s\\S]*?)```/i);\n const candidate = fenced ? fenced[1].trim() : trimmed;\n\n try {\n const parsed = JSON.parse(candidate) as unknown;\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error('Verifier JSON was not an object');\n }\n return parsed as Record<string, unknown>;\n } catch {\n throw new Error(`Verifier returned malformed JSON: ${truncate(content)}`);\n }\n}\n\nexport function normalizeVerification(\n parsed: Record<string, unknown>,\n expected: string[],\n): VerificationResult {\n const rawCriteria = Array.isArray(parsed.criteria)\n ? parsed.criteria\n : Array.isArray(parsed.results)\n ? parsed.results\n : [];\n\n const normalized = rawCriteria.map((item, index) =>\n normalizeCriterion(item, expected[index]),\n );\n\n const criteria = expected.map((criterion, index) => {\n const exact = normalized.find((item) => item.criterion === criterion);\n if (exact) return { ...exact, criterion };\n\n const loose = normalized.find(\n (item) => item.criterion.trim().toLowerCase() === criterion.trim().toLowerCase(),\n );\n if (loose) return { ...loose, criterion };\n\n if (normalized.length === expected.length) {\n return { ...normalized[index], criterion };\n }\n\n return {\n criterion,\n passed: false,\n evidence: 'Verifier did not return a verdict for this criterion',\n };\n });\n\n const omittedVerdict = criteria.some((item) =>\n item.evidence.startsWith('Verifier omitted') ||\n item.evidence.startsWith('Verifier did not return') ||\n item.evidence.startsWith('Verifier returned a non-object'),\n );\n const inconclusive =\n coerceBoolean(parsed.inconclusive) === true || omittedVerdict;\n\n return {\n passed: !inconclusive && criteria.every((item) => item.passed),\n criteria,\n inconclusive,\n };\n}\n\nfunction needsRepair(verification: VerificationResult): boolean {\n return verification.criteria.some(\n (item) =>\n item.evidence.startsWith('Verifier omitted') ||\n item.evidence.startsWith('Verifier did not return') ||\n item.evidence.startsWith('Verifier returned a non-object'),\n );\n}\n\nfunction normalizeCriterion(\n item: unknown,\n fallbackCriterion?: string,\n): CriterionVerdict {\n if (typeof item === 'string') {\n return {\n criterion: item,\n passed: false,\n evidence: 'Verifier omitted boolean passed and string evidence',\n };\n }\n\n if (!item || typeof item !== 'object') {\n return {\n criterion: fallbackCriterion ?? 'unknown',\n passed: false,\n evidence: 'Verifier returned a non-object criterion',\n };\n }\n\n const record = item as Record<string, unknown>;\n const criterion =\n readString(record.criterion) ??\n readString(record.expected) ??\n readString(record.text) ??\n fallbackCriterion ??\n 'unknown';\n\n const passed = coerceBoolean(\n record.passed ??\n record.pass ??\n record.ok ??\n record.met ??\n record.satisfied ??\n record.success ??\n record.status,\n );\n\n const evidence =\n readString(record.evidence) ??\n readString(record.reason) ??\n readString(record.explanation) ??\n readString(record.details) ??\n readString(record.notes);\n\n if (passed === undefined) {\n return {\n criterion,\n passed: false,\n evidence: evidence\n ? `Verifier omitted boolean passed. ${evidence}`\n : 'Verifier omitted boolean passed and string evidence',\n };\n }\n\n return {\n criterion,\n passed,\n evidence: evidence ?? 'Verifier omitted evidence',\n };\n}\n\nfunction coerceBoolean(value: unknown): boolean | undefined {\n if (typeof value === 'boolean') return value;\n if (typeof value === 'number') {\n if (value === 1) return true;\n if (value === 0) return false;\n }\n if (typeof value === 'string') {\n const normalized = value.trim().toLowerCase();\n if (['true', 'pass', 'passed', 'yes', 'ok', 'success'].includes(normalized)) {\n return true;\n }\n if (['false', 'fail', 'failed', 'no', 'not_passed'].includes(normalized)) {\n return false;\n }\n }\n return undefined;\n}\n\nfunction readString(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() ? value : undefined;\n}\n\nfunction truncate(value: string, max = 500): string {\n return value.length > max ? `${value.slice(0, max)}…` : value;\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import { type Page, type TestInfo } from '@playwright/test';
2
+ import type { AgentStepInput, AgentStepResult } from './agent/types.js';
3
+ export type AgentStep = (input: AgentStepInput) => Promise<AgentStepResult>;
4
+ export interface CreateAgentStepOptions {
5
+ page: Page;
6
+ testInfo: TestInfo;
7
+ }
8
+ export declare function createAgentStep({ page, testInfo, }: CreateAgentStepOptions): AgentStep;
9
+ export declare function agentStep(page: Page, input: AgentStepInput): Promise<AgentStepResult>;
10
+ //# sourceMappingURL=agent-step.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-step.d.ts","sourceRoot":"","sources":["../src/agent-step.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,IAAI,EAAE,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAGlE,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExE,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;AAE5E,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,QAAQ,GACT,EAAE,sBAAsB,GAAG,SAAS,CA4EpC;AAED,wBAAsB,SAAS,CAC7B,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC,eAAe,CAAC,CAE1B"}