donobu 2.46.1 → 2.46.3
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.
- package/dist/assets/generated/version +1 -1
- package/dist/esm/assets/generated/version +1 -1
- package/dist/esm/lib/DonobuExtendedPage.d.ts +12 -10
- package/dist/esm/lib/DonobuExtendedPage.d.ts.map +1 -1
- package/dist/esm/lib/PageAi.d.ts +103 -77
- package/dist/esm/lib/PageAi.d.ts.map +1 -1
- package/dist/esm/lib/PageAi.js +152 -272
- package/dist/esm/lib/PageAi.js.map +1 -1
- package/dist/esm/lib/originalGotoRegistry.d.ts +5 -0
- package/dist/esm/lib/originalGotoRegistry.d.ts.map +1 -0
- package/dist/esm/lib/originalGotoRegistry.js +30 -0
- package/dist/esm/lib/originalGotoRegistry.js.map +1 -0
- package/dist/esm/lib/pageAi/cache.d.ts +84 -0
- package/dist/esm/lib/pageAi/cache.d.ts.map +1 -0
- package/dist/esm/lib/pageAi/cache.js +207 -0
- package/dist/esm/lib/pageAi/cache.js.map +1 -0
- package/dist/esm/lib/pageAi/cacheEntryBuilder.d.ts +17 -0
- package/dist/esm/lib/pageAi/cacheEntryBuilder.d.ts.map +1 -0
- package/dist/esm/lib/pageAi/cacheEntryBuilder.js +25 -0
- package/dist/esm/lib/pageAi/cacheEntryBuilder.js.map +1 -0
- package/dist/esm/lib/testExtension.d.ts.map +1 -1
- package/dist/esm/lib/testExtension.js +284 -282
- package/dist/esm/lib/testExtension.js.map +1 -1
- package/dist/esm/lib/utils/selfHealing.js +2 -2
- package/dist/esm/lib/utils/selfHealing.js.map +1 -1
- package/dist/esm/managers/CodeGenerator.d.ts +8 -0
- package/dist/esm/managers/CodeGenerator.d.ts.map +1 -1
- package/dist/esm/managers/CodeGenerator.js +104 -1
- package/dist/esm/managers/CodeGenerator.js.map +1 -1
- package/dist/lib/DonobuExtendedPage.d.ts +12 -10
- package/dist/lib/DonobuExtendedPage.d.ts.map +1 -1
- package/dist/lib/PageAi.d.ts +103 -77
- package/dist/lib/PageAi.d.ts.map +1 -1
- package/dist/lib/PageAi.js +152 -272
- package/dist/lib/PageAi.js.map +1 -1
- package/dist/lib/originalGotoRegistry.d.ts +5 -0
- package/dist/lib/originalGotoRegistry.d.ts.map +1 -0
- package/dist/lib/originalGotoRegistry.js +30 -0
- package/dist/lib/originalGotoRegistry.js.map +1 -0
- package/dist/lib/pageAi/cache.d.ts +84 -0
- package/dist/lib/pageAi/cache.d.ts.map +1 -0
- package/dist/lib/pageAi/cache.js +207 -0
- package/dist/lib/pageAi/cache.js.map +1 -0
- package/dist/lib/pageAi/cacheEntryBuilder.d.ts +17 -0
- package/dist/lib/pageAi/cacheEntryBuilder.d.ts.map +1 -0
- package/dist/lib/pageAi/cacheEntryBuilder.js +25 -0
- package/dist/lib/pageAi/cacheEntryBuilder.js.map +1 -0
- package/dist/lib/testExtension.d.ts.map +1 -1
- package/dist/lib/testExtension.js +284 -282
- package/dist/lib/testExtension.js.map +1 -1
- package/dist/lib/utils/selfHealing.js +2 -2
- package/dist/lib/utils/selfHealing.js.map +1 -1
- package/dist/managers/CodeGenerator.d.ts +8 -0
- package/dist/managers/CodeGenerator.d.ts.map +1 -1
- package/dist/managers/CodeGenerator.js +104 -1
- package/dist/managers/CodeGenerator.js.map +1 -1
- package/package.json +1 -1
package/dist/esm/lib/PageAi.js
CHANGED
|
@@ -1,42 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.PageAi = void 0;
|
|
3
|
+
exports.PageAi = exports.PageAiRunner = void 0;
|
|
40
4
|
const v4_1 = require("zod/v4");
|
|
41
5
|
const ToolManager_1 = require("../managers/ToolManager");
|
|
42
6
|
const MarkObjectiveCompleteTool_1 = require("../tools/MarkObjectiveCompleteTool");
|
|
@@ -45,111 +9,59 @@ const DonobuFlow_1 = require("../managers/DonobuFlow");
|
|
|
45
9
|
const PageAiException_1 = require("../exceptions/PageAiException");
|
|
46
10
|
const ControlPanel_1 = require("../models/ControlPanel");
|
|
47
11
|
const InteractionVisualizer_1 = require("../managers/InteractionVisualizer");
|
|
48
|
-
const CodeGenerator_1 = require("../managers/CodeGenerator");
|
|
49
|
-
const promises_1 = __importDefault(require("fs/promises"));
|
|
50
|
-
const LockFile = __importStar(require("proper-lockfile"));
|
|
51
12
|
const Logger_1 = require("../utils/Logger");
|
|
52
13
|
const DonobuFlowsManager_1 = require("../managers/DonobuFlowsManager");
|
|
53
|
-
|
|
54
|
-
|
|
14
|
+
const cache_1 = require("./pageAi/cache");
|
|
15
|
+
const cacheEntryBuilder_1 = require("./pageAi/cacheEntryBuilder");
|
|
16
|
+
const CodeGenerator_1 = require("../managers/CodeGenerator");
|
|
17
|
+
const originalGotoRegistry_1 = require("./originalGotoRegistry");
|
|
18
|
+
/**
|
|
19
|
+
* Prepares and executes a Donobu autonomous flow.
|
|
20
|
+
*
|
|
21
|
+
* Responsibilities:
|
|
22
|
+
* - Gather environment data based on allowed env vars.
|
|
23
|
+
* - Seed `DonobuFlow` with deterministic metadata (run mode, allowed tools, etc.).
|
|
24
|
+
* - Run the flow, updating persisted metadata regardless of outcome.
|
|
25
|
+
*
|
|
26
|
+
* The runner does **not** perform cache lookups or updates. That is deferred to
|
|
27
|
+
* the higher-level {@link PageAi} facade so that the runner can stay focused on
|
|
28
|
+
* “how do we execute a flow safely?” rather than “should we execute a flow at
|
|
29
|
+
* all?”.
|
|
30
|
+
*/
|
|
31
|
+
class PageAiRunner {
|
|
32
|
+
constructor(donobu, persistence, gptClient) {
|
|
55
33
|
this.donobu = donobu;
|
|
56
34
|
this.persistence = persistence;
|
|
57
35
|
this.gptClient = gptClient;
|
|
58
|
-
this.cacheFilepath = cacheFilepath;
|
|
59
36
|
}
|
|
60
37
|
/**
|
|
61
|
-
* Executes
|
|
62
|
-
* on a web page. The AI agent can interact with the page using Donobu tools and will
|
|
63
|
-
* attempt to satisfy the given objective.
|
|
64
|
-
*
|
|
65
|
-
* This method supports intelligent caching: successful flows are cached and can be
|
|
66
|
-
* replayed deterministically on subsequent runs with identical parameters, significantly
|
|
67
|
-
* reducing AI token usage and execution time. When a cache hit occurs, the flow runs
|
|
68
|
-
* in INSTRUCT mode using the cached tool calls; otherwise, it runs in AUTONOMOUS mode
|
|
69
|
-
* where the AI determines the necessary actions.
|
|
70
|
-
*
|
|
71
|
-
* @template T The Zod object schema describing the expected shape of the AI result.
|
|
72
|
-
* @param page The Donobu page instance to use as the starting point of the flow.
|
|
73
|
-
* @param instruction A high-level, natural language description of what the flow should
|
|
74
|
-
* accomplish (e.g., "Fill out the login form and submit it").
|
|
75
|
-
* @param options Optional configuration for the flow execution:
|
|
76
|
-
* - `cache`: Set to false to bypass cache lookup and population (default: true)
|
|
77
|
-
* - `allowedTools`: Restrict the AI to only use specified tools by name
|
|
78
|
-
* - `maxToolCalls`: Maximum number of tool invocations allowed (default: 25)
|
|
79
|
-
* @returns A promise that resolves to the parsed result payload if a schema is provided,
|
|
80
|
-
* or void if no schema is specified.
|
|
81
|
-
* @throws {PageAiException} If the autonomous flow fails, reaches a terminal non-success
|
|
82
|
-
* state, or the result fails schema validation.
|
|
38
|
+
* Executes a flow using the provided configuration.
|
|
83
39
|
*
|
|
84
|
-
* @
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* @example
|
|
89
|
-
* // Flow with structured output and schema validation
|
|
90
|
-
* const userSchema = z.object({
|
|
91
|
-
* name: z.string(),
|
|
92
|
-
* email: z.string().email(),
|
|
93
|
-
* isPremium: z.boolean(),
|
|
94
|
-
* });
|
|
95
|
-
* const user = await pageAi.ai(
|
|
96
|
-
* page,
|
|
97
|
-
* "Extract the user profile information from the page",
|
|
98
|
-
* { schema: userSchema }
|
|
99
|
-
* );
|
|
100
|
-
*
|
|
101
|
-
* @example
|
|
102
|
-
* // Flow with custom options
|
|
103
|
-
* await pageAi.ai(
|
|
104
|
-
* page,
|
|
105
|
-
* "Navigate to the settings page",
|
|
106
|
-
* {
|
|
107
|
-
* cache: false,
|
|
108
|
-
* allowedTools: ['goto', 'click'],
|
|
109
|
-
* maxToolCalls: 10,
|
|
110
|
-
* }
|
|
111
|
-
* );
|
|
40
|
+
* @param config Runtime information prepared by the higher-level facade.
|
|
41
|
+
* @returns Parsed result (if a schema was provided) along with the executed flow.
|
|
42
|
+
* @throws PageAiException when the underlying flow fails.
|
|
112
43
|
*/
|
|
113
|
-
async
|
|
114
|
-
const
|
|
115
|
-
const startingPageUrl = page.url();
|
|
116
|
-
const jsonSchema = options?.schema ? v4_1.z.toJSONSchema(options.schema) : null;
|
|
117
|
-
const maxToolCalls = options?.maxToolCalls ?? 25;
|
|
118
|
-
const allowedTools = options?.allowedTools ?? [];
|
|
119
|
-
const envData = await this.donobu.flowsManager.buildEnvData(DonobuFlowsManager_1.DonobuFlowsManager.distillAllowedEnvVariableNames(instruction, options?.envVars));
|
|
44
|
+
async run(config) {
|
|
45
|
+
const envData = await this.donobu.flowsManager.buildEnvData(config.envVarNames);
|
|
120
46
|
const originalFlowMetadata = {
|
|
121
|
-
...page.
|
|
47
|
+
...config.page._dnb.donobuFlowMetadata,
|
|
122
48
|
};
|
|
123
|
-
const
|
|
124
|
-
? await this.getCachedFlow(page, instruction, options)
|
|
125
|
-
: null;
|
|
126
|
-
const runMode = cachedToolCalls !== null ? 'DETERMINISTIC' : 'AUTONOMOUS';
|
|
127
|
-
const autonomousFlowMetadata = {
|
|
49
|
+
const augmentedMetadata = {
|
|
128
50
|
...originalFlowMetadata,
|
|
129
|
-
runMode: runMode,
|
|
130
|
-
overallObjective: instruction,
|
|
131
|
-
resultJsonSchema: jsonSchema,
|
|
132
|
-
maxToolCalls: maxToolCalls,
|
|
51
|
+
runMode: config.runMode,
|
|
52
|
+
overallObjective: config.instruction,
|
|
53
|
+
resultJsonSchema: config.jsonSchema,
|
|
54
|
+
maxToolCalls: config.maxToolCalls,
|
|
133
55
|
state: 'UNSTARTED',
|
|
134
|
-
allowedTools: allowedTools,
|
|
56
|
+
allowedTools: config.allowedTools,
|
|
135
57
|
};
|
|
136
|
-
const toolManager =
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
: await ToolManager_1.ToolManager.defaultTools());
|
|
141
|
-
// We have to revert to the original goto method in case the autonomous
|
|
142
|
-
// flow invokes a goto tool call (and it takes care of the things that
|
|
143
|
-
// the overwritten page goto method does).
|
|
144
|
-
const pageWithOriginalGoto = Object.create(page);
|
|
145
|
-
pageWithOriginalGoto.goto = page._originalGoto;
|
|
146
|
-
const donobuFlow = new DonobuFlow_1.DonobuFlow(this.donobu.flowsManager, envData, page.context(), this.persistence, this.gptClient, toolManager, new InteractionVisualizer_1.InteractionVisualizer(autonomousFlowMetadata.defaultMessageDuration ?? 0), cachedToolCalls ?? [], [], [], {
|
|
58
|
+
const toolManager = await this.buildToolManager(config.allowedTools);
|
|
59
|
+
const pageWithOriginalGoto = Object.create(config.page);
|
|
60
|
+
pageWithOriginalGoto.goto = (0, originalGotoRegistry_1.getOriginalGoto)(config.page);
|
|
61
|
+
const donobuFlow = new DonobuFlow_1.DonobuFlow(this.donobu.flowsManager, envData, config.page.context(), this.persistence, this.gptClient, toolManager, new InteractionVisualizer_1.InteractionVisualizer(augmentedMetadata.defaultMessageDuration ?? 0), config.cachedToolCalls ?? [], [], [], {
|
|
147
62
|
current: pageWithOriginalGoto,
|
|
148
|
-
},
|
|
63
|
+
}, augmentedMetadata, new ControlPanel_1.NoOpControlPanel());
|
|
149
64
|
const rawResult = await donobuFlow.run();
|
|
150
|
-
// Unset the finality of the autonomous flow so that the test can
|
|
151
|
-
// continue and be recorded properly if there are subsequent steps.
|
|
152
|
-
// The `finally` block for the test fixture will tie up loose ends.
|
|
153
65
|
const updatedMetadata = {
|
|
154
66
|
...originalFlowMetadata,
|
|
155
67
|
inputTokensUsed: donobuFlow.metadata.inputTokensUsed,
|
|
@@ -161,171 +73,139 @@ class PageAi {
|
|
|
161
73
|
catch (error) {
|
|
162
74
|
Logger_1.appLogger.warn(`Failed to update metadata for flow: ${updatedMetadata.id}`, error);
|
|
163
75
|
}
|
|
164
|
-
if (donobuFlow.metadata.state
|
|
165
|
-
if (useCache) {
|
|
166
|
-
await this.cacheFlow(startingPageUrl, donobuFlow);
|
|
167
|
-
}
|
|
168
|
-
return options?.schema?.parse(rawResult);
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
76
|
+
if (donobuFlow.metadata.state !== 'SUCCESS') {
|
|
171
77
|
const resultBlurb = donobuFlow.metadata.result
|
|
172
78
|
? JSON.stringify(donobuFlow.metadata.result, null, 2)
|
|
173
79
|
: '';
|
|
174
80
|
Logger_1.appLogger.error(`Failed to complete instruction: ${resultBlurb}`);
|
|
175
81
|
throw new PageAiException_1.PageAiException(donobuFlow.metadata);
|
|
176
82
|
}
|
|
83
|
+
const parsedResult = (config.schema?.parse(rawResult) ??
|
|
84
|
+
undefined);
|
|
85
|
+
return {
|
|
86
|
+
donobuFlow,
|
|
87
|
+
parsedResult,
|
|
88
|
+
};
|
|
177
89
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (error.code !== 'EEXIST') {
|
|
187
|
-
throw error;
|
|
188
|
-
}
|
|
90
|
+
/**
|
|
91
|
+
* Builds a `ToolManager` constrained to the tools the flow is allowed to use.
|
|
92
|
+
* We always allow the objective bookkeeping tools even if the caller supplied
|
|
93
|
+
* a narrower list.
|
|
94
|
+
*/
|
|
95
|
+
async buildToolManager(allowedTools) {
|
|
96
|
+
if (allowedTools.length === 0) {
|
|
97
|
+
return new ToolManager_1.ToolManager(await ToolManager_1.ToolManager.defaultTools());
|
|
189
98
|
}
|
|
99
|
+
const whitelistedTools = new Set([
|
|
100
|
+
...allowedTools,
|
|
101
|
+
MarkObjectiveCompleteTool_1.MarkObjectiveCompleteTool.NAME,
|
|
102
|
+
MarkObjectiveNotCompletableTool_1.MarkObjectiveNotCompletableTool.NAME,
|
|
103
|
+
]);
|
|
104
|
+
const filtered = (await ToolManager_1.ToolManager.allTools()).filter((tool) => whitelistedTools.has(tool.name));
|
|
105
|
+
return new ToolManager_1.ToolManager(filtered);
|
|
190
106
|
}
|
|
107
|
+
}
|
|
108
|
+
exports.PageAiRunner = PageAiRunner;
|
|
109
|
+
/**
|
|
110
|
+
* High-level API used by Playwright tests. It resolves caching policy,
|
|
111
|
+
* delegates execution to {@link PageAiRunner}, and records successful runs back
|
|
112
|
+
* into the configured cache.
|
|
113
|
+
*/
|
|
114
|
+
class PageAi {
|
|
191
115
|
/**
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
*
|
|
203
|
-
*
|
|
116
|
+
* @param donobu Donobu stack providing flow managers and shared services.
|
|
117
|
+
* @param persistence Persistence layer used to store flow metadata.
|
|
118
|
+
* @param gptClient GPT client used for autonomous reasoning.
|
|
119
|
+
* @param cache Pluggable cache implementation the facade should consult.
|
|
120
|
+
*/
|
|
121
|
+
constructor(donobu, persistence, gptClient, cache) {
|
|
122
|
+
this.cache = cache;
|
|
123
|
+
this.runner = new PageAiRunner(donobu, persistence, gptClient);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Builds a `PageAi` instance configured to use the file-backed cache stored
|
|
127
|
+
* alongside the current test file. This mirrors the behaviour relied upon by
|
|
128
|
+
* the Playwright fixture.
|
|
204
129
|
*/
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
130
|
+
static withFileCache(donobu, persistence, gptClient, cacheFilepath) {
|
|
131
|
+
return new PageAi(donobu, persistence, gptClient, new cache_1.FilePageAiCache(cacheFilepath));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Public entry point invoked by `page.ai`. Handles cache lookup, delegates to
|
|
135
|
+
* the runner, and stores the result back into the cache when appropriate.
|
|
136
|
+
*/
|
|
137
|
+
async ai(page, instruction, options) {
|
|
138
|
+
const descriptor = this.buildDescriptor(page, instruction, options);
|
|
139
|
+
const cachedToolCalls = descriptor.useCache
|
|
140
|
+
? await this.cache.get(descriptor.key)
|
|
141
|
+
: null;
|
|
142
|
+
const runResult = await this.runner.run({
|
|
143
|
+
page,
|
|
144
|
+
instruction,
|
|
145
|
+
schema: descriptor.schema,
|
|
146
|
+
jsonSchema: descriptor.jsonSchema,
|
|
147
|
+
allowedTools: descriptor.allowedTools,
|
|
148
|
+
maxToolCalls: descriptor.maxToolCalls,
|
|
149
|
+
envVarNames: descriptor.envVarNames,
|
|
150
|
+
cachedToolCalls,
|
|
151
|
+
runMode: cachedToolCalls !== null ? 'DETERMINISTIC' : 'AUTONOMOUS',
|
|
213
152
|
});
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const cacheContent = await promises_1.default.readFile(this.cacheFilepath, 'utf-8');
|
|
219
|
-
const parsedContent = JSON.parse(cacheContent);
|
|
220
|
-
if (typeof parsedContent !== 'object' || parsedContent === null) {
|
|
221
|
-
throw new Error('Invalid cache file format: expected an object with a caches array.');
|
|
222
|
-
}
|
|
223
|
-
if (!Array.isArray(parsedContent.caches)) {
|
|
224
|
-
parsedContent.caches = [];
|
|
225
|
-
await promises_1.default.writeFile(this.cacheFilepath, JSON.stringify(parsedContent, null, 2), 'utf-8');
|
|
226
|
-
}
|
|
227
|
-
cache = parsedContent.caches;
|
|
228
|
-
}
|
|
229
|
-
catch (error) {
|
|
230
|
-
if (error.code !== 'ENOENT') {
|
|
231
|
-
throw error;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
// Perform the operation.
|
|
235
|
-
const { cache: modifiedCache, result } = await operation(cache);
|
|
236
|
-
// Write the cache file if it was modified.
|
|
237
|
-
if (modifiedCache !== cache) {
|
|
238
|
-
await promises_1.default.writeFile(this.cacheFilepath, JSON.stringify({ caches: modifiedCache }, null, 2), 'utf-8');
|
|
239
|
-
}
|
|
240
|
-
return result;
|
|
241
|
-
}
|
|
242
|
-
finally {
|
|
243
|
-
await release();
|
|
153
|
+
if (descriptor.useCache) {
|
|
154
|
+
const preparedToolCalls = await CodeGenerator_1.CodeGenerator.prepareToolCallsForRerun(runResult.donobuFlow.invokedToolCalls, {});
|
|
155
|
+
const cacheEntry = cacheEntryBuilder_1.PageAiCacheEntryBuilder.fromMetadata(descriptor.key.pageUrl, runResult.donobuFlow.metadata, preparedToolCalls);
|
|
156
|
+
await this.cache.put(cacheEntry);
|
|
244
157
|
}
|
|
158
|
+
return runResult.parsedResult;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Invalidates cache entries matching the provided invocation parameters.
|
|
162
|
+
*
|
|
163
|
+
* Returns `true` when a matching record existed and was removed.
|
|
164
|
+
*/
|
|
165
|
+
async invalidate(page, instruction, options) {
|
|
166
|
+
const descriptor = this.buildDescriptor(page, instruction, options);
|
|
167
|
+
return this.cache.delete(descriptor.key);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Backwards-compatible alias for callers still using the older API.
|
|
171
|
+
*/
|
|
172
|
+
async deleteCachedFlow(page, instruction, options) {
|
|
173
|
+
return this.invalidate(page, instruction, options);
|
|
245
174
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
175
|
+
/**
|
|
176
|
+
* Normalises user-provided options into a single structure that can be reused
|
|
177
|
+
* across cache operations and flow execution.
|
|
178
|
+
*/
|
|
179
|
+
buildDescriptor(page, instruction, options) {
|
|
180
|
+
const useCache = options?.cache !== undefined ? options.cache : true;
|
|
181
|
+
const schema = (options?.schema ?? undefined);
|
|
182
|
+
const jsonSchema = schema ? v4_1.z.toJSONSchema(schema) : null;
|
|
249
183
|
const maxToolCalls = options?.maxToolCalls ?? 25;
|
|
250
184
|
const allowedTools = options?.allowedTools ?? [];
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
schema
|
|
255
|
-
|
|
256
|
-
allowedTools
|
|
185
|
+
const envVarNames = DonobuFlowsManager_1.DonobuFlowsManager.distillAllowedEnvVariableNames(instruction, options?.envVars);
|
|
186
|
+
return {
|
|
187
|
+
key: this.buildCacheKey(page, instruction, jsonSchema, allowedTools, maxToolCalls),
|
|
188
|
+
schema,
|
|
189
|
+
jsonSchema: jsonSchema,
|
|
190
|
+
allowedTools,
|
|
191
|
+
maxToolCalls,
|
|
192
|
+
envVarNames,
|
|
193
|
+
useCache,
|
|
257
194
|
};
|
|
258
|
-
return cacheKey;
|
|
259
|
-
}
|
|
260
|
-
async getCachedFlow(page, instruction, options) {
|
|
261
|
-
const cacheKey = this.getCacheKey(page, instruction, options);
|
|
262
|
-
return this.withCacheLock(async (cache) => {
|
|
263
|
-
const matchingEntry = cache.find((entry) => entry.pageUrl === cacheKey.pageUrl &&
|
|
264
|
-
entry.instruction === cacheKey.instruction &&
|
|
265
|
-
JSON.stringify(entry.schema) === JSON.stringify(cacheKey.schema) &&
|
|
266
|
-
JSON.stringify(entry.allowedTools) ===
|
|
267
|
-
JSON.stringify(cacheKey.allowedTools) &&
|
|
268
|
-
entry.maxToolCalls === cacheKey.maxToolCalls);
|
|
269
|
-
return {
|
|
270
|
-
cache, // No modification
|
|
271
|
-
result: matchingEntry?.toolCallCache ?? null,
|
|
272
|
-
};
|
|
273
|
-
});
|
|
274
195
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
result: false,
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
const modifiedCache = [
|
|
291
|
-
...cache.slice(0, targetIndex),
|
|
292
|
-
...cache.slice(targetIndex + 1),
|
|
293
|
-
];
|
|
294
|
-
return {
|
|
295
|
-
cache: modifiedCache,
|
|
296
|
-
result: true,
|
|
297
|
-
};
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
async cacheFlow(pageUrl, donobuFlow) {
|
|
301
|
-
const toolCallsOnStart = await CodeGenerator_1.CodeGenerator.prepareToolCallsForRerun(donobuFlow.invokedToolCalls, {});
|
|
302
|
-
const cacheEntry = {
|
|
303
|
-
pageUrl: pageUrl,
|
|
304
|
-
instruction: donobuFlow.metadata.overallObjective,
|
|
305
|
-
schema: donobuFlow.metadata.resultJsonSchema,
|
|
306
|
-
allowedTools: donobuFlow.metadata.allowedTools,
|
|
307
|
-
maxToolCalls: donobuFlow.metadata.maxToolCalls,
|
|
308
|
-
toolCallCache: toolCallsOnStart,
|
|
196
|
+
/**
|
|
197
|
+
* Computes the cache key that uniquely identifies a `page.ai` invocation.
|
|
198
|
+
* Keep this logic in sync with any external cache generators (e.g. the code
|
|
199
|
+
* generator) so that hits and invalidations behave the same everywhere.
|
|
200
|
+
*/
|
|
201
|
+
buildCacheKey(page, instruction, schema, allowedTools, maxToolCalls) {
|
|
202
|
+
return {
|
|
203
|
+
pageUrl: page.url(),
|
|
204
|
+
instruction,
|
|
205
|
+
schema,
|
|
206
|
+
allowedTools,
|
|
207
|
+
maxToolCalls,
|
|
309
208
|
};
|
|
310
|
-
await this.withCacheLock(async (cache) => {
|
|
311
|
-
const existingIndex = cache.findIndex((entry) => entry.pageUrl === cacheEntry.pageUrl &&
|
|
312
|
-
entry.instruction === cacheEntry.instruction &&
|
|
313
|
-
JSON.stringify(entry.schema) === JSON.stringify(cacheEntry.schema) &&
|
|
314
|
-
JSON.stringify(entry.allowedTools) ===
|
|
315
|
-
JSON.stringify(cacheEntry.allowedTools) &&
|
|
316
|
-
entry.maxToolCalls === cacheEntry.maxToolCalls);
|
|
317
|
-
const modifiedCache = [...cache];
|
|
318
|
-
if (existingIndex !== -1) {
|
|
319
|
-
modifiedCache[existingIndex] = cacheEntry;
|
|
320
|
-
}
|
|
321
|
-
else {
|
|
322
|
-
modifiedCache.push(cacheEntry);
|
|
323
|
-
}
|
|
324
|
-
return {
|
|
325
|
-
cache: modifiedCache,
|
|
326
|
-
result: undefined,
|
|
327
|
-
};
|
|
328
|
-
});
|
|
329
209
|
}
|
|
330
210
|
}
|
|
331
211
|
exports.PageAi = PageAi;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageAi.js","sourceRoot":"","sources":["../../../src/lib/PageAi.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PageAi.js","sourceRoot":"","sources":["../../../src/lib/PageAi.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Page } from '@playwright/test';
|
|
2
|
+
export declare function registerOriginalGoto(page: Page, originalGoto: Page['goto']): void;
|
|
3
|
+
export declare function hasOriginalGoto(page: Page): boolean;
|
|
4
|
+
export declare function getOriginalGoto(page: Page): Page['goto'];
|
|
5
|
+
//# sourceMappingURL=originalGotoRegistry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"originalGotoRegistry.d.ts","sourceRoot":"","sources":["../../../src/lib/originalGotoRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAc7C,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,GACzB,IAAI,CAEN;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAEnD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAQxD"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerOriginalGoto = registerOriginalGoto;
|
|
4
|
+
exports.hasOriginalGoto = hasOriginalGoto;
|
|
5
|
+
exports.getOriginalGoto = getOriginalGoto;
|
|
6
|
+
/**
|
|
7
|
+
* Playwright creates a unique `goto` implementation for every page instance.
|
|
8
|
+
* Once Donobu decorates a page we override that method, so the original
|
|
9
|
+
* reference has to be stored somewhere page-specific. We can no longer stash
|
|
10
|
+
* it on `_dnb` because all decorated pages share the same `_dnb` object.
|
|
11
|
+
*
|
|
12
|
+
* This registry keeps the native implementation in a WeakMap keyed by page.
|
|
13
|
+
* Using a WeakMap ensures entries vanish automatically when a page is closed,
|
|
14
|
+
* preventing leaks while still letting us delegate back to the correct tab.
|
|
15
|
+
*/
|
|
16
|
+
const originalGotoRegistry = new WeakMap();
|
|
17
|
+
function registerOriginalGoto(page, originalGoto) {
|
|
18
|
+
originalGotoRegistry.set(page, originalGoto);
|
|
19
|
+
}
|
|
20
|
+
function hasOriginalGoto(page) {
|
|
21
|
+
return originalGotoRegistry.has(page);
|
|
22
|
+
}
|
|
23
|
+
function getOriginalGoto(page) {
|
|
24
|
+
const originalGoto = originalGotoRegistry.get(page);
|
|
25
|
+
if (!originalGoto) {
|
|
26
|
+
throw new Error('Original goto not registered for the provided page.');
|
|
27
|
+
}
|
|
28
|
+
return originalGoto;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=originalGotoRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"originalGotoRegistry.js","sourceRoot":"","sources":["../../../src/lib/originalGotoRegistry.ts"],"names":[],"mappings":";;AAcA,oDAKC;AAED,0CAEC;AAED,0CAQC;AA/BD;;;;;;;;;GASG;AACH,MAAM,oBAAoB,GAAG,IAAI,OAAO,EAAsB,CAAC;AAE/D,SAAgB,oBAAoB,CAClC,IAAU,EACV,YAA0B;IAE1B,oBAAoB,CAAC,GAAG,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;AAC/C,CAAC;AAED,SAAgB,eAAe,CAAC,IAAU;IACxC,OAAO,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC;AAED,SAAgB,eAAe,CAAC,IAAU;IACxC,MAAM,YAAY,GAAG,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAEpD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC"}
|