@wabot-dev/framework 0.9.27 → 2.0.0-beta.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 (94) hide show
  1. package/README.md +27 -0
  2. package/bin/skills.mjs +151 -0
  3. package/bin/wabot-skills.mjs +120 -0
  4. package/dist/build/build.js +1031 -8
  5. package/dist/src/addon/chat-bot/in-memory/InMemoryChatMemory.js +1 -3
  6. package/dist/src/addon/chat-bot/xai/XAIChatAdapter.js +180 -0
  7. package/dist/src/addon/chat-controller/cmd/cmdChannelSocketPath.js +1 -5
  8. package/dist/src/addon/chat-controller/hubspot/@hubspot.js +28 -0
  9. package/dist/src/addon/chat-controller/hubspot/HubSpotChannel.js +81 -0
  10. package/dist/src/addon/chat-controller/hubspot/HubSpotChannelConfig.js +20 -0
  11. package/dist/src/addon/chat-controller/hubspot/HubSpotReceiver.js +42 -0
  12. package/dist/src/addon/chat-controller/hubspot/HubSpotSender.js +118 -0
  13. package/dist/src/addon/chat-controller/hubspot/HubSpotWebhookController.js +122 -0
  14. package/dist/src/addon/chat-controller/hubspot/downloadHubSpotAttachments.js +45 -0
  15. package/dist/src/addon/chat-controller/hubspot/hubspotChannelName.js +3 -0
  16. package/dist/src/addon/chat-controller/hubspot/verifyHubSpotSignatureV3.js +28 -0
  17. package/dist/src/addon/chat-controller/{telegram/markdownToTelegramHtml.js → markdown/markdownToChatHtml.js} +5 -8
  18. package/dist/src/addon/chat-controller/slack/@slack.js +22 -0
  19. package/dist/src/addon/chat-controller/slack/SlackChannel.js +187 -0
  20. package/dist/src/addon/chat-controller/slack/SlackChannelConfig.js +12 -0
  21. package/dist/src/addon/chat-controller/slack/markdownToSlackMrkdwn.js +38 -0
  22. package/dist/src/addon/chat-controller/slack/slackChannelName.js +3 -0
  23. package/dist/src/addon/chat-controller/telegram/TelegramChannel.js +2 -2
  24. package/dist/src/addon/ui/preact/PreactRenderer.js +86 -0
  25. package/dist/src/addon/ui/preact/outlet.js +22 -0
  26. package/dist/src/addon/ui/preact/preactClientRuntime.js +67 -0
  27. package/dist/src/core/repository/CrudRepository.js +7 -7
  28. package/dist/src/feature/async/computeDedupKey.js +1 -1
  29. package/dist/src/feature/chat-controller/runChatControllers.js +4 -1
  30. package/dist/src/feature/pg/@pgExtension.js +2 -4
  31. package/dist/src/feature/project-runner/ProjectRunner.js +62 -10
  32. package/dist/src/feature/project-runner/scanner.js +1 -1
  33. package/dist/src/feature/repository/@memExtension.js +1 -2
  34. package/dist/src/feature/rest-controller/runRestControllers.js +11 -6
  35. package/dist/src/feature/ui-controller/actions.js +35 -0
  36. package/dist/src/feature/ui-controller/bundler/UiBundler.js +191 -0
  37. package/dist/src/feature/ui-controller/bundler/devMiddleware.js +41 -0
  38. package/dist/src/feature/ui-controller/bundler/index.js +4 -0
  39. package/dist/src/feature/ui-controller/bundler/manifest.js +34 -0
  40. package/dist/src/feature/ui-controller/bundler/navRuntime.js +236 -0
  41. package/dist/src/feature/ui-controller/bundler/pageAssets.js +30 -0
  42. package/dist/src/feature/ui-controller/document/escape.js +17 -0
  43. package/dist/src/feature/ui-controller/document/helpers.js +13 -0
  44. package/dist/src/feature/ui-controller/document/renderDocument.js +43 -0
  45. package/dist/src/feature/ui-controller/island/IslandRegistry.js +68 -0
  46. package/dist/src/feature/ui-controller/island/island.js +40 -0
  47. package/dist/src/feature/ui-controller/island/serialize.js +35 -0
  48. package/dist/src/feature/ui-controller/metadata/@action.js +18 -0
  49. package/dist/src/feature/ui-controller/metadata/@uiController.js +19 -0
  50. package/dist/src/feature/ui-controller/metadata/@uiMiddleware.js +20 -0
  51. package/dist/src/feature/ui-controller/metadata/@view.js +18 -0
  52. package/dist/src/feature/ui-controller/metadata/UiControllerMetadataStore.js +107 -0
  53. package/dist/src/feature/ui-controller/renderer/UiRendererRegistry.js +42 -0
  54. package/dist/src/feature/ui-controller/runUiControllers.js +285 -0
  55. package/dist/src/index.d.ts +640 -3
  56. package/dist/src/index.js +32 -3
  57. package/dist/src/testing/LlmJudge.js +93 -0
  58. package/dist/src/testing/MockChatAdapter.js +68 -0
  59. package/dist/src/testing/TestChatMemory.js +73 -0
  60. package/dist/src/testing/asyncHarness.js +66 -0
  61. package/dist/src/testing/auth.js +114 -0
  62. package/dist/src/testing/chatBotHarness.js +88 -0
  63. package/dist/src/testing/chatControllerHarness.js +94 -0
  64. package/dist/src/testing/conformance/chatAdapterConformanceCases.js +656 -0
  65. package/dist/src/testing/fixtures.js +53 -0
  66. package/dist/src/testing/helpers.js +42 -0
  67. package/dist/src/testing/index.d.ts +818 -0
  68. package/dist/src/testing/index.js +14 -0
  69. package/dist/src/testing/repositories.js +34 -0
  70. package/dist/src/testing/restHarness.js +127 -0
  71. package/dist/src/testing/testImageBase64.js +5 -0
  72. package/dist/src/testing/uiHarness.js +102 -0
  73. package/dist/src/testing/validation.js +66 -0
  74. package/dist/src/ui/client.js +6 -0
  75. package/dist/src/ui/index.d.ts +427 -0
  76. package/dist/src/ui/index.js +29 -0
  77. package/dist/src/ui/jsx-dev-runtime.d.ts +1 -0
  78. package/dist/src/ui/jsx-dev-runtime.js +1 -0
  79. package/dist/src/ui/jsx-runtime.d.ts +1 -0
  80. package/dist/src/ui/jsx-runtime.js +1 -0
  81. package/package.json +48 -11
  82. package/skills/wabot-async/SKILL.md +143 -0
  83. package/skills/wabot-auth/SKILL.md +153 -0
  84. package/skills/wabot-chat/SKILL.md +140 -0
  85. package/skills/wabot-di-config/SKILL.md +117 -0
  86. package/skills/wabot-framework/SKILL.md +81 -0
  87. package/skills/wabot-framework/references/quickstart.md +85 -0
  88. package/skills/wabot-mindset/SKILL.md +159 -0
  89. package/skills/wabot-ops/SKILL.md +151 -0
  90. package/skills/wabot-persistence/SKILL.md +159 -0
  91. package/skills/wabot-rest-socket/SKILL.md +167 -0
  92. package/skills/wabot-testing/SKILL.md +214 -0
  93. package/skills/wabot-ui/SKILL.md +201 -0
  94. package/skills/wabot-validation/SKILL.md +108 -0
@@ -0,0 +1,818 @@
1
+ import { DependencyContainer } from 'tsyringe';
2
+ import { Algorithm } from 'jsonwebtoken';
3
+
4
+ type IStorablePrimitive = null | number | string | boolean | undefined;
5
+
6
+ type IStorableType<T> = T extends IStorablePrimitive ? T : T extends (...args: any[]) => any ? never : T extends Array<infer U> ? Array<IStorableType<U>> : T extends object ? {
7
+ [K in keyof T]: IStorableType<T[K]>;
8
+ } : never;
9
+
10
+ interface ILockerKey {
11
+ lockerKey(): string | number;
12
+ }
13
+
14
+ type IStorableData<O extends object> = {
15
+ [K in keyof O]: IStorableType<O[K]>;
16
+ };
17
+
18
+ declare class Storable<D extends object> {
19
+ protected data: IStorableData<D>;
20
+ constructor(data: IStorableData<D>);
21
+ }
22
+
23
+ interface IEntityData {
24
+ id?: string;
25
+ createdAt?: number | null;
26
+ discardedAt?: number | null;
27
+ }
28
+ declare class Entity<D extends IEntityData> extends Storable<D> implements ILockerKey {
29
+ get id(): NonNullable<IStorableType<D["id"]>>;
30
+ /**
31
+ * @deprecated use id
32
+ */
33
+ getId(): NonNullable<IStorableType<D["id"]>>;
34
+ get createdAt(): Date;
35
+ /**
36
+ * @deprecated use createdAt
37
+ */
38
+ getCreatedAt(): Date;
39
+ update(newData: Partial<Omit<D, 'id' | 'createdAt' | 'discardedAt'>>): void;
40
+ wasCreated(): boolean;
41
+ validate(): void;
42
+ lockerKey(): string | number;
43
+ }
44
+
45
+ interface IChatConnection {
46
+ chatType: 'GROUP' | 'PRIVATE';
47
+ channelName: string;
48
+ id: string;
49
+ }
50
+
51
+ type IChatType = 'PRIVATE' | 'GROUP';
52
+
53
+ interface IChatAssociation {
54
+ type: string;
55
+ id: string;
56
+ }
57
+ interface IChatData extends IEntityData {
58
+ type: IChatType;
59
+ connections: IChatConnection[];
60
+ associations?: IChatAssociation[];
61
+ }
62
+ declare class Chat extends Entity<IChatData> {
63
+ constructor(data: IChatData);
64
+ isPrivate(): boolean;
65
+ isGroup(): boolean;
66
+ get connections(): {
67
+ chatType: "GROUP" | "PRIVATE";
68
+ channelName: string;
69
+ id: string;
70
+ }[];
71
+ get associations(): {
72
+ type: string;
73
+ id: string;
74
+ }[];
75
+ hasConnection(connection: IChatConnection): boolean;
76
+ getConnectionByChannel(channelName: string): {
77
+ chatType: "GROUP" | "PRIVATE";
78
+ channelName: string;
79
+ id: string;
80
+ } | null;
81
+ addConnection(connection: IChatConnection): void;
82
+ removeConnection(connection: IChatConnection): void;
83
+ hasAssociation(association: IChatAssociation): boolean;
84
+ hasAssociations(type: string): boolean;
85
+ getAssociationsByType(type: string): {
86
+ type: string;
87
+ id: string;
88
+ }[];
89
+ addAssociation(association: IChatAssociation): void;
90
+ removeAssociation(association: IChatAssociation): void;
91
+ private validatePrivateChat;
92
+ private validateGroupChat;
93
+ validate(): void;
94
+ }
95
+
96
+ type IConstructor<T> = new (...args: any[]) => T;
97
+
98
+ interface IMindsetIdentity {
99
+ name: string;
100
+ language: string;
101
+ personality?: string;
102
+ emotions?: string;
103
+ }
104
+ /** @deprecated use {@link IMindsetModelRef} */
105
+ interface IMindsetLlm {
106
+ provider?: string;
107
+ model: string;
108
+ }
109
+ interface IMindsetModelRef {
110
+ provider?: string;
111
+ model: string;
112
+ }
113
+ interface IMindsetModels {
114
+ llm?: IMindsetModelRef[];
115
+ visionLlm?: IMindsetModelRef[];
116
+ audioLlm?: IMindsetModelRef[];
117
+ speechToText?: IMindsetModelRef[];
118
+ textToSpeech?: IMindsetModelRef[];
119
+ imageGen?: IMindsetModelRef[];
120
+ embedding?: IMindsetModelRef[];
121
+ }
122
+ interface IMindset {
123
+ context(): Promise<string>;
124
+ identity(): Promise<IMindsetIdentity>;
125
+ skills(): Promise<string>;
126
+ limits(): Promise<string>;
127
+ workflow(): Promise<string>;
128
+ models?(): Promise<IMindsetModels>;
129
+ /** @deprecated implement {@link IMindset.models} instead */
130
+ llms?(): Promise<IMindsetLlm[]>;
131
+ }
132
+
133
+ type IValidateInputShape<T> = T extends Date ? Date : T extends Array<infer U> ? Array<IValidateInputShape<U>> : T extends object ? {
134
+ [K in keyof T]: IValidateInputShape<T[K]>;
135
+ } : T;
136
+
137
+ interface IMindsetParameterSchema {
138
+ type: 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object';
139
+ description?: string;
140
+ enum?: (string | number | boolean | null)[];
141
+ format?: string;
142
+ minimum?: number;
143
+ maximum?: number;
144
+ minLength?: number;
145
+ maxLength?: number;
146
+ minItems?: number;
147
+ maxItems?: number;
148
+ items?: IMindsetParameterSchema;
149
+ properties?: Record<string, IMindsetParameterSchema>;
150
+ required?: string[];
151
+ additionalProperties?: boolean | IMindsetParameterSchema;
152
+ }
153
+ interface IMindsetToolParameter {
154
+ name: string;
155
+ required: boolean;
156
+ schema: IMindsetParameterSchema;
157
+ }
158
+
159
+ interface IMindsetTool {
160
+ language: string;
161
+ name: string;
162
+ description: string;
163
+ parameters: IMindsetToolParameter[];
164
+ }
165
+
166
+ interface IChatMessagesPublicFile {
167
+ name?: string;
168
+ publicUrl: string;
169
+ base64Url?: undefined;
170
+ mimeType: string;
171
+ id: string;
172
+ }
173
+ interface IChatMessagesPrivateFile {
174
+ name?: string;
175
+ publicUrl?: undefined;
176
+ base64Url: string;
177
+ mimeType: string;
178
+ id: string;
179
+ }
180
+ type IChatMessageFile = IChatMessagesPrivateFile | IChatMessagesPublicFile;
181
+
182
+ type IChatMessageDocument = IChatMessageFile;
183
+
184
+ type IChatMessageImage = IChatMessageFile;
185
+
186
+ interface IChatMessage {
187
+ senderId?: string;
188
+ senderName?: string;
189
+ text?: string;
190
+ images?: IChatMessageImage[];
191
+ documents?: IChatMessageDocument[];
192
+ object?: object;
193
+ metadata?: Record<string, string>;
194
+ }
195
+
196
+ interface IFunctionCall {
197
+ id: string;
198
+ name: string;
199
+ arguments?: string;
200
+ result?: string;
201
+ signature?: string;
202
+ }
203
+
204
+ type IBotMessageItem = {
205
+ type: 'botMessage';
206
+ botMessage: IChatMessage;
207
+ humanMessage?: undefined;
208
+ functionCall?: undefined;
209
+ };
210
+ type IHumanMessageItem = {
211
+ type: 'humanMessage';
212
+ botMessage?: undefined;
213
+ humanMessage: IChatMessage;
214
+ functionCall?: undefined;
215
+ };
216
+ type IFunctionCallItem = {
217
+ type: 'functionCall';
218
+ botMessage?: undefined;
219
+ humanMessage?: undefined;
220
+ functionCall: IFunctionCall;
221
+ };
222
+ type IChatItem = IBotMessageItem | IHumanMessageItem | IFunctionCallItem;
223
+
224
+ interface ILanguageModelUsage {
225
+ inputTokens: number;
226
+ outputTokens: number;
227
+ cacheReadTokens?: number;
228
+ cacheWriteTokens?: number;
229
+ costUsd?: number;
230
+ provider?: string;
231
+ model?: string;
232
+ }
233
+
234
+ interface IChatAdapterNextItemsReq {
235
+ models: IMindsetModelRef[];
236
+ systemPrompt: string;
237
+ tools: IMindsetTool[];
238
+ prevItems: IChatItem[];
239
+ }
240
+ interface IChatAdapterNextItemsRes {
241
+ nextItems: IChatItem[];
242
+ usage: ILanguageModelUsage;
243
+ }
244
+ interface IChatAdapter {
245
+ nextItems(req: IChatAdapterNextItemsReq): Promise<IChatAdapterNextItemsRes>;
246
+ }
247
+
248
+ type IChatItemData = IEntityData & IChatItem;
249
+ declare class ChatItem extends Entity<IChatItemData> {
250
+ get type(): "botMessage" | "humanMessage" | "functionCall";
251
+ get botMessage(): IChatMessage;
252
+ get functionCall(): IFunctionCall;
253
+ setFunctionResult(result: string): void;
254
+ getData(): IStorableData<IChatItemData>;
255
+ }
256
+
257
+ interface IChatMemory {
258
+ findLastItems(count: number): Promise<ChatItem[]>;
259
+ create(item: ChatItem): Promise<void>;
260
+ }
261
+
262
+ declare class ChatMemory implements IChatMemory {
263
+ findLastItems(count: number): Promise<ChatItem[]>;
264
+ create(item: ChatItem): Promise<void>;
265
+ }
266
+
267
+ interface IChatRepository {
268
+ create(chat: Chat): Promise<void>;
269
+ update(chat: Chat): Promise<void>;
270
+ findByConnection(query: IChatConnection): Promise<Chat | null>;
271
+ findMemory(chatId: string): Promise<IChatMemory | null>;
272
+ findOperator(chatId: string): Promise<ChatOperator | null>;
273
+ }
274
+
275
+ declare class ChatRepository implements IChatRepository {
276
+ create(chat: Chat): Promise<void>;
277
+ update(chat: Chat): Promise<void>;
278
+ findByConnection(query: IChatConnection): Promise<Chat | null>;
279
+ findMemory(chatId: string): Promise<IChatMemory | null>;
280
+ findOperator(chatId: string): Promise<ChatOperator | null>;
281
+ }
282
+
283
+ declare class ChatOperator {
284
+ private chat;
285
+ private memory;
286
+ private repository;
287
+ constructor(chat: Chat, memory: ChatMemory, repository: ChatRepository);
288
+ saveHumanMessage(message: IChatMessage): Promise<ChatItem>;
289
+ saveBotMessage(message: IChatMessage): Promise<ChatItem>;
290
+ getConnections(): IChatConnection[];
291
+ addConnection(connection: IChatConnection): Promise<void>;
292
+ removeConnection(connection: IChatConnection): Promise<void>;
293
+ hasAssociations(type: string): boolean;
294
+ hasAssociation(association: IChatAssociation): boolean;
295
+ findAssociations(type?: string): IChatAssociation[];
296
+ addAssociation(association: IChatAssociation): Promise<void>;
297
+ removeAssociation(association: IChatAssociation): Promise<void>;
298
+ }
299
+
300
+ type IMockChatAdapterResponse = IChatItem[] | ((req: IChatAdapterNextItemsReq) => IChatItem[] | Promise<IChatItem[]>);
301
+ interface IMockChatAdapterOptions {
302
+ /**
303
+ * When the scripted queue is empty, answer with this text instead of
304
+ * throwing. Useful for tests that don't care about every bot reply.
305
+ */
306
+ fallbackReply?: string;
307
+ }
308
+ /**
309
+ * Deterministic IChatAdapter for tests: script the LLM turns with
310
+ * reply()/callTool()/enqueue() and assert on the recorded requests.
311
+ * Each queued response is consumed by one nextItems() call, so a tool
312
+ * call turn must be followed by another scripted turn (the ChatBot loop
313
+ * calls the adapter again after executing the tool).
314
+ */
315
+ declare class MockChatAdapter implements IChatAdapter {
316
+ private options;
317
+ readonly requests: IChatAdapterNextItemsReq[];
318
+ private queue;
319
+ private callIdCounter;
320
+ constructor(options?: IMockChatAdapterOptions);
321
+ /** Queue a turn where the bot answers with a plain text message. */
322
+ reply(text: string): this;
323
+ /** Queue a turn where the bot requests a tool call (executed for real by the ChatBot). */
324
+ callTool(name: string, args?: object | string): this;
325
+ /** Queue a raw turn: a list of chat items, or a function of the request. */
326
+ enqueue(response: IMockChatAdapterResponse): this;
327
+ get lastRequest(): IChatAdapterNextItemsReq | undefined;
328
+ pendingResponses(): number;
329
+ nextItems(req: IChatAdapterNextItemsReq): Promise<IChatAdapterNextItemsRes>;
330
+ }
331
+
332
+ /**
333
+ * Pure in-RAM chat memory for tests. Unlike the in-memory addon, it never
334
+ * touches the filesystem and exposes the full item list for assertions.
335
+ */
336
+ declare class TestChatMemory implements IChatMemory {
337
+ private items;
338
+ findLastItems(count: number): Promise<ChatItem[]>;
339
+ create(item: ChatItem): Promise<void>;
340
+ all(): ChatItem[];
341
+ allData(): IChatItemData[];
342
+ clear(): void;
343
+ }
344
+ /** Pure in-RAM chat repository for tests (no `.wabot/` persistence). */
345
+ declare class TestChatRepository implements IChatRepository {
346
+ private chats;
347
+ private memories;
348
+ create(chat: Chat): Promise<void>;
349
+ update(chat: Chat): Promise<void>;
350
+ findByConnection(query: IChatConnection): Promise<Chat | null>;
351
+ findMemory(chatId: string): Promise<TestChatMemory | null>;
352
+ findOperator(chatId: string): Promise<ChatOperator | null>;
353
+ }
354
+
355
+ interface IChatBotHarnessOptions<A extends IChatAdapter> {
356
+ mindset: IConstructor<IMindset>;
357
+ /** LLM adapter; defaults to a fresh MockChatAdapter. */
358
+ adapter?: A;
359
+ /** Extra DI registrations for the mindset module dependencies: [token, instance]. */
360
+ register?: [any, any][];
361
+ /** Assigned to the container-scoped Auth, like production does per message. */
362
+ authInfo?: object;
363
+ }
364
+ interface IChatBotTurn {
365
+ /** Bot messages delivered through the reply callback during this turn. */
366
+ replies: IChatMessage[];
367
+ /** Tool calls executed during this turn, with their results. */
368
+ toolCalls: IFunctionCall[];
369
+ /** Every chat item created during this turn (human, bot and tool calls). */
370
+ items: IChatItemData[];
371
+ }
372
+ /**
373
+ * Runs a real ChatBot (real MindsetOperator, system prompt, tool loop and
374
+ * argument validation) against an in-RAM memory and a pluggable adapter.
375
+ */
376
+ declare class ChatBotHarness<A extends IChatAdapter = MockChatAdapter> {
377
+ readonly adapter: A;
378
+ readonly memory: TestChatMemory;
379
+ readonly container: DependencyContainer;
380
+ private chatBot;
381
+ private operator;
382
+ constructor(options: IChatBotHarnessOptions<A>);
383
+ /** Send a human message and collect everything the bot did in response. */
384
+ send(message: string | IChatMessage): Promise<IChatBotTurn>;
385
+ /**
386
+ * Execute a single mindset tool directly (real argument validation and
387
+ * module resolution), without scripting a whole conversation.
388
+ * Returns the string result exactly as the LLM would receive it.
389
+ */
390
+ callTool(name: string, args?: object | string): Promise<string>;
391
+ /** Full chat history (data form) accumulated across turns. */
392
+ history(): IChatItemData[];
393
+ /** The real system prompt the mindset produces. */
394
+ systemPrompt(): Promise<string>;
395
+ /** The real tool definitions derived from the mindset modules. */
396
+ tools(): IMindsetTool[];
397
+ }
398
+ declare function createChatBotHarness<A extends IChatAdapter = MockChatAdapter>(options: IChatBotHarnessOptions<A>): ChatBotHarness<A>;
399
+
400
+ interface IChatControllerHarnessOptions<A extends IChatAdapter> {
401
+ controller: IConstructor<any>;
402
+ /** LLM adapter; defaults to a fresh MockChatAdapter. */
403
+ adapter?: A;
404
+ /** Extra DI registrations: [token, instance]. */
405
+ register?: [any, any][];
406
+ /** Assigned to the container-scoped Auth, like production does per message. */
407
+ authInfo?: object;
408
+ /** Chat connection the simulated messages arrive from. */
409
+ chatConnection?: IChatConnection;
410
+ }
411
+ /**
412
+ * Drives a @chatController end-to-end without a real channel: resolves the
413
+ * Chat, prepares the per-message container (same code path as production,
414
+ * including @chatBot injection) and invokes the controller method.
415
+ */
416
+ declare class ChatControllerHarness<A extends IChatAdapter = MockChatAdapter> {
417
+ readonly adapter: A;
418
+ readonly chatRepository: TestChatRepository;
419
+ readonly chatConnection: IChatConnection;
420
+ private channelContainer;
421
+ private options;
422
+ constructor(options: IChatControllerHarnessOptions<A>);
423
+ /** Deliver a message to a controller method, as if it came from a channel. */
424
+ invoke(methodName: string, message: string | IChatMessage): Promise<IChatBotTurn>;
425
+ /** Full chat history (data form) of the harness conversation. */
426
+ history(): Promise<IChatItemData[]>;
427
+ }
428
+ declare function createChatControllerHarness<A extends IChatAdapter = MockChatAdapter>(options: IChatControllerHarnessOptions<A>): ChatControllerHarness<A>;
429
+
430
+ interface ILlmJudgeOptions {
431
+ adapter: IChatAdapter;
432
+ models: IMindsetModelRef[];
433
+ }
434
+ interface ILlmJudgeEvaluateReq {
435
+ /** Chat items (e.g. harness.history()) or a pre-rendered transcript. */
436
+ transcript: (IChatItem | IChatItemData)[] | string;
437
+ /** What must hold for the evaluation to pass, in natural language. */
438
+ criteria: string;
439
+ }
440
+ interface ILlmJudgeVerdict {
441
+ pass: boolean;
442
+ reasoning: string;
443
+ }
444
+ declare function renderTranscript(items: (IChatItem | IChatItemData)[]): string;
445
+ /**
446
+ * Grades chatbot behavior with a real LLM. Provider-agnostic: the verdict is
447
+ * extracted through a forced tool call instead of parsing free-form text.
448
+ */
449
+ declare class LlmJudge {
450
+ private options;
451
+ constructor(options: ILlmJudgeOptions);
452
+ evaluate(req: ILlmJudgeEvaluateReq): Promise<ILlmJudgeVerdict>;
453
+ /** Like evaluate(), but throws (with the judge's reasoning) when it fails. */
454
+ assert(req: ILlmJudgeEvaluateReq): Promise<ILlmJudgeVerdict>;
455
+ }
456
+
457
+ /**
458
+ * A real JPEG photo of a store receipt (total: 11.570), handy for testing
459
+ * vision flows against real models without shipping binary assets.
460
+ */
461
+ declare const testImageBase64Url = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wgARCAFCBGoDASIAAhEBAxEB/8QAGgABAQADAQEAAAAAAAAAAAAAAAEDBAUCBv/EABYBAQEBAAAAAAAAAAAAAAAAAAABAv/aAAwDAQACEAMQAAAB9UyionqUSxSwBBAAUAEUAAUhEqVRJACCpQAAKAWVSCgCyAEiWhKJ4yCT0V4yQnn2Hn2IsslSJjzQx33SefdLAk90njJTDlBKIAABKIshbKLBZRAAWUWUQAAAEsAUElBZVAShKIlSUFgqVYpIomLJwTvXW2A8q9ebzI6rxVw5tLdLHk9pUA8zzyjtEUlgKBBSLAAKUl8+NLwdRob6E9EXyWAsoAPB6uvsFefSkoQGD2mQ1z3k0tw9IKUAqD0lAIoSwAAFJPGmdHw0To+aHn1qmXLzOmL5HpBXn0UEnqAACWAAACgQtQVKeaqRYALKAJYYubs6ZsbHI6hrZvHRrDwfpeRG1s5C8DvcrrGnrTqGri2NFOtpbvLOrx+xyDraW7y1m9scs6YAFRKQACqg527pbkc/d0oetjTw11df3jjPfekXZ2cC33q7Z4xbPhMWzz+gcn146q4seCk3/WIx7OpsJk5+5pGLemoZcfrIbeTX2BLAC+vHuAoBLAAsFlJy+ryTdw6vVNDFs887jBmOf0uX0TRvneHPzahm6GUWWAAAACURYFAKCRYALKARYKAEBy93zsHJ2NlWrvYs44vawRM+HYXkdTX2Tk7mfVMHno+k1+f0KbPI7GqbPL6uAmll21yCggEssFiqgCObkzahhz71rU8vZPWfTjXy7eQ86vvOYNvHmGPL5Of0cGwcHZnVXU1utqFxzbNTNfSTS6XPMmv72zAz+jxsY8hZZQQ9eUexRYQCyiWCg53RxnF62LbOTc4y58WY5vSw7ByNnIPOp1sBtSwoIsAKlEsACgFiiBEogABQxDLGIzTH7Kx09WeT28+g8D3HgyTz6Ux+yp5PaCzz6RXgyeQieg8xfbz6Dz6CQ9CgQhahKQUAgFllAoEBfPoglCUoSASgAKACAPd8+hAAAlBZSSiLAFIPUlKEtgAAAgFlAAUUBYsRKSLBLAAvg5Pv3umPT9bpzt7B5Nfzv+zLzZtmr0sGmJueTa5WXcNHo6eNceToaBu8rL1TR2NH0Y9jf5KbnP2Oiac1xi6efmGXV7OkY2zjXX3rpmXV34lz8/oWsfvAamXxkTJqTZJtXSLn9a5uPGSHn0Xnb3N6gau4ebq7SFlujdboR6xauU87vnWM+fldVJqZtQ97utnMXvnezpgAAIj17x+wKAAJVWVEsC+Dm+drIOdmi4Ozgwm/ZRzOnpJN7V2gACAAqUBVlUogAIsSSiFRiyQ5fR4++YNjBgPXR9VdPNp5E19rx4Nvn7XkzMIx72r7J581d3Q2NUnV5fQNDwyHS5fR5xk6XK6Cc549nV5fU5Z1OV1OUdaXSNfa2uadHl3fPWWq86+zhONsZsUZtLoD3g39A3MmPwbDxkAXl9LmdM53T5fUJdfYSFPn8mXpro+PGQ6HP6Ggnjq8rrHjkdfGYNnSxnlhxn0M8+iwABI9e/HsCgAJVBCwLL5PHGzdEx627z139fINtKNLd0TNs62yAgEWACygKsFsKAIliklEKnmoaev1Bj5nW9HJ3Nvyuhj6cHP6UOV0/VTlunDxz+mOZ0MheR76Y88jsjmbuYcvNvDX5vaHPzbROLv7VPGnvCaHQHL3M9OFm6vo1tqUmHMNH1tw1dHtDQ2so5d6kJQvm1eF1sww5aNbZsSg43rq008XRhy93YHI3dmmHR6kOS6sJq7sPGSoSwCkshkx+y2KqUFJZQQoGPJDg7HVhg0exF5PU9BZRyOvDn9EAQBLAAACgUUBBFlWAlhEsDT0zsMWidO8PbXoTl+TrtLGdKcjqp6mjjOnJor0HP3iuXmN5qax1Lz95K1MxkuHSXp3T2w1MhnTSTdgLobh7TROhPPoWBMGU9vMMjxT1MHsyHg9ni328+oIPUVApNZGzZCtPdVNPKZ0qASygkUAUlRKpbFWKDnnQc7aM4BqG05u8ZHK9HV8+dNeh50d4tgqaKb7V2gAAACAAoLZSSiAWWWCpKSSjldDndReb0+T1jnbOn1Uc7o8Y39bf5xtYN3RNqZ+Quz5y4z16yYT3j3OSbOXxrnSycXsJo5veAbOTVXV6nD6Kc3o4/RucXv8o6EaS83t6e0ZeLuQ6OTHkSYsuA0fTaPM09oyeWsZvO17VyOnqG9hwZDPsauwau3zuiLKghyNnV3zxl1t84nZ5nTNLc423L0WPKTz6ic/pcvp1UsAFLJSSylSj15tON2eEdbNyNw3QedLf8mvqY9utbd5Oyb2nlsrZwejasVrTDsyanQwbJ6AAAlEABQLKFElLFgBCpJ6hxuxo6q++ngzHM6+hvmPnb2um7yutyjoaWHfXT3OVtG5qb3KOhh1eiaO/wAToJt+seoe9zldc8a23prsZNfCYfN2C698HZ5vS5ybnK6ukeMmvmXH6mE6ezo7qXHkxGls83KeNnZ0Da0/ecy5ef4NnRzdI42/gzLl2OZsGDp8zpnp5qWBw93D1jH7Dk9Tl9Qx3Rzy+d3T3SefXlOb1ORuVt3z6lSglQABS1Z6Sae7xzrczJDo3U2xzOjwDpbWjnPU0aNjNy16GTn9dPYObsucOtw+4ZLBbBQJR5ABQLKLKRSyWQFSyoIFglLYJFAp4qEe4R6h5eoY/XsSeh49oSe/JfPoShPHsevHoebR4tDFmh59KPPoa2b2POPKMeQMbIXH7tTw9jD6yFx+vUSKCjDmgWDx7sMcyiWyHn0MHnZWypFliAUAFFqwXz6Jhy30aW4EwbEXBktJPVJ59jz6UIS+fUJaUBQoRZV8yxKABZRZQhQhKPNlskUwc3LvF1PPQOblz65MPT1zxsamIy7+tiNjDh2jZx6VNjY1/JtYfGkdeetMbfzv0Z4x62I6ePJxzp5NL0TY19Y3sOHYPeXxqGxnwYTNj2NM2djn9AEL50/B0vOPTOi0ts9Od0VefXOOixZUWWk1diPbFgNyefY86Xs25r4DoOdlNya2c9NHUOy0N+BVgLBFgoAKLVguLJy03NjSyG2ga+XQXNs87MbE0vB1cPnVN7Px+qe0Iw48B0PXN6K0FQUFUnmKAALBbAKsWQCebLXmz0cXscjsHL6fI6xzOlyO0OX0uOdfk9ninW5/S5h0ednxnrW6+ke9fz7Ohy+ryjq8zN6NPr8fsHnm9HmnVw5cJr7Wpvnjm4+mc33vZjS1MuI9b2blHX53S5R0vak8+vJob2h6NjRyaZsdTg9s0Oryeqeefl1F2Ohx+ygHP3ef0DVxIdD349nI6nK65zp4p0Odu882tjT3DQyYdc7vrV2YoIsUVIoChYLLShw+1yDJ0MWA6IPOPL5OXk8kzz1jMe9zuiupv87eTMDm72htk2eD3lqUAoSoAAAAKBYWyyBLJQlDma/b8nO6Hoc3pefZi0ujjMnG7Xg5Wxs5j57pZfQ0ukObl28hOX1sBi1+pD53v+Mq+ed1MJmwZ4mhdryaG9g9mPJsaxo5Oj6NbS3s5dDo4TPAefVOTk2fBzNvPtHN2Nnyc3o62U1dq7ByOvizFWHP3sOY08XQwlzaWwc3oXCYfHT8HK2Pe0aW749mj729E6Hvn78egoCCVLQC+aegoE5fW1TFLsHrJp7xr6XU1jUbOc5WPe9njD09U5/aw7CQLy8u7qHL+h19ilIBKAAsCCoFlCUWD0gSwAAMfgzsBczF6MgQBLBYLJQlAAIpYvgvrzUSiUIogAIqooiiUgok9QlKlABKBIqULCUCiLABLCLBSBLbKBErz6ALfNWyUWUAJRZ5PfnzjMnrBlPV8eTLcRMjB7XIlKx+DPJRZaqWQAQAAWUlICqACWUksMOp0RwcW7rG7t7AMfLOu5Xk694/VPWD3yTB9B8f2F7F5vRLNHAnVvHyL1GPIk5vTxnB+g+X+nPQCUASiLBLC2VYEAssE0+ed0pFgAJQQsoELLQgspAEsQACVQhh0+j4Pn/ovmfpDJZTDzetprpdv5v6QoKADH859H8unXtxHre1ds9aPShzvXQ5RodLX7S+eLs6xkvXVxOzx9qOjZUWUJRLAUigAACpQpfNlSSiUXk62zqp36HJw7vKPomuORvczrm7g2MJ8z9V8l9Eu48+CanF7CYdXscxcnc+e+gS+PeM+X+n+W+pPYCwFJZSLCKBFAWAE8+MHNjvXx6qywAARSKggoLLKWACUiLAAKAnjJ4Pl/pvm/pTIBr7GovH+j+d+iKkPbz6Qo8cvp8A7efx7NHldPmn0FDS5Xrom5klOXg2+Wv0V1vNnP2+X2o2/UpLKSygAAEABU9EoLCyiRYShr492Flg1tmnP9b0jW2PUqT0NLH0Kups2mnh6Q5mXdpx+t6o8eonH63uACAsQstAASUvmglhUsXzVAAALLXmpFQCgJUABRCklRABQDz6HD7VoFMGey8btSk4Xf8ACc3q+aKo0N6nKdUcza2PISnI6mSnkE096HKvVhg2AqUWBZQQssFlAAJVAAAICgSwACAQAFCgUCKQgWUSwSiAFICoKlEsIBKiAWUC0AACMWQqCtHObAQABcXs9JgM4IWIWosAIBZSillikKlLNOG6xc463rldQefXk8sPNOzdSG24nQNuaOgd5iylFAtEiwWBZYUAAAFgKCh5oAAQAQCAAoWgAAQIAABKAEAAoAQEEQFAFoAEpWluEWByemHuBfRJ49laG0GTQDd9EQE9B49lQAUBYFoKQBo+Q9ZgwdEJ5Dl9ANXGG5zw9bYaXVCi0EogACwKCUJQAUFI/8QALxAAAgEEAAQFBAICAwEAAAAAAQIDAAQREhATITMUIjEyQCAjNEEkUDBgQkRwQ//aAAgBAQABBQL+8x9OvWscNRmtaxQXHFgG4EZoDUaCtBWBgKF44zWoojNaCigP+lH/AFHH9oZAD9O3FpQrfWTig4b4ZYCtgflqwb5HoAc8AfoLYpWDf1sxzJC20fThmulKf5Oa/RjDvwB+l02FsDzvhXXtWIlIpOPr/ibqIl1+rPCXOi51qYNrBGY/kP7Lc5onpASzHjOmyWvA8R1rP9M5wkMewtian6RJKdFDmpFLKEbnxRspbqMOk9TSFa5UhqKQmtv5NCQmeoPyakk6rzFqNt1/zXR8qdUuBh3fSIGRirHMsmlEuVEnmlk0r7tRPmnbWv112lkZahk5grmtzcyKIpN1dyaHNwhyssulL1WmlZqSV6ZvIJCU5zAr1HxIvJcTECO2XSJmkJDSIQejey1o+hkaQ5dAjbKkmJWMklRghf6S5JEUUhSGOU8657Fov26/f/b4TfkCpvya185/Mpfy6g/KqHrc1Afu/wCa79idu7q5PkE7qGaSSWVCyrKVUatUufEVp556HpT9Y7Q+SoPyD6Qn7lucseojXUXPSOM/bf2Wvbm7YP8AGtV+1c9qHt/Em8txcPsy+xpsGWZ2SDtv7LSn9ln6Sdq17UIDXH9NcNllRQLhAjSee2te3wk8lwsqvwn74q6BWUTpQbmzS+WcyqBG21xUX5VD7c/OQC2Xzf5rvtpKvKbM0tynkjdGRpFzLJpWUdQSJrg6ybrSS7zXFL7af2WftpW1mMzsYY9ahbSVpAq25LLddiLtyey1byzuOXj+Na9i57MPa+Jcjon3ZZciO3C1cyLpbtmN/ZZ048lu4QzSry7dcQ2/d/pmiDNTpzK18saacXjD0sYXg8WzelMuw8OtKgUMAwEPURfcpYgslPGHHhlpVCj/ADSx8xRa0qBQwBHhuq24Vmj3o2wykQQsu4EGtRxhC6B6HpRGRHHpwiX+RjrTwh6FuKAwJk3jQYWntzt4XNMv24l1SRdo4xqnxJBskEPL4NbgnwoqKPlg9RFHpweBXpbdVoLgJFo3+vY/05nC0sgc0zYoSK3AuAQyngXAoMDwLAUrBuBcCgc8C4FDqK2A45HHI4ZodTw2FZ4A8c/3/pXThmtgf6M9ARzneIx0j7R6c0yRcpEfMZVpnKtbtsNNWmIzAdwF0M5RmhdnwoVpjGWikdwAY2lMLsryPqujymBjUj6gRyMIJCalk0GkgEEu9TyagLKgSTdDE27QutW+cO7PIEdKR9lbZzrIlRtsnB21UTOa52DI+kayuaDtsTqrStSv9B9BOdlOQfq5pzzTojh1aTBEjghgw5nn4STalbjrUkmlI2y/IkZpGAljCNlZJDI43icHI4zs61AzE/Mb22/uf223bRQgncagYt7Ts3GORt/Etu1cY5e38K37Vz23/Hh7V17bjPLj6R3Hle4NDoLjyzTd0ekh1uZO+Kzi7brdfqLpPU5yIU0SOLWRiNbfNRrrUxCx2va4Tdq27U4+1L+Pb9qrk4SMeWVaT28P1N5bj0pvfWG53GP8htcQE5jbZmHktfaD/JqT2QEOskYZVHlf7kts1evxz1WKPSpT9uHIjtR5ZcGK26rxuexb9YvmN27X1b22npLKI6SLms4xHadqcfZH4lp2rgfZ/wChbdq67R62sPZux9ubsp7Luril9LruXHvX23Hfl/IFN+Yel5+1/Ko/cuPThLC2YDlGcLRzOUXVeE3aj3WNSZquOkETPyoi2Lvtx+z6bj8mm/JP04JneOQC1I5dt729tnSflURkctonWfzORra+q9Ln5Dtqo2mdhrFadqXs2ownG67EPa+YRlUbkzSS4S3UqndlGqg+ZbZwlXEvkSM+Gt5AtTyDHLPg7Zxi5fyMn8a2fZLmQFZoybe3k3huCJDcq2iMGSY7zXHlpWysn3Z7gayBgV991Iul1+ovNc/8YBtLSOTOfS37k33JUTUcZe1a9rPJnuO1b9mrrtp7ZJNaHpxn/IFP+TXM+7xh/JIytv77f3v7bOl/KpjqEfdblPKZP48SyBWVxKPb8Y1LmWVIwiyDMdq3SZhy7cYTjd9mHt/NlgElC2wdQF8M2/hXzEhQSwZK2x29KktssludsYpoDzFhJcjo1vho7c5IypgZaht8My7LyJEMMJBdN1WN1qGHWpI+YBHJiCHl1NHzAY5cQxctWHkt004SRNzNJTUaagrJsOdmLbjJ1jt11jmj3ohmhQSpSmTLpugWVDo7txOcS782MkrImW/SR4fjo6zHnOYYtQ6FWPNaoI9EYlJ9zhTzEG0JbmyCdfKo8tyMonb+M+dRHIrffxHth4GDiB2YeVeM8bsYEdP7b9/5cD6sfExxx04YHHFYoqDwx/ofr9H6B/8AB5pdKLuhRg6OJtjzhVs7Mj84naVEt3LLJPhlnZSDlWlJpZXFfp5usdx1zhTcMSJ6mconOkwkkjNUswjEbbK7iMc98pLs1TycpY35ielPcDb1FSz6MjbLTz6vx9BzloNtRIUKwYE6j1ppVUqwbgWANMwUA5H1mZBSzIxrOKEw3p5wpWTYfQCD8FpyCbg4Rthxkm1oXNbeRblq8QxK+j3GCk3m45xTXKqUnD/N/SfcnIDC3Pmq5fywrqlTvSKEjtxvUqgxwneCFNVlH20c+Gg8wuE8uxeCBRyrrAjlP8eN1ESlTwniGsHauM0qgCXyS56SZmq17TLsJIuVIvVWOFkBYwnMTtqJAcr6cJO3brupzE0hzDb9m4P2N9beFNlYctgch3LMh2W47MPaqRvufSB/KMC4hkzTrsEXS4b2wjasCunFvbbEkfAXrdeWsY4n0iXLSqvLhOYbdlVAyNUmVS2XYTIBUZynCZtVRFxcDSl9PmRfk/q379Hz3NGgOZct6WlP2rL2ZCiR3kZhpb2nsn7Vr2WYKMtNJcJmGK32VkaKRTkTdYrbsVOxVYjvPO+qqusFp1jNXdJ27lqmXWCHszHdrjoinpwl7dn7Z+1G2ba17Fz2HP8AEg7TJtUnSKBcxW7ZNz2Ye3U3SbOfpT8p5AqwdTR/Lf2RcwHEihWDjgfZa/B5bGfwpUW7ZXjnFSyGQrHy4Yrd3poBEC+9ta9m47Vv2uF324+kRdDQ9PmJ5bqrcfyGOFt/NJTnC2vVj7bY4aTt2lTnZ449Vm6w2vsn7Vt0hYmV449FlbRUOVvO3F2pO3a9qSQJQ6i5AR5JA00kiBLVwBV2KTtoy864lUpG48PC6bXLqRE268JO3a+2fsxfjWnZuekDdbSDtO4Sp5NYo3cJA2JbntQdqpes306cy4WDAVdVo/ln2xlTUh8tt2uDe21NDr8BXHN/UHf43LMxhhCBjqsb7rP2V/FtOzcHEUHZ4XfsTtXAVCnt+ZPFtWZSsEegk7dsuq1N27UYhqeEgszyUkekW+kguGyh5isjwyuXmrXSGKRoyLk0w50UbvEkhM9IMJJ2rQfauI90ErIupmdrUM8lthLWPPC7zrH2/C9Z4AiQRfb8N1nh1FtHqnCTtwSrHT7TMYxyoX5YmfejH9iKTlxSS82ph5lUBZ11Nx1t4bhQguENTd7P0x/mcT+WRkIeU8kg5duuI+Dey2bWuetA5/zsDHP4kNVuuKEyl6foomxL4kUrc1I2MNSStIqx4iG8DttOyjVeE0e8ayyIJkbKe35uOI4YodOOorFag1qKxwxX60FajhgVjFZo9QF1FaisYHBVCcCoahwZdqA1FMgegMccZHJUUFxwMYaggHDQVoBRQE0yhq16cta0Wiufq0G3HQbUUBoJ9B60I1FchK9B/l/RANCMDgIAJOHLWuWtAYogGtRwIBoAfTgUQD/5ZI4RfEtStsskrK63BzHPu0kxVvFtUdxzKkkCKt0DQINSTLGFuQSDkM4FCUHhnASUO3BZ1L0TilcPwZwgVg4JwDOoKuGp5QlG5QVHKslbBRz0NZyGlVK8THhJVf6Qc8Nscc8c/wBQzhKWdG+l5VShOrUKaULSSq9Zp5VSo5Q/0mRVIPzpTtNy11gOJcCn0VbdKwDUwVY7aPVCebO8KlLd8hF3keNdYH+3B5y6+WAlklQtVoMPUzarCCJ6mVjVp7auUc1a9m4bVUhBV/sy3HV+StKirUh2fkIKj2WVo1NNCii3TpxJMjOhQI2VuDUDllPpbnjMTuh2Ti74atvNcNgL6U7nmaMKibaixaTlsgifaKJi1N0CvmGNZHVEkB+EPuymFQsJOnBjgKokMsQVYnzEicxpI+US/wBlIs06cpx1HCZ9UEAesmN/m/8AdPqn5VT+eRBhalO8wGBCf5Jq372Kmm6RRkQ2z6mWRQlmfJVt3j0ruXH/AHKb22npU3ateyyhqLYXvzXWRIeaajkYtK+tyZ3zFIJOEz+ZV1Xg3stqbBCLqo+7OpKXB9tt7qboIvPVsxxxmbWXIx6zXXovpUf5n6gP3bc5dvbD7LY5Ensi/HtmHK9fhq/LKXBNRsGXhjNYEayXGwdeXDbdm67Mh/ixey69E7XC76UntZUc/N/7lJ+XUfW7pugi63NW/wCS3pb96eXSooeDQo5NsoWz7VW/euZNI4QFXYeKp/ZZ+2pu1admnd5XjjCiTTIpul3MP5WoFL0vKA3ueLHyWlSbAwseVFNqzS7SnrHaY4XD6RwTKscEn3eM/d1GEJ5t1S+lRfln0h71uepbKQ9i09svbi/FjhLpEhQfBPtgUNJIoMdofJwJwAQwuIsLI29vB0iuz9ub8eP2XdR55fC66thuWwZJfm3KZoXDlbaNhTdu2XrUnbtPZUyGOZ7nKW6YUyjn+ITCuJEDct5LgMLUYjqDv3EXNVIGAMbeIVdVb2WnpUvbte3cPhYZY1HOQ1c0J0Nb8y6nGsviEqPz3De22GTxb2wkRyTyeRG/jRW40mhUJG2YbZlFcxauTkiCPWZeW6nZeE3dzWP5F1SuMbV7LppVAtxkwty5JJcSRLm2tSFqVhoilba1I5O6/DhOryyrrbDSMHPCQZSBwqTSh15f8eCXUSOZXlTMMU2AzGaYDA4XfRluExlnn+b0rQcQoXhjoqBeDDI5aDhykowrQQLRUVylHFYwr8NBvWOkcYTgRmlXUPGHrwkdC3UUQKNurEIBRXavDrlV1ojpHHyx9DRAtyFrw66AYDqGUJhUthQgFGIE08e9AYHApk4rXBlj3Hh6WPWmiDMLcUqBQ0QehbCok0Xkjbl9GTKC2ICW2D8Jog1CAAaeSJCDwaFWpYVWsU1urFIVSv0bddkTT6GUNXhl25QLf+VswWualc1aEimtxSsG/wAGaJGf9FyPqz/hyK3Wt1rmLQ9CcDmLXMXG645i5DZ47rXMWgcj5bpuPDVPHylt0MjeG6RRiMfXIdUilLS5/wAJ9HjfCyNzR6f20jahpiVV25i+3gz61NKagYs31v7N2Mgtya8Ka8L1RdaZdh4cmvD14fo+A0CMGJxUkzOyQtUkDYjkaIqcj5l727H3j1pzoq3MjnSbYC4WhdHcEGmzrKZhGmS+Z6iaTNSTFWEsrUZJq8RilbZeD+wd5fb8P93GwAlkz/QYzRC6t309nDAqcDlW/c+t+2p8/iTjxMhoXGFVg30XUtQ2/Wr04Wy43vrZ9Y/mX3bsh5uF70SyocLwAPZ50qUZSDu8XkQFrgY8S1Ty8wWTHi/t/wDsvt+Iawv9E3tPfT2cZ+1a9Z/rI6eGHMVAlYFXCDS2c83hPIYxEnNkAxwvvbZenC99bPtfMvu3Y+/hcx7pbycl+fHRuI6mk5r2yFFqT2R+R0nV6Dhql6R9XkiSJazFm6K4syAeDe3/AO6+34f7uN8Lzsiv389vb6zp7eNz2bbvcMj6X9gLiZSMZq4kXS1Q8yjVxJs1vDqvC5i5ghl5JSRWDzKtSObhrdOXH8yWLmrFAIjw9a5KGvCrkW6g8lM8Xt1Yi1C0kOnBoVJ8N1NrQtl1Frhl6LR9PB/cHp8XA4fr559PDnmqMDjKm8dvCUk4S8ze3L/TJbiQm3IoW7ijag0iBBRp7YsyjCcXtkavDGvDHKQqn+84+r9/4/Wv1/7KXCni8x2jZj9btigcink1b4002i27bo7artK9K7q/GWQIv3ahk2WaTQES1FJutw5A+6ojfdP9Xl9/BvbB3f8Al9P6uPYvtqb3rQ4Dh+vgT1bey57cPZn9f+J4XHvHsXvP+Q3st+6e+fZae75P/8QAFBEBAAAAAAAAAAAAAAAAAAAAoP/aAAgBAwEBPwE6f//EABQRAQAAAAAAAAAAAAAAAAAAAKD/2gAIAQIBAT8BOn//xAAtEAACAQMDAwMFAAIDAQAAAAAAARECECESIDFAQXEiUWEDMDJQgWBwE1JigP/aAAgBAQAGPwL/AAGdnG/NoMWjbm/Bx/s+PswT9jH69/4bgna8976p+y1PRomTTVz9nO3L2zfBm0Lv1Lga7qzezA7T+qZUzSNmCTBpMshFKnvb5NUkVciptVTZ2dK6FeRCqQn3JIqtJpMEkMS2TZ0mozyRTfgm1Wk9RJ8mSelr+RzbB6hMZVaKbSP9RxJxBUTf+3o82VpkptVZ2+pNn99eRFJT7n4spcOBQRWSii2oo83qIs7fUK/JFpEVeCfcY2SSLpaKilCHCyfi0IYxlT+Sqzf6dUe4hVob2anwYt9PzZVdjkx2Ka/YlMb2VN9xuR1/fXkWRf8AUT9rKlFJk9PBQ3wLJBR5u7siBsqVWJGSyoQyCO9kMXSz7FLXYwaqnkhWqGMaqwenuIr8/p5tk07MmLJ2hmDFsjqtrtk5Zj78HJCIZgkycskg5stzlHCt8mWQh0iVsGWyCBojpWrQYZke6Cf9XYvi2TDvi2b53c35vz/mfPAnS7c4HUmSc4E5lCqPgS5RJKZD4NRM4NL4JJTNFRI6pg0u2o01cmDUQ+SO7NZ8jaY6tTJNCG5tgkm8mKRSjUcYIgk9KM7Ya+xBLRJB61CJRpvhGbz1LppPck00inglbJXBnjrWVWa+baR2qm8n8svJSIkoFBSylGCiO5R4soKfYgcEWdNtPuR7jqGVDHJN6hHyKRbE0Z2U2VvjZm1ccdhoZA/NnbHJkq+ENPqGSxlQ33Kp2vrmVDQ/NtVVv6VDsz+Csik/pQIpKH4t9Nn0/AijyfTu9ke13UnbJAldnptAkh6j+i3UeLLc0hubV+RjKvNoPTwaWhjH1PJ8W/pVO5dc0+5KG2ZszQx0ke5/xvk0dzT8GjuKn5PBE5Ro7i+BfAqVyUv2MFNK7FL9iRR2KKvYk9JqfDZJMjKqrNO31PIqFtq8Wjs7K39FAsZZOyjZp2MZWVeR+Bj82kk1rlGWakKpi6jQuERSho0jpe5dd8mWQSfkzklcidRBNJqqtqpPUQTQaq7eg1VEDgdVXJAzUyBpE9z5RB8jQ7aqSHZtLJweq7RB4OCItBHYpb2pxZNW1PZrRwZ5JpIEiYJs/Y4KVbBT08W4PUTQeojZ8GeP/jpJEvglGOCcwM4OCqTSj1kjVJ6kSenJFeCT0KSKsMlE6T8cXkln4kd7STaFeBO0bJObSyUS7ZZi2bSyV9jLITvHU6YPxJa2QuTKgk/E/Ekikirn9JVPYhjptpV9FnU/cfwQuRzZs1GpGBGBSIxaRCQhNWqfZXpYrf8AJ2KWSKv52Myf+SRFRJNRPYRqJKhWS3Q+D0kPtbIyqe219H221NoeCoyYtrZrE9kwJ0+/XfUs7L4v4tV5KvA7Y4GrO/8A5ME1FNU4JGU29J6zyN+4/NqSkVNJBSaUUedjGMYiq+TBVI6SoptS/ndJJVV72/gyqCWyVdj6HBMkPlbdK4GkZ4NSvULZT4IeeuqnvZjK6rNlVfuMa+RlQvpq1RF9KtJNqfA7ZtTUvcSq7ESOme9qReBuoiSnOYG3yUw++yoYxiKrIyT7mKcHtIxWW5oh5IVv5b0jP7dj6GIt9TZopPkmzHZi2LAtPPXTTyQZ5KldoStroNPBBxJwZJX4mCENxlmVaHSJISHbHJDpE3wahsl2wLxefey2MaqPg0o0s0pYIRFRTSj6dHaCIKWvckh2pfzu/myfizkxsY5tP35Ght9zTaRzZkQRHJpPgXsJXcckNCrfv+lxt4Rxfi3Bxfi0ELdi2dkWyY2cb8ITtkgwjgzu1bJ6rCtrvwcGDg4+xwcf6t4tGk/E4PxM0YMXwc5MmNmSFd07ZZK2ZOTBLObZZ+Rh/tcnO3JCtkx9iOvppIgdNngbOB4GyOxjsOn2HqHA17FUmEZOSrzaFyUzZ3ntZU+4hQUnB7Gizp7GUSatjSJm2Hkh9rPN8bUrQLzfTSTI55IXA3qZI7MlPpcckPY5NVJJL4NaeCSXkpaZN/kTbZD467+2fi1NKuqbO31LaaeR+5Uqjm/1PNvg/tn4uz+mbfCKWhVUmmq3B8q2gSuz6hFn8Gn/ALDHaSpsjZTenyK38tWV+RlfsMZULPSVRzJ6kY2NmmyGKyKb0ikz12fezs/N6n2s7fUIXJqq5tIz+2r825yf21Xi7P7Z09j5FNqRK0fO5lZPYbKmylpQfwdmORxsp820sp87fqMr8jKiqzJnpKmx7fc10iFakRSK9AkmJddqXY06TVUMqdqj5trpIp5M8me1sHuiEs3r8kH5O3I/A7O0Iy82pq+TkUcCrOTUMdW2pMekle5LNS7WZyU0+4vSimpKNlPmyZT5vI8lVXuVJrkccQOB0vkgYpduOiqn3I2Mio00kGmrsaVwJJGmrsUwsIi9FXsKWS+v4V8XxbJhWmLYtMXdV9V8Xgycu8mEZtgjc7afmSEQaR2T9rIi6d4OXabYvBqtBhkt9LFJnbFptO3JJP8AqzJyfkfkcmP8e5OTk5vycnJyYvyc9dySmVZPyZE/Yn7kyxZ/c4OduDn7DIknUzk/JkTb8j8mfkQqiXbTQZZ6WaauvqvJFNuxFVscnaDB2PVZwmYRwZ2Oy6X0i/RcWWx/Zq8Cq9haUcC1IxshM1VWQ3ekfXPY9kWYvO3B+JxA071WXS5/Ruy2P7XN2Km6gbquh3pH11Wxp8H5GGYRmz8CbOTDMcnrqg/LJ2IQ5u7LpfSZn9E7LY/suOSmXdoTvpFVN18DTMO2COtja5vMbeTm0kycsyYZFoNX7lie1om+D1bZkxUz8mZqYryJbeYM/wDwvz/jkPYlSOV9pfPTtdySTCFTXt1drY5NUuLRTyam3H+M0+bv7StR1KEIV1dWYxj6r//EACgQAAMAAgEDBAMBAQADAAAAAAABESExQRBRYSAwQHGBkaFQsWDR4f/aAAgBAQABPyH/AG8DSSIiGqTAlHgjVnpNq2ZGymhOw07ENSIUuOnIkSCSSiFLWhbUuBI4WdjjCUFgizGzjo0SBdpC8ODHIJ9TBx/lr4r9idF49qfAhPmahcCyvjT2Di+QviclcYmnopSlIs6UuQmnE9SEZsZDF+Fu2J7ZfdsFk59qzY88X8hvIJDiEtwfVS5OQ+QviPY676JQcWsTTEo2kyLVcAq0yigacC0khuCO43xyKPFyaLkxdELT2TJ3HxBPv79qW6ItkzO/qH/CpPfRiqsoq2d09GdFQoiQRtWh6OjPo1zkXnZkqDQ4M3mSXkMYAqV1vucFjyJrfrXvK3yIZ26EnbNwEMUqTGupqCvBuzo0QTT0JoioxwJprHxb8B7ghz5Ek+0eduErZmUMj2jNmzeiBG/YUrB5GFBTFLGnK/Rb0C+waGxxOBC/3ND3uyht6QnyJ++78ISjg08Z4hEWWBZNjJ1NsVF1TeJ0Z5CzmsjX5xrdxl4CJDUjPkcmKZO4mSX9hRFlptbH5mmRRsMlIPlvgmE1oT0/hrGLUzRfS4+hpeoveawYDuwcpCg85bJ8MnAW/AGy+B8hpRwXGVB5jKXyYVKKmUf4j0OEwOGTUxtVwfyCMqyhCSCwWKfgx8/QXdqCXCI1E+w8+pmzuSZR/wBRNsbxGje8mDXd49/ZD5e/HYaNqaF6IXP2EQSPY5NoT9imC1YPyDEkkNH9Ql3gTA0L+AfoukPv5IszCFdwETWE4mS1in0Dx3IW02IRS8ggj8s2coNX+PiaK4BskK2J9EFKWDomBm3MpfX0bL7iiK8w7m4NOtMw8Qr+UvbsAxlwLGmT7Ihc10WzDZsLsz89ceIYhqzg+2KbIvbUhuQmhz5RmXEYvnOTY6xlFpQuffN+3PRaJePJCM+B4pAmrDh3sjcC4HXkiPxDF/F7E5dxUYxdQnBsJE6PPrGVo5wWKWaxiTEerlliFrJRlWjiW0+ws+s/jE598F+EKTp96ZfYK79kfy+te79mNxNpNSQ7CZkWt3HXlWIXODL6jLcohbOc4y5VeAxx8r/IhT5oSmhCJcBq3XQhGl1kR0c3UeARKEImKDrWZhDJlCQhUErRLjwRLY9kQPqCfG/YnS9ddASSVCcXC07hpGSWxSQaI35Mt9xdComsnRufJ9ZYkjpQXctcNH5gEqBhmQ0FnWNC1aOhnixDyo+TKvEQNWJPPksidnrXu+RPeDJZMVyPNkLDTcJThsWi7i3fkcyeA+jlGzAtFvuz/gbNC0cCyXj1Trw6fej/AJ6eevLOOr6T/BlO/GX+vDZMhGOBObcFkZU/6Qjj15OmzYj6G+RoGIZ5C+dR/wANgxliOR4TyE08rQxMfRjgkLggZSTMoqrVFqoq0MR3vRtJCR6fsX4q+EvgeTKywxbGi24LQfp/PznpSsk6FtBFU9Ca1wNY5IfsNeSCotyUtwGinG13IrjbHtjynA2rKSNi25Yp/CDXZcD9oZkbDGxhI/owUC5thpCy7JiOXocqafYoah3k2Yy2+YZOJDBvAQavwMI6wxigB3ZnhDDpmy+s4Z7nkaLUebsJVZ60UEaaGhupJMmKrSG9xP0GcGDFooloyl6O0zRgihlGoNC4wJ30OyMiTtBR7jED85TpEWMf8GxkrCHCwTqolayXxfsr33gYxhclXIXfpz+szdIWrR6FYMGab+TOrXYyc+xN0biH5Yi+BXNscstvJlXNZSDKQnVdkNaXcSodUIaQXgyHjqLpXBo5VGq7roRo2zbcwVISGGxrJDS2EwmoIa5MyvsJ2ChYKE1ZSZjCYL4TLE2Y2F2hjAmm1o5Om8DlR8s76EI4Y4p9nA7GWrKEJ5Mil5gi44JK6QxXaoRH0Pb4o17hIcdCJPhokL6GcryLsKDw9ENkxk2QaIXhJxltGjDMWV0NGeC02/I6xgOSqMGJWUH1o7vYXvpAPvYURVkSsKbvJist0hn4eu8DqXkwPmv+kz6YYrfcIJyxzdHAh6XY5PIt50a/Y2/Z+pEp4iWTYzuNlmDfBgvwJ3cm2uTYLCaH4E2BazkN+gS8A/DGx5E/CIZRabP4M/xGxoXuSO4+xqo7C0rlbFFbg8QolbwPp/KZ5sI/FbEwBdSx1XAfH6FuHYieDjr3Io9BZSY7hcUSM5vVCfmPuZqVD7HlE10f5xK8hL4hsvtpmTRhUNJsUd4P2yeHkbvsL30JY2iGajwMYnMCvZ8hE/CLD6sil5QsV82wjvVYF3cs5im2NrJwh9jRjl2Erx5Ytcl6GvDopeCqX6rFgdooKh2GYXSiUkeZSO6OQjMyg80MRcBYbC90bJLT0gpSPghFt6Nq42FNHwO1akMA2sLwImrhGNxnZH2+MWFiE12E+rgO2jgVKZNcdNmRM1xcFKiZRbdFWN9FqzsMXdhjgZr0b4OJC9GIccYgznHIzJU7G+kwJi5jM6xgRJSeOjYXQ5+5jj30VOB9xIBEZ5jMDJRnLHrPHsL3kYZMmdzoeIYCE15rEuYXoGDRl5PvfJghjyxjq64DE0aFA9h1I4mJFuH2PCbPPPR9RlVNY1wZOyNW5FLx2OMWVhQ66Yyi0WNqYiD08DBrQZQyJYYBsfZuACqlklCl7GIaE9Fe41reRtH2DZsfYrJsfdkPW+ejXij4Qvs+25ENwYH28O4zhjFa7lD0PgkTWxmOWjG8DaIzFZGtM/Y4lpkk6Mk0KcQIraOWVFcHssAunA2yx3EkSfkpLYftUL4DKRsc0qslgLjaFxdbRq0n2NSK7UwFINxsouD7ewvfyU5H/uOME/YuqaxURKx9iJCr0PwffKl1WOjV68MWxG20aU6TNLm9YPKhxBrAsLrzR7ohcuqSRDiDNi7HPTRx08BzocpOnEEi0c9Iuwkl7DSfCFjq8+iCLpAkOC0qRF2IkoiBKjcFGj2aU9lei+1wRUeydhr1RX/A3ingUT2jMwLOTMGORcsoi9Px1hxenB5NLRnBOxxjpRGOqeCQV7dZkY+mC+jn42zZj4S9hzv6EN5Lgi2fgwfghi9YY+bejGSyyO2KrLrsOmmgZLbbojMaCqN53HttR0GsVUkpGRC1GtERyVhfqGKFo0yhozhs1AE5nBbVgb1TEWgRgLzSG3I4hxKT5E047DgWiiSWwgrhYGXoyiclz0WspPq3RtIaVdBSVFwRCE2oU3AkJ4NckNkE1alyTzZ6UBEhXEdG0t+vTej0a5MwkxKRQihfRoH7a9HBcEmtiaxRy4H1gmkUUx9w3JlQY1SGk0Q1sYgrHueDzOrhnROJ/NfYxheUnbRPiqnB5MyD5PsThOcGMbaFMplMh5TOQ2o4R3QbFUosZLTwibkLlWUZH6CVFkSxFR74+C0Qf5/RvBlDHTkJoW7MQLR5LcHtfB5wwfLsnCim2jNtrHbHgoMoA+hOnqtyM16v+sU9aE9oIco+cgDg8YJ2zbMe+hZeDtAnCaP5D+HpHumT052ZkLUzTWRl7hjkzE9gmrsIeCdGLiERYbODZdl2Il9yRe/yNs0qV3gx/wDHV85sYTZAiH/nFLJx4dtouZHRb3kUzj0S8ro6JiTDQ/L2n8Hk4Hn3D2fQ2N9jV6Y4MEy4+8XODOfYW0vQZThZbcRFh2/hlPAhU8u7KbSHJ4DELjwZcf1RCIMTuERJSiLkwQ2XlWFgx62EtN59ARo5kYfQUW03kjHFa1B6CpUYpvp/OMwQmfZY4u/xGJe6IaYET2GV2C3u5njE1fA1+o8jIJzenFncw9kod+hcbgNHCxxuwinRs3SQsfUd6WIx+g2HcXuvo3XBlX/qMpdRYY4vSRvuFbA0bTBYmvItndH/AHZreDD6/QuJnNNfbn8F50PA1vMNVbw0ZXiMpHpCvhL0W8XArjY16BDfGTGX4TBLIP0B7EKEang38jbkfZ3Y7owhclsy7uKXvTucD0GafA2CSps8o6VHhm1hwMu2MU86HE7gneBzTdujPsEOaK+wopYA47UskhioLRHos0UX9Rj3hlkEefGWNzj7FG8uUNXCi6GBaDTRsUX+I/n6pY+KJRelPRmBepClaEPXTbLpI7A7iGol4Pkb8DyR+IscAvd4OxQTIe1GlsnnpBAbvZYNfYoFKWjH6zJK7mE81iqsVaEyvt1cLVvgN9I7Ro+XpDPDhykyjZC1TcGNq2NRGizgY0UaRKsZEJrt0IE36FCszYmbad2Yh4l2MMZ81DBJIhs1GYPxQyyZZRwDCdD7PASFy8wYluCm0qCMsnCIMGONl0bb4FM+A+iyoYrDVu7KywtJo/DRvabL6ttuDLvkmCC8/A4YzwIgq0aGfAta5BquhhXdbg2FIxD6FGsAqIkgpfDU8BGJhE2MksMslL1zbhUYz0ZeoWVdkxo3gbdpkfm7j2Lln8IhQV2roRKe6unYOCj9w79h7vItjXR5HOJax40lk17EEraiI4BXlIuKUoueFYlDpdXSKmjiXwVO8pgXD5b2c9PojeTbyIk32G6OkJOCPoauCDwkYOj2LfR4X6ISi4HlSJ/YkWkILxwO6wJMcFnlCTRBQJ4ArhBrgbNqiSCSNaHlM0nYmaALE/A8iEj0YRo1oWLsEJBwIdCYknEvkU6cIZprJo0cGXRwg5iogkUaGjusEGo/o5kl+hbposEUJ6JSWS9ui2VrsXIiysiFtYOfB99EiCyQ+yiwJce7wf0bVZMsiiWJB/5Xo8od7Gd4Ep2BCxcHDBdl03qEtI8ceh9obVBKLHsv4iMfk11vowVdhCGK5mjnotHByvRz0vTXpVTO76Z9Ffw3/i5Xob6L0Unpvn238TKHDC33YZMayNyYDbJh7EzHocvkMsmhctDiNfkQJ50yD6DiVCVNsCFmEKtoaOYZOhhJtiEM1lEDbFFXpgaIiGaZkutnFCpQ7IORRRaA/ih5EHMno0qJbpDQ2WEvHVqn7N6Pq3EJ030pSl+GqrQwyQTq6/k2xDmg3QjCQVRRkV49N4aEtVfN4PskeqUySNjrQzBomYO6zmIxVxWhPdTZsFokESeR3ILWEsfI0hEoh9+1Mz34EJ0i7L/ZMqxskfa0M8FzYExtc9FzJuQyc4MIYtJ9j0G8ClKuF7VMWXilZg2efeCI2EayborhzwIcDGKOBK7D6cjcVGIOIhDEF00KCDxs2Ir6LOmZx0aXgiY16GedF5JSlj6cozV9HVzuEzVm5ofDFOlZmE8De7Q8ZjVZcveRDYj8mVtv8nHwOR7EtXhYg+PASODrQY4soaVNFx+Ci4ZQIIWfZmtaPuUASo6VXRi5XLQopmqQ/A9l/EQnCp0KkRi0P2Rl07fJ5MPwWUl3FwIvpY0WSPJDne6E4Y6MLSMo7zax6OMWrOhVk0JoORzbYcoSoL27iI+GhebEK1A6C5HbgUxnojInoYtYfR4MpcFl8hCngnT+QzYQJTo6JQ9ZDmmmMKdibo4Q9UK7VSh18iPsG53K0PiCDwV0XL7Fy3Sq3tMZ2JT9A2jRdK4g0d9ilkUE9k18F8FjKvIEmdfgqFw+rzCK1YztNbyc4wV4FfrJajaz+XpCmLfjCa215F8vgY4keFDdytIqnELLdF9qhwNlEa/IT4Y0XrZy02phsg3MLuxjX0NyLIxr+D/o6YxP5BwX3Fcjy6E08o/oGxEEbgcT3Q3Mis2GIaSCaO0jsZzwW3MITySRmJhMNxlK8ZHLrrxfBk2oryMPPZeRS3+BAs2GG+ysQ8CgbYmGjM9UTqFwDLg6pZp46be8fQZVdzVhfexmP0kOChuOB3YupJkLopR++9dBZGmY40hzla655nY5i9ZC2b5guGZKZgr6MSbRdyFe3W0x4IODqO5OBfLazR8bxA8ipsYn2wm1ui8iX6BVn+QvIoaDuFwMq+Wyy89SWMHKh768mRsSDc/clX2LEBvCybZCSrKrMXl0qVDqFzvdjtZSQV5i1HkVKWZkia3E4MomHI174RfyRb4xSKeD9Ceha6RjFolqak7I3IymemkItXhQzPuxTqMcylFOwCZkb4Ij6yzNFkdo9icw3ixI0J5mmhPKsGDhgzTU5NYeYEWOEGH5VFUZILUnk1v3C9CHnApQKqvLMi8UXcZMD/pjRgaO+4W7vYmtApoodnRC9OFdkyB1WgG0Dg1GDJwL4r9OzgyWUchJcE2mZQcGUPRZQ1sTiYE5f4GuBubBt6FcEdaXkTeCkUiUEspdjvCY+l1bgxbkdN2aghYQhYLmFqQouQ5djyjVOIH4JsMCtShkCUiCG77Yua9GhkPkSLG3e5ITt8hhbktpyI7MRUrPqNFhG64YqIWui+2MdqCWnIhTcMSKkqctlu7FO2QQcZwJ3LYntR4EVMmNbwMjP2Mtf2J4h9eh+telRF7i9jOMjRf9w3BaFpngWLWtjC/rX0ONsEQYYJ0SNa31SxBEQJk9t+4/Xyixm+mNT2X7nY053+VPmz2Me4zm+pPE6PfSmIcfM2qDhZKrCIsRMDXG1E9er2IMIn7d8lS2/wDZbE7n01ckcP2L3MwaMQPtzwxk6QsH2AuFO0nDQQ0Yvcc3cF8QPhQXgfzFzYlXMzQdYoLWBvhYyudMLpjvkqpRxb30sBXV5I7iMdLh0vTnolaCmCg0ttgep8Wwvf8AwW2SpjLIutbZkr64o32IzR/YxdykJPRVjZC8spIUORmOsGzaFKf4DHNh6xR+a5BcN6H4H1XIXRuZ36ibJ/NWL9jYvB3B1dhLWaEBCz4GzJoQtusi0suNCmp0swPF2ETnRQkwG4TBJkSn6Bpj8or0/Q5qGn9E11dc/rGaX7Gv0fFPQICCsX189jRMi7DRNU/k6tiyhdkyX7GQQAqzQuxF/wCpUZHvAlrbMro+yMGx4MHILCGTszbFXTDHsZKSyU03z83T9ituZ0Y0J3GvSqXAwvvoZs2Vz0T9MZq72EskvBZgyJqlCa/A77focjMOWHAhF+Iv9H83xUTFf8LL6BWlzyfweiMjEpTj12ZdxZUR8GOSH2im8HbrohtjvYYpEuxwQfE79Hso/wCz5fk8mK/ZLeuxx0wfYlWMJscw2UMlomvu6ZfaGktMX5RHJQ7Y1pSsouTgjC74NqlWmhDLV4H2pzew5WeT+b4ru5BNFNFdn8f4H8A5+QwUu3XwMliM9OCVJD3Dion6KaeApvsb2JJ7LCn6GYJj+BBXOOlHOJiVudldV9hK23kRY0No5+WzwLA5znrFQ5Cb8GNlwrVlGCg8LpONo12fQ6cYttDVJshK2ooI7Ri2r7mfYYDGDNu4bfmFirt8TQ9ZJcdOH+Bmpdj7SzAeh3cRsY5O5Ns4MehpWGQya7GGzzbfuM+BALjk2ZJnb9njtdfBRemJLwyeBuSmzRL/ABZm9Z0SnTm+u4L8hvBx73HrY17s14Hn0LAsHPSbpCSXAkQ4HsoXJBqng0+mhZL0RsTu6b/8bmf8Hk530syIfWzgvNKb0VcoIbXYv6Mdui+S/wDdUGRvRnpgWVV00qnS9eL0gMgJkywnTn06+C4oO+elWjmgvCUuM9HsiHhkRu2+k2x2G5BhmJb8hMzFKfy3/uPpz6fwGWfcfQ9oezjq2H8Ij+7o3Y+phcD376qaN32f1H8vR/8AA2FvpFUiRksKma2KqP4TKvcVOyHw8Z+V/9oADAMBAAIAAwAAABCXOr7Zef8A7HG/5SlIvzPbPT/3KOCB9n3f/mWPW1PVv7fxLd599tZnlxpd9tpf/PPKoBx8BR9O7jKzxHrd7FV05JNGGUN9nb1yS7x3bn33nqDa3zNP7TZd99d195JNRl1pJ/D3/wA52bTMbKWchxyw00z6b/UCHQW1gP8A6cetE0utUePJove5cau/9cteFv33V3WnE8MMv8d9f/8A/wDTacQBVaaY9Zy7cyWV6Xji47wdvdvw7bR9wVR70+9wyg+NY9331QRddfaVdUba7w7XVw98y1/YTMfebQUzVd77yzywlghk52f/ALv77x3y2XHerpT3gIt4WtMPGEMEl32mWnsfL5b2mEMP+sMLkDVnfUHmU/8A33XrrGiaqbzBbzqmTgEFht1ZGvavAGZ4/vb7DDDPP99pcpXHD+MotBDD/wAyw6MDCIXGc202l/69yzluognwc7mz24UQ86221jfg79oGx8z43WfR8/faeR3x8/vCmAgwcw6wpYPJGWC8+61r5n418vkutojzyj7bSy1yxx+0oXp0238y0/zXz7X67eeaRx0x0kApSAQV/wDP9eXl2iydf+fxTYcsYYoY8Oa4afHElO/dctO93/5MF8uhDGkecMFPOEUX38evdx6Y2kEE8MvdUBDyrxMvbbdNddLJ7K5tuo8+MIbqM+cOvJqq7ZkcfslIr/ecvKstv2v+NtmHZaz/ANNNdbrBVtBc8oxDKMonPGCiKX7riSKyfTGi/DL73rfe3qPbTjxK673W6bDD059n/wB367u3/wCWWVVu11WmnD7ykVfgddse+vc8Okeeu9+d9PsOO8N5tPb/APzfvHFnT02DXDXQs15TSKKq2PHYQ0XqXDfrpMvap7hdr7bLXzbHXpTH773vf37XzRre+fp/LTTbPT9n6KDnDXAsR5HMcK2oJpC3O2zbDznh43rr5rPPnrnLfr33rvbzrvDXjTLjXrHujbnTv7fz71rLmbzBbEwMIS4ZDql3LSfmDPPLDVBkWtiMjvTnbzrPTHHK6D73fjzjf7/fzb3njHHrHbPrJbyqbLD9CT1NhMe+4dWWdyTDXzHNp5tJ1MIDeKXzHLzLvNgmvQmwpDx1xjjTh9d5H/nvzl9FljkUHBDmLrOaqGuqhIKCWoLXnLDpn9DBA/r3bH/LrK23ct7zpJBT3fqvdVNT3/t/pzptppLPDRPrXr89W2CHvPHX++Q6jrLX71BB1LyI7iRBbxFHuGfOJtb3F5pD/Oys5T5X/vZzPXVtpj3vBDzbHKVePTvjTrTTatbrrrHbj99ZnuknzlBpDxRqaCSyPDBdvpDXYuUaII8+okM4VN9hZT7BDTDxqqjnr3jXvzDvHX7nrHXrFJxTRW/fX9+//wD7TDOz6xUXefTSXfUy6wPP8+foEffcRZa0Qw/6wfx2xw9w/wCu/ddHwO99cNfNN+UOL+N3z/8A/wDPffg/ffYfXfYfQQ/wwAI4Q43AA3Qwf3/4/wAF10GN+OOOOP8AfD/AhDDffDjjfjj/AP/EAB0RAAIBBQEBAAAAAAAAAAAAAAERABAwQFBgIHD/2gAIAQMBAT8QuHFGgMFBfNx6k0dHHQcOIeAUUOCfR8nUCKERRYxoO2fCCPQD5sM5akbdx2D7FDvjYcPxAcOIcpZrvjij7O+Fkw7v/8QAHBEAAQQDAQAAAAAAAAAAAAAAAREgUGAAMEAQ/9oACAECAQE/EHJUkxN4oKSp8DhYjIjmMqtLTeN4gRbRTjCIxMTuMyNxj08S9LoXFafBIjmDTQhRxRxQCwsHYc//xAAoEAEAAgICAgICAwEAAwEAAAABABEhMRBBIFFhcTCBkaGxwUDR8OH/2gAIAQEAAT8Q4qVKhOvO/wADDwuEWoPBKjL8Bl+F+KwzGjv8HedSsxyMwCh+IBQFEL5fcAUog3AmtAtsTAmyIEj2mFVLnH6lAoAGqi26XG1FjojYNTNjDu6g8WDZDWINRhmW5ngHNkO3ewgH9lIbDVYA1HtaTcGB3cVgJdwaaj4gdwflPWHyQQZATEoodkVQPF8KjLhO481Co+FQ5XyqVGBjy2jxcvjqVK56hO514HHcCtzcDmuEm0CIE09oGI7lQ1GbhDcsWhljnMz4s78CXDLMRCvzUzud8P4HUau3jMoID5jyPgPBDyfw5TBhVRwxMcE1uLRDJzXB+N3DXj1No+d8dwhxfjuDowiC2CDkcNuTPuWQyWRA5gVAtqFoseO+XPDJOJn4Qw1KGMY81+M3O5rSYwJVMTPfg/ga47MOD4vKBehB4TRNZ/UIcnJ515pBgcriZemHBqWxgRegM1g5uX4sPFjudS/HbyuXCHhcvxNzH4Pct0reviPRFOItACZIbI0qzcQ/0hs6sC8Qm1J9zB+zdwfmqy24AOMEvZw1qCci3VsoKbb+Ii4C1nFvUSqhTqUJA7Mwzi7OKiBSG/mBA35J5Eq4XmiJiUt3IDuJpL1Ctnw3CkLPzDF0RKiAHUpSj3WYUVV9zK4A0kwyr4zAnCsdRp9dSjuNmG4pmyYSD18dzXt9xR1kupSsj4jLDdGNwsts6icXG6Xr3CxbYwwJ9BMkbqAseljqAX2XlD23cbGEtWg79Phcuor/ADG3Yj2qkNwMTQgl2J/cS80SoGmbDuICmWIS6Q74+AamRd1EFO2JBYjqELUPuXnKHD51E8iMphGLly4cd/hdMwlxHHvp/cfVAlLiqqFoIBaeri4YLuoDF7biQsVFuW6wfmYzEVc61ILnRtUhB7Bj4ggislo6esGquB2shqKnzNYRKm6v6mUvN9oFrW24LTbGCC6VPUVK6lfgfElpkgLa/wD2lw6GCB7o4gvNoYi6hTFkXUEo6lVFh6uCBSHUEGnZrDBMb6lRYGgXVZikqJeYaqqrMWj4RsvQTOoG8REQYvUFsdQXIqaxcVt3qrnrxx9QVBZh+5i6/DqBz6CATB21BMSwcxg9FcfOQ5VcpBSsXgQH113Et5FwVcLuHG0l83LjFmGvyouu8MA9hxmA6RuKZgKyC4nrPsirTL1GQXcI0+8dZ9EtvSOaq7xDmmt4mfhRbUNkUVFxkQVtVAw7ahfcvyrl46/AmYcXKhKz+HJEF7YRNuVglOrAFSrR3lErIAGACklRjEAhVDGh3id5w6gsLZ/xZUbr6m7ZHEKgdGo4+wt7M+WuL+IGw5rUddzEzzVcJS76SgNh9RKLNW0AWFEyTqsgbHPglS+L8e4IrKG7jj9yph8IPtFNQhaLVwLWNQ0LHekS6hh8wMqtMyk4VeI7brL8ywxAMOFStc+xglSPzC9HMCxseY17Z2ZdJ3RGAldkoWqohmaLMExzen4hoYRlcJfiAFtCMu7aVMebIgFgzA80pESyLCDnylfEIVsIR7GGJfibhrxvkJXha71CN2VjM2N/3BAMAX/EbkfRMaM2zJC0YiF2XxLWq2EqQNEGD0P9gvNmHwy4HMbS8FaVA9ATBQ1DUfKvHqd+b+A+TM9birrBmLAapiAYTWj4lynM2FOkJUwhXMK2YWpvWIYy2PUQoGD/AJYsHzADaKLE0pRZCrOWHcWltC/cM2FC4QVLPQhuEMJrCJcfuOXapYbJTUaK0A/cyb3yRWE6jwBKRKjgWepcfRYYT3EKIqZ9QMLQuCv2JcMFUhKXYGUJfqMIu3QRolFRD9YsPUHuYWT0nTHSEEbK3GF8txCjNTttiPcmpKg6KsZeEzow9NkMrhSNS2DWI7Ro0l1G7MKt3fEG2AmEOf2IF+rom69qmMvZHd/r4PL5I+BHnqJYGjqOVeyO9kqXY0DNRh6YQSNPFRpV0B+5klpvDhVYERxfpKwjdfuJIblJZJeSC0+ccV9fnrMqV+Im51K4J1OvBXfDEBTAVDm3uXwWKJYVuVw8gxGxMYSg+ofZO4Vr1EK1rubcPXUbUHbG4YvAVqbUF2EoYDGCXyAi5qod/MOncaFT7NzRVTdsK30e4tuGGuepVvgXHVRyD4mZauHiBc5gAdUrFFyuojDacNQjcDZcLAB0keQR6vEWtBUV9Du+vqMAAsW6ggLRuXhXdBIaIkF/QqD0DHJSMACIrSOtRm6mysPxL5KGknxhy7g41FXeqiqdBCIFmERfXdNQsKlb3CAtFRnCkVhMdal6bAEuXw8qocvgR4OKbFojLMGoJ97cSvKRXBugal9vbWwRVbBcPrhUXRYFMmZjL94QLNjQyg1CiUtp2j5P4CVKlSpXhXNwcS6xDXFdJYG5lcwfMbq3ABrEwyI1z3KJhy0l21GynQRbbODLqKYrpi2n3yLR1NOoaJvMxcQzuZal1mfPnf4WXOpfK1+4b83MKwA+4cmocPDxUMc3L8Tc083g8Hl9y74PEjKHf4TxIfgqMrmrYLd4hlgVCADEWruW9wamPTA9M3UNX+krSD5hOQPXDWAlqQ/UpvcoQ/uANTEi3KKZfuWKQIihiE6EZaHcvV1UwyOxCbrWpgbjgSQaqmxipDA7jeALcC3GJZZTD9QLRMQdIsLlmxxCaqyBC1qLaoo7mcNQehWA1zWLuL8y5cB7lxghuX4LL8nje5gKODc23wSo+DDzeHjzYeKSpUaFmHzC4Flu1kQwUjVCvgXTi2ZDNIt6zD8B4kOK4qV4LLl8d0QHqgLiGcsh3LAaGSObha35JdppeIxehdRlOsmJIDpIjCv1ECV3l9RKilgqKkgZN0XcZ3FUQuCivsjaTcMRibdPULqqzdlSYFECzMlTPEBoTuLrZ6gSlomJ9H9sB+lfylSutA7lYaOPUAH8S6LrkZiABklg3R9oUZ3NnEIXjRDqU4NW4+Kt7ja1KbjwoLUZXBm8sA+ow9REOGFHuErL0ud9Bkl5I4m9SF1EDDeYXwGX0xjAKi5h2dlxVKvbC0ZqVFastw8anUcL6j6TMIq06l1SGjO4fzjBZnDG1s5Upw+oNtRgbhzlN0fEGlLNkEeEXRMM2x4j0huGlsYDVN0st3AhfUGAK7g65ZSnTqYmZmHQC8QyX15PDzw+L5Jwiy6C1irgsCCWgZVB1rDMXk0dIb6m5ViaWjLxUBpeu2NiW1Xc7JLZd+TyPicBK8KiSonDuVA3Hq20L6FNxFKWI3AUVH/WIjlR4LVyTbUU+orEGNH82EIyC0ARkozkwVLQt+oWc0by94qFvuEtSoyEawI3cC0i8IClQCs+5QLw3f1KUBaZuEYpGUgih24/UMZSFxq2iQ0sD7fMEWuCXIAYwvfDZATBVShNpNVPgtzcqxChQVGNcilnpFR7IEUSzKotzEasbC2FvJ0uEoH+TNvl5DJCoH3C2tJNgag2wBTo3MYds5IZnApYUWrTUQSIdzQUdNRYXMpbgEexLqS1oRBBpMFwsS9wNHuAEy1r9QAjSGHWK6XKGHcNc7IFEDMdtSpy6XLiZNVGQqFZMzKKlVGCGFNtfxLsrJ83LlckIVcczAKXVVMKQnqKjUCnuBXUiU9xi86gQaYUGK143LlzFl+dwh4EYTaJDc5fuDRicZgq3Kp/EOEUV/uCsFsqUm7whLmmdXCdoOYbWLHfkw4PE4NeNR4fAd/UYG8WjOg2YhUhajO9P/awixcAS6ovYYbmCkba2P8ApMm1hgLWShMs00APa5YX9co14oqCehDIx3VywGzWWXK/9keuwf3qJSKpGfIH/YXsV/xFZM4QlFEWKS/B+5sxun+S1/avcVgNC7PcBqNe0QRJ0EePU9nTqG5GkeJsa6grAw5mIORqoTdMkPYSWQJUDoemUTCYRgohtbHaEFYlgomoFA7QysvFkC7gdLtQfcAjpPcbaFS6gII4FkE7z/pEAMUipprqIFDceiejqYv0JQJdn+kvdYojei4Np1AX36iVuXNlZtRbb1KANgCWwJU+SClFWoYauy3/AMjBbPfxDvNRwqrPuGfsKuCyAdBEexD3LGYX/kqumRTf3EZdPeWx16+fJ3xt5PFQ8TcxslFoO5YsLV9MJQIqv6lrQuz9xRVXiyl2yypt4AlSqt8kIwTN35PJ4nmvLMQ4Xv8AUHt0gmMbEeXCqj5l/wAbD+ZZM5ZcJoEGoaDtf5EFKML2rAkiFCLAKhFU9uzGEAKgpleFQeYwq+o2pWLjh2EZtgoIGwu39zMKL/mAGNxh0KFKQkpwr8FQpRRSEPgaCiTFptNHwX/EVny1UI7ywUKslivAgXUZ/WBcx4wuYnkv3CWbboZvVppusbjZ0KllTLdNRrrp4foTA+ya+IbzdmB14VtP1KYb2JyimZNwi2Of1GDoWN0wij2fzEq7KGPqACyKWNY3UMlwcjKCog8rC/8ASXKGhEC0o1EqrA6OpY5X1KhZfUyZuxC1y1svRWKYgZO/7lmdjP6hp9Koq2UEDbXxGFyS6gEiw/xAMsWKlxnWgYJAjTEwILG4fdcr4Got8bQfyoG0uJwFEVpKZnZGgLdpNsFXCdcpX7lel0ZgDRQhO+CzVXLC9MR1Y09eTyeJOvF5InDbgKK1GKVLFUNggrXe42RrAjkyOmZl/NRZ+dl1OlVsIxsFm24A5Fv4REcVvGIMYjq7lDjKHW7LsGoQzVndxZgCqIMWG81NusDNx1rMI2qP5qLFIe8xw5ExB465IMq8pcfDekoW6RouJ8hoxdvcRN1XDNzhbg1IZEe5bBUwRklWtWMg2hCq9+0LX3G6otKuoEZjFkM5V9nuDsbbL1FoCoBUK/cwm2NVNoiEQhVVKLWVErQVcIEAp8wBBR3mOYBU4lzIGLitFWnMKQaI4SWAqJIZjz8BIcgbqVCK2Yk/sTvNkIkxNy6UMC7KuCkKsQ5+uydFf9S0IoZbh4R2ZV4XjUR3ay6mNoRKiQdVXUTgV9QK3bH8SkRAaIp0WKmIMhGVK4riozaDjg/H9EwKWEGVAPu3uBTQ6Y07ae7jfK2BqWRZZ2h7MFXyXeJlJU4Ll6a0wX+frw7jyR5JcvqDpC6dQhT/ADHAWZlFjBpIDYaYC3dfM+kNSyplgU3EU9Ip90TK+5RKKfEJ0PqFUhMGqmcHfuBV1MhjcABt9zJDFepZFJTAZqoYv5jjUcsqIZS0SilmSAHO4Ly0Ss6MEfiXTG5RtshdCxTQ3eoUYjXqUjFPxG2XXFFujBcKlDlHJVYgF4Z+IooGYkKOh+4hnG56jlirVy8J7iwtx8QGpUGp3cFP/XAxzFm0lBQBAA+Y2BNsobAvUS6XiLEpNKRu0+qhQgHxFNg/ZLKirgJS6bmUFaXfuAUjEqVK4rMqMIrIcXKVCdS+oFc3NmNzINpDNX3FaoYgM6KjgNJoPWY5blWjc0wmXSSzKllBg/FefEgEolxc8kI7lQg84MpGtHOal6Y1Dq4YFQMrl9RC1CdMoGx2gEs1MjUQl3GjqLi5au01LyBm4BaSANT3qKnVwvJKlhtM2KzNrDXcycqCGbDuotNVMFxcFlpDLUWhSKAO1lGWLm3NzcqLmnLDcs2JY3/EFFhC1uqitLMzauuOpuUkXLhwEXqVKlcXzcvw/U6uDmJaiI4BKtUHKdEu+o8s1CLLl+NSpvxUqVmKmiF3lomL3+5kybl8JBnMDQh6O5laTIWoHUGuW/iZ0JiSrYaJXGUANhbMuypd+VcVKzK5ODl5IR3zcQOYoV8ywhh+oGMXt6gmgOLPcqIx3cP7wW4NJULi2GuEZbcBlOp8GJNLTqUAaotgADZLQLpphSL5F7l0LFJhYNwCqTVxGhQyhPKOMwUE0CWjUmZaqvu4NtU7uWyd9wcAqlRRFOkQuq3ME34tHRAlsN79xCUxA6m6m4utwci3THV9kpArObqp3SNwYiMJ2piNdwsrF8EooLWFa7dwq9jPvKJYUdMdLtMpZALJjH9IbqmUodsBVVo4X95MHJa08UtquXBuViEdRVBKai8SNkxVkBUqo5IL6g20MEWqocbVwtBi0Zhm/guC3YTFR6jvwfDaXLlwlRiWxIDfvqWgG63DY8zld/qU7BhJWrhYTRgWv9SzTLeYGNnWYQDmriVlmquVqXVUEvBUuDARVdouIowoLBhq/N47jycDL8iPNsraZ27J3VouLbFMYl/bEH7l5PTMYMNGJX6pAsEBpuFNh6QQJLDB1ICPggIajT5Cw3gbiw1AVSCHu1iqTOdQJIQgtkaVC3Xy1mYF2s1mMIM9Ny3NFyO4hqPTUu68PshE7Dcf0t3UZVQPlLTq95qyyQRzFo2wWy6iGwC8QeyJYMhtuVkC+kKyChcx1ZlZGECnGAqq6qVd1ESuOuGFPeGZYJn2jDcUOxNS77kRXxCs0aD9zfcN5xGR0TgQ9atokGdN8Myt8R03qMflHCfFsakCdQjkYNAMtbjncMYuM+utRUUfuOrL8ozYpjWvluZTWjpiFUh+ZdC+oCrdQWXI/wDI6W20tkj141Hi4Ph1UGxBdZUMwObBa2QUVVddOUFyBDdZbLJZAjsMxjtQqf3NmzdjLgPxupWnqABsRTqVw1s1E21bMvh09JmPJTeS45mQNPmKn4ebwTbkh4v4D/Mq0Spdzcp5piEn0sMlCp0WDLwKqsR2vRO7C1gFZwTcD/8AaOhMlQ61YESO+SBd9q6jKmTiLWoRArxcIrFZj4jgUGhhwCpSVD/ciAYyjGDMpVLaL1AqrpZClHTGBFRcy1EjMvS+i8QRAoQ4zBYkKidbkogrgHGAA4gaxHO4/kVuG0xcPUGvxTcCkKSq+bH+IcRoPX1E4VVElPvGG6QgVlVZuFWaAtn8kxBBqLKTBWauHdGpLvLZontxP3mKK4ziREUL0ZeYsNCMHcDk7jC8AZSmEu7SpkLZcQJQoXg4gF8bh6eliFGLbQgYMJnZGAlFMv8AyXPsf9lu3iI81K4IbntBo+4TqrLYnCUtC24rvHm+QIvZUuK9nEvHY01ALVi/siDssXiOV2bfaOIIwWxKCy90petrGWGNWd8vI+JYFDDLKLIApiEVLsCVXNXKiRhNpUNwhwQL4fwUFimBvT1KsUY1gNdUNyxeaFwg1hcEB2k3VksUAoeEEQu3PqNKNyO8xIatYh+IKYrOG6eyYQv4+YtDTjB+BRUrS/rMiMjR3tmX1MGL7fcYzwggyaYEwsrA49wEhWEFjuzcpp23JASnsGIlgXKJQySG0Te9ToBQOktD5i5GEqVReWaCtrl6UWWxrAkCjcglgNAvdQytEor4sZjRUFtwXJ3ne3QqUjFa3CC625gJaW5gPwJqKCOH6omvLqElLaXqKq4bIZpCtR6umFoXRcHJ2BLb1iXN10QyAoIc6uYEFmotUTRCmoqCZxb70lK+4T3LhR6CW8NMSECsH7l9T/2mgiGHv/kYL1f+xfazauVgy78Tnr8RLX0RtGzzDmVFYj6a1TKQzx6hkhdvddRTYqq9StJeWOyKGCXh69TBJcFJdW5WzkNHN2xKWGdaYEqAA9jiZKiuIaYk1uo1Z9QlrycMTklQhyNcPkcAfLcoNrbGpizYjgYTF3Mstjc2sWMH6GlxA9PcHYlzZmBgKPsuY2gsYIEq+2CIV/sY8ZBoNS0IUu5qgNQKFLVeKjnsN1BFJb+6mgE6eoyrXVdypN7PuNxImm5XBCXEMkMJL2H9R/srYgwTA9wdrwp6luwBICna2lTtFJ+IQVXSqmAFfqZqKjMiVWjcsrsOCDSkX1BWLmF1DGhW8RHcSJcBsQyPqZMsoVqjABeu4NEPUp/Ya/dTM/JrDBlNdYMEE3isIFVRaleLxP8AMBq2QvYtWiUUtupGcRD1Ceaa1FxU2xKmumHAMvcqJXYEoI3RDQ3uZE+MUJmRhTBPXEI+trXuGWwrais11BTClju3+SgFWv8AsHiOmIBqJfNSvIxwTtDYQnndLBBenHcQ2qu4RMLVR3RxASKmp3tQvUI2SL1Go5EfuU8xq5QKBn4mZKzP3UJVI2Ww23MV6mZo65PDBHwbmYhXoHTcwXxNQYMuDymIwhwQ8q4eTjIpqINmVVoXKBeVzFFb6xKy6O5i4OkWx1eIFDS93BoAfUqm9ncHQI5zMjY+pkWt6OojfyFIAZ9ekCTo9IC39BcaoBXRErG2F5/jIqy81iGgB8ItUJ8kUaP0RMAJbLaEioQEBruOWH2LhQYv1KFf7XDlFrodRlS7Ny1bD+I6UNQRVQqIWq3+oEK/lAESn9QwQb9wQuiuBbqNStDvcxG4ehMYEL01AgMHqZWuH4jwAHogNG/ZPQD+JmWUfZBQjTWIiDHGgHfxCQG1kLia1VS/UQzYlCBj4S7CyKIFQmEISpgwUq40cMO4+uiUMtRcDSDAezERIV1iY8HaqhZUAJgJlb/pOmCi0kUraORCyEBFCpfCy5fgeFtq3HT0glm6ajL9mCXFEr0QgLa241d91UHSK7uAzTB+hjBKfC1uIAWieyGGw9UQKx1HClr4guA/UpqGoMXGNxCNj8kqBAdJMFBdZ3yeGzxB53wkqV4LLP13EeAfRnrsm5V9xsmKll1EDqdX1BJaRsq9+4xu8yy7xAhk7mwwpDIZptipFSiuY54LJsYF4RsY0gWXcwtyxbufMZUuHqDVifBBvJifYjneIXddRxpIh3KSsnF+ILqJmHgZLgy+DwVKxw+Ljl574uMvh4PAhDx73FChKfACJi7mU1uXKZQxBKMYwmeoL0gqb4rk8DXiPJ/CsdN5MHuVIYFLNkqfVC/uNHp8zRTfcq6IoFx5ZovcoyAdmiEobdMoYuDhgtZR2jsqL6PSdfIdsyQvqDtmIuWJgE2R4DB3HTLGsQ0wddBDoWNbnTsh9aLmLkqpi/XzCtNX1AFNG5eo/mGCWY6zBVmJDg+5Vja9RFg33FQyJXuvuUhH5gcMTT/pU188qKupSCEYJbWdxG0EQkDMoEu8kOO4zuEWuBuac5iVL4VUaeE3DUfE8SEPCscfMXtmHYNjy1m6RD+9Ku2/UpLm7H7luU/EMB1DywYvuLU1x3G9DR3KEywOoj49Q8HfgeT+C6xKuBpb0SIbKnqOwkBQjAa+2dfC4lCjKo/ETZz2kROBBUbBShCUCWA9MRaNMg3HTxh+4Z0KB6R6IjYfEZtAjBf76GGP9kFmCTD6mGA9GWWqK2NMgbi5ONV6hEUv2RUaoggwS0xNxtqdDuIqK7LlGQVRc7AW0QrnJSG/YQj8wmAsbmFOgE3FFr4BiE6sRxLQ17JLH1US9R3YXtIwgmgkeEocHqagwN7RBNkzUGvV8wiLOmO3SsB3PS2P1Es7tFOiLv7lbPccJL2UBdTdaZ9cO/1Bp3SJocIJnZcT+KMMu5myGmVwpZFnv4A4hK+2Ev8AtLFHuGEQLZxEy0qzN4Xie0qY7Rpgxr7sYxDL7gyDuBXDrxPAhCG5r4stWpkIqrpLgAOiJEyd8vXGoZNFpI2RZoKnfGxmK3X6hVsZogBq6F/cu6INpbocg1LjMlnGkR2A6Q2aoqCwzoZg2D4XwMDg7/Efgd3E38MpbyGMKIoJzAUF13CbKbAMAsqimXRKDbwRCwMCBiiosd3qUJ6KlgQIeeWp7geEbz0gLxHPiVhhbduvVXFjeiW+Z2+oLqKEe3kJcKDAqEvNlxoVb/I060M2QzqHc1ENaIqG13BKtBjqLMK7HGoqxolB6lJAypNQMl+qKl9tBrEs4etQgY0EcZlkqbOpA8XZFkrNwZPSitbcSwkpM3NKhXdzTqkERMz2ARpaXaRs/uVYsfrBERBoD+4pmhYWXcSqqNUjRUuT+RBXMUTF3ikxzU9kyY26FiHGKYmmxLgglhFpAFd3/ipsxkn8wg2YSZC+mGwXaYUIDS1DyT6mDWWZ74dc1KleBK4Nw5RA2dSgVro3iLkD9IOxj1zVDpIeBVLiIEgZIZ2pN79kzbNb1D6bSEIvchNOp88tjqjbnwpCOnrNzLj9YzW0Ie0AAeYxfxng+FXX5lADuDCrAszMwV9y4OcM7DSNEzivqdmu5bLXcy5SInUBscVDW+BZkszWSWsKln1BD1MJRlsZjjTj1hyruUiGCgpFlXig/uIVnUFtf/CETVp4dXL2myKCprJnY57gzf8AxUdiyy4iwgVPuYdBk/cqGgdywhQuaUy1IoKNF9sGRgGvmYVi3j+IjzkzDRhM+7qWVNYcwqqwlpYHpB3AuUfOw2ALrEWlgx7k0RpbZi0Xgru2JZLv/iYkx/0S1pV/MWKUgOrS1fuVYrES8hnqBUIsrjBuQYhnbG0+oRdCSp/8zMBVvEpaPvfEzSF0XidiqNTPaXsmFaBV+plKu3b+47NomYLMMVmWHymXcpYVDhfiHpMm7v5lRJUryqBceKmPtcAI7rDL8ypG1IIkrWgI9xaCFZUG2LlQXUTUJGIOfgfwndwrmKIbIaDSuGleK3HomVV8wp4VIzPTKjcq49zVJMpqJdSFqGB9TaPFc3zUrwuXLlwZfDHXgYK+YwYeGmfHcw0Vq7lC253CovDCVVzCRZrAlivvUFgtEC1MSBw45SEAX0uoPYDvEBASB9QI2f5C3p36l3nYRWxYxMNUqajR9aQim2TFbgcTMgBpiGQ3+4thJ9wWL7f5B2SW9kdAeo+JUh7Kws6lUhbW2PFZapY+1noJXb2xS+gT4RRlKgEpFWoNF7jErFUoV3ar/aVXDgsjErsmNcsXP0cUZkJKvuZ+21kfIIYogtWz+kcRDs/cUKh2Q9gyZAmllQcw5UfIZKlg5gZOspuWJX3F7qCiFgYQpGpWBFSFrU/3BJLKX7ghgFyIWV3Oh+LlfWVqpjcSGYA6bKuCTZa5SUOkQ1fhBpgb7mDWuHhxL8R4POS31MKM1BwBUymihKmQMEeFjBbbg4Ld8R8gtfcoIKjJKXmuz8XxcyCyrl4AD+o8oeRc3rjZMm3iVUJlbmsqg7I9MVJFK+rlYfU2j5HNy+Hm+CG4PBceGBiUuUqkT8JB2gHsMwDDECmikZdTlll/bG3sYY1dB1CFMJt6hQKmriqXQ9hGaLW9QOJMGiNiqyiiKsX0m6prEMBD4iCNQcN3RqWzA0RuLbsgWJpcaACH0mRbFKMToTaxukPZHKCNVCBbxsgs1PcPBS6wJdBoq6glMgZKzB4x91P0NEaMqq5+YvHdQSi6YGVX2Q0QBDSnolXk9kvYKdQjJQ7itjW3DEZT7m4VNoAU+odWl09Cipgh3EsYyLuyAvfqEH0lrpkxBsD6YIv3GZtGmICy9+4nKoE3Vsj7WNmYrsoIBqjdy6Nq2XDjQrMMH0L0RkS3dQwFoxCvpDwEOb4WfA2yyfMcotl3tMuIvbuM2VSvhF9d+4bVupRHdlZgmL7KjYFLYsSqBVRDgcsKQoWF3KbORxUfKlmKlftd1GheXS5lyy6lETXuYKtHUVAgAUep8SuTfgfiEIQ4deQSsspVk1vP1FVhZ5OGVHjQjRrM3GHi8dR18twtekIDiMTlhxctl+FeO/AleAj+UnXC9TqOuDXDw8K4x3LPXGmCQs6Y2a8eoQ4ONo5lvxtUCIPVxCqE+4KMomYbbzBKKXFOhPvk5OHwOa4IeFeJghbSzY73CMFPQi4PTPcYFJpSHF8sq5Y0zAP6l8Hizvilo0iABV0Qus8Mrjvl8q8CVKlRPx3DipUqVMSpXLzXjcdS4ATCUuC3rHIo4zEjKplwvJx1xcARnFSKWJ9xTIfBdLUWdMuThuAwhiNtkmc5I1ykkTmaziD2Zgxgi6gVuM1LgFur1FXZUODk/IeIw4YwasbOo0i4/EoWlFMYx/SKDMncdCwu0ipvE2RdD6JVk49wugdxLTrpD+5W7TTFI62LlOGMs9TDuUuiUGwSXUoxMLQxKRKiA2TEtvc4dynmdVe5Zr65vweKleB4HL2Qz0qGTyZfgMuXDll83N8VK5eKjK0Ib4e9TECci4kptSXOodz3MFZDZK26n3DUuDLnUrh0TSEmmcsMQ2U1Krc7Bt2ZiqAmlm8sNkY0fiIbq/aXVPeYYdvUu/Iq4euoIXA1hl/M7mWhTITLawLcE4PAfxXzfFSoQ4YMRY30cdZukWRDJo2lgoFmYjfardJZ7LqnEoZi27xBhTTClQR8I3cvtAalJFdfuJMo8OoK5PaVNFuiVdlqowBWsmIWIfgjCIA5qFG5azSFdimaYZajiBBiyFKovVVh41xXFc1mV5XN2NQr6oNrpS2mJKmUTPqV4MqVycnL4MPJ5TKAbIJtMPUGBwSBw6iIuesQU4saiFL7ho4PHNuxJGXMq0jaG0Mqri37x1gSBcHpLHqVmiWAoxqAlgSkUPJpLM3CCBRMcQjplQL2C4WZA+pfQKPcQ6kNoS9JruyiB4HFSpUqVxcvyNcEOGHrg39OWMGCydCdQ2i6qKNJ7RicIB+osl9S47XioSC8SnTBKsJGEbM+4JVkb/UEwcEG1gEBgUY3Cib69g8FaIKa+uCBQN2kys1VQEvBZbNSm4g6hK76Q4rxrzXw1HMGoWQZVGO4Pd1La3w/mfB/Eo6mSOgnadLJN0xXAN2Q2gqIFUAwwHqdX1DMJUqBCc0KZ2fYORNu5YsNV6ixgZVE5udQOliob3UYDbmAwstlLcCiZRLRd01DvEFLnLFaHLKvVTGShb4nkst/AcnHc19JcK6+sfdhhVw3G0pj1tq4+EQKxVR9IAUwahZ0vqZ8FO53gsgXjSh/UyRN+YUVOyHIaGsTBoHW4SV2Eu1WyYiGUlQ7g9IwMwVfcEKOXcFB9kcjUZTfO3hwRnXDDwqBwx8Dcb01APhdTvwFx4eouZsGn4nxPF8nxYFq9oQjCiFcZcC7lz6JHfUD4BL0OoFdAy5w3wcBAqyqgQH5QxivcUMOJY/KIe8yuCohHxFjMu4wrJvU7hSJyMrhGNO6Cj9g6gbEEtRrHEqBSrgeJ5PLya578e4FGbCCLzTce+qmUK1DdxIHSQoG8bgCz9WXxQ6SVhBHolA9EAVtqoh0E3c1YF6QczfiEti26hPOcdwyEtziZBhgIlyxzZFN3vGYq0dwiJUNxu3bqoLDNKja1ptUZk0rgjOuGHk8UymVLm4uhmMqC7jUwzmOBazUC377l+L4PieLDxfIO0KIWAYUb2HOREgYLcoOGMk1Fb99SxXW9xugh9xQaOQVQRmW2WQoLOTbGbSMCFowe7DWGYNLgRBsmKMmcwwVuia4cjSxl4quaCUjqCtyvUvNsJabfiBRX8Q8TyPM474IEqJOoCNwxO4YVnslYi3VkvI+pT5IuIFIyhfzKgK45lFOxHLcSwPUuk+JRFsqAFfExRRmYFRzBRjvi4vKy5fi5lcXw8DRLKRzRiq2zvxIx8a9i/LqDBU0VBLj+J1KdD0iVfNWRKGV0DPxycq77JrAfRAFfcBmY+0VALlwYP1NljcRCWeo0jAIBqKLVMGlTJcbLh4w3EcmKg8qWPuXSvuaUxwVBl/mYeQxal/nqVKlSpUTyqVGXww8bly5uVzfFRly/wAKy765K7anaVZDBZifXjeJdBZKXupVameyJwypUqVy+JwQ8AtCN1KaO5jQSmvggNlSl37gos4bvOCIdlo1LnPUNWrGW2RQRAxn1EXnB7lrNg0ws1eXUD2HG0WEr8J5MJ14G5pDg4ri/wDwKOL5uXGVKjKlSvI4qMrlPxLC3UtMGh7ijKQWWhvqah+S4Ft1Q1fUA66g2WS81P4INlkUmsEUPhO2BiVqPmVD5jinc7lJLJTddxEC6eXk8CDLjCAriOR9CO53bEVU3eKgJMtUxF4F2sMPtqOGmC8n6iMBMDG2W6dEur2GYLKwwQSGbV1CAATuMVQoqFqxV3By6wvBGEvwvxPwHFSobmkODvwYf+A6nf4GHLvxOHUdzr8jCY1UJFoQ6gCv2gtcvlBTDBcNzCj3MXXC2j7lQkoUxP6+acPuTO7O52mYx0/fA1bPc2Qj4vJDfJuOuha7JjX7xpQu0JfWe03O/c/xf5NvBkDrGJQaKrVQjB0DBABLM4YgqKrVTV66TAZBhcAgStJGbDlh+/Hv8JH8Jx//2Q==";
462
+ /** A minimal one-page PDF whose content is the text "Hello World". */
463
+ declare const testPdfBase64Url = "data:application/pdf;base64,JVBERi0xLjEKMSAwIG9iajw8L1R5cGUvQ2F0YWxvZy9QYWdlcyAyIDAgUj4+ZW5kb2JqCjIgMCBvYmo8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PmVuZG9iagozIDAgb2JqPDwvVHlwZS9QYWdlL1BhcmVudCAyIDAgUi9NZWRpYUJveFswIDAgMjAwIDIwMF0vUmVzb3VyY2VzPDwvRm9udDw8L0YxPDwvVHlwZS9Gb250L1N1YnR5cGUvVHlwZTEvQmFzZUZvbnQvSGVsdmV0aWNhPj4+Pj4+L0NvbnRlbnRzIDQgMCBSPj5lbmRvYmoKNCAwIG9iajw8L0xlbmd0aCA0ND4+c3RyZWFtCkJUIC9GMSAxMiBUZiA1MCAxMDAgVGQgKEhlbGxvIFdvcmxkKSBUaiBFVAplbmRzdHJlYW0gZW5kb2JqCnhyZWYKMCA1CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAwOSAwMDAwMCBuIAowMDAwMDAwMDUzIDAwMDAwIG4gCjAwMDAwMDAwOTggMDAwMDAgbiAKMDAwMDAwMDIyOCAwMDAwMCBuIAp0cmFpbGVyPDwvU2l6ZSA1L1Jvb3QgMSAwIFI+PgpzdGFydHhyZWYKMzIwCiUlRU9GCg==";
464
+ declare function humanMessage(message: string | IChatMessage): IChatMessage;
465
+ interface IFileFixtureOptions {
466
+ text?: string;
467
+ id?: string;
468
+ mimeType?: string;
469
+ publicUrl?: string;
470
+ base64Url?: string;
471
+ }
472
+ /** Human message carrying an image; defaults to the embedded receipt photo. */
473
+ declare function imageMessage(options?: IFileFixtureOptions): IChatMessage;
474
+ /** Human message carrying a document; defaults to the embedded sample PDF. */
475
+ declare function documentMessage(options?: IFileFixtureOptions): IChatMessage;
476
+ declare function humanItem(message: string | IChatMessage): IChatItem;
477
+ declare function botItem(message: string | IChatMessage): IChatItem;
478
+
479
+ interface IChatAdapterConformanceReq {
480
+ adapter: IChatAdapter;
481
+ model: string;
482
+ }
483
+ interface IChatAdapterConformanceCase {
484
+ name: string;
485
+ run: () => Promise<void>;
486
+ }
487
+ /**
488
+ * Provider-agnostic conformance suite for IChatAdapter implementations.
489
+ * Runner-agnostic: each case is a plain async function, so it can be wired
490
+ * to node:test, vitest or bun test by the caller.
491
+ */
492
+ declare function chatAdapterConformanceCases({ adapter, model, }: IChatAdapterConformanceReq): IChatAdapterConformanceCase[];
493
+
494
+ interface IApiKeyData<A extends object> extends IEntityData {
495
+ secretHash?: string;
496
+ metadata?: Record<string, string>;
497
+ authInfo: A;
498
+ name: string;
499
+ }
500
+ declare class ApiKey<A extends object> extends Entity<IApiKeyData<A>> {
501
+ static PREFIX: string;
502
+ static hashSecret(secret: string): string;
503
+ get authInfo(): IStorableType<A>;
504
+ get metadata(): {
505
+ [x: string]: string;
506
+ };
507
+ get name(): string;
508
+ setAuthInfo(authInfo: IStorableType<A>): void;
509
+ generateSecret(): string;
510
+ isValidSecret(secret: string): boolean;
511
+ validateSecret(secret: string): void;
512
+ }
513
+
514
+ interface IApiKeyRepository<A extends object> {
515
+ find(id: string): Promise<ApiKey<A> | null>;
516
+ findOrThrow(id: string): Promise<ApiKey<A>>;
517
+ findByMetadata(metadata: Record<string, string>): Promise<ApiKey<A>[]>;
518
+ create(item: ApiKey<A>): Promise<void>;
519
+ generate(req: IGenerateApiKeyReq<A>): Promise<IGenerateApiKeyRes<A>>;
520
+ findBySecret(secret: string): Promise<ApiKey<A> | null>;
521
+ findAndValidate(secret: string): Promise<IStorableType<A>>;
522
+ }
523
+ interface IGenerateApiKeyReq<A extends object> {
524
+ name: string;
525
+ metadata?: Record<string, string>;
526
+ authInfo: IStorableType<A>;
527
+ }
528
+ interface IGenerateApiKeyRes<A extends object> {
529
+ apiKey: ApiKey<A>;
530
+ secret: string;
531
+ }
532
+
533
+ declare class ApiKeyRepository<A extends object> implements IApiKeyRepository<A> {
534
+ find(id: string): Promise<ApiKey<A> | null>;
535
+ findOrThrow(id: string): Promise<ApiKey<A>>;
536
+ findByMetadata(metadata: Record<string, string>): Promise<ApiKey<A>[]>;
537
+ create(item: ApiKey<A>): Promise<void>;
538
+ generate(req: IGenerateApiKeyReq<A>): Promise<IGenerateApiKeyRes<A>>;
539
+ findBySecret(secret: string): Promise<ApiKey<A> | null>;
540
+ findAndValidate(secret: string): Promise<IStorableType<A>>;
541
+ }
542
+
543
+ interface IRepositoryConfig<P extends Entity<IEntityData>> {
544
+ table: string;
545
+ constructor: IConstructor<P>;
546
+ schema?: string;
547
+ }
548
+
549
+ type QueryPrefix = 'find' | 'findOne' | 'count' | 'exists' | 'delete';
550
+ type QueryOperator = 'Equals' | 'Not' | 'Like' | 'NotLike' | 'In' | 'NotIn' | 'Gt' | 'Gte' | 'Lt' | 'Lte' | 'IsNull' | 'IsNotNull';
551
+ type QueryConnector = 'And' | 'Or';
552
+ interface IQueryCondition {
553
+ field: string;
554
+ operator: QueryOperator;
555
+ connector?: QueryConnector;
556
+ }
557
+ interface IQueryOrderBy {
558
+ field: string;
559
+ direction: 'ASC' | 'DESC';
560
+ }
561
+ interface IQueryAst {
562
+ prefix: QueryPrefix;
563
+ conditions: IQueryCondition[];
564
+ orderBy: IQueryOrderBy[];
565
+ limit?: number;
566
+ }
567
+
568
+ interface IRepositoryRuntime<P extends Entity<IEntityData>> {
569
+ find(id: string): Promise<P | null>;
570
+ findOrThrow(id: string): Promise<P>;
571
+ findByIds(ids: string[]): Promise<P[]>;
572
+ findAll(): Promise<P[]>;
573
+ create(item: P): Promise<void>;
574
+ update(item: P): Promise<void>;
575
+ delete(item: P): Promise<void>;
576
+ runQuery(ast: IQueryAst, args: unknown[]): Promise<P[]>;
577
+ runCount(ast: IQueryAst, args: unknown[]): Promise<number>;
578
+ runExists(ast: IQueryAst, args: unknown[]): Promise<boolean>;
579
+ runDelete(ast: IQueryAst, args: unknown[]): Promise<void>;
580
+ }
581
+
582
+ interface IRepositoryAdapter {
583
+ readonly id: symbol;
584
+ build<P extends Entity<IEntityData>>(config: IRepositoryConfig<P>): IRepositoryRuntime<P>;
585
+ buildExtension?<E>(config: IRepositoryConfig<any>, ExtensionCtor: IConstructor<E>): E;
586
+ }
587
+
588
+ interface IMemoryRepositoryAdapterOptions {
589
+ persist?: boolean;
590
+ dir?: string;
591
+ maxItems?: number;
592
+ }
593
+ declare class MemoryRepositoryAdapter implements IRepositoryAdapter {
594
+ readonly id: symbol;
595
+ private stores;
596
+ private readonly persistOptions;
597
+ constructor(options?: IMemoryRepositoryAdapterOptions);
598
+ private getStore;
599
+ build<P extends Entity<IEntityData>>(config: IRepositoryConfig<P>): IRepositoryRuntime<P>;
600
+ buildExtension<E>(config: IRepositoryConfig<any>, ExtensionCtor: IConstructor<E>): E;
601
+ }
602
+
603
+ declare class Env {
604
+ private envType;
605
+ constructor();
606
+ isDevelopment(): boolean;
607
+ isProduction(): boolean;
608
+ isTesting(): boolean;
609
+ requireString(varName: string, options?: {
610
+ default?: string;
611
+ }): string;
612
+ requireNumber(varName: string, options?: {
613
+ default?: number;
614
+ }): number;
615
+ }
616
+
617
+ declare class JwtConfig {
618
+ secretOrPublicKey: string;
619
+ secretOrPrivateKey: string;
620
+ algorithm: Algorithm;
621
+ accessExpirationSeconds: number;
622
+ refreshExpirationSeconds: number;
623
+ constructor(env: Env);
624
+ }
625
+
626
+ interface ITestJwtOptions {
627
+ secret?: string;
628
+ accessExpirationSeconds?: number;
629
+ }
630
+ /**
631
+ * JWT setup for tests: a JwtConfig with a test secret (no JWT_SECRET env var
632
+ * needed) plus a signer, so the real JwtGuardMiddleware validates the tokens.
633
+ */
634
+ declare class TestJwt {
635
+ readonly config: JwtConfig;
636
+ constructor(options?: ITestJwtOptions);
637
+ /** Sign a valid access token carrying the given authInfo. */
638
+ sign(authInfo: object): string;
639
+ /** A token signed with a different secret; useful for 401 tests. */
640
+ signInvalid(authInfo?: object): string;
641
+ }
642
+ declare function setupTestJwt(options?: ITestJwtOptions): TestJwt;
643
+ /**
644
+ * In-RAM IApiKeyRepository so the real ApiKeyGuardMiddleware can run in
645
+ * tests. Register it with: register: [[ApiKeyRepository, testApiKeys]].
646
+ */
647
+ declare class TestApiKeyRepository<A extends object> extends ApiKeyRepository<A> {
648
+ private items;
649
+ private idCounter;
650
+ /** Create a key for the given authInfo and return its secret. */
651
+ addKey(authInfo: IStorableType<A>, name?: string): Promise<string>;
652
+ find(id: string): Promise<ApiKey<A> | null>;
653
+ findOrThrow(id: string): Promise<ApiKey<A>>;
654
+ findByMetadata(metadata: Record<string, string>): Promise<ApiKey<A>[]>;
655
+ create(item: ApiKey<A>): Promise<void>;
656
+ generate(req: IGenerateApiKeyReq<A>): Promise<IGenerateApiKeyRes<A>>;
657
+ findBySecret(secret: string): Promise<ApiKey<A> | null>;
658
+ findAndValidate(secret: string): Promise<IStorableType<A>>;
659
+ }
660
+
661
+ interface IRestHarnessOptions {
662
+ controllers: IConstructor<any>[];
663
+ /** Extra DI registrations visible to middlewares and controllers: [token, instance]. */
664
+ register?: [any, any][];
665
+ /** Enable JWT support: registers a test JwtConfig so @jwtGuard works. */
666
+ jwt?: boolean | ITestJwtOptions;
667
+ }
668
+ interface IRestRequestOptions {
669
+ body?: unknown;
670
+ headers?: Record<string, string>;
671
+ query?: Record<string, string>;
672
+ }
673
+ interface IRestResponse {
674
+ status: number;
675
+ body: any;
676
+ headers: Headers;
677
+ }
678
+ /**
679
+ * Mounts @restController classes on a private HTTP server (ephemeral port)
680
+ * and exercises the real pipeline: parsers, middlewares/guards, validation
681
+ * and error mapping.
682
+ */
683
+ declare class RestHarness {
684
+ readonly container: DependencyContainer;
685
+ readonly jwt?: TestJwt;
686
+ private server;
687
+ private baseUrl;
688
+ private constructor();
689
+ static create(options: IRestHarnessOptions): Promise<RestHarness>;
690
+ private listen;
691
+ get url(): string;
692
+ request(method: string, path: string, options?: IRestRequestOptions): Promise<IRestResponse>;
693
+ /**
694
+ * Client that sends every request with a freshly signed Bearer token for
695
+ * the given authInfo (requires the jwt option).
696
+ */
697
+ as(authInfo: object): {
698
+ request: (method: string, path: string, options?: IRestRequestOptions) => Promise<IRestResponse>;
699
+ };
700
+ close(): Promise<void>;
701
+ }
702
+ declare function createRestHarness(options: IRestHarnessOptions): Promise<RestHarness>;
703
+
704
+ interface IUiHarnessOptions {
705
+ controllers: IConstructor<any>[];
706
+ /** Extra DI registrations visible to middlewares and controllers: [token, instance]. */
707
+ register?: [any, any][];
708
+ }
709
+ interface IUiRequestOptions {
710
+ body?: unknown;
711
+ headers?: Record<string, string>;
712
+ query?: Record<string, string>;
713
+ /** Follow redirects ("follow", default) or capture them ("manual"). */
714
+ redirect?: 'follow' | 'manual';
715
+ }
716
+ interface IUiResponse {
717
+ status: number;
718
+ /** Raw response body. */
719
+ text: string;
720
+ headers: Headers;
721
+ /** Parse the body as JSON (for @action responses). */
722
+ json(): any;
723
+ }
724
+ /**
725
+ * Mounts @uiController classes on a private HTTP server (ephemeral port) and
726
+ * exercises the real pipeline: middlewares/guards, validation, SSR and actions.
727
+ * Islands render as static SSR HTML (no client bundling needed for tests).
728
+ */
729
+ declare class UiHarness {
730
+ readonly container: DependencyContainer;
731
+ private server;
732
+ private baseUrl;
733
+ private constructor();
734
+ static create(options: IUiHarnessOptions): Promise<UiHarness>;
735
+ private listen;
736
+ get url(): string;
737
+ /** GET a view; returns the rendered HTML document. */
738
+ get(path: string, options?: IUiRequestOptions): Promise<IUiResponse>;
739
+ /** POST to an @action route (e.g. "/my/ui/_action/addTodo"). */
740
+ action(path: string, body?: unknown, options?: IUiRequestOptions): Promise<IUiResponse>;
741
+ request(method: string, path: string, options?: IUiRequestOptions): Promise<IUiResponse>;
742
+ close(): Promise<void>;
743
+ }
744
+ declare function createUiHarness(options: IUiHarnessOptions): Promise<UiHarness>;
745
+
746
+ interface ICronHandler {
747
+ handle(): void | Promise<void>;
748
+ handleError?(e: any): void | Promise<void>;
749
+ }
750
+
751
+ interface IAsyncHarnessOptions {
752
+ /** Extra DI registrations for handler dependencies: [token, instance]. */
753
+ register?: [any, any][];
754
+ /** Assigned to the container-scoped Auth. */
755
+ authInfo?: object;
756
+ }
757
+ /**
758
+ * Executes @command handlers and @cronHandler jobs inline — no PostgreSQL,
759
+ * no polling workers — with the same validation production applies.
760
+ */
761
+ declare class AsyncHarness {
762
+ readonly container: DependencyContainer;
763
+ constructor(options?: IAsyncHarnessOptions);
764
+ /**
765
+ * Validate the command data and run its handler immediately, like the
766
+ * JobRunner does for a scheduled job. Returns the transformed command.
767
+ */
768
+ execute<C>(commandConstructor: IConstructor<C>, data?: IValidateInputShape<C>): Promise<C>;
769
+ /** Run a @cronHandler's handle() once, immediately. */
770
+ runCron(cronConstructor: IConstructor<ICronHandler>): Promise<void>;
771
+ }
772
+ declare function createAsyncHarness(options?: IAsyncHarnessOptions): AsyncHarness;
773
+
774
+ /**
775
+ * Back every @repository with an in-RAM adapter (no PostgreSQL, no `.wabot/`
776
+ * persistence). Call it once at the top of a test file, before resolving any
777
+ * repository (each @repository caches its runtime on first use). Test files
778
+ * run in separate processes, so each file gets its own empty store.
779
+ */
780
+ declare function useMemoryRepositories(): MemoryRepositoryAdapter;
781
+ interface IEntityFixtureOptions {
782
+ id?: string;
783
+ createdAt?: Date | number;
784
+ }
785
+ /**
786
+ * Build an entity in "already created" state (id and createdAt set), so
787
+ * tests can seed data without going through a repository create().
788
+ */
789
+ declare function entityFixture<D extends IEntityData, E extends Entity<D>>(entityConstructor: IConstructor<E>, data: Omit<D, keyof IEntityData>, options?: IEntityFixtureOptions): E;
790
+
791
+ interface IValidationIssue {
792
+ path: string;
793
+ message: string;
794
+ }
795
+ interface IValidationFixtureResult<T> {
796
+ value?: T;
797
+ issues: IValidationIssue[];
798
+ }
799
+ /** Validate data against a decorated model and get flattened issues. */
800
+ declare function validateFixture<T>(modelConstructor: IConstructor<T>, data: unknown): IValidationFixtureResult<T>;
801
+ /** Assert the data is valid for the model; returns the transformed value. */
802
+ declare function assertValid<T>(modelConstructor: IConstructor<T>, data: unknown): T;
803
+ /** Assert the data is invalid; optionally that a specific path has an issue. */
804
+ declare function assertInvalid<T>(modelConstructor: IConstructor<T>, data: unknown, options?: {
805
+ path?: string;
806
+ }): IValidationIssue[];
807
+
808
+ /** Poll a condition until it holds or the timeout elapses. */
809
+ declare function waitUntil(condition: () => Promise<boolean>, timeoutMs?: number, intervalMs?: number): Promise<void>;
810
+ declare function wait(timeoutMs?: number): Promise<void>;
811
+ interface ICronValidationOptions {
812
+ timezone?: string;
813
+ toleranceMs?: number;
814
+ }
815
+ /** Check that a sequence of dates matches consecutive firings of a cron expression. */
816
+ declare function isValidCronSequence(cronExpression: string, dates: Date[], options?: ICronValidationOptions): boolean;
817
+
818
+ export { AsyncHarness, ChatBotHarness, ChatControllerHarness, type IAsyncHarnessOptions, type IChatAdapterConformanceCase, type IChatAdapterConformanceReq, type IChatBotHarnessOptions, type IChatBotTurn, type IChatControllerHarnessOptions, type ICronValidationOptions, type IEntityFixtureOptions, type IFileFixtureOptions, type ILlmJudgeEvaluateReq, type ILlmJudgeOptions, type ILlmJudgeVerdict, type IMockChatAdapterOptions, type IMockChatAdapterResponse, type IRestHarnessOptions, type IRestRequestOptions, type IRestResponse, type ITestJwtOptions, type IUiHarnessOptions, type IUiRequestOptions, type IUiResponse, type IValidationFixtureResult, type IValidationIssue, LlmJudge, MockChatAdapter, RestHarness, TestApiKeyRepository, TestChatMemory, TestChatRepository, TestJwt, UiHarness, assertInvalid, assertValid, botItem, chatAdapterConformanceCases, createAsyncHarness, createChatBotHarness, createChatControllerHarness, createRestHarness, createUiHarness, documentMessage, entityFixture, humanItem, humanMessage, imageMessage, isValidCronSequence, renderTranscript, setupTestJwt, testImageBase64Url, testPdfBase64Url, useMemoryRepositories, validateFixture, wait, waitUntil };