feat: AK optional
This commit is contained in:
@@ -153,42 +153,6 @@ PageAgent is now ready for production use. The API is stable and breaking change
|
|||||||
- **Ask User Tool** - Agent can ask users for clarification
|
- **Ask User Tool** - Agent can ask users for clarification
|
||||||
- **i18n Support** - English and Chinese localization
|
- **i18n Support** - English and Chinese localization
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Version 1.0.0
|
|
||||||
interface PageAgentConfig {
|
|
||||||
// LLM Configuration (required)
|
|
||||||
baseURL: string
|
|
||||||
apiKey: string
|
|
||||||
model: string
|
|
||||||
temperature?: number
|
|
||||||
maxRetries?: number
|
|
||||||
customFetch?: typeof fetch
|
|
||||||
|
|
||||||
// Agent Configuration
|
|
||||||
language?: 'en-US' | 'zh-CN'
|
|
||||||
maxSteps?: number // default: 20
|
|
||||||
customTools?: Record<string, PageAgentTool> // experimental
|
|
||||||
instructions?: InstructionsConfig
|
|
||||||
transformPageContent?: (content: string) => string | Promise<string>
|
|
||||||
experimentalScriptExecutionTool?: boolean // default: false
|
|
||||||
|
|
||||||
// Lifecycle Hooks (experimental)
|
|
||||||
onBeforeTask?: (agent, result) => void
|
|
||||||
onAfterTask?: (agent, result) => void
|
|
||||||
onBeforeStep?: (agent, stepCount) => void
|
|
||||||
onAfterStep?: (agent, history) => void
|
|
||||||
onDispose?: (agent, reason?) => void
|
|
||||||
|
|
||||||
// Page Controller Configuration
|
|
||||||
enableMask?: boolean // default: true
|
|
||||||
viewportExpansion?: number
|
|
||||||
interactiveBlacklist?: Element[]
|
|
||||||
interactiveWhitelist?: Element[]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Packages
|
### Packages
|
||||||
|
|
||||||
| Package | Description |
|
| Package | Description |
|
||||||
|
|||||||
@@ -115,8 +115,8 @@ import type {
|
|||||||
|
|
||||||
export interface ExecuteConfig {
|
export interface ExecuteConfig {
|
||||||
baseURL: string
|
baseURL: string
|
||||||
apiKey: string
|
|
||||||
model: string
|
model: string
|
||||||
|
apiKey?: string
|
||||||
|
|
||||||
// Include the initial tab where page JS starts. Default: true.
|
// Include the initial tab where page JS starts. Default: true.
|
||||||
includeInitialTab?: boolean
|
includeInitialTab?: boolean
|
||||||
@@ -205,8 +205,8 @@ import type {
|
|||||||
|
|
||||||
interface ExecuteConfig {
|
interface ExecuteConfig {
|
||||||
baseURL: string
|
baseURL: string
|
||||||
apiKey: string
|
|
||||||
model: string
|
model: string
|
||||||
|
apiKey?: string
|
||||||
includeInitialTab?: boolean
|
includeInitialTab?: boolean
|
||||||
onStatusChange?: (status: AgentStatus) => void
|
onStatusChange?: (status: AgentStatus) => void
|
||||||
onActivity?: (activity: AgentActivity) => void
|
onActivity?: (activity: AgentActivity) => void
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import type { LLMConfig } from '@page-agent/llms'
|
|||||||
// Demo LLM for testing
|
// Demo LLM for testing
|
||||||
export const DEMO_MODEL = 'qwen3.5-plus'
|
export const DEMO_MODEL = 'qwen3.5-plus'
|
||||||
export const DEMO_BASE_URL = 'https://page-ag-testing-ohftxirgbn.cn-shanghai.fcapp.run'
|
export const DEMO_BASE_URL = 'https://page-ag-testing-ohftxirgbn.cn-shanghai.fcapp.run'
|
||||||
export const DEMO_API_KEY = 'NA'
|
// export const DEMO_API_KEY = 'NA'
|
||||||
|
|
||||||
export const DEMO_CONFIG: LLMConfig = {
|
export const DEMO_CONFIG: LLMConfig = {
|
||||||
apiKey: DEMO_API_KEY,
|
|
||||||
baseURL: DEMO_BASE_URL,
|
baseURL: DEMO_BASE_URL,
|
||||||
model: DEMO_MODEL,
|
model: DEMO_MODEL,
|
||||||
|
// apiKey: DEMO_API_KEY,
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Legacy testing endpoints that should be auto-migrated to DEMO_BASE_URL */
|
/** Legacy testing endpoints that should be auto-migrated to DEMO_BASE_URL */
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { siGithub } from 'simple-icons'
|
import { siGithub } from 'simple-icons'
|
||||||
|
|
||||||
import { DEMO_API_KEY, DEMO_BASE_URL, DEMO_MODEL, isTestingEndpoint } from '@/agent/constants'
|
import { DEMO_BASE_URL, DEMO_MODEL, isTestingEndpoint } from '@/agent/constants'
|
||||||
import type { ExtConfig, LanguagePreference } from '@/agent/useAgent'
|
import type { ExtConfig, LanguagePreference } from '@/agent/useAgent'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
@@ -27,9 +27,9 @@ interface ConfigPanelProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
||||||
const [apiKey, setApiKey] = useState(config?.apiKey || DEMO_API_KEY)
|
|
||||||
const [baseURL, setBaseURL] = useState(config?.baseURL || DEMO_BASE_URL)
|
const [baseURL, setBaseURL] = useState(config?.baseURL || DEMO_BASE_URL)
|
||||||
const [model, setModel] = useState(config?.model || DEMO_MODEL)
|
const [model, setModel] = useState(config?.model || DEMO_MODEL)
|
||||||
|
const [apiKey, setApiKey] = useState(config?.apiKey)
|
||||||
const [language, setLanguage] = useState<LanguagePreference>(config?.language)
|
const [language, setLanguage] = useState<LanguagePreference>(config?.language)
|
||||||
const [maxSteps, setMaxSteps] = useState<number | undefined>(config?.maxSteps)
|
const [maxSteps, setMaxSteps] = useState<number | undefined>(config?.maxSteps)
|
||||||
const [systemInstruction, setSystemInstruction] = useState(config?.systemInstruction ?? '')
|
const [systemInstruction, setSystemInstruction] = useState(config?.systemInstruction ?? '')
|
||||||
@@ -44,9 +44,9 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
|||||||
const [showApiKey, setShowApiKey] = useState(false)
|
const [showApiKey, setShowApiKey] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setApiKey(config?.apiKey || DEMO_API_KEY)
|
|
||||||
setBaseURL(config?.baseURL || DEMO_BASE_URL)
|
setBaseURL(config?.baseURL || DEMO_BASE_URL)
|
||||||
setModel(config?.model || DEMO_MODEL)
|
setModel(config?.model || DEMO_MODEL)
|
||||||
|
setApiKey(config?.apiKey)
|
||||||
setLanguage(config?.language)
|
setLanguage(config?.language)
|
||||||
setMaxSteps(config?.maxSteps)
|
setMaxSteps(config?.maxSteps)
|
||||||
setSystemInstruction(config?.systemInstruction ?? '')
|
setSystemInstruction(config?.systemInstruction ?? '')
|
||||||
@@ -194,7 +194,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
|||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<label className="text-xs text-muted-foreground">Model</label>
|
<label className="text-xs text-muted-foreground">Model</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="gpt-5.2"
|
placeholder="gpt-5.1"
|
||||||
value={model}
|
value={model}
|
||||||
onChange={(e) => setModel(e.target.value)}
|
onChange={(e) => setModel(e.target.value)}
|
||||||
className="text-xs h-8"
|
className="text-xs h-8"
|
||||||
@@ -206,7 +206,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
|||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Input
|
<Input
|
||||||
type={showApiKey ? 'text' : 'password'}
|
type={showApiKey ? 'text' : 'password'}
|
||||||
placeholder="sk-..."
|
// placeholder="sk-..."
|
||||||
value={apiKey}
|
value={apiKey}
|
||||||
onChange={(e) => setApiKey(e.target.value)}
|
onChange={(e) => setApiKey(e.target.value)}
|
||||||
className="text-xs h-8"
|
className="text-xs h-8"
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ export type Execute = (task: string, config: ExecuteConfig) => Promise<Execution
|
|||||||
|
|
||||||
export interface ExecuteConfig {
|
export interface ExecuteConfig {
|
||||||
baseURL: string
|
baseURL: string
|
||||||
apiKey: string
|
|
||||||
model: string
|
model: string
|
||||||
|
apiKey?: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to include the initial tab (that holds this main world script) in the task.
|
* Whether to include the initial tab (that holds this main world script) in the task.
|
||||||
@@ -30,7 +30,6 @@ export default defineUnlistedScript(() => {
|
|||||||
if (task.trim().length === 0) throw new Error('Task cannot be empty')
|
if (task.trim().length === 0) throw new Error('Task cannot be empty')
|
||||||
if (!config) throw new Error('Config is required')
|
if (!config) throw new Error('Config is required')
|
||||||
if (!config.baseURL) throw new Error('Config must have a baseURL')
|
if (!config.baseURL) throw new Error('Config must have a baseURL')
|
||||||
if (!config.apiKey) throw new Error('Config must have an apiKey')
|
|
||||||
if (!config.model) throw new Error('Config must have a model')
|
if (!config.model) throw new Error('Config must have a model')
|
||||||
|
|
||||||
const id = getId()
|
const id = getId()
|
||||||
@@ -85,8 +84,8 @@ export default defineUnlistedScript(() => {
|
|||||||
task,
|
task,
|
||||||
config: {
|
config: {
|
||||||
baseURL: config.baseURL,
|
baseURL: config.baseURL,
|
||||||
apiKey: config.apiKey,
|
|
||||||
model: config.model,
|
model: config.model,
|
||||||
|
apiKey: config.apiKey,
|
||||||
includeInitialTab: config.includeInitialTab,
|
includeInitialTab: config.includeInitialTab,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export class OpenAIClient implements LLMClient {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: `Bearer ${this.config.apiKey}`,
|
...(this.config.apiKey && { Authorization: `Bearer ${this.config.apiKey}` }),
|
||||||
},
|
},
|
||||||
body: JSON.stringify(requestBody),
|
body: JSON.stringify(requestBody),
|
||||||
signal: abortSignal,
|
signal: abortSignal,
|
||||||
|
|||||||
@@ -8,17 +8,17 @@ export type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool }
|
|||||||
|
|
||||||
export function parseLLMConfig(config: LLMConfig): Required<LLMConfig> {
|
export function parseLLMConfig(config: LLMConfig): Required<LLMConfig> {
|
||||||
// Runtime validation as defensive programming (types already guarantee these)
|
// Runtime validation as defensive programming (types already guarantee these)
|
||||||
if (!config.baseURL || !config.apiKey || !config.model) {
|
if (!config.baseURL || !config.model) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'[PageAgent] LLM configuration required. Please provide: baseURL, apiKey, model. ' +
|
'[PageAgent] LLM configuration required. Please provide: baseURL, model. ' +
|
||||||
'See: https://alibaba.github.io/page-agent/docs/features/models'
|
'See: https://alibaba.github.io/page-agent/docs/features/models'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
baseURL: config.baseURL,
|
baseURL: config.baseURL,
|
||||||
apiKey: config.apiKey,
|
|
||||||
model: config.model,
|
model: config.model,
|
||||||
|
apiKey: config.apiKey || '',
|
||||||
temperature: config.temperature ?? DEFAULT_TEMPERATURE,
|
temperature: config.temperature ?? DEFAULT_TEMPERATURE,
|
||||||
maxRetries: config.maxRetries ?? LLM_MAX_RETRIES,
|
maxRetries: config.maxRetries ?? LLM_MAX_RETRIES,
|
||||||
customFetch: (config.customFetch ?? fetch).bind(globalThis), // fetch will be illegal unless bound
|
customFetch: (config.customFetch ?? fetch).bind(globalThis), // fetch will be illegal unless bound
|
||||||
|
|||||||
@@ -89,8 +89,8 @@ export interface InvokeResult<TResult = unknown> {
|
|||||||
*/
|
*/
|
||||||
export interface LLMConfig {
|
export interface LLMConfig {
|
||||||
baseURL: string
|
baseURL: string
|
||||||
apiKey: string
|
|
||||||
model: string
|
model: string
|
||||||
|
apiKey?: string
|
||||||
|
|
||||||
temperature?: number
|
temperature?: number
|
||||||
maxRetries?: number
|
maxRetries?: number
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ const port = parseInt(env.PORT || '38401')
|
|||||||
|
|
||||||
/** @type {Record<string, string>} */
|
/** @type {Record<string, string>} */
|
||||||
const llmConfig = {}
|
const llmConfig = {}
|
||||||
if (env.LLM_API_KEY) llmConfig.apiKey = env.LLM_API_KEY
|
|
||||||
if (env.LLM_BASE_URL) llmConfig.baseURL = env.LLM_BASE_URL
|
if (env.LLM_BASE_URL) llmConfig.baseURL = env.LLM_BASE_URL
|
||||||
if (env.LLM_MODEL_NAME) llmConfig.model = env.LLM_MODEL_NAME
|
if (env.LLM_MODEL_NAME) llmConfig.model = env.LLM_MODEL_NAME
|
||||||
|
if (env.LLM_API_KEY) llmConfig.apiKey = env.LLM_API_KEY
|
||||||
|
|
||||||
// --- Hub bridge (HTTP + WebSocket) ---
|
// --- Hub bridge (HTTP + WebSocket) ---
|
||||||
|
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ export const CDN_DEMO_CN_URL =
|
|||||||
// Demo LLM for website testing (homepage quick trial uses flash)
|
// Demo LLM for website testing (homepage quick trial uses flash)
|
||||||
export const DEMO_MODEL = 'qwen3.5-flash'
|
export const DEMO_MODEL = 'qwen3.5-flash'
|
||||||
export const DEMO_BASE_URL = 'https://page-ag-testing-ohftxirgbn.cn-shanghai.fcapp.run'
|
export const DEMO_BASE_URL = 'https://page-ag-testing-ohftxirgbn.cn-shanghai.fcapp.run'
|
||||||
export const DEMO_API_KEY = 'NA'
|
// export const DEMO_API_KEY = ''
|
||||||
|
|||||||
@@ -129,12 +129,6 @@ const result = await agent.execute('Fill in the form with test data')`}
|
|||||||
? 'LLM API 的基础 URL(如 https://api.openai.com/v1)'
|
? 'LLM API 的基础 URL(如 https://api.openai.com/v1)'
|
||||||
: 'Base URL of the LLM API (e.g., https://api.openai.com/v1)',
|
: 'Base URL of the LLM API (e.g., https://api.openai.com/v1)',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'apiKey',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
description: isZh ? 'API 密钥' : 'API key for authentication',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'model',
|
name: 'model',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@@ -143,6 +137,12 @@ const result = await agent.execute('Fill in the form with test data')`}
|
|||||||
? '模型名称(如 gpt-5.2, anthropic/claude-4.5-haiku)'
|
? '模型名称(如 gpt-5.2, anthropic/claude-4.5-haiku)'
|
||||||
: 'Model name (e.g., gpt-5.2, anthropic/claude-4.5-haiku)',
|
: 'Model name (e.g., gpt-5.2, anthropic/claude-4.5-haiku)',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'apiKey',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description: 'LLM AK',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
|||||||
@@ -187,39 +187,7 @@ localStorage.setItem('PageAgentExtUserAuthToken', '<your-token-from-extension>')
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
code={
|
code={`import type {
|
||||||
isZh
|
|
||||||
? `import type {
|
|
||||||
AgentActivity,
|
|
||||||
AgentStatus,
|
|
||||||
ExecutionResult,
|
|
||||||
HistoricalEvent
|
|
||||||
} from '@page-agent/core'
|
|
||||||
|
|
||||||
interface ExecuteConfig {
|
|
||||||
baseURL: string // LLM API 端点
|
|
||||||
apiKey: string // API 密钥
|
|
||||||
model: string // 模型名称
|
|
||||||
|
|
||||||
includeInitialTab?: boolean
|
|
||||||
onStatusChange?: (status: AgentStatus) => void
|
|
||||||
onActivity?: (activity: AgentActivity) => void
|
|
||||||
onHistoryUpdate?: (history: HistoricalEvent[]) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
type Execute = (task: string, config: ExecuteConfig) => Promise<ExecutionResult>
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
PAGE_AGENT_EXT_VERSION?: string
|
|
||||||
PAGE_AGENT_EXT?: {
|
|
||||||
version: string
|
|
||||||
execute: Execute
|
|
||||||
stop: () => void
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
: `import type {
|
|
||||||
AgentActivity,
|
AgentActivity,
|
||||||
AgentStatus,
|
AgentStatus,
|
||||||
ExecutionResult,
|
ExecutionResult,
|
||||||
@@ -228,8 +196,8 @@ declare global {
|
|||||||
|
|
||||||
interface ExecuteConfig {
|
interface ExecuteConfig {
|
||||||
baseURL: string // LLM API endpoint
|
baseURL: string // LLM API endpoint
|
||||||
apiKey: string // API key
|
|
||||||
model: string // Model name
|
model: string // Model name
|
||||||
|
apiKey?: string // LLM AK
|
||||||
|
|
||||||
includeInitialTab?: boolean
|
includeInitialTab?: boolean
|
||||||
onStatusChange?: (status: AgentStatus) => void
|
onStatusChange?: (status: AgentStatus) => void
|
||||||
@@ -248,8 +216,7 @@ declare global {
|
|||||||
stop: () => void
|
stop: () => void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`}
|
||||||
}
|
|
||||||
language="typescript"
|
language="typescript"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -129,10 +129,9 @@ const pageAgent = new PageAgent({
|
|||||||
model: 'MiniMax-M2.7'
|
model: 'MiniMax-M2.7'
|
||||||
});
|
});
|
||||||
|
|
||||||
// Self-hosted models (e.g., Ollama)
|
// Self-hosted models (e.g., Ollama) — no apiKey needed
|
||||||
const pageAgent = new PageAgent({
|
const pageAgent = new PageAgent({
|
||||||
baseURL: 'http://localhost:11434/v1',
|
baseURL: 'http://localhost:11434/v1',
|
||||||
apiKey: 'NA',
|
|
||||||
model: 'qwen3:14b'
|
model: 'qwen3:14b'
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -280,7 +279,6 @@ LLM_MODEL_NAME="qwen3:14b"`}
|
|||||||
<CodeEditor
|
<CodeEditor
|
||||||
code={`const agent = new PageAgent({
|
code={`const agent = new PageAgent({
|
||||||
baseURL: '/api/llm-proxy',
|
baseURL: '/api/llm-proxy',
|
||||||
apiKey: 'NA',
|
|
||||||
model: 'gpt-5.1',
|
model: 'gpt-5.1',
|
||||||
customFetch: (url, init) =>
|
customFetch: (url, init) =>
|
||||||
fetch(url, { ...init, credentials: 'include' }),
|
fetch(url, { ...init, credentials: 'include' }),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { Particles } from '../../components/ui/particles'
|
|||||||
import {
|
import {
|
||||||
CDN_DEMO_CN_URL,
|
CDN_DEMO_CN_URL,
|
||||||
CDN_DEMO_URL,
|
CDN_DEMO_URL,
|
||||||
DEMO_API_KEY,
|
// DEMO_API_KEY,
|
||||||
DEMO_BASE_URL,
|
DEMO_BASE_URL,
|
||||||
DEMO_MODEL,
|
DEMO_MODEL,
|
||||||
} from '../../constants'
|
} from '../../constants'
|
||||||
@@ -94,7 +94,7 @@ export default function HeroSection() {
|
|||||||
apiKey:
|
apiKey:
|
||||||
import.meta.env.DEV && import.meta.env.LLM_API_KEY
|
import.meta.env.DEV && import.meta.env.LLM_API_KEY
|
||||||
? import.meta.env.LLM_API_KEY
|
? import.meta.env.LLM_API_KEY
|
||||||
: DEMO_API_KEY,
|
: undefined,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user