@loadmill/droid-cua 2.8.0 → 2.9.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.
@@ -9,7 +9,6 @@ import { getConfiguredStepDelayMs } from "../utils/step-delay.js";
9
9
  import { printCliOutput } from "../utils/console-output.js";
10
10
  import { annotateScreenshotArtifact, createAnnotatedScreenshotFromBase64 } from "../utils/run-screenshot-recorder.js";
11
11
  import { CAPTURE_SOURCES } from "./capture-sources.js";
12
- const VALID_CUA_MODELS = new Set(["gpt-5.4", "gpt-5.4-mini", "computer-use-preview"]);
13
12
  function extractActionableOutputItems(items) {
14
13
  const entries = [];
15
14
  for (const item of items) {
@@ -189,13 +188,14 @@ export class ExecutionEngine {
189
188
  this.screenshotDir = options.screenshotDir || null;
190
189
  this.screenshotRecorder = options.screenshotRecorder || null;
191
190
  this.strictMode = options.strictMode === true;
192
- this.stepDelayMs = getConfiguredStepDelayMs();
191
+ this.stepDelayMs = options.stepDelayMs ?? getConfiguredStepDelayMs();
193
192
  this.reportedScreenshotWriteError = false;
194
193
  this.reportedScreenshotAnnotateError = false;
195
194
  this.lastRecordedScreenshotPaths = [];
196
195
  this.getScreenshotAsBase64 = options.getScreenshotAsBase64 || getScreenshotAsBase64;
197
196
  this.handleModelAction = options.handleModelAction || handleModelAction;
198
197
  this.sendCUARequest = options.sendCUARequest || sendCUARequest;
198
+ this.reviseTestScript = options.reviseTestScript || null;
199
199
  this.getCurrentPlatform = options.getCurrentPlatform || getCurrentPlatform;
200
200
  }
201
201
  async saveScreenshotArtifact(screenshotBase64, metadata = {}, options = {}) {
@@ -579,9 +579,6 @@ export class ExecutionEngine {
579
579
  note: runtimeNote
580
580
  });
581
581
  }
582
- const selectedCuaModel = VALID_CUA_MODELS.has(process.env.OPENAI_CUA_MODEL)
583
- ? process.env.OPENAI_CUA_MODEL
584
- : "gpt-5.4";
585
582
  const input = [{
586
583
  type: "computer_call_output",
587
584
  call_id,
@@ -589,9 +586,6 @@ export class ExecutionEngine {
589
586
  type: "computer_screenshot",
590
587
  image_url: `data:image/png;base64,${screenshotBase64}`,
591
588
  },
592
- ...(selectedCuaModel === "computer-use-preview"
593
- ? { current_url: this.getCurrentPlatform() === "ios" ? "ios://simulator" : "android://device" }
594
- : {}),
595
589
  ...(pendingSafetyChecks.length > 0 ? { acknowledged_safety_checks: pendingSafetyChecks } : {})
596
590
  }];
597
591
  if (runtimeNote) {
@@ -0,0 +1,96 @@
1
+ import OpenAI from "openai";
2
+ import { readContextOptimizationEnv, normalizeContextOptimizationEnabled, normalizeContextOptimizationThreshold, } from "./context-optimization.js";
3
+ import { resolveCuaModel } from "./cua-models.js";
4
+ import { normalizeStepDelayMs } from "../utils/step-delay.js";
5
+ export function createEmptyPromptCustomizations() {
6
+ return {
7
+ basePromptInstructions: "",
8
+ designModeInstructions: "",
9
+ executionModeInstructions: "",
10
+ };
11
+ }
12
+ export function resolvePromptCustomizations(rawValue = null) {
13
+ const resolved = createEmptyPromptCustomizations();
14
+ if (!rawValue || typeof rawValue !== "object" || Array.isArray(rawValue)) {
15
+ return resolved;
16
+ }
17
+ for (const key of Object.keys(resolved)) {
18
+ if (typeof rawValue[key] === "string") {
19
+ resolved[key] = rawValue[key];
20
+ }
21
+ }
22
+ return resolved;
23
+ }
24
+ function trimNullableString(value) {
25
+ if (typeof value !== "string") {
26
+ return null;
27
+ }
28
+ const trimmed = value.trim();
29
+ return trimmed || null;
30
+ }
31
+ export function resolveRuntimeCredentials({ openAiApiKey = null, loadmillApiToken = null, loadmillBaseUrl = null, } = {}) {
32
+ return {
33
+ openAiApiKey: trimNullableString(openAiApiKey),
34
+ loadmillApiToken: trimNullableString(loadmillApiToken),
35
+ loadmillBaseUrl: trimNullableString(loadmillBaseUrl),
36
+ };
37
+ }
38
+ export function resolveRuntimeConfig({ cuaModel = null, strictMode = false, stepDelayMs = undefined, contextOptimizationEnabled = undefined, contextOptimizationThreshold = undefined, promptCustomizations = null, appContext = null, } = {}) {
39
+ return {
40
+ cuaModel: resolveCuaModel(cuaModel),
41
+ strictMode: strictMode === true,
42
+ stepDelayMs: normalizeStepDelayMs(Number(stepDelayMs)),
43
+ contextOptimizationEnabled: normalizeContextOptimizationEnabled(contextOptimizationEnabled),
44
+ contextOptimizationThreshold: normalizeContextOptimizationThreshold(contextOptimizationThreshold),
45
+ promptCustomizations: resolvePromptCustomizations(promptCustomizations),
46
+ appContext: appContext && typeof appContext === "object"
47
+ ? {
48
+ enabled: appContext.enabled !== false,
49
+ path: trimNullableString(appContext.path ?? appContext.contextPath),
50
+ status: typeof appContext.status === "string" ? appContext.status : null,
51
+ text: typeof appContext.text === "string" ? appContext.text : "",
52
+ message: trimNullableString(appContext.message),
53
+ }
54
+ : {
55
+ enabled: true,
56
+ path: null,
57
+ status: null,
58
+ text: "",
59
+ message: null,
60
+ },
61
+ };
62
+ }
63
+ export function resolveRuntimeConfigFromEnv(env = process.env, overrides = {}) {
64
+ const contextOptimization = readContextOptimizationEnv(env);
65
+ return resolveRuntimeConfig({
66
+ cuaModel: env.OPENAI_CUA_MODEL,
67
+ stepDelayMs: env.DROID_CUA_STEP_DELAY_MS,
68
+ contextOptimizationEnabled: contextOptimization.enabled,
69
+ contextOptimizationThreshold: contextOptimization.threshold,
70
+ ...overrides,
71
+ });
72
+ }
73
+ export function resolveRuntimeCredentialsFromEnv(env = process.env, overrides = {}) {
74
+ return resolveRuntimeCredentials({
75
+ openAiApiKey: env.OPENAI_API_KEY,
76
+ loadmillApiToken: env.LOADMILL_API_TOKEN,
77
+ loadmillBaseUrl: env.LOADMILL_BASE_URL,
78
+ ...overrides,
79
+ });
80
+ }
81
+ export function createOpenAiClient(apiKey) {
82
+ return new OpenAI({
83
+ apiKey: trimNullableString(apiKey) || undefined,
84
+ });
85
+ }
86
+ export function createRunContext({ config, credentials }) {
87
+ const resolvedConfig = resolveRuntimeConfig(config);
88
+ const resolvedCredentials = resolveRuntimeCredentials(credentials);
89
+ return {
90
+ config: resolvedConfig,
91
+ credentials: resolvedCredentials,
92
+ clients: {
93
+ openai: createOpenAiClient(resolvedCredentials.openAiApiKey),
94
+ },
95
+ };
96
+ }
@@ -3,8 +3,8 @@
3
3
  */
4
4
  import { executeLoadmillCommand } from "../integrations/loadmill/index.js";
5
5
  import { printCliOutput } from "../utils/console-output.js";
6
- function getLoadmillSiteBaseUrl() {
7
- const rawBaseUrl = process.env.LOADMILL_BASE_URL || "https://app.loadmill.com/api";
6
+ function getLoadmillSiteBaseUrl(loadmillClient = null) {
7
+ const rawBaseUrl = loadmillClient?.getBaseUrl?.() || "https://app.loadmill.com/api";
8
8
  return rawBaseUrl.replace(/\/api\/?$/, "");
9
9
  }
10
10
  /**
@@ -45,6 +45,8 @@ export function extractLoadmillCommand(userInput) {
45
45
  */
46
46
  export async function executeLoadmillInstruction(command, isHeadlessMode, context, stepContext = null) {
47
47
  const addOutput = context?.addOutput || printCliOutput;
48
+ const loadmillClient = context?.loadmillClient || null;
49
+ const openAiClient = context?.openAiClient || null;
48
50
  const meta = {
49
51
  runId: context?.runId,
50
52
  stepId: stepContext?.stepId,
@@ -54,14 +56,16 @@ export async function executeLoadmillInstruction(command, isHeadlessMode, contex
54
56
  type: 'info',
55
57
  text: `[Loadmill] Executing: ${command}`,
56
58
  eventType: 'system_message',
57
- payload: { loadmillCommand: command, loadmillBaseUrl: getLoadmillSiteBaseUrl() },
59
+ payload: { loadmillCommand: command, loadmillBaseUrl: getLoadmillSiteBaseUrl(loadmillClient) },
58
60
  ...meta
59
61
  });
60
62
  const result = await executeLoadmillCommand(command, {
63
+ loadmillClient,
64
+ openAiClient,
61
65
  onProgress: ({ message, runId }) => {
62
66
  const payload = runId
63
- ? { loadmillSuiteRunId: runId, loadmillCommand: command, loadmillBaseUrl: getLoadmillSiteBaseUrl() }
64
- : { loadmillCommand: command, loadmillBaseUrl: getLoadmillSiteBaseUrl() };
67
+ ? { loadmillSuiteRunId: runId, loadmillCommand: command, loadmillBaseUrl: getLoadmillSiteBaseUrl(loadmillClient) }
68
+ : { loadmillCommand: command, loadmillBaseUrl: getLoadmillSiteBaseUrl(loadmillClient) };
65
69
  addOutput({ type: 'info', text: `[Loadmill] ${message}`, eventType: 'system_message', payload, ...meta });
66
70
  }
67
71
  });
@@ -84,14 +88,15 @@ export async function executeLoadmillInstruction(command, isHeadlessMode, contex
84
88
  */
85
89
  export async function handleLoadmillFailure(command, error, isHeadlessMode, context, stepContext = null, suiteRunId = null) {
86
90
  const addOutput = context?.addOutput || printCliOutput;
91
+ const loadmillClient = context?.loadmillClient || null;
87
92
  const meta = {
88
93
  runId: context?.runId,
89
94
  stepId: stepContext?.stepId,
90
95
  instructionIndex: stepContext?.instructionIndex
91
96
  };
92
97
  const payload = suiteRunId
93
- ? { loadmillSuiteRunId: suiteRunId, loadmillCommand: command, loadmillBaseUrl: getLoadmillSiteBaseUrl() }
94
- : { loadmillCommand: command, loadmillBaseUrl: getLoadmillSiteBaseUrl() };
98
+ ? { loadmillSuiteRunId: suiteRunId, loadmillCommand: command, loadmillBaseUrl: getLoadmillSiteBaseUrl(loadmillClient) }
99
+ : { loadmillCommand: command, loadmillBaseUrl: getLoadmillSiteBaseUrl(loadmillClient) };
95
100
  addOutput({ type: 'error', text: '[Loadmill] FAILED', eventType: 'error', payload, ...meta });
96
101
  addOutput({ type: 'error', text: `Command: ${command}`, eventType: 'error', payload, ...meta });
97
102
  addOutput({ type: 'error', text: `Error: ${error}`, eventType: 'error', payload, ...meta });
@@ -148,14 +153,15 @@ export async function handleLoadmillFailure(command, error, isHeadlessMode, cont
148
153
  */
149
154
  export function handleLoadmillSuccess(command, result, context, stepContext = null) {
150
155
  const addOutput = context?.addOutput || printCliOutput;
156
+ const loadmillClient = context?.loadmillClient || null;
151
157
  const meta = {
152
158
  runId: context?.runId,
153
159
  stepId: stepContext?.stepId,
154
160
  instructionIndex: stepContext?.instructionIndex
155
161
  };
156
162
  const payload = result.runId
157
- ? { loadmillSuiteRunId: result.runId, loadmillCommand: command, loadmillBaseUrl: getLoadmillSiteBaseUrl() }
158
- : { loadmillCommand: command, loadmillBaseUrl: getLoadmillSiteBaseUrl() };
163
+ ? { loadmillSuiteRunId: result.runId, loadmillCommand: command, loadmillBaseUrl: getLoadmillSiteBaseUrl(loadmillClient) }
164
+ : { loadmillCommand: command, loadmillBaseUrl: getLoadmillSiteBaseUrl(loadmillClient) };
159
165
  if (result.action === "search") {
160
166
  addOutput({ type: 'success', text: `[Loadmill] Found ${result.result.flows.length} flow(s)`, eventType: 'system_message', payload, ...meta });
161
167
  result.result.flows.forEach((flow, i) => {
@@ -1,15 +1,9 @@
1
- import OpenAI from "openai";
2
- import { readContextOptimizationEnv } from "../core/context-optimization.js";
3
1
  import { buildTestRevisionSystemPrompt } from "../prompts/editor.js";
4
- import { buildAppContextCompactionInput } from "../prompts/execution.js";
2
+ import { createOpenAiClient, resolveRuntimeConfig } from "../core/runtime-config.js";
3
+ import { DEFAULT_CONTEXT_OPTIMIZATION_THRESHOLD } from "../core/context-optimization.js";
5
4
  import { logger } from "../utils/logger.js";
6
5
  import { CuaDebugTracer } from "../utils/cua-debug-tracer.js";
7
- let openai = null;
8
6
  const cuaDebugTracer = new CuaDebugTracer(logger);
9
- const VALID_CUA_MODELS = new Set(["gpt-5.4", "gpt-5.4-mini", "computer-use-preview"]);
10
- function getSelectedCuaModel() {
11
- return VALID_CUA_MODELS.has(process.env.OPENAI_CUA_MODEL) ? process.env.OPENAI_CUA_MODEL : "gpt-5.4";
12
- }
13
7
  function normalizeMessageContent(content) {
14
8
  if (Array.isArray(content)) {
15
9
  return content.map((item) => {
@@ -90,37 +84,14 @@ export function buildCuaInput({ messages, screenshotBase64, previousResponseId,
90
84
  }
91
85
  return input;
92
86
  }
93
- function buildContextManagement(cuaModel) {
94
- if (cuaModel === "computer-use-preview") {
95
- return undefined;
96
- }
97
- const { enabled, threshold } = readContextOptimizationEnv();
98
- if (!enabled) {
87
+ function buildContextManagement(contextOptimizationEnabled, contextOptimizationThreshold) {
88
+ if (!contextOptimizationEnabled) {
99
89
  return undefined;
100
90
  }
101
- return [{ type: "compaction", compact_threshold: threshold }];
91
+ return [{ type: "compaction", compact_threshold: contextOptimizationThreshold }];
102
92
  }
103
- export function buildCuaRequestParams({ cuaModel, previousResponseId, deviceInfo, input }) {
104
- const contextManagement = buildContextManagement(cuaModel);
105
- if (cuaModel === "computer-use-preview") {
106
- return {
107
- model: cuaModel,
108
- previous_response_id: previousResponseId || undefined,
109
- input,
110
- store: true,
111
- truncation: "auto",
112
- tools: [
113
- {
114
- type: "computer_use_preview",
115
- display_width: deviceInfo.scaled_width,
116
- display_height: deviceInfo.scaled_height,
117
- environment: "browser",
118
- },
119
- previewClickTool,
120
- ],
121
- reasoning: { summary: "concise" },
122
- };
123
- }
93
+ export function buildCuaRequestParams({ cuaModel, previousResponseId, input, contextOptimizationEnabled = true, contextOptimizationThreshold = DEFAULT_CONTEXT_OPTIMIZATION_THRESHOLD, }) {
94
+ const contextManagement = buildContextManagement(contextOptimizationEnabled, contextOptimizationThreshold);
124
95
  return {
125
96
  model: cuaModel,
126
97
  reasoning: { effort: "medium" },
@@ -130,97 +101,23 @@ export function buildCuaRequestParams({ cuaModel, previousResponseId, deviceInfo
130
101
  store: true,
131
102
  truncation: "auto",
132
103
  tools: [
133
- {
134
- type: "computer",
135
- },
104
+ { type: "computer" },
136
105
  previewClickTool,
137
106
  ],
138
107
  };
139
108
  }
140
- function shouldMapPreviewAccessError(err) {
141
- const status = err?.status;
142
- const code = typeof err?.code === "string" ? err.code.toLowerCase() : "";
143
- const type = typeof err?.type === "string" ? err.type.toLowerCase() : "";
144
- const message = typeof err?.message === "string" ? err.message.toLowerCase() : "";
145
- if (status === 403 || status === 404)
146
- return true;
147
- if (code.includes("model_not_found") || code.includes("permission"))
148
- return true;
149
- if (type.includes("permission"))
150
- return true;
151
- return (message.includes("computer-use-preview") &&
152
- (message.includes("access") || message.includes("permission") || message.includes("not found") || message.includes("unsupported")));
153
- }
154
- export function shouldMapMiniCompatibilityError(err) {
155
- const status = Number(err?.status);
156
- const code = typeof err?.code === "string" ? err.code.toLowerCase() : "";
157
- const type = typeof err?.type === "string" ? err.type.toLowerCase() : "";
158
- const message = typeof err?.message === "string" ? err.message.toLowerCase() : "";
159
- const mentionsMiniModel = message.includes("gpt-5.4-mini");
160
- const mentionsComputerTool = message.includes("computer") || message.includes("tool");
161
- const mentionsUnsupported = (message.includes("unsupported") ||
162
- message.includes("not supported") ||
163
- message.includes("does not support"));
164
- if (code.includes("model_not_found")) {
165
- return true;
166
- }
167
- if (status === 404) {
168
- return mentionsMiniModel && (message.includes("not found") || mentionsUnsupported);
169
- }
170
- if (code.includes("unsupported")) {
171
- return mentionsMiniModel || mentionsComputerTool;
172
- }
173
- if (!type.includes("invalid")) {
174
- return false;
175
- }
176
- return mentionsUnsupported && (mentionsMiniModel || mentionsComputerTool);
177
- }
178
- function mapCuaError(err, cuaModel) {
179
- if (cuaModel === "computer-use-preview" && shouldMapPreviewAccessError(err)) {
180
- const mapped = new Error("OpenAI API key does not have access to computer-use-preview. Switch to gpt-5.4 in Settings > CUA Model.");
181
- mapped.cause = err;
182
- return mapped;
183
- }
184
- if (cuaModel === "gpt-5.4-mini" && shouldMapMiniCompatibilityError(err)) {
185
- const mapped = new Error("OpenAI rejected gpt-5.4-mini for computer-use execution. Switch to gpt-5.4 or computer-use-preview in Settings > CUA Model.");
186
- mapped.cause = err;
187
- return mapped;
188
- }
189
- return err;
190
- }
191
- export function isNonRetryableCuaError(err) {
192
- const status = Number(err?.status);
193
- const code = typeof err?.code === "string" ? err.code.toLowerCase() : "";
194
- const type = typeof err?.type === "string" ? err.type.toLowerCase() : "";
195
- const message = typeof err?.message === "string" ? err.message.toLowerCase() : "";
196
- if ([401, 403, 404].includes(status)) {
197
- return true;
198
- }
199
- return (code.includes("model_not_found") ||
200
- code.includes("permission") ||
201
- type.includes("permission") ||
202
- message.includes("does not have access to computer-use-preview") ||
203
- message.includes("switch to gpt-5.4 in settings > cua model") ||
204
- message.includes("openai rejected gpt-5.4-mini for computer-use execution") ||
205
- message.includes("switch to gpt-5.4 or computer-use-preview in settings > cua model"));
206
- }
207
- function getOpenAI() {
208
- if (!openai) {
209
- openai = new OpenAI({
210
- apiKey: process.env.OPENAI_API_KEY,
211
- });
212
- }
213
- return openai;
214
- }
215
109
  /**
216
110
  * Revise a test script based on user feedback using simple chat completion
217
- * @param {string} originalScript - The original test script
218
- * @param {string} revisionRequest - User's requested changes
111
+ * @param {{
112
+ * client: import("openai").default,
113
+ * originalScript: string,
114
+ * revisionRequest: string,
115
+ * }} options
219
116
  * @returns {Promise<string>} - The revised test script
220
117
  */
221
- export async function reviseTestScript(originalScript, revisionRequest) {
222
- const response = await getOpenAI().chat.completions.create({
223
- model: "gpt-4o",
118
+ export async function reviseTestScript({ client, originalScript, revisionRequest }) {
119
+ const response = await client.chat.completions.create({
120
+ model: "gpt-5.4",
224
121
  messages: [{
225
122
  role: "system",
226
123
  content: buildTestRevisionSystemPrompt(originalScript, revisionRequest)
@@ -228,29 +125,28 @@ export async function reviseTestScript(originalScript, revisionRequest) {
228
125
  });
229
126
  return response.choices[0].message.content.trim();
230
127
  }
231
- export async function compactAppContext({ contextDocument, taskDescription, tokenBudget }) {
232
- const response = await getOpenAI().responses.create({
233
- model: "gpt-5.4",
234
- temperature: 0,
235
- input: buildAppContextCompactionInput({
236
- contextDocument,
237
- taskDescription,
238
- tokenBudget,
239
- })
240
- });
241
- return {
242
- briefing: typeof response.output_text === "string" ? response.output_text.trim() : "",
243
- outputTokens: typeof response.usage?.output_tokens === "number" ? response.usage.output_tokens : null,
244
- };
245
- }
246
- export async function sendCUARequest({ messages, screenshotBase64, previousResponseId, callId, deviceInfo, debugContext, }) {
247
- const cuaModel = getSelectedCuaModel();
128
+ /**
129
+ * @param {{
130
+ * client: import("openai").default,
131
+ * cuaModel: string,
132
+ * contextOptimizationEnabled?: boolean,
133
+ * contextOptimizationThreshold?: number,
134
+ * messages: Array<unknown>,
135
+ * screenshotBase64?: string,
136
+ * previousResponseId?: string | null,
137
+ * callId?: string | null,
138
+ * deviceInfo?: unknown,
139
+ * debugContext?: unknown,
140
+ * }} options
141
+ */
142
+ export async function sendCUARequest({ client, cuaModel, contextOptimizationEnabled = true, contextOptimizationThreshold = DEFAULT_CONTEXT_OPTIMIZATION_THRESHOLD, messages, screenshotBase64, previousResponseId, callId, deviceInfo, debugContext, }) {
248
143
  const input = buildCuaInput({ messages, screenshotBase64, previousResponseId, callId });
249
144
  const requestParams = buildCuaRequestParams({
250
145
  cuaModel,
251
146
  previousResponseId,
252
- deviceInfo,
253
147
  input,
148
+ contextOptimizationEnabled,
149
+ contextOptimizationThreshold,
254
150
  });
255
151
  const trace = cuaDebugTracer.startTurn({
256
152
  requestParams,
@@ -262,12 +158,44 @@ export async function sendCUARequest({ messages, screenshotBase64, previousRespo
262
158
  });
263
159
  logger.debug("CUA Request:", trace.requestLog);
264
160
  try {
265
- const response = await getOpenAI().responses.create(requestParams);
161
+ const response = await client.responses.create(requestParams);
266
162
  cuaDebugTracer.onResponse(trace, response);
267
163
  return response;
268
164
  }
269
165
  catch (err) {
270
166
  cuaDebugTracer.onError(trace, err);
271
- throw mapCuaError(err, cuaModel);
167
+ throw err;
272
168
  }
273
169
  }
170
+ /**
171
+ * @param {{
172
+ * client?: import("openai").default | null,
173
+ * config?: Record<string, unknown>,
174
+ * }} options
175
+ */
176
+ export function createOpenAiExecutor({ client, config }) {
177
+ const runtimeConfig = resolveRuntimeConfig(config);
178
+ const openAiClient = client || createOpenAiClient(null);
179
+ return {
180
+ client: openAiClient,
181
+ config: runtimeConfig,
182
+ async sendCUARequest(args) {
183
+ // Bind model and request-shaping config to the active run so execution
184
+ // stays deterministic even if settings change after the run starts.
185
+ return await sendCUARequest({
186
+ client: openAiClient,
187
+ cuaModel: runtimeConfig.cuaModel,
188
+ contextOptimizationEnabled: runtimeConfig.contextOptimizationEnabled,
189
+ contextOptimizationThreshold: runtimeConfig.contextOptimizationThreshold,
190
+ ...args,
191
+ });
192
+ },
193
+ async reviseTestScript(originalScript, revisionRequest) {
194
+ return await reviseTestScript({
195
+ client: openAiClient,
196
+ originalScript,
197
+ revisionRequest,
198
+ });
199
+ },
200
+ };
201
+ }