@qwen-code/arena-agent 0.0.7-preview.0 → 0.0.7

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.
Files changed (51) hide show
  1. package/.env.example +15 -5
  2. package/README.md +127 -16
  3. package/dist/agent.d.ts +9 -1
  4. package/dist/agent.js +249 -94
  5. package/dist/agent.js.map +1 -1
  6. package/dist/anthropic.d.ts +16 -0
  7. package/dist/anthropic.js +391 -0
  8. package/dist/anthropic.js.map +1 -0
  9. package/dist/attachments.d.ts +29 -0
  10. package/dist/attachments.js +222 -0
  11. package/dist/attachments.js.map +1 -0
  12. package/dist/canonical-data.d.ts +2 -0
  13. package/dist/canonical-data.js +4 -0
  14. package/dist/canonical-data.js.map +1 -0
  15. package/dist/cli.js +55 -13
  16. package/dist/cli.js.map +1 -1
  17. package/dist/compaction.d.ts +16 -0
  18. package/dist/compaction.js +183 -0
  19. package/dist/compaction.js.map +1 -0
  20. package/dist/config.d.ts +6 -1
  21. package/dist/config.js +28 -2
  22. package/dist/config.js.map +1 -1
  23. package/dist/history.d.ts +3 -3
  24. package/dist/history.js +4 -4
  25. package/dist/history.js.map +1 -1
  26. package/dist/mock.js +24 -1
  27. package/dist/mock.js.map +1 -1
  28. package/dist/protocol.d.ts +4 -2
  29. package/dist/protocol.js +17 -1
  30. package/dist/protocol.js.map +1 -1
  31. package/dist/provider.d.ts +4 -0
  32. package/dist/provider.js +163 -4
  33. package/dist/provider.js.map +1 -1
  34. package/dist/repo.js +2 -5
  35. package/dist/repo.js.map +1 -1
  36. package/dist/server.js +9 -174
  37. package/dist/server.js.map +1 -1
  38. package/dist/spec.d.ts +7 -9
  39. package/dist/spec.js +125 -27
  40. package/dist/spec.js.map +1 -1
  41. package/dist/tools.d.ts +5 -1
  42. package/dist/tools.js +45 -23
  43. package/dist/tools.js.map +1 -1
  44. package/dist/tui.js +48 -12
  45. package/dist/tui.js.map +1 -1
  46. package/dist/types.d.ts +21 -9
  47. package/dist/web/app.js +2 -3
  48. package/dist/web/app.js.map +1 -1
  49. package/package.json +4 -4
  50. package/docs/SDK.md +0 -521
  51. package/spec/tools.json +0 -418
package/.env.example CHANGED
@@ -1,6 +1,6 @@
1
1
  # Model name. Provider prefixes are stripped for direct connections and kept
2
2
  # when LITELLM_PROXY_URL is configured.
3
- ARENA_MODEL=openai/gpt-4.1-mini
3
+ ARENA_MODEL=openai/qwen-latest-series-invite-beta-v92
4
4
  # ARENA_MODEL=anthropic/claude-sonnet-4-20250514
5
5
  # ARENA_MODEL=gemini/gemini-2.5-pro
6
6
 
@@ -12,29 +12,39 @@ GEMINI_API_KEY=
12
12
  # LITELLM_PROXY_URL=http://127.0.0.1:4000/v1
13
13
  # LITELLM_API_KEY=
14
14
 
15
- # Direct OpenAI-compatible endpoint, for example DashScope:
15
+ # Direct OpenAI-compatible DashScope endpoint:
16
16
  # ARENA_MODEL=qwen3.7-max
17
17
  # Default model used by the reproduction scripts:
18
18
  # ARENA_API_MODEL=qwen-latest-series-invite-beta-v92
19
19
  # ARENA_API_BASE=https://dashscope.aliyuncs.com/compatible-mode/v1
20
20
  # ARENA_API_KEY=
21
21
 
22
- # Trace overrides.
23
- # ARENA_MAX_TOKENS=32768
22
+ # Trace-compatible model settings.
23
+ # ARENA_MAX_TOKENS=131072
24
24
  # ARENA_THINKING_BUDGET=81920
25
25
  # ARENA_ENABLE_THINKING=true
26
- # ARENA_MAX_STEPS=64
26
+ # Context is summarized before a model call when the estimated input reaches
27
+ # this fraction of the configured model context window.
28
+ # ARENA_CONTEXT_WINDOW=1000000
29
+ # ARENA_CONTEXT_COMPACTION_RATIO=0.8
30
+ # ARENA_COMPACTION_SUMMARY_MAX_TOKENS=8192
27
31
  # ARENA_EXTRA_BODY={"enable_thinking":true}
28
32
  # Transient model failures use full-jitter exponential backoff. The retry delay
29
33
  # is capped by ARENA_MODEL_RETRY_MAX_MS and honors Retry-After when present.
30
34
  # ARENA_MODEL_MAX_RETRIES=5
31
35
  # ARENA_MODEL_RETRY_BASE_MS=1000
32
36
  # ARENA_MODEL_RETRY_MAX_MS=30000
37
+ # A stream that stays open but delivers no bytes for this long is aborted and
38
+ # retried like a network fault (0 disables). Some gateways stall silently.
39
+ # ARENA_MODEL_STREAM_IDLE_TIMEOUT_MS=300000
33
40
 
34
41
  # Daemon storage namespace. One deployed daemon should use one authenticated
35
42
  # tenant identity; sessions underneath it have independent workspaces.
36
43
  # ARENA_TENANT_ID=local
37
44
  # ARENA_SESSION_WORKSPACE_MODE=empty
45
+ # Session state (histories, session metadata, daemon session workspaces)
46
+ # defaults to ~/.arena so runs never write into the task workspace.
47
+ # ARENA_HOME=~/.arena
38
48
 
39
49
  # Tool backends.
40
50
  DASHSCOPE_API_KEY=
package/README.md CHANGED
@@ -41,8 +41,8 @@ export ARENA_MODEL=anthropic/claude-sonnet-4-20250514
41
41
  ```
42
42
 
43
43
  设置 `LITELLM_PROXY_URL` 时,模型名会原样发送给 LiteLLM。没有 Proxy 时,
44
- 也支持直连 OpenAI、Anthropic OpenAI-compatibility 与 Gemini
45
- OpenAI-compatibility 接口:
44
+ 也支持直连:`openai/` 与 `gemini/` 前缀走各自的 OpenAI-compatibility 接口,
45
+ `anthropic/` 前缀直接使用原生 Anthropic Messages 协议(`/v1/messages`):
46
46
 
47
47
  ```bash
48
48
  # OpenAI
@@ -56,8 +56,35 @@ export ANTHROPIC_API_KEY=...
56
56
  # Gemini
57
57
  export ARENA_MODEL=gemini/gemini-2.5-pro
58
58
  export GEMINI_API_KEY=...
59
+
60
+ # Anthropic 兼容网关(如 DashScope)
61
+ export ARENA_MODEL=anthropic/qwen-latest-series-invite-beta-v92
62
+ export ARENA_API_BASE=https://dashscope.aliyuncs.com/apps/anthropic/v1
63
+ export ARENA_API_KEY=...
59
64
  ```
60
65
 
66
+ Anthropic 直连默认只用 `x-api-key` 鉴权(同时携带 Authorization 会让部分
67
+ 兼容网关挂起);若网关要求 Bearer token(如 DashScope 的
68
+ claude-code-proxy),收到 401 后会自动切换鉴权方式并记住。流式连接保持
69
+ 打开但长时间无字节时(部分网关会静默停滞),按
70
+ `ARENA_MODEL_STREAM_IDLE_TIMEOUT_MS`(默认 300000,设 0 关闭)中止并按
71
+ 网络故障重试。
72
+
73
+ Anthropic 直连在原生协议上完整支持 extended thinking:
74
+ `ARENA_ENABLE_THINKING` / `ARENA_THINKING_BUDGET` 映射为
75
+ `thinking.budget_tokens`(Anthropic 要求启用 thinking 时不调整
76
+ temperature,此时不发送该参数)。带签名的 thinking/redacted_thinking
77
+ block 会原样保存在对应 assistant 消息的 `reasoning_details` 字段——
78
+ Anthropic 要求 tool-use 续跑时回传签名 thinking block,重放历史时会按
79
+ 原顺序还原;无签名的 thinking block(部分网关返回 `signature: ""`)
80
+ 不参与重放,因为协议不需要它们,而且某些网关收到会挂起。
81
+ `reasoning_content` 仍是纯文本推理,历史契约不变。向
82
+ OpenAI-compatible 服务重放同一份历史时,`reasoning_details` 与 `usage`
83
+ 一样在 wire 边界剥离。usage 会映射为 OpenAI 风格字段(`prompt_tokens`
84
+ 含 cache 创建/读取部分),同时保留 `input_tokens`、
85
+ `cache_read_input_tokens` 等原生字段。`ARENA_API_BASE` 对 anthropic
86
+ 前缀同样生效,可指向兼容 Messages 协议的网关。
87
+
61
88
  任何 OpenAI-compatible 服务都可通过 `ARENA_API_BASE`、`ARENA_API_KEY`
62
89
  直连。CLI、TUI 和服务端会读取工作区根目录的 `.env`,且不会覆盖进程中已经
63
90
  存在的环境变量。当前 DashScope 配置示例:
@@ -66,16 +93,54 @@ export GEMINI_API_KEY=...
66
93
  ARENA_MODEL=qwen3.7-max
67
94
  ARENA_API_BASE=https://dashscope.aliyuncs.com/compatible-mode/v1
68
95
  ARENA_API_KEY=sk-...
96
+ ARENA_ENABLE_THINKING=true
97
+ ARENA_THINKING_BUDGET=81920
98
+ ARENA_MAX_TOKENS=131072
99
+ ARENA_CONTEXT_WINDOW=1000000
100
+ ARENA_CONTEXT_COMPACTION_RATIO=0.8
101
+ ARENA_COMPACTION_SUMMARY_MAX_TOKENS=8192
69
102
  ```
70
103
 
71
104
  `DASHSCOPE_API_KEY` 单独供搜索、图片生成和语音工具使用,不会作为模型连接的
72
105
  密钥。
73
106
 
107
+ Harness 会在每次模型调用前估算消息、reasoning、工具 schema 和图片占用;达到
108
+ `ARENA_CONTEXT_WINDOW × ARENA_CONTEXT_COMPACTION_RATIO`(默认 80%)时,调用
109
+ 当前模型把已完成的旧回合生成结构化续作摘要。旧前缀会被替换为与 Arena trace
110
+ 一致的 leading `<arena-system-message>`,当前用户回合及其后的 tool-call / result
111
+ 链保持原样。摘要重点保留目标、硬约束、发现、已完成/未完成事项、下一步和相关
112
+ 文件路径;摘要调用默认关闭 thinking,输出上限由
113
+ `ARENA_COMPACTION_SUMMARY_MAX_TOKENS` 控制(默认 8192)。
114
+
115
+ `ARENA_CONTEXT_WINDOW` 应设置为所用模型的真实上下文窗口;默认 `1000000` 是
116
+ 当前 trace 模型的暂定值。token 数目前是本地保守估算,并非 provider tokenizer
117
+ 的精确计数。如果摘要请求失败,Harness 会保留原历史并继续正常模型请求,避免
118
+ 摘要服务的瞬时故障直接中断任务。
119
+
120
+ 每次模型请求都会通过 OpenAI-compatible 的 `stream_options.include_usage`
121
+ 请求 token 用量,并把服务端在最终 chunk 上报的 `usage` 对象原样记录到该轮
122
+ assistant 消息的 `usage` 字段(`history.json` 与事件流中均可见),供训练
123
+ 和成本统计使用。向模型重放历史时,`usage` 会在 wire 边界剥离,不会发回
124
+ 服务端。`model.complete` 事件同样携带该轮 `usage`;上下文压缩的摘要调用
125
+ 不会在历史中留下消息,其用量通过 `compaction` 事件上报,只在事件流中可
126
+ 见。不支持 `stream_options` 的服务可通过 `ARENA_EXTRA_BODY` 覆盖该参数。
127
+
74
128
  模型请求遇到网络错误、HTTP 408/409/429 或 5xx 时会自动重试,默认最多重试
75
129
  5 次。没有 `Retry-After` 时使用带随机抖动的指数退避;可通过
76
130
  `ARENA_MODEL_MAX_RETRIES`、`ARENA_MODEL_RETRY_BASE_MS` 和
77
131
  `ARENA_MODEL_RETRY_MAX_MS` 调整。设 `ARENA_MODEL_MAX_RETRIES=0` 可关闭。
78
- 流式响应已经开始产出后发生的错误不会整轮重放。
132
+
133
+ 流式响应**开始产出之后**发生的网络中断(如 socket 断开、连接重置、
134
+ `fetch failed`/`terminated`)同样会整轮重试:请求本身是幂等的,会用相同的
135
+ 消息历史重新发起,累加器每次重试都会重置,因此写入 `history.json` 的是一次
136
+ 干净完整的 assistant 输出。重试沿用上面同一组配置,并在事件流中先发出一个
137
+ `model.retry` 事件(TUI 会显示「reconnecting after a network error」提示)。
138
+ 用户主动取消(abort)不会被当作可重试的网络故障。
139
+
140
+ Agent turn 没有 step 数或整轮墙钟时长上限,会持续执行到模型正常结束、
141
+ 请求用户输入、发生不可恢复错误,或用户主动取消。工具自身仍可定义单次调用
142
+ 的超时。每个完整的 assistant/tool step 都会原子保存到 `history.json`;
143
+ 进程被外部超时终止时,已完成步骤仍可恢复,最多丢失当时正在执行的一步。
79
144
 
80
145
  ## Headless
81
146
 
@@ -88,9 +153,12 @@ arena -p 'create a hello.html landing page' --output stream-json
88
153
  daemon 相同的目录层级和 JSON 格式:
89
154
 
90
155
  ```text
91
- .arena/tenants/<tenantId>/sessions/<sessionId>/history.json
156
+ ~/.arena/tenants/<tenantId>/sessions/<sessionId>/history.json
92
157
  ```
93
158
 
159
+ session 状态统一存放在 `~/.arena`(可用 `ARENA_HOME` 覆盖),不写入
160
+ workspace,因此不会污染任务工作区或被 diff 工作区的 verifier 观察到。
161
+
94
162
  输出事件及 JSON 结果中的 `session_id` 就是路径里的 `<sessionId>`。
95
163
  `history.json` 的格式为 `{"messages":[...]}`,即使结果为 `needs_input`
96
164
  或 `error` 也会保存。需要跨命令续写同一份显式历史时,可另外传入
@@ -106,21 +174,53 @@ arena -p '继续实现刚才的方案' --session-id my-session
106
174
  ```
107
175
 
108
176
  首次执行会创建对应的 `history.json`;后续执行会先加载已有消息,追加本轮
109
- 消息后再原子重写完整 JSON 文件。`--session-id` 与 `--history-file` 不能
110
- 同时使用。
177
+ 消息,并在初始 user 消息及每个完整 assistant/tool step 后原子重写完整
178
+ JSON 文件。`--session-id` 与 `--history-file` 不能同时使用。
179
+
180
+ 需要附带用户上传文件(尤其是参考图片)时,使用可重复的 `--attach`
181
+ (别名 `--image`),值可以是本地路径或 http(s) URL:
182
+
183
+ ```bash
184
+ arena -p 'redraw this photo with the reference face' \
185
+ --attach ./my_gf_reference.png \
186
+ --attach 'https://example.com/uploads/photo.jpg'
187
+ ```
188
+
189
+ 每个附件会被保存到工作区 `uploads/`(模型内可通过
190
+ `/home/user/uploads/<name>` 访问;重名自动加 `-2`、`-3` 后缀,URL 无扩展名
191
+ 时按图片字节特征补全扩展名)。本轮 user message 与 daemon 附件上传共用同
192
+ 一构造逻辑,并使用与 Arena trace 相同的原生部件格式:图片部件
193
+ `{"image": ...}` 在前,随后是 prompt 文本 `{"text": ...}`,最后追加一条
194
+ `<arena-system-message>`,列出保存到 `/home/user/uploads/` 的文件名。单条
195
+ 消息最多 10 个附件,单个附件上限 20 MB、总量上限 50 MB,与 daemon 一致。
196
+
197
+ 多模态 user 消息(附件、图片 `read_file` 回灌、中断通知)在历史与事件流中
198
+ 一律使用 Arena 原生部件 `{"image": ...}` / `{"text": ...}`,与 baseline
199
+ trace 逐字段同构;仅在向模型发起 chat-completions 请求的边界处转换为
200
+ OpenAI 风格的 `image_url` 部件。`--attach` 传 URL 时 `image` 直接记录该
201
+ URL(与 baseline 引用附件的方式一致,模型服务端会自行下载;重放历史要求
202
+ 该 URL 仍可访问);本地文件没有可引用的 URL,`image` 存自包含的 base64
203
+ data-URI。
111
204
 
112
205
  `stream-json` 每行一个 JSON 对象。`start`、`message`、`result` 事件中的
113
206
  `messages` 与输入样例保持相同契约:
114
207
 
115
208
  - system/user:`role` + `content`;图片读取后会持久化一条 multimodal user
116
209
  message
117
- - assistant:`role` + `content` + `reasoning_content`,工具轮含 `tool_calls`
210
+ - assistant:`role` + `content` + `reasoning_content`,工具轮含 `tool_calls`;
211
+ 服务端上报用量时含 `usage`
118
212
  - tool:`role` + `tool_call_id` + JSON 字符串 `content`
119
213
 
120
214
  `assistant.delta` 实时提供 content、reasoning 与 tool-call 参数碎片;
121
- `model.start`、`model.complete`、`tool.start`、`tool.complete` 标记阶段。
122
- 最后的 `result` 带完整可重放消息。当 headless 模型调用 `ask_user` 时,
123
- 输出 `input.required` 后以 `needs_input` 结束,不会阻塞训练 worker。
215
+ `model.start`、`model.complete`、`model.retry`、`tool.start`、`tool.complete`
216
+ 标记阶段。最后的 `result` 带完整可重放消息。
217
+
218
+ `ask_user` 需要交互式前端(TUI/WebUI)来收集选项,因此 **headless(`-p`)与
219
+ 无会话的 `/query` 端点不会把 `ask_user` 提供给模型**——只有 TUI 和 WebUI
220
+ 交互会话(都注入了 ask-user handler)才会开放该工具。这样 headless 不会再
221
+ 卡在 `needs_input` 上阻塞训练 worker。作为兜底,若续写的历史或模型幻觉在无
222
+ handler 的会话里仍调用了 `ask_user`,工具会返回一个 error 结果(提示模型用
223
+ 合理默认值继续),而不是 `needs_input`。
124
224
 
125
225
  图片 `read_file` 的 tool result 使用占位文本;真实图片作为紧随工具结果组的
126
226
  multimodal user message 注入并写入历史,因此后续重放不依赖原图片仍然存在。
@@ -135,6 +235,12 @@ arena tui
135
235
  arena tui --workspace . --model mock/ask-user --session-id my-session
136
236
  ```
137
237
 
238
+ workspace 默认是启动命令时的当前目录(可用 `--workspace` 覆盖)。TUI 会把
239
+ 实际 workspace root 和 cwd 动态注入 system prompt 与工具 schema;因此本机
240
+ 运行时使用本机路径,部署进沙箱后会自然使用沙箱内的实际路径,不需要切换模式。
241
+ TUI 没有内置文件 viewer,因此不会向模型提供 `present_file`;模型会直接报告
242
+ 生成文件的实际路径。
243
+
138
244
  assistant 的 reasoning、正文和工具调用状态会边生成边输出。每轮结束都会把
139
245
  完整消息原子写入与 headless、daemon 相同的 `history.json`;再次传入相同的
140
246
  `--session-id` 会恢复上下文。也可改用工作区内的显式
@@ -241,10 +347,11 @@ npm start
241
347
  reasoning、工具输入输出、交互问题及 workspace 文件。session ID 由服务端
242
348
  生成;打开页面或点击 New Chat 时只保留浏览器内的空白草稿,首次发送消息
243
349
  (包括带附件的消息)时才创建 session。每个 session 的凭证哈希、历史、
244
- workspace 和 artifacts 分别保存在:
350
+ workspace 和 artifacts 分别保存在(根目录 `~/.arena` 可用 `ARENA_HOME`
351
+ 覆盖):
245
352
 
246
353
  ```text
247
- .arena/tenants/<tenantId>/sessions/<sessionId>/
354
+ ~/.arena/tenants/<tenantId>/sessions/<sessionId>/
248
355
  session.json
249
356
  history.json
250
357
  workspace/
@@ -324,10 +431,14 @@ curl -N -X POST \
324
431
 
325
432
  ## 工具
326
433
 
327
- 工具规范逐字保存在 `spec/tools.json`,运行时加载并在执行前使用 AJV 校验
328
- 原始 Draft-07 schema。system prompt 以占位符模板编译在 `src/prompts.ts`;
329
- 逐字捕获样本 `spec/message.json` 与 `spec/message-repo.json` 只作为仓库内
330
- 测试 fixture,包含真实会话数据,不随 npm 包发布。工具列表:
434
+ 原始规范在源码仓库中逐字保存在:
435
+
436
+ - `spec/message.json`
437
+ - `spec/tools.json`
438
+
439
+ 构建时会将两份规范生成为 `dist` 内的 JS 数据模块;npm 包不包含原始
440
+ `spec/` 目录,运行时也不依赖仓库文件。执行工具前仍使用 AJV 校验原始
441
+ Draft-07 schema。repo mode 的动态提示词模板保存在 `src/prompts.ts`:
331
442
 
332
443
  `bash`、`ask_user`、`edit_file`、`read_file`、`fetch_page`、`web_search`、
333
444
  `write_file`、`image_search`、`present_file`、`generate_image`、
package/dist/agent.d.ts CHANGED
@@ -1,12 +1,16 @@
1
1
  import type { ArenaConfig } from "./config.js";
2
2
  import { EventSink } from "./protocol.js";
3
- import type { AgentResult, AskUserHandler, ClientContext, Message, ModelClient, RepoContext, UserTurnMessage } from "./types.js";
3
+ import { ToolExecutor } from "./tools.js";
4
+ import type { AgentResult, AskUserHandler, ClientContext, Message, ModelClient, RepoContext, UserTurnMessage, WorkspaceContext } from "./types.js";
4
5
  interface AgentOptions {
5
6
  client?: ModelClient;
7
+ tools?: ToolExecutor;
6
8
  askUserHandler?: AskUserHandler;
7
9
  sessionId?: string;
8
10
  signal?: AbortSignal;
9
11
  clientContext?: ClientContext;
12
+ workspaceContext?: WorkspaceContext;
13
+ onCheckpoint?: (messages: readonly Message[]) => Promise<void>;
10
14
  }
11
15
  interface AgentRunOptions {
12
16
  interrupted?: boolean;
@@ -18,10 +22,14 @@ export declare class Agent {
18
22
  private readonly sink;
19
23
  private readonly options;
20
24
  private readonly client;
25
+ private readonly tools;
21
26
  readonly sessionId: string;
22
27
  constructor(config: ArenaConfig, sink: EventSink, options?: AgentOptions);
23
28
  run(prompt: string, history?: Message[], runOptions?: AgentRunOptions): Promise<AgentResult>;
29
+ private checkpoint;
30
+ private maybeCompact;
24
31
  private assistantTurn;
32
+ private retryBackoff;
25
33
  }
26
34
  export declare function finalText(result: AgentResult): string;
27
35
  export {};