@qwen-code/arena-agent 0.0.8 → 0.0.9-dev.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/.env.example +42 -11
- package/README.md +119 -34
- package/dist/agent.d.ts +2 -0
- package/dist/agent.js +57 -36
- package/dist/agent.js.map +1 -1
- package/dist/anthropic.d.ts +1 -1
- package/dist/anthropic.js +68 -7
- package/dist/anthropic.js.map +1 -1
- package/dist/cli.js +10 -4
- package/dist/cli.js.map +1 -1
- package/dist/compaction.js +2 -1
- package/dist/compaction.js.map +1 -1
- package/dist/config.d.ts +11 -0
- package/dist/config.js +73 -9
- package/dist/config.js.map +1 -1
- package/dist/conversation-metrics.d.ts +61 -0
- package/dist/conversation-metrics.js +433 -0
- package/dist/conversation-metrics.js.map +1 -0
- package/dist/history.d.ts +31 -2
- package/dist/history.js +53 -3
- package/dist/history.js.map +1 -1
- package/dist/metrics.d.ts +61 -0
- package/dist/metrics.js +578 -0
- package/dist/metrics.js.map +1 -0
- package/dist/mock.js +153 -0
- package/dist/mock.js.map +1 -1
- package/dist/protocol.d.ts +1 -1
- package/dist/protocol.js +9 -2
- package/dist/protocol.js.map +1 -1
- package/dist/provider.js +22 -8
- package/dist/provider.js.map +1 -1
- package/dist/runtime-assets.d.ts +3 -0
- package/dist/runtime-assets.js +16 -0
- package/dist/runtime-assets.js.map +1 -0
- package/dist/sdk.d.ts +28 -1
- package/dist/sdk.js +38 -0
- package/dist/sdk.js.map +1 -1
- package/dist/server.js +486 -36
- package/dist/server.js.map +1 -1
- package/dist/side-query.d.ts +31 -0
- package/dist/side-query.js +138 -0
- package/dist/side-query.js.map +1 -0
- package/dist/tools.js +122 -45
- package/dist/tools.js.map +1 -1
- package/dist/tui.js +4 -3
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +155 -0
- package/dist/web/app.js +333 -10
- package/dist/web/app.js.map +1 -1
- package/docs/SDK.md +563 -0
- package/package.json +2 -1
- package/web/index.html +25 -0
- package/web/styles.css +109 -3
package/docs/SDK.md
ADDED
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
# Arena TypeScript SDK 使用指南
|
|
2
|
+
|
|
3
|
+
Arena SDK 是 `arena serve` 的 TypeScript 客户端,支持 Node.js 20+ 和现代
|
|
4
|
+
浏览器。它封装了 session 创建与恢复、token 刷新、SSE 流式事件、附件、
|
|
5
|
+
`ask_user`、取消和历史读取。
|
|
6
|
+
|
|
7
|
+
## 1. 启动 Arena 服务
|
|
8
|
+
|
|
9
|
+
SDK 不直接在客户端运行模型和工具;需要先启动 Arena 服务:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install
|
|
13
|
+
npm run build
|
|
14
|
+
arena serve
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
默认地址是 `http://127.0.0.1:3000`。模型和工具密钥配置在 Arena 服务端,
|
|
18
|
+
不要放进使用 SDK 的浏览器代码。
|
|
19
|
+
|
|
20
|
+
## 2. 安装与导入
|
|
21
|
+
|
|
22
|
+
发布到 npm 后:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install arena-codeagent-harness
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
从本地 tarball 安装:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm pack
|
|
32
|
+
npm install /path/to/arena-codeagent-harness-0.2.0.tgz
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
SDK 使用 ESM:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import {
|
|
39
|
+
ArenaClient,
|
|
40
|
+
ArenaApiError,
|
|
41
|
+
type ArenaCredentials,
|
|
42
|
+
type ArenaInputRequest,
|
|
43
|
+
type ArenaServerEvent,
|
|
44
|
+
} from "arena-codeagent-harness";
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 3. 三个核心概念
|
|
48
|
+
|
|
49
|
+
- `ArenaClient`:连接一个 Arena 服务。
|
|
50
|
+
- `ArenaSession`:一段持久对话及其独立 workspace。
|
|
51
|
+
- Turn:从一条用户消息开始,到 Agent 成功、失败或被取消为止。
|
|
52
|
+
|
|
53
|
+
同一个 session 可以运行多个 Turn。历史、文件和 workspace 会在这些 Turn
|
|
54
|
+
之间保留。
|
|
55
|
+
|
|
56
|
+
## 4. 最短可用示例
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { ArenaClient } from "arena-codeagent-harness";
|
|
60
|
+
|
|
61
|
+
const arena = new ArenaClient({
|
|
62
|
+
baseUrl: "http://127.0.0.1:3000",
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const session = await arena.sessions.create();
|
|
66
|
+
const result = await session.run({
|
|
67
|
+
input: "创建一个 hello.html landing page",
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
console.log(result.status);
|
|
71
|
+
console.log(result.text);
|
|
72
|
+
console.log(result.files);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`run()` 会收集完整 Turn,适合脚本、服务端任务和不需要逐字渲染的场景。
|
|
76
|
+
|
|
77
|
+
返回值主要包含:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
{
|
|
81
|
+
status: "success" | "error" | "unknown";
|
|
82
|
+
text: string;
|
|
83
|
+
reasoning: string;
|
|
84
|
+
messages: Message[];
|
|
85
|
+
files: Record<string, unknown>[];
|
|
86
|
+
events: ArenaServerEvent[];
|
|
87
|
+
error?: string;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
如果只需要一次调用,也可以让 client 自动创建 session:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
const result = await arena.run({
|
|
95
|
+
input: "分析这个项目",
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
console.log(result.text);
|
|
99
|
+
console.log(result.session.credentials);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## 5. Credential、token 与 resume
|
|
103
|
+
|
|
104
|
+
创建 session 后,服务端返回一组 session credential:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
{
|
|
108
|
+
sessionId: string;
|
|
109
|
+
token: string;
|
|
110
|
+
expiresAt?: string;
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
这里的 token 只用于访问对应 Arena session,不是模型 API Key。
|
|
115
|
+
|
|
116
|
+
### 不需要跨进程恢复
|
|
117
|
+
|
|
118
|
+
什么都不用保存:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const arena = new ArenaClient({
|
|
122
|
+
baseUrl: "http://127.0.0.1:3000",
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const session = await arena.sessions.create();
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 保存最新 credential
|
|
129
|
+
|
|
130
|
+
SDK 创建 session 或刷新 token 后会调用 `onCredentials`。应用应该始终覆盖
|
|
131
|
+
旧值:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
let savedCredentials: ArenaCredentials | undefined;
|
|
135
|
+
|
|
136
|
+
const arena = new ArenaClient({
|
|
137
|
+
baseUrl: "http://127.0.0.1:3000",
|
|
138
|
+
onCredentials(credentials) {
|
|
139
|
+
savedCredentials = credentials;
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Node.js 服务通常应把它保存到数据库或自己的加密存储。浏览器演示可以保存到
|
|
145
|
+
`localStorage`,但生产环境需要评估 XSS 风险:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
const arena = new ArenaClient({
|
|
149
|
+
baseUrl: "http://127.0.0.1:3000",
|
|
150
|
+
onCredentials(credentials) {
|
|
151
|
+
localStorage.setItem(
|
|
152
|
+
"arena-session",
|
|
153
|
+
JSON.stringify(credentials),
|
|
154
|
+
);
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 恢复 session
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
const credentials = JSON.parse(
|
|
163
|
+
localStorage.getItem("arena-session")!,
|
|
164
|
+
) as ArenaCredentials;
|
|
165
|
+
|
|
166
|
+
const session = arena.sessions.resume(credentials);
|
|
167
|
+
const history = await session.getHistory();
|
|
168
|
+
|
|
169
|
+
console.log(history.messages);
|
|
170
|
+
console.log(history.activePrompt);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`getHistory()` 同时会设置 SSE 续传需要的 `lastEventId`。恢复后再调用
|
|
174
|
+
`run()`、`stream()` 或 `connect()` 即可继续。
|
|
175
|
+
|
|
176
|
+
当前服务端还没有用户账号维度的 `sessions.list()`/`sessions.open()`。
|
|
177
|
+
跨设备登录应由服务端把 session 绑定到可信的 `userId`,并在用户登录后重新
|
|
178
|
+
签发短期 session token,而不是在设备之间同步旧 token。
|
|
179
|
+
|
|
180
|
+
## 6. 流式模式
|
|
181
|
+
|
|
182
|
+
### 单个 Turn:`stream()`
|
|
183
|
+
|
|
184
|
+
`stream()` 会自动建立 SSE、上传附件、发送消息,并在本次 Turn 完成后停止:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
for await (const event of session.stream({
|
|
188
|
+
input: "创建一个 landing page",
|
|
189
|
+
})) {
|
|
190
|
+
switch (event.type) {
|
|
191
|
+
case "text-delta":
|
|
192
|
+
process.stdout.write(String(event.delta || ""));
|
|
193
|
+
break;
|
|
194
|
+
|
|
195
|
+
case "reasoning-delta":
|
|
196
|
+
console.log("Reasoning:", event.delta);
|
|
197
|
+
break;
|
|
198
|
+
|
|
199
|
+
case "tool-input-start":
|
|
200
|
+
console.log("Tool:", event.toolName);
|
|
201
|
+
break;
|
|
202
|
+
|
|
203
|
+
case "tool-output-available":
|
|
204
|
+
console.log("Tool output:", event.output);
|
|
205
|
+
break;
|
|
206
|
+
|
|
207
|
+
case "data-workspace":
|
|
208
|
+
console.log("Workspace file:", event.data);
|
|
209
|
+
break;
|
|
210
|
+
|
|
211
|
+
case "trigger:turn-complete":
|
|
212
|
+
console.log("Turn status:", event.status);
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`stream()` 默认在连接断开后使用最近的 SSE event ID 续传。
|
|
219
|
+
|
|
220
|
+
### 多个 Turn 共用连接:`connect()` + `send()`
|
|
221
|
+
|
|
222
|
+
Web UI 一般建立一条长期连接,然后发送多个 Turn:
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
const events = session.connect({
|
|
226
|
+
onEvent(event) {
|
|
227
|
+
renderEvent(event);
|
|
228
|
+
},
|
|
229
|
+
onStateChange(state) {
|
|
230
|
+
console.log("SSE:", state);
|
|
231
|
+
},
|
|
232
|
+
onError(error) {
|
|
233
|
+
console.error("SSE error:", error);
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
await events.ready;
|
|
238
|
+
|
|
239
|
+
await session.send({ input: "第一个问题" });
|
|
240
|
+
// 收到 trigger:turn-complete 后才能发送下一条。
|
|
241
|
+
|
|
242
|
+
events.close();
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
也可以把持久连接作为 async iterable 消费:
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
const events = session.connect();
|
|
249
|
+
await events.ready;
|
|
250
|
+
|
|
251
|
+
void session.send({ input: "分析 workspace" });
|
|
252
|
+
|
|
253
|
+
for await (const event of events) {
|
|
254
|
+
console.log(event);
|
|
255
|
+
if (event.type === "trigger:turn-complete") break;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
events.close();
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
`events.close()` 只关闭本地 SSE,不会取消服务端正在运行的 Turn。
|
|
262
|
+
|
|
263
|
+
### 手动重试失败的模型节点
|
|
264
|
+
|
|
265
|
+
自动模型重试耗尽后,`trigger:turn-complete` 会携带
|
|
266
|
+
`status: "error"`、`retryable: true` 和本次 `messageId`。可以在同一个
|
|
267
|
+
SSE 连接上主动重试:
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
if (
|
|
271
|
+
event.type === "trigger:turn-complete" &&
|
|
272
|
+
event.status === "error" &&
|
|
273
|
+
event.retryable
|
|
274
|
+
) {
|
|
275
|
+
await session.retry({ messageId: String(event.messageId) });
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
`retry()` 不会追加新的 user message,也不会重跑已经完成的工具。服务端从
|
|
280
|
+
失败模型调用之前的最后一个完整消息节点恢复;如果重试过程中又完成了工具、
|
|
281
|
+
随后再次失败,下一次 `retry()` 会从更新后的节点继续。`getHistory()` 的
|
|
282
|
+
`retryable` 和 `retryMessageId` 可用于页面刷新后恢复重试按钮。消息内容始终以
|
|
283
|
+
`history.json` 为唯一真相源;辅助重试状态必须与它的哈希匹配才会生效。
|
|
284
|
+
|
|
285
|
+
### 常用事件
|
|
286
|
+
|
|
287
|
+
| 事件 | 含义 |
|
|
288
|
+
|---|---|
|
|
289
|
+
| `start` | Turn 已开始 |
|
|
290
|
+
| `reasoning-start/delta/end` | reasoning 流 |
|
|
291
|
+
| `text-start/delta/end` | 最终文本流 |
|
|
292
|
+
| `tool-input-start/delta/available` | 模型正在生成工具调用 |
|
|
293
|
+
| `tool-output-available` | 工具执行结果 |
|
|
294
|
+
| `data-agent-status` | 当前模型或工具阶段 |
|
|
295
|
+
| `data-user-input-request` | `ask_user` 正在等待回答 |
|
|
296
|
+
| `data-user-input-submitted` | 回答已经提交 |
|
|
297
|
+
| `data-workspace` | workspace 文件可用 |
|
|
298
|
+
| `data-present-file` | 应立即展示的文件 |
|
|
299
|
+
| `data-messages` | 最新完整消息历史 |
|
|
300
|
+
| `data-session-metrics` | 截至当前 turn 的完整 session 指标快照 |
|
|
301
|
+
| `trigger:turn-complete` | Turn 已结束 |
|
|
302
|
+
| `error` | Agent 或协议错误 |
|
|
303
|
+
|
|
304
|
+
## 7. `ask_user`
|
|
305
|
+
|
|
306
|
+
### 回调自动回答
|
|
307
|
+
|
|
308
|
+
适合 CLI、后端服务或已有交互回调的应用:
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
const result = await session.run({
|
|
312
|
+
input: "帮我设计一个网站",
|
|
313
|
+
onInputRequired: async (request) => {
|
|
314
|
+
const question = request.questions[0];
|
|
315
|
+
if (!question) return [];
|
|
316
|
+
const option = question.options[0];
|
|
317
|
+
if (!option) return [];
|
|
318
|
+
|
|
319
|
+
return [
|
|
320
|
+
{
|
|
321
|
+
questionId: question.id,
|
|
322
|
+
optionId: option.id,
|
|
323
|
+
},
|
|
324
|
+
];
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
如果要提交自由文本:
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
return [
|
|
333
|
+
{
|
|
334
|
+
questionId: question.id,
|
|
335
|
+
customResponse: "使用深色极简风格",
|
|
336
|
+
},
|
|
337
|
+
];
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### UI 手动回答
|
|
341
|
+
|
|
342
|
+
不传 `onInputRequired`,收到事件后渲染自己的 UI:
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
for await (const event of session.stream({
|
|
346
|
+
input: "帮我设计一个网站",
|
|
347
|
+
})) {
|
|
348
|
+
if (event.type !== "data-user-input-request") continue;
|
|
349
|
+
|
|
350
|
+
const request = event.data as ArenaInputRequest;
|
|
351
|
+
const answers = await showQuestionDialog(request.questions);
|
|
352
|
+
|
|
353
|
+
await session.respondToInput(request.requestId, answers);
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
答案格式:
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
type ArenaAnswer = {
|
|
361
|
+
questionId: string;
|
|
362
|
+
optionId?: string;
|
|
363
|
+
customResponse?: string;
|
|
364
|
+
};
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## 8. 取消
|
|
368
|
+
|
|
369
|
+
取消服务端正在运行的 Turn:
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
const cancelled = await session.cancel();
|
|
373
|
+
console.log(cancelled);
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Undo 最近完成的 Turn(只能在 Turn 停止运行后调用):
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
const undone = await session.undo();
|
|
380
|
+
if (undone.undone) {
|
|
381
|
+
input.value = undone.input;
|
|
382
|
+
renderHistory(undone.messages);
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
服务端会恢复提交该 Turn 之前的完整消息历史和 session workspace,并清除
|
|
387
|
+
该 Turn 的内存事件,因而下一次发送不会携带已撤销的上下文。Undo 是单步的;
|
|
388
|
+
发送下一条消息后会建立新的 checkpoint。
|
|
389
|
+
|
|
390
|
+
使用 `AbortController`:
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
const controller = new AbortController();
|
|
394
|
+
|
|
395
|
+
setTimeout(() => controller.abort(), 5_000);
|
|
396
|
+
|
|
397
|
+
for await (const event of session.stream({
|
|
398
|
+
input: "执行一个复杂任务",
|
|
399
|
+
signal: controller.signal,
|
|
400
|
+
})) {
|
|
401
|
+
console.log(event);
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
`stream()` 的 signal 被 abort 时,SDK 默认也会调用服务端取消接口。设置
|
|
406
|
+
`cancelOnAbort: false` 可以只停止本地消费:
|
|
407
|
+
|
|
408
|
+
```ts
|
|
409
|
+
for await (const event of session.stream({
|
|
410
|
+
input: "继续在服务端运行",
|
|
411
|
+
signal: controller.signal,
|
|
412
|
+
cancelOnAbort: false,
|
|
413
|
+
})) {
|
|
414
|
+
console.log(event);
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
被取消的 partial assistant/tool 历史会由服务端保存。下次发送消息时,服务端
|
|
419
|
+
会注入一次 interruption notice,Agent 可以继续处理。
|
|
420
|
+
|
|
421
|
+
## 9. 附件与 workspace 文件
|
|
422
|
+
|
|
423
|
+
### 浏览器 `File`
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
const file = inputElement.files![0]!;
|
|
427
|
+
|
|
428
|
+
const result = await session.run({
|
|
429
|
+
input: "总结这个文件",
|
|
430
|
+
files: [file],
|
|
431
|
+
});
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Node.js
|
|
435
|
+
|
|
436
|
+
```ts
|
|
437
|
+
import { readFile } from "node:fs/promises";
|
|
438
|
+
|
|
439
|
+
const bytes = await readFile("./report.pdf");
|
|
440
|
+
|
|
441
|
+
const result = await session.run({
|
|
442
|
+
input: "总结这份报告",
|
|
443
|
+
files: [
|
|
444
|
+
{
|
|
445
|
+
name: "report.pdf",
|
|
446
|
+
data: new Uint8Array(bytes),
|
|
447
|
+
type: "application/pdf",
|
|
448
|
+
},
|
|
449
|
+
],
|
|
450
|
+
});
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
`run()`、`stream()` 和 `send()` 都会先上传 `files`,再把附件引用放入消息。
|
|
454
|
+
|
|
455
|
+
### 分步上传
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
const attachment = await session.upload({
|
|
459
|
+
name: "notes.txt",
|
|
460
|
+
data: new TextEncoder().encode("Project notes"),
|
|
461
|
+
type: "text/plain",
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
await session.send({
|
|
465
|
+
input: "整理这份笔记",
|
|
466
|
+
attachments: [attachment],
|
|
467
|
+
});
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### 下载 workspace 文件
|
|
471
|
+
|
|
472
|
+
```ts
|
|
473
|
+
const blob = await session.download("hello.html");
|
|
474
|
+
console.log(await blob.text());
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
`workspaceUrl(path)` 可以生成文件 URL。跨域浏览器场景下,直接打开 URL
|
|
478
|
+
不一定携带 Authorization header,应优先使用 `download()`,或让应用服务端
|
|
479
|
+
代理下载。
|
|
480
|
+
|
|
481
|
+
## 10. 错误处理
|
|
482
|
+
|
|
483
|
+
非 2xx API 响应会抛出 `ArenaApiError`:
|
|
484
|
+
|
|
485
|
+
```ts
|
|
486
|
+
try {
|
|
487
|
+
await session.send({ input: "hello" });
|
|
488
|
+
} catch (error) {
|
|
489
|
+
if (error instanceof ArenaApiError) {
|
|
490
|
+
console.error(error.status);
|
|
491
|
+
console.error(error.body);
|
|
492
|
+
} else {
|
|
493
|
+
throw error;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
流式 Agent 错误也可能作为 `error` 和 `trigger:turn-complete` 事件出现。
|
|
499
|
+
`run()` 会把最终状态放在 `result.status` 和 `result.error`。
|
|
500
|
+
`getHistory().metrics` 与 `run().metrics` 返回相同版本的持久化指标快照;没有
|
|
501
|
+
完成过 turn 的旧 session 返回 `null`。`data-session-metrics` 总是在对应的
|
|
502
|
+
`trigger:turn-complete` 之前发送。
|
|
503
|
+
|
|
504
|
+
## 11. 浏览器直接导入
|
|
505
|
+
|
|
506
|
+
`arena serve` 会在 `/sdk.js` 提供相同的 ESM:
|
|
507
|
+
|
|
508
|
+
```html
|
|
509
|
+
<script type="module">
|
|
510
|
+
import { ArenaClient } from "/sdk.js";
|
|
511
|
+
|
|
512
|
+
const arena = new ArenaClient();
|
|
513
|
+
const session = await arena.sessions.create();
|
|
514
|
+
console.log(await session.run({ input: "你好" }));
|
|
515
|
+
</script>
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
同源使用时 `baseUrl` 可以省略。跨域时传入完整 Arena 服务地址,并正确配置
|
|
519
|
+
CORS 和认证策略。
|
|
520
|
+
|
|
521
|
+
## 12. API 速查
|
|
522
|
+
|
|
523
|
+
### `ArenaClient`
|
|
524
|
+
|
|
525
|
+
```ts
|
|
526
|
+
arena.getConfig()
|
|
527
|
+
arena.createSession(options?)
|
|
528
|
+
arena.sessions.create(options?)
|
|
529
|
+
arena.resumeSession(credentials)
|
|
530
|
+
arena.sessions.resume(credentials)
|
|
531
|
+
arena.run(streamOptions, createSessionOptions?)
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### `ArenaSession`
|
|
535
|
+
|
|
536
|
+
```ts
|
|
537
|
+
session.credentials
|
|
538
|
+
session.getHistory()
|
|
539
|
+
session.refreshToken()
|
|
540
|
+
session.run(options)
|
|
541
|
+
session.stream(options)
|
|
542
|
+
session.connect(options?)
|
|
543
|
+
session.send(options)
|
|
544
|
+
session.retry(options?)
|
|
545
|
+
session.upload(file)
|
|
546
|
+
session.download(path)
|
|
547
|
+
session.workspaceUrl(path)
|
|
548
|
+
session.respondToInput(requestId, answers)
|
|
549
|
+
session.cancel()
|
|
550
|
+
session.undo()
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### `ArenaEventStream`
|
|
554
|
+
|
|
555
|
+
```ts
|
|
556
|
+
await events.ready
|
|
557
|
+
await events.closed
|
|
558
|
+
events.close()
|
|
559
|
+
|
|
560
|
+
for await (const event of events) {
|
|
561
|
+
// ...
|
|
562
|
+
}
|
|
563
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qwen-code/arena-agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9-dev.1",
|
|
4
4
|
"description": "A TypeScript CodeAgent harness reconstructed from Arena message and tool traces.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./dist/sdk.d.ts",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
"dist",
|
|
22
|
+
"docs/SDK.md",
|
|
22
23
|
"web",
|
|
23
24
|
"README.md",
|
|
24
25
|
".env.example"
|
package/web/index.html
CHANGED
|
@@ -128,6 +128,25 @@
|
|
|
128
128
|
<span></span>
|
|
129
129
|
<strong id="connection-label">Loading</strong>
|
|
130
130
|
</div>
|
|
131
|
+
|
|
132
|
+
<section class="session-metrics" id="session-metrics" aria-label="Live session metrics">
|
|
133
|
+
<article class="metric-chip" id="feedback-metric" data-state="neutral">
|
|
134
|
+
<small>Praise vs complaint</small>
|
|
135
|
+
<strong id="feedback-metric-value" data-mobile-value="P—">—</strong>
|
|
136
|
+
</article>
|
|
137
|
+
<article class="metric-chip" id="steer-metric" data-state="neutral">
|
|
138
|
+
<small>Steerability</small>
|
|
139
|
+
<strong id="steer-metric-value" data-mobile-value="S—">—</strong>
|
|
140
|
+
</article>
|
|
141
|
+
<article class="metric-chip" id="bash-metric" data-state="neutral">
|
|
142
|
+
<small>Bash recovery</small>
|
|
143
|
+
<strong id="bash-metric-value" data-mobile-value="B—">—</strong>
|
|
144
|
+
</article>
|
|
145
|
+
<article class="metric-chip" id="tool-metric" data-state="neutral">
|
|
146
|
+
<small>Tool hallucination</small>
|
|
147
|
+
<strong id="tool-metric-value" data-mobile-value="T—">—</strong>
|
|
148
|
+
</article>
|
|
149
|
+
</section>
|
|
131
150
|
</header>
|
|
132
151
|
|
|
133
152
|
<div class="announcement" id="announcement">
|
|
@@ -193,9 +212,15 @@
|
|
|
193
212
|
>
|
|
194
213
|
◫ <span id="artifact-count">0</span>
|
|
195
214
|
</button>
|
|
215
|
+
<button class="undo-button" id="undo" type="button" hidden>
|
|
216
|
+
↶ Undo
|
|
217
|
+
</button>
|
|
196
218
|
<button class="stop-button" id="stop" type="button" hidden>
|
|
197
219
|
<span></span> Stop
|
|
198
220
|
</button>
|
|
221
|
+
<button class="retry-button" id="retry" type="button" hidden>
|
|
222
|
+
Retry
|
|
223
|
+
</button>
|
|
199
224
|
<button class="send-button" id="send" type="submit" aria-label="Send message">
|
|
200
225
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
201
226
|
<path d="M3 12h18m0 0-8.5-8.5M21 12l-8.5 8.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|