promptfoo 0.119.13 → 0.119.14

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 (131) hide show
  1. package/dist/package.json +28 -26
  2. package/dist/src/app/assets/index-eJ2lMe94.js +51 -0
  3. package/dist/src/app/assets/{source-map-support-Bnh0UQ2S.js → source-map-support-1v4oeb7P.js} +1 -1
  4. package/dist/src/app/assets/sync-CtLQRuC1.js +1 -0
  5. package/dist/src/app/assets/{vendor-charts-T60Uk0Z3.js → vendor-charts-DnVv66VV.js} +1 -1
  6. package/dist/src/app/assets/{vendor-markdown-DLig-KJh.js → vendor-markdown-DCpQIyMA.js} +1 -1
  7. package/dist/src/app/assets/{vendor-mui-core-5BLaiG3c.js → vendor-mui-core-Boqnpf9f.js} +1 -1
  8. package/dist/src/app/assets/{vendor-mui-icons-fn39Fu2e.js → vendor-mui-icons-B8MqoVbj.js} +1 -1
  9. package/dist/src/app/assets/vendor-mui-x-CGSS6QHF.js +45 -0
  10. package/dist/src/app/assets/{vendor-utils-DYBMEuwX.js → vendor-utils-DdfHIEy8.js} +1 -1
  11. package/dist/src/app/index.html +7 -7
  12. package/dist/src/assertions/guardrails.d.ts +1 -1
  13. package/dist/src/assertions/guardrails.js +18 -9
  14. package/dist/src/assertions/index.d.ts +1 -1
  15. package/dist/src/assertions/index.js +9 -3
  16. package/dist/src/assertions/searchRubric.d.ts +3 -0
  17. package/dist/src/assertions/searchRubric.js +18 -0
  18. package/dist/src/commands/eval.js +1 -1
  19. package/dist/src/commands/modelScan.d.ts +7 -1
  20. package/dist/src/commands/modelScan.js +121 -59
  21. package/dist/src/database/index.d.ts +6 -0
  22. package/dist/src/database/index.js +11 -0
  23. package/dist/src/database/tables.d.ts +46 -24
  24. package/dist/src/envars.d.ts +17 -0
  25. package/dist/src/generated/constants.js +1 -1
  26. package/dist/src/logger.d.ts +5 -0
  27. package/dist/src/logger.js +28 -0
  28. package/dist/src/main.js +17 -6
  29. package/dist/src/matchers.d.ts +1 -0
  30. package/dist/src/matchers.js +80 -0
  31. package/dist/src/models/eval.d.ts +2 -1
  32. package/dist/src/models/eval.js +44 -2
  33. package/dist/src/prompts/grading.d.ts +1 -0
  34. package/dist/src/prompts/grading.js +26 -1
  35. package/dist/src/prompts/index.d.ts +1 -0
  36. package/dist/src/prompts/index.js +4 -1
  37. package/dist/src/providers/adaline.gateway.js +2 -2
  38. package/dist/src/providers/anthropic/defaults.d.ts +1 -1
  39. package/dist/src/providers/anthropic/defaults.js +15 -0
  40. package/dist/src/providers/azure/chat.d.ts +3 -1
  41. package/dist/src/providers/azure/chat.js +16 -3
  42. package/dist/src/providers/azure/defaults.js +660 -141
  43. package/dist/src/providers/azure/responses.d.ts +5 -0
  44. package/dist/src/providers/azure/responses.js +33 -4
  45. package/dist/src/providers/azure/types.d.ts +4 -0
  46. package/dist/src/providers/bedrock/agents.d.ts +1 -1
  47. package/dist/src/providers/bedrock/agents.js +2 -2
  48. package/dist/src/providers/bedrock/base.d.ts +40 -0
  49. package/dist/src/providers/bedrock/base.js +171 -0
  50. package/dist/src/providers/bedrock/converse.d.ts +146 -0
  51. package/dist/src/providers/bedrock/converse.js +1044 -0
  52. package/dist/src/providers/bedrock/index.d.ts +1 -34
  53. package/dist/src/providers/bedrock/index.js +4 -159
  54. package/dist/src/providers/bedrock/knowledgeBase.d.ts +1 -1
  55. package/dist/src/providers/bedrock/knowledgeBase.js +2 -2
  56. package/dist/src/providers/bedrock/nova-sonic.d.ts +2 -1
  57. package/dist/src/providers/bedrock/nova-sonic.js +2 -2
  58. package/dist/src/providers/claude-agent-sdk.d.ts +58 -1
  59. package/dist/src/providers/claude-agent-sdk.js +22 -1
  60. package/dist/src/providers/defaults.js +4 -0
  61. package/dist/src/providers/github/defaults.js +6 -6
  62. package/dist/src/providers/google/types.d.ts +25 -0
  63. package/dist/src/providers/google/util.d.ts +2 -0
  64. package/dist/src/providers/google/vertex.js +78 -22
  65. package/dist/src/providers/{groq.d.ts → groq/chat.d.ts} +26 -20
  66. package/dist/src/providers/groq/chat.js +79 -0
  67. package/dist/src/providers/groq/index.d.ts +5 -0
  68. package/dist/src/providers/groq/index.js +24 -0
  69. package/dist/src/providers/groq/responses.d.ts +106 -0
  70. package/dist/src/providers/groq/responses.js +64 -0
  71. package/dist/src/providers/groq/types.d.ts +44 -0
  72. package/dist/src/providers/groq/types.js +3 -0
  73. package/dist/src/providers/groq/util.d.ts +15 -0
  74. package/dist/src/providers/groq/util.js +28 -0
  75. package/dist/src/providers/mcp/client.d.ts +8 -0
  76. package/dist/src/providers/mcp/client.js +60 -10
  77. package/dist/src/providers/mcp/types.d.ts +21 -0
  78. package/dist/src/providers/openai/chatkit-pool.d.ts +114 -0
  79. package/dist/src/providers/openai/chatkit-pool.js +548 -0
  80. package/dist/src/providers/openai/chatkit-types.d.ts +73 -0
  81. package/dist/src/providers/openai/chatkit-types.js +3 -0
  82. package/dist/src/providers/openai/chatkit.d.ts +76 -0
  83. package/dist/src/providers/openai/chatkit.js +879 -0
  84. package/dist/src/providers/openai/codex-sdk.d.ts +109 -0
  85. package/dist/src/providers/openai/codex-sdk.js +346 -0
  86. package/dist/src/providers/openai/defaults.d.ts +2 -0
  87. package/dist/src/providers/openai/defaults.js +10 -4
  88. package/dist/src/providers/registry.js +48 -9
  89. package/dist/src/providers/responses/types.d.ts +1 -1
  90. package/dist/src/providers/sagemaker.d.ts +2 -2
  91. package/dist/src/providers/webSearchUtils.d.ts +17 -0
  92. package/dist/src/providers/webSearchUtils.js +169 -0
  93. package/dist/src/providers/xai/chat.d.ts +61 -0
  94. package/dist/src/providers/xai/chat.js +68 -3
  95. package/dist/src/providers/xai/responses.d.ts +189 -0
  96. package/dist/src/providers/xai/responses.js +268 -0
  97. package/dist/src/redteam/constants/plugins.d.ts +1 -1
  98. package/dist/src/redteam/constants/plugins.js +1 -1
  99. package/dist/src/redteam/constants/strategies.d.ts +1 -1
  100. package/dist/src/redteam/constants/strategies.js +1 -0
  101. package/dist/src/redteam/plugins/vlguard.d.ts +53 -4
  102. package/dist/src/redteam/plugins/vlguard.js +362 -46
  103. package/dist/src/redteam/providers/constants.d.ts +2 -2
  104. package/dist/src/redteam/providers/constants.js +2 -2
  105. package/dist/src/redteam/providers/crescendo/index.d.ts +1 -1
  106. package/dist/src/redteam/providers/crescendo/index.js +5 -3
  107. package/dist/src/redteam/providers/hydra/index.js +1 -1
  108. package/dist/src/server/routes/modelAudit.js +4 -4
  109. package/dist/src/share.js +4 -2
  110. package/dist/src/telemetry.js +44 -8
  111. package/dist/src/types/env.d.ts +3 -0
  112. package/dist/src/types/env.js +1 -0
  113. package/dist/src/types/index.d.ts +896 -615
  114. package/dist/src/types/index.js +1 -0
  115. package/dist/src/types/providers.d.ts +1 -0
  116. package/dist/src/types/tracing.d.ts +3 -0
  117. package/dist/src/util/database.d.ts +6 -4
  118. package/dist/src/util/file.js +6 -4
  119. package/dist/src/util/modelAuditCliParser.d.ts +4 -4
  120. package/dist/src/util/xlsx.js +52 -26
  121. package/dist/src/validators/providers.d.ts +142 -122
  122. package/dist/src/validators/providers.js +4 -6
  123. package/dist/src/validators/redteam.d.ts +36 -28
  124. package/dist/src/validators/redteam.js +9 -3
  125. package/dist/tsconfig.tsbuildinfo +1 -1
  126. package/package.json +28 -26
  127. package/dist/drizzle/CLAUDE.md +0 -65
  128. package/dist/src/app/assets/index-DifT6VGT.js +0 -51
  129. package/dist/src/app/assets/sync-Oo-W_Rbj.js +0 -1
  130. package/dist/src/app/assets/vendor-mui-x-C2xF-yiO.js +0 -45
  131. package/dist/src/providers/groq.js +0 -48
@@ -0,0 +1,114 @@
1
+ /**
2
+ * ChatKit Browser Pool
3
+ *
4
+ * Manages a pool of browser contexts for concurrent ChatKit evaluations.
5
+ * This significantly reduces resource usage compared to spawning separate
6
+ * browsers for each test.
7
+ *
8
+ * Architecture:
9
+ * - Single browser process (shared across all tests)
10
+ * - Multiple browser contexts (isolated like incognito windows)
11
+ * - Shared HTTP server with per-workflow template routing
12
+ * - Pages are workflow-specific (different workflows get different pages)
13
+ */
14
+ import { type BrowserContext, type Page } from 'playwright';
15
+ interface PooledPage {
16
+ context: BrowserContext;
17
+ page: Page;
18
+ ready: boolean;
19
+ inUse: boolean;
20
+ templateKey: string;
21
+ }
22
+ interface ChatKitPoolConfig {
23
+ maxConcurrency: number;
24
+ headless: boolean;
25
+ serverPort?: number;
26
+ }
27
+ /**
28
+ * Singleton browser pool for ChatKit evaluations.
29
+ * Supports high concurrency by reusing browser contexts.
30
+ * Each workflow gets its own isolated pages via template routing.
31
+ */
32
+ export declare class ChatKitBrowserPool {
33
+ private static instance;
34
+ private static cleanupRegistered;
35
+ private browser;
36
+ private server;
37
+ private serverPort;
38
+ private pages;
39
+ private waitQueue;
40
+ private config;
41
+ private templates;
42
+ private initialized;
43
+ private initPromise;
44
+ private idleTimer;
45
+ private constructor();
46
+ /**
47
+ * Register process exit handlers to clean up browser resources
48
+ */
49
+ private static registerCleanupHandlers;
50
+ /**
51
+ * Get the singleton pool instance
52
+ */
53
+ static getInstance(config?: Partial<ChatKitPoolConfig>): ChatKitBrowserPool;
54
+ /**
55
+ * Reset the singleton (for testing)
56
+ */
57
+ static resetInstance(): void;
58
+ /**
59
+ * Generate a template key from workflow configuration.
60
+ * This ensures different workflows get isolated pages.
61
+ */
62
+ static generateTemplateKey(workflowId: string, version?: string, userId?: string): string;
63
+ /**
64
+ * Register a template for a workflow configuration
65
+ */
66
+ setTemplate(templateKey: string, html: string): void;
67
+ /**
68
+ * Initialize the pool - launches browser and creates server
69
+ */
70
+ initialize(): Promise<void>;
71
+ private doInitialize;
72
+ /**
73
+ * Acquire a page from the pool for a specific template.
74
+ * Only returns pages configured for the requested template.
75
+ * Blocks if all pages are in use.
76
+ */
77
+ acquirePage(templateKey: string): Promise<PooledPage>;
78
+ /**
79
+ * Release a page back to the pool
80
+ */
81
+ releasePage(pooledPage: PooledPage): Promise<void>;
82
+ /**
83
+ * Try to serve waiting requests by creating new pages if we have capacity
84
+ */
85
+ private tryServeWaiters;
86
+ /**
87
+ * Schedule automatic shutdown if pool remains idle
88
+ */
89
+ private scheduleIdleShutdown;
90
+ /**
91
+ * Cancel scheduled idle shutdown
92
+ */
93
+ private cancelIdleTimer;
94
+ /**
95
+ * Create a new pooled page with ChatKit initialized for a specific template
96
+ */
97
+ private createPooledPage;
98
+ private refreshPooledPage;
99
+ /**
100
+ * Get pool statistics
101
+ */
102
+ getStats(): {
103
+ total: number;
104
+ inUse: number;
105
+ waiting: number;
106
+ templates: number;
107
+ };
108
+ /**
109
+ * Shutdown the pool and release all resources
110
+ */
111
+ shutdown(): Promise<void>;
112
+ }
113
+ export {};
114
+ //# sourceMappingURL=chatkit-pool.d.ts.map
@@ -0,0 +1,548 @@
1
+ "use strict";
2
+ /**
3
+ * ChatKit Browser Pool
4
+ *
5
+ * Manages a pool of browser contexts for concurrent ChatKit evaluations.
6
+ * This significantly reduces resource usage compared to spawning separate
7
+ * browsers for each test.
8
+ *
9
+ * Architecture:
10
+ * - Single browser process (shared across all tests)
11
+ * - Multiple browser contexts (isolated like incognito windows)
12
+ * - Shared HTTP server with per-workflow template routing
13
+ * - Pages are workflow-specific (different workflows get different pages)
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
48
+ var __importDefault = (this && this.__importDefault) || function (mod) {
49
+ return (mod && mod.__esModule) ? mod : { "default": mod };
50
+ };
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.ChatKitBrowserPool = void 0;
53
+ const playwright_1 = require("playwright");
54
+ const http = __importStar(require("http"));
55
+ const logger_1 = __importDefault(require("../../logger"));
56
+ const providerRegistry_1 = require("../providerRegistry");
57
+ // Pool configuration constants
58
+ const CHATKIT_READY_TIMEOUT_MS = 60000;
59
+ const PAGE_REFRESH_TIMEOUT_MS = 60000;
60
+ const PAGE_ACQUIRE_TIMEOUT_MS = 120000;
61
+ const IDLE_SHUTDOWN_DELAY_MS = 5000; // Shutdown pool if idle for this long
62
+ /**
63
+ * Singleton browser pool for ChatKit evaluations.
64
+ * Supports high concurrency by reusing browser contexts.
65
+ * Each workflow gets its own isolated pages via template routing.
66
+ */
67
+ class ChatKitBrowserPool {
68
+ constructor(config) {
69
+ this.browser = null;
70
+ this.server = null;
71
+ this.serverPort = 0;
72
+ this.pages = [];
73
+ this.waitQueue = [];
74
+ this.templates = new Map(); // templateKey -> HTML
75
+ this.initialized = false;
76
+ this.initPromise = null;
77
+ this.idleTimer = null;
78
+ this.config = config;
79
+ }
80
+ /**
81
+ * Register process exit handlers to clean up browser resources
82
+ */
83
+ static registerCleanupHandlers() {
84
+ if (ChatKitBrowserPool.cleanupRegistered) {
85
+ return;
86
+ }
87
+ ChatKitBrowserPool.cleanupRegistered = true;
88
+ const cleanup = () => {
89
+ if (ChatKitBrowserPool.instance) {
90
+ // Synchronous cleanup - close browser immediately
91
+ ChatKitBrowserPool.instance.shutdown().catch(() => { });
92
+ ChatKitBrowserPool.instance = null;
93
+ }
94
+ };
95
+ // beforeExit fires when event loop is empty - allows cleanup of browser
96
+ // which otherwise keeps the event loop alive
97
+ process.on('beforeExit', () => {
98
+ if (ChatKitBrowserPool.instance) {
99
+ ChatKitBrowserPool.instance.shutdown().catch(() => { });
100
+ ChatKitBrowserPool.instance = null;
101
+ }
102
+ });
103
+ process.on('exit', cleanup);
104
+ process.on('SIGINT', () => {
105
+ cleanup();
106
+ process.exit(130);
107
+ });
108
+ process.on('SIGTERM', () => {
109
+ cleanup();
110
+ process.exit(143);
111
+ });
112
+ }
113
+ /**
114
+ * Get the singleton pool instance
115
+ */
116
+ static getInstance(config) {
117
+ if (!ChatKitBrowserPool.instance) {
118
+ ChatKitBrowserPool.instance = new ChatKitBrowserPool({
119
+ maxConcurrency: config?.maxConcurrency ?? 4,
120
+ headless: config?.headless ?? true,
121
+ serverPort: config?.serverPort ?? 0,
122
+ });
123
+ ChatKitBrowserPool.registerCleanupHandlers();
124
+ // Register with providerRegistry for cleanup at end of evaluation
125
+ // This is cleaner than relying only on process exit handlers
126
+ const instance = ChatKitBrowserPool.instance;
127
+ providerRegistry_1.providerRegistry.register({
128
+ async shutdown() {
129
+ if (instance) {
130
+ await instance.shutdown();
131
+ ChatKitBrowserPool.instance = null;
132
+ }
133
+ },
134
+ });
135
+ }
136
+ else if (config) {
137
+ // Warn if different config is requested for existing instance
138
+ const existing = ChatKitBrowserPool.instance.config;
139
+ if ((config.maxConcurrency !== undefined &&
140
+ config.maxConcurrency !== existing.maxConcurrency) ||
141
+ (config.headless !== undefined && config.headless !== existing.headless)) {
142
+ logger_1.default.warn('[ChatKitPool] Pool already exists with different config, ignoring new config', {
143
+ existing: { maxConcurrency: existing.maxConcurrency, headless: existing.headless },
144
+ requested: { maxConcurrency: config.maxConcurrency, headless: config.headless },
145
+ });
146
+ }
147
+ }
148
+ return ChatKitBrowserPool.instance;
149
+ }
150
+ /**
151
+ * Reset the singleton (for testing)
152
+ */
153
+ static resetInstance() {
154
+ if (ChatKitBrowserPool.instance) {
155
+ ChatKitBrowserPool.instance.shutdown().catch((err) => {
156
+ logger_1.default.debug('[ChatKitPool] Error during shutdown:', { error: String(err) });
157
+ });
158
+ ChatKitBrowserPool.instance = null;
159
+ }
160
+ // Don't reset cleanupRegistered - process handlers should only be registered once
161
+ }
162
+ /**
163
+ * Generate a template key from workflow configuration.
164
+ * This ensures different workflows get isolated pages.
165
+ */
166
+ static generateTemplateKey(workflowId, version, userId) {
167
+ // Use a simple concatenation - workflowId is the primary differentiator
168
+ // version and userId are included for completeness but workflowId is key
169
+ return `${workflowId}:${version || 'default'}:${userId || 'default'}`;
170
+ }
171
+ /**
172
+ * Register a template for a workflow configuration
173
+ */
174
+ setTemplate(templateKey, html) {
175
+ const existing = this.templates.get(templateKey);
176
+ if (existing !== html) {
177
+ this.templates.set(templateKey, html);
178
+ logger_1.default.debug('[ChatKitPool] Registered template', { templateKey });
179
+ // Mark pages with this template as needing refresh if template changed
180
+ for (const page of this.pages) {
181
+ if (page.templateKey === templateKey) {
182
+ page.ready = false;
183
+ }
184
+ }
185
+ }
186
+ }
187
+ /**
188
+ * Initialize the pool - launches browser and creates server
189
+ */
190
+ async initialize() {
191
+ if (this.initialized) {
192
+ return;
193
+ }
194
+ // Prevent multiple concurrent initializations
195
+ if (this.initPromise) {
196
+ return this.initPromise;
197
+ }
198
+ this.initPromise = this.doInitialize();
199
+ await this.initPromise;
200
+ this.initPromise = null;
201
+ }
202
+ async doInitialize() {
203
+ logger_1.default.debug('[ChatKitPool] Initializing browser pool', {
204
+ maxConcurrency: this.config.maxConcurrency,
205
+ });
206
+ // Create shared HTTP server with per-template routing
207
+ this.server = http.createServer((req, res) => {
208
+ // Extract template key from URL path: /template/<key>
209
+ const url = new URL(req.url || '/', `http://localhost`);
210
+ const pathParts = url.pathname.split('/').filter(Boolean);
211
+ if (pathParts[0] === 'template' && pathParts[1]) {
212
+ const templateKey = decodeURIComponent(pathParts[1]);
213
+ const template = this.templates.get(templateKey);
214
+ if (template) {
215
+ res.writeHead(200, { 'Content-Type': 'text/html' });
216
+ res.end(template);
217
+ return;
218
+ }
219
+ }
220
+ // Fallback: 404 for unknown templates
221
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
222
+ res.end('Template not found');
223
+ });
224
+ await new Promise((resolve, reject) => {
225
+ this.server.once('error', (err) => {
226
+ reject(new Error(`Failed to start ChatKit pool server: ${err.message}`));
227
+ });
228
+ this.server.listen(this.config.serverPort, () => {
229
+ const address = this.server.address();
230
+ this.serverPort = typeof address === 'object' ? address?.port || 0 : 0;
231
+ logger_1.default.debug('[ChatKitPool] Server started', { port: this.serverPort });
232
+ resolve();
233
+ });
234
+ });
235
+ // Launch single browser
236
+ try {
237
+ this.browser = await playwright_1.chromium.launch({
238
+ headless: this.config.headless,
239
+ });
240
+ }
241
+ catch (error) {
242
+ const msg = error instanceof Error ? error.message : String(error);
243
+ if (msg.includes("Executable doesn't exist")) {
244
+ throw new Error('Playwright browser not installed. Run: npx playwright install chromium');
245
+ }
246
+ throw error;
247
+ }
248
+ this.initialized = true;
249
+ logger_1.default.debug('[ChatKitPool] Browser pool initialized');
250
+ }
251
+ /**
252
+ * Acquire a page from the pool for a specific template.
253
+ * Only returns pages configured for the requested template.
254
+ * Blocks if all pages are in use.
255
+ */
256
+ async acquirePage(templateKey) {
257
+ // Cancel any pending idle shutdown since we're being used
258
+ this.cancelIdleTimer();
259
+ await this.initialize();
260
+ // Ensure template is registered
261
+ if (!this.templates.has(templateKey)) {
262
+ throw new Error(`Template not registered: ${templateKey}. Call setTemplate first.`);
263
+ }
264
+ // Try to find an available ready page with matching template
265
+ const available = this.pages.find((p) => !p.inUse && p.ready && p.templateKey === templateKey);
266
+ if (available) {
267
+ available.inUse = true;
268
+ logger_1.default.debug('[ChatKitPool] Acquired existing page', {
269
+ templateKey,
270
+ poolSize: this.pages.length,
271
+ });
272
+ return available;
273
+ }
274
+ // Try to find an idle page with matching template that needs refresh
275
+ const needsRefresh = this.pages.find((p) => !p.inUse && !p.ready && p.templateKey === templateKey);
276
+ if (needsRefresh) {
277
+ await this.refreshPooledPage(needsRefresh);
278
+ needsRefresh.inUse = true;
279
+ logger_1.default.debug('[ChatKitPool] Acquired and refreshed page', {
280
+ templateKey,
281
+ poolSize: this.pages.length,
282
+ });
283
+ return needsRefresh;
284
+ }
285
+ // Create new page if under limit
286
+ if (this.pages.length < this.config.maxConcurrency) {
287
+ const pooledPage = await this.createPooledPage(templateKey);
288
+ pooledPage.inUse = true;
289
+ this.pages.push(pooledPage);
290
+ logger_1.default.debug('[ChatKitPool] Created new page', {
291
+ templateKey,
292
+ poolSize: this.pages.length,
293
+ });
294
+ return pooledPage;
295
+ }
296
+ // Wait for a page with matching template to become available
297
+ logger_1.default.debug('[ChatKitPool] Waiting for available page', {
298
+ templateKey,
299
+ poolSize: this.pages.length,
300
+ waiting: this.waitQueue.length + 1,
301
+ });
302
+ return new Promise((resolve, reject) => {
303
+ const timeoutId = setTimeout(() => {
304
+ const index = this.waitQueue.findIndex((w) => w.resolve === wrappedResolve);
305
+ if (index >= 0) {
306
+ this.waitQueue.splice(index, 1);
307
+ }
308
+ reject(new Error(`Timeout waiting for available page after ${PAGE_ACQUIRE_TIMEOUT_MS}ms. ` +
309
+ `Pool has ${this.pages.length} pages, ${this.pages.filter((p) => p.inUse).length} in use.`));
310
+ }, PAGE_ACQUIRE_TIMEOUT_MS);
311
+ const wrappedResolve = (page) => {
312
+ clearTimeout(timeoutId);
313
+ resolve(page);
314
+ };
315
+ this.waitQueue.push({ templateKey, resolve: wrappedResolve });
316
+ });
317
+ }
318
+ /**
319
+ * Release a page back to the pool
320
+ */
321
+ async releasePage(pooledPage) {
322
+ const originalTemplateKey = pooledPage.templateKey;
323
+ // Keep inUse=true during refresh to prevent race conditions
324
+ // Reset the page for next use by reloading
325
+ try {
326
+ await this.refreshPooledPage(pooledPage);
327
+ }
328
+ catch (error) {
329
+ logger_1.default.warn('[ChatKitPool] Failed to reset page, recreating', { error });
330
+ // Page is broken, remove it from the pool
331
+ const index = this.pages.indexOf(pooledPage);
332
+ if (index >= 0) {
333
+ this.pages.splice(index, 1);
334
+ }
335
+ try {
336
+ await pooledPage.context.close();
337
+ }
338
+ catch {
339
+ // Ignore close errors
340
+ }
341
+ // Create replacement - if this fails, we just reduce pool size
342
+ // The pool will recover by creating new pages on demand
343
+ try {
344
+ const newPage = await this.createPooledPage(originalTemplateKey);
345
+ this.pages.push(newPage);
346
+ pooledPage = newPage;
347
+ }
348
+ catch (createError) {
349
+ logger_1.default.warn('[ChatKitPool] Failed to create replacement page', { error: createError });
350
+ // Pool size is now reduced - try to serve any waiting requests by creating pages for them
351
+ // This prevents deadlock when all pages fail
352
+ await this.tryServeWaiters();
353
+ this.scheduleIdleShutdown();
354
+ return;
355
+ }
356
+ }
357
+ // If someone is waiting for this template, give them the page directly
358
+ const waiterIndex = this.waitQueue.findIndex((w) => w.templateKey === pooledPage.templateKey);
359
+ if (waiterIndex >= 0) {
360
+ const waiter = this.waitQueue.splice(waiterIndex, 1)[0];
361
+ pooledPage.inUse = true;
362
+ waiter.resolve(pooledPage);
363
+ this.cancelIdleTimer();
364
+ }
365
+ else {
366
+ // No one waiting for this template, mark as available
367
+ pooledPage.inUse = false;
368
+ // Check if we can serve waiters for other templates now that we have capacity
369
+ await this.tryServeWaiters();
370
+ this.scheduleIdleShutdown();
371
+ }
372
+ }
373
+ /**
374
+ * Try to serve waiting requests by creating new pages if we have capacity
375
+ */
376
+ async tryServeWaiters() {
377
+ // Process waiters while we have capacity and waiters exist
378
+ while (this.waitQueue.length > 0 && this.pages.length < this.config.maxConcurrency) {
379
+ const waiter = this.waitQueue.shift();
380
+ if (!waiter) {
381
+ break;
382
+ }
383
+ try {
384
+ const newPage = await this.createPooledPage(waiter.templateKey);
385
+ newPage.inUse = true;
386
+ this.pages.push(newPage);
387
+ waiter.resolve(newPage);
388
+ logger_1.default.debug('[ChatKitPool] Created page for waiting request', {
389
+ templateKey: waiter.templateKey,
390
+ poolSize: this.pages.length,
391
+ remainingWaiters: this.waitQueue.length,
392
+ });
393
+ }
394
+ catch (error) {
395
+ logger_1.default.warn('[ChatKitPool] Failed to create page for waiter', {
396
+ templateKey: waiter.templateKey,
397
+ error,
398
+ });
399
+ // Put waiter back at the front of the queue to retry later
400
+ this.waitQueue.unshift(waiter);
401
+ break;
402
+ }
403
+ }
404
+ }
405
+ /**
406
+ * Schedule automatic shutdown if pool remains idle
407
+ */
408
+ scheduleIdleShutdown() {
409
+ // Cancel any existing timer
410
+ this.cancelIdleTimer();
411
+ // Check if pool is completely idle (no pages in use, no waiters)
412
+ const inUseCount = this.pages.filter((p) => p.inUse).length;
413
+ if (inUseCount === 0 && this.waitQueue.length === 0 && this.pages.length > 0) {
414
+ logger_1.default.debug('[ChatKitPool] Pool idle, scheduling shutdown', {
415
+ delay: IDLE_SHUTDOWN_DELAY_MS,
416
+ });
417
+ this.idleTimer = setTimeout(() => {
418
+ // Double-check still idle
419
+ const stillInUse = this.pages.filter((p) => p.inUse).length;
420
+ if (stillInUse === 0 && this.waitQueue.length === 0) {
421
+ logger_1.default.debug('[ChatKitPool] Auto-shutting down idle pool');
422
+ this.shutdown().catch((err) => {
423
+ logger_1.default.debug('[ChatKitPool] Error during idle shutdown', { error: String(err) });
424
+ });
425
+ ChatKitBrowserPool.instance = null;
426
+ }
427
+ }, IDLE_SHUTDOWN_DELAY_MS);
428
+ // Don't let the timer prevent process exit
429
+ if (this.idleTimer.unref) {
430
+ this.idleTimer.unref();
431
+ }
432
+ }
433
+ }
434
+ /**
435
+ * Cancel scheduled idle shutdown
436
+ */
437
+ cancelIdleTimer() {
438
+ if (this.idleTimer) {
439
+ clearTimeout(this.idleTimer);
440
+ this.idleTimer = null;
441
+ }
442
+ }
443
+ /**
444
+ * Create a new pooled page with ChatKit initialized for a specific template
445
+ */
446
+ async createPooledPage(templateKey) {
447
+ if (!this.browser) {
448
+ throw new Error('Browser not initialized');
449
+ }
450
+ const context = await this.browser.newContext({
451
+ viewport: { width: 800, height: 600 },
452
+ });
453
+ // Set longer default timeout to prevent Playwright's 30s default from interfering
454
+ context.setDefaultTimeout(120000);
455
+ try {
456
+ const page = await context.newPage();
457
+ // Navigate to the template-specific URL
458
+ const templateUrl = `http://localhost:${this.serverPort}/template/${encodeURIComponent(templateKey)}`;
459
+ await page.goto(templateUrl, {
460
+ waitUntil: 'domcontentloaded',
461
+ });
462
+ await page.waitForFunction(() => window.__state?.ready === true, {
463
+ timeout: CHATKIT_READY_TIMEOUT_MS,
464
+ });
465
+ return {
466
+ context,
467
+ page,
468
+ ready: true,
469
+ inUse: false,
470
+ templateKey,
471
+ };
472
+ }
473
+ catch (error) {
474
+ // Clean up context if page creation/initialization fails
475
+ try {
476
+ await context.close();
477
+ }
478
+ catch {
479
+ // Ignore close errors
480
+ }
481
+ throw error;
482
+ }
483
+ }
484
+ async refreshPooledPage(pooledPage) {
485
+ logger_1.default.debug('[ChatKitPool] Refreshing page', { timeout: PAGE_REFRESH_TIMEOUT_MS });
486
+ await pooledPage.page.reload({ waitUntil: 'domcontentloaded' });
487
+ await pooledPage.page.waitForFunction(() => window.__state?.ready === true, {
488
+ timeout: PAGE_REFRESH_TIMEOUT_MS,
489
+ });
490
+ pooledPage.ready = true;
491
+ }
492
+ /**
493
+ * Get pool statistics
494
+ */
495
+ getStats() {
496
+ return {
497
+ total: this.pages.length,
498
+ inUse: this.pages.filter((p) => p.inUse).length,
499
+ waiting: this.waitQueue.length,
500
+ templates: this.templates.size,
501
+ };
502
+ }
503
+ /**
504
+ * Shutdown the pool and release all resources
505
+ */
506
+ async shutdown() {
507
+ logger_1.default.debug('[ChatKitPool] Shutting down');
508
+ // Cancel any pending idle timer
509
+ this.cancelIdleTimer();
510
+ // Clear pending waiters - they will timeout via PAGE_ACQUIRE_TIMEOUT_MS
511
+ if (this.waitQueue.length > 0) {
512
+ logger_1.default.debug('[ChatKitPool] Clearing pending waiters', { count: this.waitQueue.length });
513
+ this.waitQueue = [];
514
+ }
515
+ // Close all contexts
516
+ for (const pooledPage of this.pages) {
517
+ try {
518
+ await pooledPage.context.close();
519
+ }
520
+ catch {
521
+ // Ignore errors during shutdown
522
+ }
523
+ }
524
+ this.pages = [];
525
+ // Close browser
526
+ if (this.browser) {
527
+ try {
528
+ await this.browser.close();
529
+ }
530
+ catch {
531
+ // Ignore errors
532
+ }
533
+ this.browser = null;
534
+ }
535
+ // Close server
536
+ if (this.server) {
537
+ this.server.close();
538
+ this.server = null;
539
+ }
540
+ this.initialized = false;
541
+ this.templates.clear();
542
+ logger_1.default.debug('[ChatKitPool] Shutdown complete');
543
+ }
544
+ }
545
+ exports.ChatKitBrowserPool = ChatKitBrowserPool;
546
+ ChatKitBrowserPool.instance = null;
547
+ ChatKitBrowserPool.cleanupRegistered = false;
548
+ //# sourceMappingURL=chatkit-pool.js.map