page-agent-sdk 2.5.0 → 2.5.1
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/README.md +13 -13
- package/README.zh-CN.md +8 -8
- package/dist/page-agent-sdk.iife.js +54 -54
- package/dist/page-agent-sdk.js +35 -25
- package/dist/page-agent-sdk.umd.cjs +6 -6
- package/package.json +1 -1
- package/skills/page-agent-sdk-integrate/SKILL.md +39 -30
- package/skills/page-agent-sdk-integrate/references/advanced.md +37 -41
- package/skills/page-agent-sdk-integrate/references/api.md +38 -40
- package/skills/page-agent-sdk-integrate/references/options.md +17 -14
- package/skills/page-agent-sdk-integrate/references/quickstart.md +43 -43
- package/skills/page-agent-sdk-integrate/references/use-cases.md +66 -62
- package/types/index.d.ts +7 -1
|
@@ -1,77 +1,73 @@
|
|
|
1
|
-
# Advanced examples — custom tools, skills, subagents, MCP, dynamic
|
|
1
|
+
# Advanced examples — custom tools, skills, subagents, MCP, dynamic schema
|
|
2
2
|
|
|
3
3
|
Detailed, copy-paste examples for the extensibility surfaces. Read the section matching the user's need.
|
|
4
4
|
|
|
5
|
-
## 0. Dynamic
|
|
5
|
+
## 0. Dynamic schema (lazy-loaded / runtime swap) — `sdk.setData` / `getData`
|
|
6
6
|
|
|
7
|
-
When
|
|
7
|
+
When the page schema changes dynamically (lazy-loaded components with different structures), swap the whole main data config at runtime — tools pick up the new bind/schema immediately, no agent rebuild.
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
10
|
const sdk = createChatSdk({
|
|
11
11
|
container: '#chat', llm: { ... },
|
|
12
|
-
systemPrompt: '
|
|
13
|
-
|
|
14
|
-
// statically-declared ones (always present)
|
|
15
|
-
{ path: 'app.config', description: '全局配置', schema: z.record(z.any()) },
|
|
16
|
-
],
|
|
12
|
+
systemPrompt: '你是 JSON 操作助手,按当前 schema 操作主数据。',
|
|
13
|
+
data: { schema: initialSchema, bind: initialObj, description: '初始数据' },
|
|
17
14
|
}).mount()
|
|
18
15
|
|
|
19
|
-
//
|
|
20
|
-
function
|
|
21
|
-
sdk.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
sdk.removeDataSlot(`app.components.${id}`)
|
|
16
|
+
// later: swap to a different schema + bind (lazy-loaded / dynamic)
|
|
17
|
+
function onSchemaChange(newConfig: { schema: z.ZodType; bind: any; description?: string }) {
|
|
18
|
+
sdk.setData({
|
|
19
|
+
schema: newConfig.schema, // new zod schema (validation + field hints auto-injected)
|
|
20
|
+
bind: newConfig.bind, // new reactive/plain object
|
|
21
|
+
description: newConfig.description,
|
|
22
|
+
})
|
|
23
|
+
// 立即生效:AI 现在能 write newConfig.bind,按其 schema 校验
|
|
28
24
|
}
|
|
29
25
|
|
|
30
|
-
//
|
|
31
|
-
const current:
|
|
26
|
+
// 查看当前配置(反映运行时 swap)
|
|
27
|
+
const current: DataConfig | undefined = sdk.getData()
|
|
32
28
|
```
|
|
33
29
|
|
|
34
30
|
Notes:
|
|
35
|
-
- `
|
|
36
|
-
-
|
|
37
|
-
- `
|
|
38
|
-
- `
|
|
31
|
+
- `setData` replaces the whole config; old snapshots cleared & optimistic-lock hash reset.
|
|
32
|
+
- `inspect().data` 与 `verify`(默认 `createWriteBackCheck`)均反映 `setData` 后的最新 schema/bind(verify 每次 check 实时取 `getData()`)。
|
|
33
|
+
- `capabilities.dataOps:false` 时 `setData`/`getData` 仍可调用(仅替换配置,工具不暴露);工具调用为 no-op。
|
|
34
|
+
- `summarization` 压缩时自动注入当前 `getData()` 的 description 进摘要 system 消息,LLM 不会基于过时记忆操作旧 schema。
|
|
39
35
|
|
|
40
|
-
**完整可运行示例**:`examples/dynamic-demo/`(dev 启动后访问 `/examples/dynamic-demo/`)——
|
|
36
|
+
**完整可运行示例**:`examples/dynamic-demo/`(dev 启动后访问 `/examples/dynamic-demo/`)—— 演示运行时 `setData` 切换不同 schema 的组件数据,AI 立即可按新 schema 操作。
|
|
41
37
|
|
|
42
38
|
### 动态场景下「压缩后不丢信息」的保障(内置,无需额外配置)
|
|
43
39
|
|
|
44
|
-
|
|
40
|
+
schema 随时 swap,长会话压缩后 LLM 可能基于过时记忆操作旧 schema。SDK 内置两道保障:
|
|
45
41
|
|
|
46
|
-
- **A.
|
|
47
|
-
- **C. preserveLastToolResults**:`contextOptions.preserveLastToolResults`(默认 `['
|
|
42
|
+
- **A. 压缩时注入数据描述**:`summarization` 中间件压缩 older 轮次时,自动把当前 `getData()` 的 `description` 作为一段附进摘要 system 消息(不进压缩)。LLM 即便忘了历史 `describe`,每轮仍看得到「当前主数据是什么」,不会操作过时 schema。`dataOps` 关闭时返回空,无影响。
|
|
43
|
+
- **C. preserveLastToolResults**:`contextOptions.preserveLastToolResults`(默认 `['describe_data','read']`)指定这些工具的步骤 `result` 在跨轮摘要时额外保留摘要片段进 summaryMsg。即便 older 轮被摘要,关键字段说明仍在摘要里,LLM 不必反复 `describe`。设为 `[]` 关闭。
|
|
48
44
|
|
|
49
45
|
```ts
|
|
50
46
|
// 默认即开启 A + C;如需关闭或自定义:
|
|
51
47
|
createChatSdk({
|
|
52
48
|
contextOptions: {
|
|
53
49
|
preserveLastToolResults: [], // 关闭 C(不保留工具结果摘要)
|
|
54
|
-
//
|
|
50
|
+
// getRegisteredData 由 SDK 内部注入(来自 sdk.getData),无需手动传
|
|
55
51
|
},
|
|
56
52
|
// ...
|
|
57
53
|
})
|
|
58
54
|
```
|
|
59
55
|
|
|
60
|
-
- **B.
|
|
56
|
+
- **B. 写操作返回附当前可操作字段列表**:`set`/`edit`/`delete` 成功返回末尾自动附 `(当前可操作字段: a, b, c)`,LLM 写完即知全貌,减少 `describe` 调用。超过 8 项或过长时只报数量,避免提示过长。
|
|
61
57
|
- **D. `systemPromptHelpers.reliableWriteRules`**:导出的标准化「可靠写入规则」片段,建议拼进 `systemPrompt`:
|
|
62
58
|
|
|
63
59
|
```ts
|
|
64
60
|
import { systemPromptHelpers } from 'page-agent-sdk'
|
|
65
61
|
createChatSdk({
|
|
66
|
-
systemPrompt:
|
|
62
|
+
systemPrompt: `你是 JSON 操作助手。\n${systemPromptHelpers.reliableWriteRules}`,
|
|
67
63
|
// ...
|
|
68
64
|
})
|
|
69
65
|
```
|
|
70
|
-
内容:改前先 `
|
|
66
|
+
内容:改前先 `read` 读真实值、字段以 `describe` 为准、写错看校验错误重试、优先 `edit` 增量 patch。避免集成方忘了写这些元规则导致 LLM 凭记忆瞎改。
|
|
71
67
|
|
|
72
68
|
## 1. Custom tools (`defineTool`)
|
|
73
69
|
|
|
74
|
-
Custom tools extend the agent beyond built-in `
|
|
70
|
+
Custom tools extend the agent beyond built-in `dataOps`/`fetch`. Use them to expose your product's API to the AI.
|
|
75
71
|
|
|
76
72
|
### Minimal
|
|
77
73
|
|
|
@@ -99,31 +95,31 @@ const updatePrice = defineTool({
|
|
|
99
95
|
schema: z.object({ sku: z.string(), price: z.number().positive() }),
|
|
100
96
|
handler: async ({ sku, price }) => {
|
|
101
97
|
const ok = await api.setPrice(sku, price)
|
|
102
|
-
if (!ok) return toolError({
|
|
98
|
+
if (!ok) return toolError({ code: 'NOT_FOUND', message: `SKU ${sku} 不存在` })
|
|
103
99
|
return `已更新 ${sku} 价格为 ${price}`
|
|
104
100
|
},
|
|
105
101
|
})
|
|
106
102
|
```
|
|
107
103
|
|
|
108
|
-
### Coexisting with
|
|
104
|
+
### Coexisting with dataOps
|
|
109
105
|
|
|
110
|
-
Mix custom tools with built-in data
|
|
106
|
+
Mix custom tools with built-in data tools:
|
|
111
107
|
|
|
112
108
|
```ts
|
|
113
109
|
createChatSdk({
|
|
114
110
|
container: '#chat', llm: { ... },
|
|
115
|
-
|
|
116
|
-
tools: [lookupOrder, updatePrice], // custom + built-in
|
|
111
|
+
data: { schema: z.object({ config: z.record(z.any()) }), bind: { config: {} } },
|
|
112
|
+
tools: [lookupOrder, updatePrice], // custom + built-in dataOps together
|
|
117
113
|
}).mount()
|
|
118
114
|
```
|
|
119
115
|
|
|
120
|
-
### Pure custom-tool agent (no
|
|
116
|
+
### Pure custom-tool agent (no dataOps)
|
|
121
117
|
|
|
122
118
|
```ts
|
|
123
119
|
createChatSdk({
|
|
124
120
|
container: '#chat', llm: { ... },
|
|
125
121
|
tools: [lookupOrder, updatePrice],
|
|
126
|
-
capabilities: {
|
|
122
|
+
capabilities: { dataOps: false, fetch: false }, // drop built-ins
|
|
127
123
|
}).mount()
|
|
128
124
|
```
|
|
129
125
|
|
|
@@ -205,7 +201,7 @@ createChatSdk({
|
|
|
205
201
|
{
|
|
206
202
|
id: 'reviewer',
|
|
207
203
|
description: '审查专家:检查代码/配置的安全与性能问题',
|
|
208
|
-
tools: ['read', '
|
|
204
|
+
tools: ['read', 'search_data'],
|
|
209
205
|
systemPrompt: '你是审查专家,只报告问题不改数据。',
|
|
210
206
|
temperature: 0.1,
|
|
211
207
|
},
|
|
@@ -270,7 +266,7 @@ createChatSdk({
|
|
|
270
266
|
container: '#chat',
|
|
271
267
|
llm: { apiKey, baseUrl, model },
|
|
272
268
|
systemPrompt: '...',
|
|
273
|
-
|
|
269
|
+
data: { schema: z.object({ data: z.record(z.any()) }), bind: { data: {} } },
|
|
274
270
|
tools: [lookupOrder, updatePrice], // custom tools
|
|
275
271
|
skills: [apiDesignSkill, brandSkill], // progressive skills
|
|
276
272
|
subagents: [{ id: 'researcher', description: '...', tools: ['fetch_document'] }], // pre-declared
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# API reference — instance methods, tool/skill definition, data
|
|
1
|
+
# API reference — instance methods, tool/skill definition, data tools, events
|
|
2
2
|
|
|
3
3
|
## ChatSdk instance (`createChatSdk(...)` return)
|
|
4
4
|
|
|
@@ -9,12 +9,11 @@
|
|
|
9
9
|
| `messages` | `AgentMessage[]` (reactive) | The conversation. Headless reads this to render. UI shares the same array (single source). |
|
|
10
10
|
| `send(message)` | `(msg: string) => Promise<string>` | Send a user message (invoke mode, no stream events). Returns final content. |
|
|
11
11
|
| `stream` | `(messages, onEvent, signal?) => Promise<string>` | Low-level stream. UI uses this internally; headless can call directly for streaming. |
|
|
12
|
-
| `inspect()` | `() => AgentInfo` | Inspect agent: tools/skills/
|
|
12
|
+
| `inspect()` | `() => AgentInfo` | Inspect agent: tools/skills/data/middleware/todos/mcp.servers (each tool's `source`: `builtin`/`mcp:<name>`/`user`). DebugDrawer uses this. |
|
|
13
13
|
| `switchSession(id?)` | `(id?: string) => Promise<string>` | Switch session context (load or create by id). Requires `storage` enabled. |
|
|
14
14
|
| `hook(handler)` | `(h: SdkEventHandler) => () => void` | Runtime event subscription (multi-listener, returns unsubscribe). Complements `onEvent`. |
|
|
15
|
-
| `
|
|
16
|
-
| `
|
|
17
|
-
| `listDataSlots()` | `() => DataSlotSpec[]` | List currently-registered data slots (reflects dynamic add/remove). |
|
|
15
|
+
| `setData(config)` | `(config: DataConfig) => void` | Runtime swap the main data config (`{ schema, bind, description? }`). Tools pick up new bind/schema immediately, no rebuild. Clears snapshots & resets optimistic-lock hash. |
|
|
16
|
+
| `getData()` | `() => DataConfig \| undefined` | Read current main data config (reflects runtime `setData`). `undefined` if `dataOps` disabled. |
|
|
18
17
|
| `restoreLastCheckpoint()` | `() => boolean` | Restore last good checkpoint (needs `checkpoint` enabled). |
|
|
19
18
|
| `listCheckpoints()` | `() => CheckpointMeta[]` | List available checkpoints. |
|
|
20
19
|
|
|
@@ -33,7 +32,7 @@ const addTool = defineTool({
|
|
|
33
32
|
createChatSdk({ tools: [addTool], /* llm, ... */ }).mount()
|
|
34
33
|
```
|
|
35
34
|
|
|
36
|
-
`handler` receives validated args; return a string (or structured result stringified). Errors via `toolError({
|
|
35
|
+
`handler` receives validated args; return a string (or structured result stringified). Errors via `toolError({ code, message })`.
|
|
37
36
|
|
|
38
37
|
## defineSkill (progressive disclosure)
|
|
39
38
|
|
|
@@ -68,44 +67,42 @@ Spread into options for common scenarios.
|
|
|
68
67
|
import { createChatSdk, systemPromptHelpers } from 'page-agent-sdk'
|
|
69
68
|
|
|
70
69
|
createChatSdk({
|
|
71
|
-
systemPrompt:
|
|
70
|
+
systemPrompt: `你是 JSON 操作助手。\n${systemPromptHelpers.reliableWriteRules}`,
|
|
72
71
|
llm, container,
|
|
73
72
|
}).mount()
|
|
74
73
|
```
|
|
75
74
|
|
|
76
|
-
`reliableWriteRules` — standardized "reliable write rules": read before write (`read`),
|
|
75
|
+
`reliableWriteRules` — standardized "reliable write rules": read before write (`read`), fields per `read({jsonPath})` (returns format hint), retry on schema-validation errors, prefer `write` with `patch` incremental edits. Recommended for any scenario involving data writes.
|
|
77
76
|
|
|
78
|
-
## Built-in data
|
|
77
|
+
## Built-in data tools (auto-injected when `capabilities.dataOps`)
|
|
79
78
|
|
|
80
|
-
Default `toolMode:'simple'` exposes high-level `read`/`write` + advanced query/snapshot tools (low-level `get`/`set`/`edit`/`delete`/`
|
|
79
|
+
Default `toolMode:'simple'` exposes high-level `read`/`write` + advanced query/snapshot tools (low-level `get`/`set`/`edit`/`delete`/`describe` are hidden, merged into `read`/`write`). `toolMode:'advanced'` exposes all; `toolMode:'minimal'` only `read`/`write`.
|
|
81
80
|
|
|
82
81
|
| Tool | Purpose | Mode |
|
|
83
82
|
|---|---|---|
|
|
84
|
-
| **`read`** / **`write`** (2.2+, recommended) | High-level entry: `read({
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `delete_data_slot` | Delete a path | advanced |
|
|
92
|
-
| `snapshot_data_slot` | Manual snapshot | simple/advanced |
|
|
83
|
+
| **`read`** / **`write`** (2.2+, recommended) | High-level entry: `read({jsonPath?, fields?, depth?})` lists/reads (supports field projection + depth truncation); `write({value?, patch?, patches?, del?})` merges set/edit/delete + auto optimistic lock + auto snapshot | simple/minimal |
|
|
84
|
+
| `describe_data` | Show main data description + schema field descriptions | advanced |
|
|
85
|
+
| `get_data` | Read main data (supports `jsonPath` precise sub-path read) | advanced |
|
|
86
|
+
| `set_data` | Write whole main data (schema-validated, scoped to declared fields) | advanced |
|
|
87
|
+
| `edit_data` | Patch by `jsonPath` (set/remove/merge/append) — avoids re-sending large JSON | advanced |
|
|
88
|
+
| `delete_data` | Delete a sub-path (jsonPath) | advanced |
|
|
89
|
+
| `snapshot_data` | Manual snapshot | simple/advanced |
|
|
93
90
|
| `list_data_snapshots` | List snapshots | simple/advanced |
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `eval_script` | Sandboxed script on data (
|
|
91
|
+
| `restore_data` | Restore (no id = most recent) | simple/advanced |
|
|
92
|
+
| `query_data` / `search_data` | JSONPath query / full-text search | simple/advanced |
|
|
93
|
+
| `eval_script` | Sandboxed script on data (query/transform; transform supports `{patches:[...]}` incremental mode) | simple/advanced |
|
|
97
94
|
|
|
98
|
-
**Key rule**: `write`/`set`/`edit`/`delete` only affect **declared**
|
|
95
|
+
**Key rule**: `write`/`set`/`edit`/`delete` only affect **schema-declared** top-level fields (ZodObject auto-whitelist; undeclared fields hidden/denied). Invalid schema → structured error, no write. `write`/`edit` writes in-place (preserves Vue reactive refs). `write` auto-tracks hash from `read` for optimistic lock (no manual `expectedHash` needed). Whole-set / `set_data` / `eval` transform become **merge** semantics in whitelist mode (only updates declared fields, undeclared fields preserved — prevents accidental deletion).
|
|
99
96
|
|
|
100
97
|
### write / jsonPath edit operations
|
|
101
98
|
|
|
102
|
-
`write({
|
|
99
|
+
`write({ value, patch: { op, jsonPath } })` (or `write({ patch: { jsonPath }, del: true })` to delete; or `write({ patches: [...] })` for batch atomic):
|
|
103
100
|
- `set` — set a sub-path (or whole value when no `patch`)
|
|
104
101
|
- `remove` — remove a sub-path / array element
|
|
105
102
|
- `merge` — shallow-merge an object
|
|
106
103
|
- `append` — append to an array
|
|
107
104
|
|
|
108
|
-
Example: `write({
|
|
105
|
+
Example: `write({ value: 9.9, patch: { op: 'set', jsonPath: 'items.0.price' } })` — precise local edit, no full re-send. `value` is a JSON object (recommended) or JSON string. `patches: [{op:'set', jsonPath:'a', value:1}, {op:'append', jsonPath:'items', value:newItem}]` — batch atomic (any failure → whole batch rolled back).
|
|
109
106
|
|
|
110
107
|
## Built-in fetch tools (`capabilities.fetch`)
|
|
111
108
|
|
|
@@ -122,36 +119,37 @@ Example: `write({ path: 'app.items', value: 9.9, patch: { op: 'set', jsonPath: '
|
|
|
122
119
|
| `tool_result` | `name, result, status` | Tool returns (`status`: `done`/`error`) |
|
|
123
120
|
| `subagent` | `taskId, label, kind, name, args?, result?, status?` | Subagent tool progress (forwarded to UI, NOT into main LLM context) |
|
|
124
121
|
| `done` | `content` | Agent round completes |
|
|
125
|
-
| `
|
|
122
|
+
| `data_change` | `operation, value?` | Main data was written via `write` (infers `set`/`edit`/`delete` from args) or low-level `set`/`edit`/`delete`/`restore_data` |
|
|
126
123
|
| `message_update` | `count` | The `messages` array changed |
|
|
127
124
|
| `error` | `message` | An error occurred (abort excluded) |
|
|
128
125
|
|
|
129
126
|
`approval_request` is **NOT** forwarded via `onEvent`/`hook` (UI handles it; headless integrators use a custom approval middleware listener).
|
|
130
127
|
|
|
131
|
-
## `
|
|
128
|
+
## `data` config (single main object — schema + bind + auto field-hints)
|
|
132
129
|
|
|
133
|
-
`
|
|
130
|
+
`data` is the single entry for main-data config — combining schema declaration + object direct-bind + auto field-hint injection:
|
|
134
131
|
|
|
135
132
|
```ts
|
|
136
|
-
import { reactive } from 'vue' // or any reactivity impl
|
|
137
|
-
const PageSchema = z.object({
|
|
133
|
+
import { reactive } from 'vue' // or any reactivity impl; plain object also works
|
|
134
|
+
const PageSchema = z.object({
|
|
135
|
+
title: z.string().describe('页面标题'),
|
|
136
|
+
count: z.number().describe('计数器'),
|
|
137
|
+
})
|
|
138
138
|
const page = reactive({ title: '首页', count: 0 }) // reactive recommended for UI auto-refresh
|
|
139
139
|
|
|
140
140
|
createChatSdk({
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
},
|
|
147
|
-
],
|
|
141
|
+
data: {
|
|
142
|
+
schema: PageSchema, // write validation + field .describe() auto-injected into systemPrompt「可操作数据」section + ZodObject top-level keys auto-whitelist
|
|
143
|
+
bind: page, // reactive/plain object; tools read/write directly (no window)
|
|
144
|
+
description: '页面配置', // optional; auto-generated if omitted
|
|
145
|
+
},
|
|
148
146
|
})
|
|
149
147
|
// LLM write page → page reactively updates; integrator changes page → LLM read sees it
|
|
150
148
|
```
|
|
151
149
|
|
|
152
|
-
- **`bind` is
|
|
153
|
-
-
|
|
154
|
-
- **
|
|
150
|
+
- **`bind` is required** (any object): reactive → auto-refresh on write (recommended for UI); plain object → write works but no auto-refresh (suitable for headless / backend; integrator uses `onEvent`/`hook` `data_change` to be notified). Tools mutate in-place (`restoreInPlace`), compatible with reactive proxies; plain objects also write fine.
|
|
151
|
+
- **Notifying the outside world of changes**: subscribe `data_change` via `onEvent` (constructor) or `sdk.hook` (runtime, multi-listener, cancellable) — fires after `write`/`set`/`edit`/`delete`/`restore`, with `operation`/`value`. For Vue + reactive bind, template/watch auto-react (no manual notify needed); `onEvent` can coexist for audit/analytics.
|
|
152
|
+
- **Runtime swap**: `sdk.setData({ schema, bind, description? })` replaces the whole config; tools pick up immediately (no rebuild). Snapshots & lock hash reset.
|
|
155
153
|
|
|
156
154
|
## Exported building blocks (for custom UIs)
|
|
157
155
|
|
|
@@ -7,7 +7,7 @@ Full reference for `createChatSdk(options)`. Grouped by purpose. Required: `llm`
|
|
|
7
7
|
| Option | Type | Default | Purpose / when |
|
|
8
8
|
|---|---|---|---|
|
|
9
9
|
| `llm` | `LLMConfig \| BaseChatModel` | — (required) | The model. `LLMConfig = { apiKey, baseUrl, model, temperature?, maxTokens? }` (OpenAI-compatible; DeepSeek default). Or pass any LangChain `BaseChatModel` (e.g. `ChatAnthropic`, install its peerDep). |
|
|
10
|
-
| `systemPrompt` | `string` | built-in default (
|
|
10
|
+
| `systemPrompt` | `string` | built-in default (JSON-operation assistant + reliable write rules) | Agent identity/instructions. Inject here, not hardcoded. Keep single-line in `.env` (`VITE_AI_SYSTEM_PROMPT`). If omitted, a built-in default is used (JSON-operation assistant + `systemPromptHelpers.reliableWriteRules`); passing your own fully overrides it (append `systemPromptHelpers.reliableWriteRules` yourself if needed). |
|
|
11
11
|
| `id` | `string` | random + warn | Stable agent id for multi-agent isolation & persistence. **Must pass a stable value** if you use `storage` or run multiple agents on one page. |
|
|
12
12
|
| `title` / `placeholder` | `string` | — | Dialog title / input placeholder (cosmetic). |
|
|
13
13
|
|
|
@@ -17,17 +17,20 @@ Full reference for `createChatSdk(options)`. Grouped by purpose. Required: `llm`
|
|
|
17
17
|
|---|---|---|---|
|
|
18
18
|
| `container` | `string \| HTMLElement` | — | Where the built-in dialog mounts. Required when `ui !== false`. |
|
|
19
19
|
| `ui` | `boolean \| 'default'` | `true` | `false` = headless (no built-in dialog; you build UI from `sdk.messages` + `sdk.send`). `'default'` = built-in `ChatDialog`. |
|
|
20
|
-
| `streaming` | `boolean` | `true` | Stream tokens live. `false` = wait for full reply. Headless `sdk.send` always uses invoke (no stream events, but
|
|
20
|
+
| `streaming` | `boolean` | `true` | Stream tokens live. `false` = wait for full reply. Headless `sdk.send` always uses invoke (no stream events, but data/message/error still fire). |
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## Data operation (the core)
|
|
23
23
|
|
|
24
24
|
| Option | Type | Default | Purpose / when |
|
|
25
25
|
|---|---|---|---|
|
|
26
|
-
| `
|
|
27
|
-
| `maxSnapshots` | `number` | 20 |
|
|
28
|
-
| `permissions` | `PermissionRule[]` | off | Scope whitelist (first-match-wins) for fine-grained per-
|
|
26
|
+
| `data` | `DataConfig` | — | Declare the single main data object: `{ schema, bind, description? }`. `schema` = zod (write validation + field `.describe()` auto-injected into prompt + ZodObject top-level keys auto-whitelist); `bind` = reactive/plain object (tools read/write directly, no `window`); `description` = optional data purpose. **This is the key integration step.** |
|
|
27
|
+
| `maxSnapshots` | `number` | 20 | Snapshot stack depth for `restore_data` (per-path snapshots auto-stored before set/edit/delete). |
|
|
28
|
+
| `permissions` | `PermissionRule[]` | off | Scope whitelist (first-match-wins) for fine-grained per-jsonPath/tool rules. Default off (all schema-declared fields writable). |
|
|
29
|
+
| `toolMode` | `'simple' \| 'advanced' \| 'minimal'` | `simple` | `simple` = high-level `read`/`write` + query/search/eval/snapshot (hides low-level get/set/edit/delete); `advanced` = all tools; `minimal` = only `read`/`write`. |
|
|
30
|
+
| `interceptors` | `{ read?, write?, input?, output? }` | — | `read(value)` desensitize/derive (changes only what LLM sees); `write(payload, current)` transform/audit/reject (return `{error}`); `input`/`output` for message-level interception. |
|
|
31
|
+
| `autoLock` | `boolean` | `true` | Auto optimistic lock: `write` compares LLM's last `read` hash with current; mismatch → `VERSION_CONFLICT` (or human resolution via `onConflict`). |
|
|
29
32
|
|
|
30
|
-
`
|
|
33
|
+
`DataConfig = { schema: z.ZodType; bind: any; description?: string }`. `bind` is any object — reactive (Vue auto-refresh) or plain (use `onEvent('data_change')` to re-render). Field `.describe()` text on `schema` is auto-extracted and injected into the system prompt so the LLM knows each field's purpose.
|
|
31
34
|
|
|
32
35
|
## Tools, skills, memory
|
|
33
36
|
|
|
@@ -44,8 +47,8 @@ Full reference for `createChatSdk(options)`. Grouped by purpose. Required: `llm`
|
|
|
44
47
|
|
|
45
48
|
| Flag | Off when... |
|
|
46
49
|
|---|---|
|
|
47
|
-
| `
|
|
48
|
-
| `fetch` | No web fetching needed. ⚠️ turning off `
|
|
50
|
+
| `dataOps` | Pure research agent, no data edits (also drops data tools from subagents). |
|
|
51
|
+
| `fetch` | No web fetching needed. ⚠️ turning off `dataOps` also strips subagent data tools. |
|
|
49
52
|
| `planning` | Don't want `write_todos` planning. |
|
|
50
53
|
| `skills` | Don't want progressive skill loading. |
|
|
51
54
|
| `vfs` | No in-memory workspace; ⚠️ large tool results then truncate instead of offloading. |
|
|
@@ -70,7 +73,7 @@ Full reference for `createChatSdk(options)`. Grouped by purpose. Required: `llm`
|
|
|
70
73
|
| Option | Type | Default | Purpose / when |
|
|
71
74
|
|---|---|---|---|
|
|
72
75
|
| `contextPreset` | `'auto'\|'conservative'\|'aggressive'` | `auto` | `conservative` = save cost; `aggressive` = save context. `contextOptions` fine-tunes further. |
|
|
73
|
-
| `contextOptions` | `object` | — | Detailed compression params (overrides preset). `false` disables compression. Key fields: `windowRounds`, `summaryThresholdRounds`, `contextWindow`, `summaryThresholdRatio`, `windowRatio`, `enableRecall`, `recallTopK`, `enableLLMSummary`, `preserveLastToolResults` (default `['
|
|
76
|
+
| `contextOptions` | `object` | — | Detailed compression params (overrides preset). `false` disables compression. Key fields: `windowRounds`, `summaryThresholdRounds`, `contextWindow`, `summaryThresholdRatio`, `windowRatio`, `enableRecall`, `recallTopK`, `enableLLMSummary`, `preserveLastToolResults` (default `['describe_data','read']` — keep these tools' result summaries in the compressed summary so field descriptions survive compression; set `[]` to disable). `getRegisteredData` is injected internally by the SDK (from `sdk.getData`) to embed a live data description in the summary — no need to set it manually. |
|
|
74
77
|
| `summaryLlm` | `BaseChatModel \| LLMConfig` | main `llm` | Use a cheaper/faster model for summarization. |
|
|
75
78
|
| `summaryTemperature` | `number` | 0.3 | Summary model temperature. |
|
|
76
79
|
| `summaryMaxTokens` | `number` | 1024 | Summary output cap. |
|
|
@@ -86,7 +89,7 @@ Full reference for `createChatSdk(options)`. Grouped by purpose. Required: `llm`
|
|
|
86
89
|
## Verify (self-check before return)
|
|
87
90
|
|
|
88
91
|
`capabilities.verify: true` enables. `verify: { check?, maxAttempts?, adversarial? }`:
|
|
89
|
-
- `check` omitted → default `createWriteBackCheck()` (scans all writes, reads back + schema-validates; skips legitimately-rejected writes).
|
|
92
|
+
- `check` omitted → default `createWriteBackCheck()` (scans all writes, reads back from `data.bind` + schema-validates; skips legitimately-rejected writes). Read-back root auto-bound to `data.bind` (adapts to `sdk.setData` runtime swap).
|
|
90
93
|
- custom `check: async ({ messages, state }) => ({ ok, feedback? })` — return **actionable** feedback.
|
|
91
94
|
- `adversarial: true` → after check passes, spawn a read-only "refuter" subagent (costs extra rounds; for semantically complex cases).
|
|
92
95
|
- `maxAttempts` (default 2) caps self-correction loops.
|
|
@@ -97,15 +100,15 @@ Full reference for `createChatSdk(options)`. Grouped by purpose. Required: `llm`
|
|
|
97
100
|
|
|
98
101
|
## Checkpoint (session rollback)
|
|
99
102
|
|
|
100
|
-
`checkpoint: true \| { maxCheckpoints?, auto? }` — per-round snapshot of (messages + data
|
|
103
|
+
`checkpoint: true \| { maxCheckpoints?, auto? }` — per-round snapshot of (messages + main data bind + vfs + todos). Read-back/restore auto-bound to `data.bind` via `getData` (adapts to `sdk.setData` runtime swap; in-place restore preserves reactive refs). `restoreLastCheckpoint()` / LLM tool `restore_last_checkpoint` / UI button. Distinct from dataOps per-path snapshots (checkpoint = whole-session rollback).
|
|
101
104
|
|
|
102
105
|
## Persistence (storage)
|
|
103
106
|
|
|
104
107
|
| Option | Type | Default | Purpose / when |
|
|
105
108
|
|---|---|---|---|
|
|
106
|
-
| `storage` | `'indexed'\|'session'\|'local'\|'memory'\| StorageConfig \| false` | `false` (off) | Off by default; assign to enable. Persists messages/vfs/todos/memory (NOT
|
|
109
|
+
| `storage` | `'indexed'\|'session'\|'local'\|'memory'\| StorageConfig \| false` | `false` (off) | Off by default; assign to enable. Persists messages/vfs/todos/memory (NOT `bind` — it may contain non-serializable content; store & re-inject via `sdk.setData`). Auto-degrades to memory if backend unavailable (private mode / quota). |
|
|
107
110
|
| `session` | `SessionOptions` | — | Session control (resume by id, etc.). |
|
|
108
|
-
| `shareContext` | `boolean` | `false` | `true` → multiple `createChatSdk` with same `id` share one `AgentCore` (same agent, multiple dialog views on a page). |
|
|
111
|
+
| `shareContext` | `boolean` | `false` | `true` → multiple `createChatSdk` with same `id` share one `AgentCore` (same agent, multiple dialog views on a page; shares `data.bind` too). |
|
|
109
112
|
|
|
110
113
|
## MCP (external tools)
|
|
111
114
|
|
|
@@ -5,30 +5,33 @@ From the smallest working setup to a full-featured integration. Read top-down; s
|
|
|
5
5
|
## Stage 0 — Prerequisites
|
|
6
6
|
|
|
7
7
|
- An OpenAI-compatible LLM endpoint (DeepSeek works out of the box). Get an API key.
|
|
8
|
-
-
|
|
8
|
+
- A main data object you want the AI to edit (any plain or reactive object).
|
|
9
9
|
|
|
10
10
|
## Stage 1 — Minimal (5 lines, CDN, no build)
|
|
11
11
|
|
|
12
|
-
Drop into any HTML page. The built-in dialog mounts itself. (`systemPrompt` is optional — a built-in default is used if omitted: a generic
|
|
12
|
+
Drop into any HTML page. The built-in dialog mounts itself. (`systemPrompt` is optional — a built-in default is used if omitted: a generic JSON-operation assistant + `systemPromptHelpers.reliableWriteRules`. Shown here explicitly for clarity.)
|
|
13
13
|
|
|
14
14
|
```html
|
|
15
15
|
<div id="root"></div>
|
|
16
16
|
<script src="https://unpkg.com/page-agent-sdk"></script>
|
|
17
17
|
<script>
|
|
18
|
-
|
|
18
|
+
const app = { title: 'Hello', theme: 'light' } // plain object (no window needed)
|
|
19
19
|
ChatSdk.createChatSdk({
|
|
20
20
|
container: '#root',
|
|
21
21
|
llm: { apiKey: 'sk-...', baseUrl: 'https://api.deepseek.com/v1', model: 'deepseek-chat' },
|
|
22
|
-
systemPrompt: 'You are a
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
systemPrompt: 'You are a JSON operation assistant. Read/write the main data via tools.',
|
|
23
|
+
data: {
|
|
24
|
+
schema: ChatSdk.z.object({
|
|
25
|
+
title: ChatSdk.z.string().describe('标题'),
|
|
26
|
+
theme: ChatSdk.z.enum(['light', 'dark']).describe('主题'),
|
|
27
|
+
}),
|
|
28
|
+
bind: app,
|
|
29
|
+
},
|
|
27
30
|
}).mount()
|
|
28
31
|
</script>
|
|
29
32
|
```
|
|
30
33
|
|
|
31
|
-
Talk to it: "change theme to dark" → AI calls `write({
|
|
34
|
+
Talk to it: "change theme to dark" → AI calls `write({ value: 'dark', patch: { op: 'set', jsonPath: 'theme' } })` → `app.theme === 'dark'`.
|
|
32
35
|
|
|
33
36
|
## Stage 2 — npm + module project
|
|
34
37
|
|
|
@@ -40,17 +43,20 @@ npm i page-agent-sdk zod @langchain/openai @langchain/core
|
|
|
40
43
|
import { createChatSdk, z } from 'page-agent-sdk'
|
|
41
44
|
import 'page-agent-sdk/style.css'
|
|
42
45
|
|
|
43
|
-
|
|
46
|
+
const app = { title: 'Hello', theme: 'light', items: [] }
|
|
44
47
|
|
|
45
48
|
const sdk = createChatSdk({
|
|
46
49
|
container: '#root',
|
|
47
50
|
llm: { apiKey: import.meta.env.VITE_AI_API_KEY, baseUrl: 'https://api.deepseek.com/v1', model: 'deepseek-chat' },
|
|
48
|
-
systemPrompt: 'You are a
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
systemPrompt: 'You are a JSON operation assistant. Read/write the main data via tools.',
|
|
52
|
+
data: {
|
|
53
|
+
schema: z.object({
|
|
54
|
+
title: z.string().describe('标题'),
|
|
55
|
+
theme: z.enum(['light', 'dark']).describe('主题'),
|
|
56
|
+
items: z.array(z.object({ name: z.string(), price: z.number() })).describe('列表项'),
|
|
57
|
+
}),
|
|
58
|
+
bind: app,
|
|
59
|
+
},
|
|
54
60
|
}).mount()
|
|
55
61
|
```
|
|
56
62
|
|
|
@@ -61,10 +67,10 @@ Subscribe via `onEvent` (constructor) or `sdk.hook` (runtime, multi-listener, ca
|
|
|
61
67
|
```ts
|
|
62
68
|
const sdk = createChatSdk({
|
|
63
69
|
onEvent(e) {
|
|
64
|
-
if (e.type === '
|
|
70
|
+
if (e.type === 'data_change') renderUI() // host page refresh (plain-object bind needs this; reactive bind auto-refreshes)
|
|
65
71
|
if (e.type === 'error') console.error(e.message)
|
|
66
72
|
},
|
|
67
|
-
// ...llm,
|
|
73
|
+
// ...llm, data...
|
|
68
74
|
}).mount()
|
|
69
75
|
|
|
70
76
|
// runtime listener (e.g. analytics), cancellable
|
|
@@ -72,6 +78,8 @@ const off = sdk.hook((e) => { if (e.type === 'tool_call') track(e.name) })
|
|
|
72
78
|
// off()
|
|
73
79
|
```
|
|
74
80
|
|
|
81
|
+
> For Vue + `reactive()` bind, template/watch auto-refresh on write — no manual notify needed. For plain-object bind (React/vanilla/Node), subscribe `data_change` to re-render. Both can coexist (reactive for UI, `onEvent` for audit).
|
|
82
|
+
|
|
75
83
|
## Stage 4 — Headless (custom UI, framework-agnostic)
|
|
76
84
|
|
|
77
85
|
No built-in dialog; drive the reactive `messages` array yourself.
|
|
@@ -79,7 +87,7 @@ No built-in dialog; drive the reactive `messages` array yourself.
|
|
|
79
87
|
```ts
|
|
80
88
|
const sdk = createChatSdk({
|
|
81
89
|
ui: false, // headless
|
|
82
|
-
llm: { ... }, systemPrompt: '...',
|
|
90
|
+
llm: { ... }, systemPrompt: '...', data: { schema, bind: appObj },
|
|
83
91
|
}).mount()
|
|
84
92
|
|
|
85
93
|
// your own UI reads sdk.messages (reactive) and calls sdk.send
|
|
@@ -92,10 +100,10 @@ Reusable `ChatDialog` / `MessageContent` / `CodePreview` components + `useChat`
|
|
|
92
100
|
|
|
93
101
|
```ts
|
|
94
102
|
createChatSdk({
|
|
95
|
-
// ...llm,
|
|
103
|
+
// ...llm, data...
|
|
96
104
|
capabilities: { verify: true }, // write-back self-check before agent returns
|
|
97
105
|
verify: { maxAttempts: 2 }, // auto-correct on failure (default check = write-back read + schema)
|
|
98
|
-
approval: { tools: ['write'] },
|
|
106
|
+
approval: { tools: ['write'] }, // human-confirm before writes
|
|
99
107
|
checkpoint: true, // session-level rollback on bad edits
|
|
100
108
|
maxParallelTools: 1, // serial tool calls (safe for stateful middleware)
|
|
101
109
|
contextPreset: 'conservative', // save cost on long sessions
|
|
@@ -107,43 +115,35 @@ createChatSdk({
|
|
|
107
115
|
```ts
|
|
108
116
|
createChatSdk({
|
|
109
117
|
id: 'my-page-agent', // STABLE id (multi-agent isolation); omit = random + warn
|
|
110
|
-
storage: 'indexed', // persist messages/vfs/todos/memory to IndexedDB
|
|
111
|
-
// ...llm,
|
|
118
|
+
storage: 'indexed', // persist messages/vfs/todos/memory to IndexedDB (NOT bind — store & re-inject via sdk.setData)
|
|
119
|
+
// ...llm, data...
|
|
112
120
|
}).mount()
|
|
113
121
|
|
|
114
122
|
// later, switch session:
|
|
115
123
|
await sdk.switchSession('session-abc') // load or create
|
|
116
124
|
```
|
|
117
125
|
|
|
118
|
-
## Stage 7 —
|
|
126
|
+
## Stage 7 — Swap data at runtime (dynamic / lazy-loaded schema)
|
|
119
127
|
|
|
120
|
-
|
|
128
|
+
When the page schema changes dynamically (e.g. lazy-loaded components with different structures), swap the whole main data config at runtime — tools pick up the new bind/schema immediately, no rebuild.
|
|
121
129
|
|
|
122
130
|
```ts
|
|
123
131
|
const sdk = createChatSdk({
|
|
124
132
|
container: '#root', llm: { ... },
|
|
125
|
-
|
|
126
|
-
dataSlots: [{ path: 'app.components', description: '动态组件容器(按 id 存)', schema: z.record(z.string(), z.any()) }],
|
|
133
|
+
data: { schema: initialSchema, bind: initialObj, description: '初始数据' },
|
|
127
134
|
}).mount()
|
|
128
135
|
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
// component unmounts → unregister (snapshot stack cleaned too)
|
|
139
|
-
function unmountComp(id: string) {
|
|
140
|
-
delete window.app.components[id]
|
|
141
|
-
sdk.removeDataSlot(`app.components.${id}`)
|
|
142
|
-
}
|
|
143
|
-
sdk.listDataSlots() // live registry (reflects dynamic add/remove)
|
|
136
|
+
// later: swap to a different schema + bind (lazy-loaded / dynamic)
|
|
137
|
+
sdk.setData({
|
|
138
|
+
schema: newSchema, // new zod schema (validation + field hints auto-injected)
|
|
139
|
+
bind: newObj, // new reactive/plain object
|
|
140
|
+
description: '新数据',
|
|
141
|
+
})
|
|
142
|
+
// tools now operate on newObj with newSchema — immediately, no rebuild
|
|
143
|
+
sdk.getData() // read current config
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
-
>
|
|
146
|
+
> `summarization` auto-embeds the current data description in compressed summaries, so the agent won't act on stale memory after a swap. Snapshots & optimistic-lock hash reset on swap (old snapshots cleared).
|
|
147
147
|
|
|
148
148
|
**Full runnable demo**: `examples/dynamic-demo/` (`npm run dev` → `/examples/dynamic-demo/`).
|
|
149
149
|
|