Repository: microsoft/playwright
File: packages/playwright-core/src/server/agent/pageAgent.ts (TypeScript)

Code before:

### Expectation
${expectation}
`;

  await runLoop(progress, context, expectTools, task, undefined, callParams);
  await updateCache(context, cacheKey);
}

export async function pageAgentExtract(progress: Progress, context: Context, query: string, schema: loopTypes.Schema, callParams: CallParams): Promise<any> {

  const task = `
### Instructions
Extract the following information from the page. Do not perform any actions, just extract the information.

### Query
${query}`;
  const { result } = await runLoop(progress, context, [], task, schema, callParams);
  return result;
}

async function runLoop(progress: Progress, context: Context, toolDefinitions: ToolDefinition[], userTask: string, resultSchema: loopTypes.Schema | undefined, params: CallParams): Promise<{
  result: any
}> {
  const { page } = context;
  if (!context.agentParams.api || !context.agentParams.model)
    throw new Error(`This action requires the API and API key to be set on the page agent. Did you mean to --run-agents=missing?`);
  if (!context.agentParams.apiKey)
    throw new Error(`This action requires API key to be set on the page agent.`);
  if (context.agentParams.apiEndpoint && !URL.canParse(context.agentParams.apiEndpoint))
    throw new Error(`Agent API endpoint "${context.agentParams.apiEndpoint}" is not a valid URL.`);

  const { full } = await page.snapshotForAI(progress);
  const { tools, callTool, reportedResult, refusedToPerformReason } = toolsForLoop(progress, context, toolDefinitions, { resultSchema, refuseToPerform: 'allow' });
  const secrets = Object.fromEntries((context.agentParams.secrets || [])?.map(s => ([s.name, s.value])));

  const apiCacheTextBefore = context.agentParams.apiCacheFile ?
    await fs.promises.readFile(context.agentParams.apiCacheFile, 'utf-8').catch(() => '{}') : '{}';
  const apiCacheBefore = JSON.parse(apiCacheTextBefore);

  const loop = new Loop({
    api: context.agentParams.api as any,
    apiEndpoint: context.agentParams.apiEndpoint,
    apiKey: context.agentParams.apiKey,
    apiTimeout: context.agentParams.apiTimeout ?? 0,
    model: context.agentParams.model,
    maxTokens: params.maxTokens ?? context.maxTokensRemaining(),
    maxToolCalls: params.maxActions ?? context.agentParams.maxActions ?? 10,
    maxToolCallRetries: params.maxActionRetries ?? context.agentParams.maxActionRetries ?? 3,
    summarize: true,
    debug,

Review comment (suggestion):
I stumble across this error a lot and it's not very useful, because it's unclear what "This action" refers to. Unsure what a better message would be, but we should improve it.

Code after:

### Expectation
${expectation}
`;

  await runLoop(progress, context, expectTools, task, undefined, callParams);
  await updateCache(context, cacheKey);
}

export async function pageAgentExtract(progress: Progress, context: Context, query: string, schema: loopTypes.Schema, callParams: CallParams): Promise<any> {

  const task = `
### Instructions
Extract the following information from the page. Do not perform any actions, just extract the information.

### Query
${query}`;
  const { result } = await runLoop(progress, context, [], task, schema, callParams);
  return result;
}

async function runLoop(progress: Progress, context: Context, toolDefinitions: ToolDefinition[], userTask: string, resultSchema: loopTypes.Schema | undefined, params: CallParams): Promise<{
  result: any
}> {
  if (!context.agentParams.api || !context.agentParams.model)
    throw new Error(`This action requires the API and API key to be set on the page agent. Did you mean to --run-agents=missing?`);
  if (!context.agentParams.apiKey)
    throw new Error(`This action requires API key to be set on the page agent.`);
  if (context.agentParams.apiEndpoint && !URL.canParse(context.agentParams.apiEndpoint))
    throw new Error(`Agent API endpoint "${context.agentParams.apiEndpoint}" is not a valid URL.`);

  const snapshot = await context.takeSnapshot(progress);
  const { tools, callTool, reportedResult, refusedToPerformReason } = toolsForLoop(progress, context, toolDefinitions, { resultSchema, refuseToPerform: 'allow' });
  const secrets = Object.fromEntries((context.agentParams.secrets || [])?.map(s => ([s.name, s.value])));

  const apiCacheTextBefore = context.agentParams.apiCacheFile ?
    await fs.promises.readFile(context.agentParams.apiCacheFile, 'utf-8').catch(() => '{}') : '{}';
  const apiCacheBefore = JSON.parse(apiCacheTextBefore);

  const loop = new Loop({
    api: context.agentParams.api as any,
    apiEndpoint: context.agentParams.apiEndpoint,
    apiKey: context.agentParams.apiKey,
    apiTimeout: context.agentParams.apiTimeout ?? 0,
    model: context.agentParams.model,
    maxTokens: params.maxTokens ?? context.maxTokensRemaining(),
    maxToolCalls: params.maxActions ?? context.agentParams.maxActions ?? 10,
    maxToolCallRetries: params.maxActionRetries ?? context.agentParams.maxActionRetries ?? 3,
    summarize: true,
    debug,
    callTool,