mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-11-09 20:07:43 +08:00
Merge pull request #7770 from deepseek-harness/fix/ptc-run-code-call-limit
fix(ptc): 按超时预算限制 run_code 工具调用总数
This commit is contained in:
@@ -18,11 +18,11 @@
|
||||
en: 4cd76a34f4712e1a
|
||||
zh: 303ef72a18644d7c
|
||||
/agent-note-ptc-mode-the-model-writes-typescript-against-the-tool-registry/decision/the-run-code-tool-and-the-dispatch-bridge:
|
||||
en: 6a0e2698b082978e
|
||||
zh: fbb03503520b30e9
|
||||
en: c3d079330748cd24
|
||||
zh: 4e2cc41d00227780
|
||||
/agent-note-ptc-mode-the-model-writes-typescript-against-the-tool-registry/decision/observability-tool-ptc-dispatch:
|
||||
en: b8e4cf9433c7ee18
|
||||
zh: e5f4e8d7d03d3a7b
|
||||
en: 5423da23d4e8d1d9
|
||||
zh: f1198d4f224f3fe6
|
||||
/agent-note-ptc-mode-the-model-writes-typescript-against-the-tool-registry/decision/the-ptc-runtime-seam:
|
||||
en: 4c423f4e9cef6055
|
||||
zh: 5c23a3637eb420c0
|
||||
|
||||
@@ -56,13 +56,15 @@ Under `'ptc'` the registry owns `run_code` as a reserved presentation transport
|
||||
|
||||
**Concurrency is bounded, not serialized.** Each run owns a dispatch queue that starts calls strictly in submission order and classifies each one through `registry.executionMode`, the same fail-closed `isConcurrencySafe` contract the native loop uses. Consecutive parallel-classified calls overlap up to `maxParallelSubCalls` (default 10; `1` restores serial dispatch); an exclusive call drains the pool and runs alone. Settlement abandons queued calls that have not started. This note shipped the serialized placeholder; the [live-parallel reference](../../../../packages/core/tools/README.md) owns the scheduler that replaced it.
|
||||
|
||||
**Tool-call allowance.** Counting attempts before validation and queuing bounds loops that repeatedly fail or enqueue work. The allowance scales with the resolved timeout and permits bursts; it does not enforce a rolling window. Exhaustion uses the run-scoped abort path so a program cannot catch a binding failure and keep running. Active calls are canceled, queued calls are abandoned, and settlement waits for cleanup. Completed effects remain; a new run receives a fresh allowance. The [executor README](../../../../packages/core/tool-executor-ptc/README.md#use-this-package) owns configuration and failure reporting.
|
||||
|
||||
**Presentation.** `run_code`'s render intent is decided here per the [render-intent Agent Note](../architecture/2026-07-02-tool-render-intent-union.md): `presentCall` creates a `generic` card with `kind: 'execute'`, the program text as its title, and the same program text as `rawInput`; `run_code` intentionally declares no `presentResult`, so the TUI and host/client runtime (Web) complete that card through their generic raw-content fallback using the final durable `tool/result.content`, including captured logs plus the returned value, failure, or post-policy spill preview. This is not a `terminal` card: that card's semantics are "a shell command in a working directory", which a program is not. See the [result-card completeness note](../../archived/bug-fix/2026-07-20-code-mode-result-card-completeness.md).
|
||||
|
||||
### Observability: `tool/ptc-dispatch`
|
||||
|
||||
Each sub-dispatch appends a log-only `tool/ptc-dispatch-start` event at pool entry and a `tool/ptc-dispatch` settle event containing parent and child call ids, tool identity, normalized arguments, and the complete rendered `content`/`isError` outcome. It remains outside model history but available to persistence and UIs. Appends occur inside the open `run_code` turn. Direct executions without an agent still run but cannot log the event.
|
||||
|
||||
New sub-calls use `<parent>:ptc:<n>` ids, numbered in submission order. All call ids are opaque to consumers: migration preserves every historical id byte-for-byte, including `:code:` substrings, so dispatch pairs, spill references, and other correlations remain intact. The bridge attributes forwarded image context to `{ kind: 'plugin', plugin: 'tools-ptc' }`.
|
||||
New sub-calls use `<parent>:ptc:<n>` ids, numbered by tool-call attempt. Invalid arguments consume a number without a dispatch event, so logged ids may have gaps. All call ids are opaque to consumers: migration preserves every historical id byte-for-byte, including `:code:` substrings, so dispatch pairs, spill references, and other correlations remain intact. The bridge attributes forwarded image context to `{ kind: 'plugin', plugin: 'tools-ptc' }`.
|
||||
|
||||
The [V2-to-V3 PTC specification](../../../../packages/session/session-format-v2-to-v3/README.md#ptc-vocabulary) owns exact historical tag and attribution conversion; [native V3 admission](../../../../packages/session/session-format-v2-to-v3/README.md#native-v3-admission) owns predecessor-tag refusal. These are not runtime aliases: an opaque extension must not acquire PTC lifecycle meaning merely through a version change.
|
||||
|
||||
|
||||
@@ -56,13 +56,15 @@ Cloudflare 的 [Code Mode](https://blog.cloudflare.com/code-mode/) 提出了一
|
||||
|
||||
**并发是有界的,而非被序列化。** 每次 run 拥有一个分发队列,严格按提交顺序启动调用,并通过 `registry.executionMode` 对每个调用分类——与原生循环所用的 fail-closed `isConcurrencySafe` 约定相同。连续的 parallel 类调用最多重叠 `maxParallelSubCalls` 个(默认 10;设为 `1` 恢复串行分发);exclusive 类调用会排空池并单独运行。结算时放弃尚未开始的排队调用。本 note 交付的是被序列化的占位实现;取代它的调度器由[实时并行 参考](../../../../packages/core/tools/README.zh.md) 负责。
|
||||
|
||||
**工具调用额度。** 在验证和排队之前对尝试计数,可以限制重复失败或持续入队的循环。额度随最终超时时间缩放,允许集中调用,不实施滑动窗口限制。额度耗尽时使用运行级取消路径,使程序无法捕获绑定错误后继续运行。活动调用被取消,排队调用被放弃,结算会等待清理完成。已完成的操作仍然生效;新运行获得完整额度。[执行器 README](../../../../packages/core/tool-executor-ptc/README.zh.md#use-this-package) 负责配置与失败报告。
|
||||
|
||||
**呈现。** `run_code` 的 render intent 按[呈现意图 Agent Note](../architecture/2026-07-02-tool-render-intent-union.zh.md)在此决定:`presentCall` 创建一个 `generic` 卡片,`kind: 'execute'`,以程序文本作为标题,并将同一程序文本作为 `rawInput`;`run_code` 有意不声明 `presentResult`,因此 TUI 和宿主/客户端运行时(Web)会通过通用原始内容回退机制,使用最终持久化的 `tool/result.content` 补全该卡片,其中包括捕获的日志,以及返回值、失败信息或 post-policy spill 预览。这不是 `terminal` 卡片:该卡片的语义是「工作目录中的 shell 命令」,程序不是。参见[结果卡片完整性说明](../../archived/bug-fix/2026-07-20-code-mode-result-card-completeness.md)。
|
||||
|
||||
### 可观测性:`tool/ptc-dispatch`
|
||||
|
||||
每次子分发在进入分发池时追加一个仅日志的 `tool/ptc-dispatch-start` 事件,并以一个 `tool/ptc-dispatch` 结算事件收尾,后者包含父子 call id、工具标识、规范化参数以及完整渲染后的 `content`/`isError` 结果。它不进入模型历史,但可供持久化和 UI 使用。追加发生在开放的 `run_code` 轮次内。没有 agent 的直接执行仍然运行,但无法记录该事件。
|
||||
|
||||
新子调用使用 `<parent>:ptc:<n>` 标识,按提交顺序编号。消费者将所有 call id 视为不透明值:迁移逐字节保留每个历史标识,包括 `:code:` 子串,因此分发事件对、spill 引用及其他关联保持完整。桥接层将转发图片上下文的来源标记为 `{ kind: 'plugin', plugin: 'tools-ptc' }`。
|
||||
新子调用使用 `<parent>:ptc:<n>` 标识,按工具调用尝试编号。无效参数会占用编号而不产生分发事件,因此日志中的编号可能有空缺。消费者将所有 call id 视为不透明值:迁移逐字节保留每个历史标识,包括 `:code:` 子串,因此分发事件对、spill 引用及其他关联保持完整。桥接层将转发图片上下文的来源标记为 `{ kind: 'plugin', plugin: 'tools-ptc' }`。
|
||||
|
||||
[V2 到 V3 PTC 规范](../../../../packages/session/session-format-v2-to-v3/README.zh.md#ptc-vocabulary)负责精确的历史标签与归属转换;[原生 V3 准入](../../../../packages/session/session-format-v2-to-v3/README.zh.md#native-v3-admission)负责前代标签拒绝。这些不是运行时别名:不透明扩展不能仅因版本变化就获得 PTC 生命周期含义。
|
||||
|
||||
|
||||
@@ -4076,6 +4076,8 @@ export interface Config {
|
||||
```ts config-catalog
|
||||
/** Program execution limits. */
|
||||
export interface Config {
|
||||
/** Nested tool-call attempts per minute of resolved runtime timeout, including failed calls. */
|
||||
maxSubCallsPerMinute: number
|
||||
/** Maximum overlapping concurrency-safe program subcalls. */
|
||||
maxParallelSubCalls: number
|
||||
}
|
||||
|
||||
@@ -4078,6 +4078,8 @@ export interface Config {
|
||||
```ts config-catalog
|
||||
/** Program execution limits. */
|
||||
export interface Config {
|
||||
/** Nested tool-call attempts per minute of resolved runtime timeout, including failed calls. */
|
||||
maxSubCallsPerMinute: number
|
||||
/** Maximum overlapping concurrency-safe program subcalls. */
|
||||
maxParallelSubCalls: number
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
en: 6cc2cd9ced70bb09
|
||||
zh: 1c0e6cb4ba2ed637
|
||||
/tool-schema-catalog/tool-package-map:
|
||||
en: 17d0b1d1a3c15c7f
|
||||
zh: 1a9d90fc99e0e848
|
||||
en: 7e4516e824b1500e
|
||||
zh: b0ea6f61792d98a0
|
||||
/tool-schema-catalog/deepseek-ai-dsh-plugin-manager:
|
||||
en: e0143916bb634762
|
||||
zh: e0143916bb634762
|
||||
@@ -72,8 +72,8 @@
|
||||
en: 8c8643ef46fd075f
|
||||
zh: 8c8643ef46fd075f
|
||||
/tool-schema-catalog/deepseek-ai-dsh-tool-executor-ptc/run-code:
|
||||
en: a5d49232a604a2fb
|
||||
zh: 720040d11a3e31c2
|
||||
en: 7917e16cbc2c366f
|
||||
zh: c39eb0420544f04a
|
||||
/tool-schema-catalog/deepseek-ai-dsh-plan-mode:
|
||||
en: b500b530845e26a9
|
||||
zh: b500b530845e26a9
|
||||
|
||||
@@ -21,7 +21,7 @@ This table connects model-visible tool names to the plugin package and service s
|
||||
| `@deepseek-ai/dsh-experimental-browser-use-stagehand-native` | `stagehand_act`, `stagehand_extract`, `stagehand_navigate`, `stagehand_observe`, `stagehand_screenshot`, `stagehand_tabs` | `ctx.browserUse`, `ctx.agents`, `ctx.tools`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | - |
|
||||
| `@deepseek-ai/dsh-action-cards` | `propose_action` | `ctx.tools`, `ctx.agents`, `ctx.sessionProjections`, `ctx.remoteAccess` | `tool/call`, `tool/result`, `plugin:action-cards/decision`, `user/message` | - | Declared only to an Agent that loaded the skill of a registered card kind; the kind packages register their actions, and the Personalized recommendations bundle supplies the shipped kinds. |
|
||||
| `@deepseek-ai/dsh-tool-ask-user` | `ask_user_question` | `ctx.tools`, `ctx.userQuestions` | `tool/call`, `tool/result after an answer or timeout`, `late user/message` | - | ask_user_question keeps the original blocking behavior by default; set `mode: timed` to opt into a foreground timeout and pending result while the question remains answerable. In timed mode, `timeout: -1` keeps that call blocking indefinitely. |
|
||||
| `@deepseek-ai/dsh-tool-executor-ptc` | `run_code` | `ctx.workingDirectory (Agent executions)`, `ctx.tools`, `ctx.ptcRuntime`, `ctx.systemPrompt` | `tool/call`, `one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call`, `tool/result` | - | The PTC executor exposes only run_code to the model and declares other visible capabilities in a generated SDK in the runtime’s language. Program bindings enter the shared guarded tool pipeline in submission order; concurrency-safe bodies overlap up to maxParallelSubCalls, and nested results remain linked to the outer program. |
|
||||
| `@deepseek-ai/dsh-tool-executor-ptc` | `run_code` | `ctx.workingDirectory (Agent executions)`, `ctx.tools`, `ctx.ptcRuntime`, `ctx.systemPrompt` | `tool/call`, `one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call`, `tool/result` | - | The PTC executor exposes only run_code to the model and declares other visible capabilities in a generated SDK in the runtime’s language. Program bindings enter the shared guarded tool pipeline in submission order; concurrency-safe bodies overlap up to maxParallelSubCalls, and nested results remain linked to the outer program. The resolved timeout and maxSubCallsPerMinute set the total call allowance; exceeding it terminates the program. |
|
||||
| `@deepseek-ai/dsh-plan-mode` | `exit_plan_mode` | `ctx.tools`, `ctx.systemPrompt`, `ctx.agents`, `ctx.userQuestions (execution time, opportunistic)` | `tool/call`, `plan/mode inactive on an approved review`, `tool/result` | - | exit_plan_mode stays in the model-facing schema while planning is inactive so transitions add no tool-catalog churn on top of the plan-policy change. Its execute path rejects calls outside plan mode; in plan mode it presents the plan over the user-questions seam (approve / keep planning with feedback), and approval logs plan mode inactive at the step boundary. |
|
||||
| `@deepseek-ai/dsh-tool-bash` | `bash` | `ctx.workingDirectory`, `ctx.tools`, `ctx.shell`, `ctx.systemPrompt`, `ctx.shellEnv`, `ctx.jobs for run_in_background and the job-backed foreground path` | `tool/call`, `tool/result` | - | The bash tool is the model-facing consumer of the bash executor seam. With a job registry composed every call registers with the generic `ctx.jobs` runtime as it starts, collected/stopped through the `job_*` tools from `@deepseek-ai/dsh-tool-jobs`; without one, or with `enableRunInBackground: false`, the tool registers a foreground-only schema without the `run_in_background` parameter. |
|
||||
| `@deepseek-ai/dsh-tool-present` | `present` | `ctx.workingDirectory`, `ctx.tools`, `ctx.fs`, `ctx.agents` | `tool/call`, `deliverables/presented after a successful final result`, `tool/result` | - | Deliveries belong to the calling Session; Web ui-deliverables supplies source-file opening and cards. |
|
||||
@@ -661,7 +661,7 @@ Execute a TypeScript program against the available tools. Takes two required arg
|
||||
|
||||
Source: [`packages/core/tool-executor-ptc/src/ptc.ts`](../packages/core/tool-executor-ptc/src/ptc.ts)
|
||||
|
||||
The PTC executor exposes only run_code to the model and declares other visible capabilities in a generated SDK in the runtime’s language. Program bindings enter the shared guarded tool pipeline in submission order; concurrency-safe bodies overlap up to maxParallelSubCalls, and nested results remain linked to the outer program.
|
||||
The PTC executor exposes only run_code to the model and declares other visible capabilities in a generated SDK in the runtime’s language. Program bindings enter the shared guarded tool pipeline in submission order; concurrency-safe bodies overlap up to maxParallelSubCalls, and nested results remain linked to the outer program. The resolved timeout and maxSubCallsPerMinute set the total call allowance; exceeding it terminates the program.
|
||||
|
||||
<a id="deepseek-aidsh-plan-mode"></a>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
| `@deepseek-ai/dsh-experimental-browser-use-stagehand-native` | `stagehand_act`、`stagehand_extract`、`stagehand_navigate`、`stagehand_observe`、`stagehand_screenshot`、`stagehand_tabs` | `ctx.browserUse`、`ctx.agents`、`ctx.tools`、`ctx.systemPrompt` | `tool/call`、`tool/result` | - | - |
|
||||
| `@deepseek-ai/dsh-action-cards` | `propose_action` | `ctx.tools`、`ctx.agents`、`ctx.sessionProjections`、`ctx.remoteAccess` | `tool/call`、`tool/result`、`plugin:action-cards/decision`、`user/message` | - | 只向加载过已注册卡片种类 Skill 的 Agent 声明;各种类的包注册自己的动作,个性化推荐 Bundle 提供随产品发布的种类。 |
|
||||
| `@deepseek-ai/dsh-tool-ask-user` | `ask_user_question` | `ctx.tools`、`ctx.userQuestions` | `tool/call`、`tool/result after an answer or timeout`、`late user/message` | - | ask_user_question 默认保持原有阻塞行为;设置 `mode: timed` 后才启用前台超时与 pending 结果,同时问题仍可回答;timed 模式内 `timeout: -1` 让本次调用无限期阻塞。 |
|
||||
| `@deepseek-ai/dsh-tool-executor-ptc` | `run_code` | `ctx.workingDirectory (Agent executions)`、`ctx.tools`、`ctx.ptcRuntime`、`ctx.systemPrompt` | `tool/call`、`one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call`、`tool/result` | - | PTC 执行器只向模型公开 run_code,并在运行时语言生成的 SDK 中声明其他可见能力。程序绑定按提交顺序进入共享的受守卫工具流水线;并行安全的函数体最多重叠 maxParallelSubCalls 个,嵌套结果保持与外层程序关联。 |
|
||||
| `@deepseek-ai/dsh-tool-executor-ptc` | `run_code` | `ctx.workingDirectory (Agent executions)`、`ctx.tools`、`ctx.ptcRuntime`、`ctx.systemPrompt` | `tool/call`、`one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call`、`tool/result` | - | PTC 执行器只向模型公开 run_code,并在运行时语言生成的 SDK 中声明其他可见能力。程序绑定按提交顺序进入共享的受守卫工具流水线;并行安全的函数体最多重叠 maxParallelSubCalls 个,嵌套结果保持与外层程序关联。最终超时时间与 maxSubCallsPerMinute 决定调用总额度;超限会终止程序。 |
|
||||
| `@deepseek-ai/dsh-plan-mode` | `exit_plan_mode` | `ctx.tools`、`ctx.systemPrompt`、`ctx.agents`、`ctx.userQuestions (execution time, opportunistic)` | `tool/call`、`plan/mode inactive on an approved review`、`tool/result` | - | 规划未激活时,exit_plan_mode 仍保留在面向模型的 schema 中,这样状态转换不会在规划策略变更之外额外造成工具目录变动。其执行路径会拒绝规划模式之外的调用;在规划模式下,它通过用户交互 seam 提交计划(批准/根据反馈继续规划),批准后会在步骤边界记录规划模式已停用。 |
|
||||
| `@deepseek-ai/dsh-tool-bash` | `bash` | `ctx.workingDirectory`、`ctx.tools`、`ctx.shell`、`ctx.systemPrompt`、`ctx.shellEnv`、`ctx.jobs for run_in_background and the job-backed foreground path` | `tool/call`、`tool/result` | - | bash 工具是 bash 执行器 seam 面向模型的消费方。组合中有 job 注册表时,每次调用一启动就注册到通用 `ctx.jobs` 运行时,并通过 `job_*` 工具(来自 `@deepseek-ai/dsh-tool-jobs`)收集/停止;没有注册表或 `enableRunInBackground: false` 时,工具注册不带 `run_in_background` 参数的纯前台 schema。 |
|
||||
| `@deepseek-ai/dsh-tool-present` | `present` | `ctx.workingDirectory`、`ctx.tools`, `ctx.fs`, `ctx.agents` | `tool/call`, `deliverables/presented 在成功的最终结果之后`, `tool/result` | - | 交付归调用方 Session 所有;Web ui-deliverables 提供源文件打开与卡片。 |
|
||||
@@ -665,7 +665,7 @@ ask_user_question 默认保持原有阻塞行为;设置 `mode: timed` 后才
|
||||
|
||||
来源:[`packages/core/tool-executor-ptc/src/ptc.ts`](../packages/core/tool-executor-ptc/src/ptc.ts)
|
||||
|
||||
PTC 执行器只向模型公开 run_code,并在运行时语言生成的 SDK 中声明其他可见能力。程序绑定按提交顺序进入共享的受守卫工具流水线;并行安全的函数体最多重叠 maxParallelSubCalls 个,嵌套结果保持与外层程序关联。
|
||||
PTC 执行器只向模型公开 run_code,并在运行时语言生成的 SDK 中声明其他可见能力。程序绑定按提交顺序进入共享的受守卫工具流水线;并行安全的函数体最多重叠 maxParallelSubCalls 个,嵌套结果保持与外层程序关联。最终超时时间与 maxSubCallsPerMinute 决定调用总额度;超限会终止程序。
|
||||
|
||||
<a id="deepseek-aidsh-plan-mode"></a>
|
||||
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
en: e253ed9cec03a396
|
||||
zh: 922fde282e2b0fd2
|
||||
/deepseek-ai-dsh-tool-executor-ptc/summary:
|
||||
en: 66e159c308afb6c1
|
||||
zh: 29b1a646ae7f918a
|
||||
en: 351b679ea266c40e
|
||||
zh: 72337ba82be757df
|
||||
/deepseek-ai-dsh-tool-executor-ptc/table-of-contents:
|
||||
en: d152484eb41ac6b4
|
||||
zh: 09388d293f9be9cb
|
||||
/deepseek-ai-dsh-tool-executor-ptc/use-this-package:
|
||||
en: b6b7ffab35e1a725
|
||||
zh: fc5db2524e1507d6
|
||||
en: 2db07bc05f05d202
|
||||
zh: 4efdaab79fcc00af
|
||||
/deepseek-ai-dsh-tool-executor-ptc/understand-the-implementation:
|
||||
en: 9d4f61c351945d30
|
||||
zh: 1c575526230b0e02
|
||||
@@ -33,17 +33,20 @@
|
||||
en: eaefd7303f22977d
|
||||
zh: 9c425f9501128224
|
||||
/deepseek-ai-dsh-tool-executor-ptc/model-experience/ptc-mode-schema-and-system-prompt/what-the-model-sees:
|
||||
en: 52e1437dc292ad3a
|
||||
zh: 43fcfb76a3715f51
|
||||
en: 3c4ee15c38a82543
|
||||
zh: c032ede27425f917
|
||||
/deepseek-ai-dsh-tool-executor-ptc/model-experience/ptc-mode-schema-and-system-prompt/what-the-model-sees/typescript-ptc-mode-sdk-instructions-with-bash:
|
||||
en: 3a8df15e4ae32f9e
|
||||
zh: fc4a800ba363e835
|
||||
/deepseek-ai-dsh-tool-executor-ptc/model-experience/ptc-mode-schema-and-system-prompt/what-the-model-sees/tool-call-allowance-exhausted:
|
||||
en: 079e063e47dff7ed
|
||||
zh: 271ee8bb3441eb2e
|
||||
/deepseek-ai-dsh-tool-executor-ptc/model-experience/ptc-mode-schema-and-system-prompt/token-effect:
|
||||
en: 12296fcafc14562c
|
||||
zh: f8e9351bb8c6437d
|
||||
en: 0ae2c687fcf8f300
|
||||
zh: 8a1c5a88a1ca9bbe
|
||||
/deepseek-ai-dsh-tool-executor-ptc/model-experience/ptc-mode-schema-and-system-prompt/kv-cache-effect:
|
||||
en: 78906628d634441f
|
||||
zh: 492f133aea50cfe3
|
||||
en: 4bf1cd2890cfe6c4
|
||||
zh: 5b230b0adc09ddfb
|
||||
/deepseek-ai-dsh-tool-executor-ptc/known-limitations-and-deferred-work:
|
||||
en: 5030c17a9c6a2a85
|
||||
zh: 6334bbe1a1d19cb4
|
||||
|
||||
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
|
||||
|
||||
## Summary
|
||||
|
||||
Use this executor when models should call tools through `run_code` programs. Mount a compatible PTC runtime and tool registry first. Scoped restrictions determine the generated SDK; only `run_code` is directly callable. Configure concurrency for parallel-safe program subcalls.
|
||||
Use this executor when models should call tools through `run_code` programs. Mount a compatible PTC runtime and tool registry first. Scoped restrictions determine the generated SDK; only `run_code` is directly callable. Configure the total tool-call limit per program and concurrency for parallel-safe subcalls.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@@ -30,11 +30,14 @@ Add this row to a composition with `dsh-tools` mounted. The nearest scoped execu
|
||||
```yaml
|
||||
- name: '@deepseek-ai/dsh-tool-executor-ptc'
|
||||
config:
|
||||
maxSubCallsPerMinute: 1024
|
||||
maxParallelSubCalls: 10
|
||||
```
|
||||
|
||||
Root `run_code` calls are exclusive and execute serially. `maxParallelSubCalls` limits parallel-safe nested calls within each program and defaults to `10`; it must be a positive integer, with `1` selecting serial execution. Exclusive calls form barriers.
|
||||
|
||||
`maxSubCallsPerMinute` defaults to `1024` and must be a positive integer. Each `run_code` gets `floor(resolved timeoutMs / 60000 × maxSubCallsPerMinute)` tool-call attempts: the default Node timeout of two minutes allows 2048. Runtime defaults and timeout caps apply before this calculation; no deadline means no finite call allowance. This is a total allowance, not a rolling window, so calls may happen in a burst. All tool bindings share the count, including queued calls, invalid arguments, and failed tools. The next attempt terminates the entire program through the runtime abort path, cancels active subcalls, and abandons queued calls before returning `CODE_RUN_FAILED`. As with a timeout, catching a binding error cannot keep the program running; retained runtime state is discarded. Completed tool effects remain, and the next program starts a fresh count.
|
||||
|
||||
This package declares `ctx.ptcRuntime` and `ctx.systemPrompt` as required injections; Agent-owned programs need `ctx.workingDirectory`. The executor activates only after its runtime is available and withdraws when that runtime unloads. An enabled PTC executor without a runtime stops application startup. A withdrawn PTC selection never falls back to native execution.
|
||||
|
||||
Shared dispatch events and the `ptc-mode` message-source declaration live in `@deepseek-ai/dsh-tool-executor-ptc/types`; Client code imports the same declarations through `@deepseek-ai/dsh-tool-executor-ptc/client`. Both entries exclude Host service declarations. Host policy extensions use `import type` from the package entry for `PtcDispatchLog` and `tools/ptc-dispatch-log` without mounting the executor.
|
||||
@@ -85,7 +88,7 @@ When the caller cancels and the runtime reports `abort`, `run_code` waits for cl
|
||||
|
||||
#### What the model sees
|
||||
|
||||
PTC mode exposes the generated [`run_code` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tool-executor-ptc), the SDK instructions below, and the generated exact SDK block for the loaded runtime's language. Both runtime languages list `code` before `description`; this is presentation order, not an enforced JSON member order. REPL and Script runtimes share the generic tool description and SDK instructions; their `executionInstructions` supply syntax and state rules. The TypeScript bash example returns a value for function-body runtimes, and prints output for REPL and Script runtimes. The instructions identify generated declarations as program-only bindings. When the current `bash` parameter schema accepts the example arguments, they also show a complete `run_code` call around `tools.bash(...)`. The `tools:sdk` section uses first-party order 5000 and disables prompt-variable interpolation, preserving literal `{{…}}` text in tool descriptions and schemas for both runtime languages. Under `ptc`, the prompt also carries the `tools:ptc-only` rule earlier in the first-party order, so the model reads which tools it may call before it reads what each one is for.
|
||||
PTC mode exposes the generated [`run_code` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tool-executor-ptc), the SDK instructions below, and the generated exact SDK block for the loaded runtime's language. Both runtime languages list `code` before `description`; this is presentation order, not an enforced JSON member order. REPL and Script runtimes share the generic tool description and SDK instructions; their `executionInstructions` supply syntax and state rules. The TypeScript bash example returns a value for function-body runtimes, and prints output for REPL and Script runtimes. The instructions identify generated declarations as program-only bindings. When the current `bash` parameter schema accepts the example arguments, they also show a complete `run_code` call around `tools.bash(...)`. The `tools:sdk` section uses first-party order 5000 and disables prompt-variable interpolation, preserving literal `{{…}}` text in tool descriptions and schemas for both runtime languages. Under `ptc`, the prompt also carries the `tools:ptc-only` rule earlier in the first-party order, so the model reads which tools it may call before it reads what each one is for. Exhausting the tool-call allowance returns a failed `run_code` result with the limit, rejected call number, termination notice, and possible infinite-loop warning. Runtime cleanup diagnostics and captured output remain visible.
|
||||
|
||||
##### TypeScript PTC mode SDK instructions with bash
|
||||
|
||||
@@ -106,13 +109,19 @@ Inside the program:
|
||||
Program-only SDK bindings:
|
||||
```
|
||||
|
||||
##### Tool-call allowance exhausted
|
||||
|
||||
```markdown
|
||||
Error: code run failed (abort): run_code exceeded its limit of 2048 tool calls (1024 per minute of its timeout); call 2049 was not dispatched. The run_code program may contain an infinite loop. Check loop termination or split the work into smaller run_code calls.
|
||||
```
|
||||
|
||||
#### Token effect
|
||||
|
||||
Fixed per-request cost proportional to the visible definitions. PTC mode trades end-tool schemas for generated SDK text plus one transport schema rather than promising a universal reduction.
|
||||
Fixed per-request cost proportional to the visible definitions. PTC mode trades end-tool schemas for generated SDK text plus one transport schema rather than promising a universal reduction. The allowance adds no SDK or schema text; its diagnostic contributes tokens through the failed run's result.
|
||||
|
||||
#### KV Cache effect
|
||||
|
||||
Prefix-stable while the PTC mode selection, generated SDK, transport schema, and visible tool set are unchanged. Mode or filter changes may invalidate reuse from the first changed prompt or schema token.
|
||||
Prefix-stable while the PTC mode selection, generated SDK, transport schema, and visible tool set are unchanged. Mode or filter changes may invalidate reuse from the first changed prompt or schema token. Changing `maxSubCallsPerMinute` does not change prompt or schema tokens.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ kind: "package-reference"
|
||||
|
||||
## 概述
|
||||
|
||||
当模型应通过 `run_code` 程序调用工具时,使用此执行器。先挂载兼容的 PTC 运行时和工具注册表。作用域限制决定生成的 SDK;只有 `run_code` 能被直接调用。可配置并行安全的程序子调用并发上限。
|
||||
当模型应通过 `run_code` 程序调用工具时,使用此执行器。先挂载兼容的 PTC 运行时和工具注册表。作用域限制决定生成的 SDK;只有 `run_code` 能被直接调用。可配置每个程序的工具调用总数上限,以及并行安全的子调用并发上限。
|
||||
|
||||
## 目录
|
||||
|
||||
@@ -30,11 +30,14 @@ kind: "package-reference"
|
||||
```yaml
|
||||
- name: '@deepseek-ai/dsh-tool-executor-ptc'
|
||||
config:
|
||||
maxSubCallsPerMinute: 1024
|
||||
maxParallelSubCalls: 10
|
||||
```
|
||||
|
||||
根 `run_code` 调用是独占调用,按串行执行。`maxParallelSubCalls` 限制每个程序中并行安全的嵌套调用数,默认值为 `10`;值必须是正整数,`1` 表示串行执行。独占调用构成屏障。
|
||||
|
||||
`maxSubCallsPerMinute` 默认值为 `1024`,必须是正整数。每次 `run_code` 的工具调用额度为 `floor(最终 timeoutMs / 60000 × maxSubCallsPerMinute)`:Node 默认超时为两分钟,因此允许 2048 次。计算前先应用运行时默认值和超时上限;没有截止时间时,调用额度也没有有限上限。这是总额度,不是滑动窗口,因此可以集中调用。所有工具绑定共用计数,排队调用、无效参数和失败调用也计入。达到上限后,下一次尝试会通过运行时取消路径终止整个程序,取消进行中的子调用,并放弃排队调用,然后返回 `CODE_RUN_FAILED`。与超时一样,捕获绑定错误也不能让程序继续运行;运行时保留的状态会被丢弃。已完成的工具操作仍然生效,新的程序重新计数。
|
||||
|
||||
本包将 `ctx.ptcRuntime` 和 `ctx.systemPrompt` 声明为必需注入;带 Agent 的程序要求 `ctx.workingDirectory`。执行器在运行时可用后才激活,并在运行时卸载时撤回。启用 PTC 执行器但未提供运行时会阻止应用启动。已撤回的 PTC 选择不会回退到原生执行。
|
||||
|
||||
共享分派事件与 `ptc-mode` 消息来源声明位于 `@deepseek-ai/dsh-tool-executor-ptc/types`;Client 代码通过 `@deepseek-ai/dsh-tool-executor-ptc/client` 导入同一组声明。两个入口都不引入 Host 服务声明。Host 策略扩展通过包主入口的 `import type` 获取 `PtcDispatchLog` 与 `tools/ptc-dispatch-log`,无需挂载执行器。
|
||||
@@ -85,7 +88,7 @@ PTC 执行器公开保留的 `run_code` 传输以及按所加载运行时语言
|
||||
|
||||
#### 模型看到什么
|
||||
|
||||
PTC mode 会公开生成的 [`run_code` schema](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tool-executor-ptc)、下方 SDK 说明,以及按所加载运行时语言生成的精确 SDK 块。两种运行时语言都将 `code` 列在 `description` 前;这是呈现顺序,不强制 JSON 成员顺序。REPL 与 Script 运行时共用通用工具描述和 SDK 说明;语法与状态规则由各自的 `executionInstructions` 提供。TypeScript bash 示例对函数体运行时返回值,对 REPL 和 Script 运行时打印输出。说明将生成声明明确标为只能在程序内使用的绑定。当当前 `bash` 参数 schema 接受示例参数时,说明还会给出以 `run_code` 包住 `tools.bash(...)` 的完整调用。`tools:sdk` 段使用 first-party 顺序 5000,并关闭提示词变量插值,使两种运行时语言都原样保留工具描述和 schema 中的 `{{…}}` 文本。在 `ptc` 下,提示词还会带上处于更早 first-party 顺序的 `tools:ptc-only` 规则,让模型先读到「可以调用哪些工具」再读「每个工具做什么」。
|
||||
PTC mode 会公开生成的 [`run_code` schema](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tool-executor-ptc)、下方 SDK 说明,以及按所加载运行时语言生成的精确 SDK 块。两种运行时语言都将 `code` 列在 `description` 前;这是呈现顺序,不强制 JSON 成员顺序。REPL 与 Script 运行时共用通用工具描述和 SDK 说明;语法与状态规则由各自的 `executionInstructions` 提供。TypeScript bash 示例对函数体运行时返回值,对 REPL 和 Script 运行时打印输出。说明将生成声明明确标为只能在程序内使用的绑定。当当前 `bash` 参数 schema 接受示例参数时,说明还会给出以 `run_code` 包住 `tools.bash(...)` 的完整调用。`tools:sdk` 段使用 first-party 顺序 5000,并关闭提示词变量插值,使两种运行时语言都原样保留工具描述和 schema 中的 `{{…}}` 文本。在 `ptc` 下,提示词还会带上处于更早 first-party 顺序的 `tools:ptc-only` 规则,让模型先读到「可以调用哪些工具」再读「每个工具做什么」。 工具调用额度耗尽时,`run_code` 返回失败结果,说明额度、被拒绝的调用序号、程序已终止,以及可能存在死循环。运行时清理诊断和已捕获的输出仍然可见。
|
||||
|
||||
##### 带 bash 的 TypeScript PTC mode SDK 说明
|
||||
|
||||
@@ -106,13 +109,19 @@ Inside the program:
|
||||
Program-only SDK bindings:
|
||||
```
|
||||
|
||||
##### 工具调用额度耗尽
|
||||
|
||||
```markdown
|
||||
Error: code run failed (abort): run_code exceeded its limit of 2048 tool calls (1024 per minute of its timeout); call 2049 was not dispatched. The run_code program may contain an infinite loop. Check loop termination or split the work into smaller run_code calls.
|
||||
```
|
||||
|
||||
#### Token 影响
|
||||
|
||||
每次请求的固定成本与可见定义成正比。PTC mode 使用生成的 SDK 文本加一个传输 schema 取代最终工具 schema,但不承诺普遍减少成本。
|
||||
每次请求的固定成本与可见定义成正比。PTC mode 使用生成的 SDK 文本加一个传输 schema 取代最终工具 schema,但不承诺普遍减少成本。调用额度不增加 SDK 或 schema 文本;超限诊断通过该次运行的失败结果消耗 token。
|
||||
|
||||
#### KV Cache 影响
|
||||
|
||||
只要 PTC mode 选择、生成的 SDK、传输 schema 与可见工具集合不变,前缀就保持稳定。模式或筛选器变更可能从第一个改变的提示词或 schema token 起使复用失效。
|
||||
只要 PTC mode 选择、生成的 SDK、传输 schema 与可见工具集合不变,前缀就保持稳定。模式或筛选器变更可能从第一个改变的提示词或 schema token 起使复用失效。修改 `maxSubCallsPerMinute` 不会改变提示词或 schema token。
|
||||
|
||||
## 已知限制与延期工作
|
||||
|
||||
|
||||
@@ -3,11 +3,14 @@ import z from '@deepseek-ai/schemastery'
|
||||
|
||||
/** Program execution limits. */
|
||||
export interface Config {
|
||||
/** Nested tool-call attempts per minute of resolved runtime timeout, including failed calls. */
|
||||
maxSubCallsPerMinute: number
|
||||
/** Maximum overlapping concurrency-safe program subcalls. */
|
||||
maxParallelSubCalls: number
|
||||
}
|
||||
|
||||
/** Validated program subcall concurrency; one makes subcalls serial. */
|
||||
/** Validated timeout-based call allowance and concurrency; parallelism of one makes subcalls serial. */
|
||||
export const Config: z<Partial<Config>, Config> = z.object({
|
||||
maxSubCallsPerMinute: z.natural().min(1).default(1024),
|
||||
maxParallelSubCalls: z.natural().min(1).default(10),
|
||||
})
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
/** Program execution failures reported by PTC. @module */
|
||||
import { HarnessError } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
/**
|
||||
* Host-only abort reason for an exhausted tool-call allowance; the excess call is never dispatched.
|
||||
* The executor recognizes this class at settlement and reports CODE_RUN_FAILED to the model.
|
||||
*/
|
||||
export class ToolCallLimitExceededError extends Error {
|
||||
/**
|
||||
* Describe the rejected call and how to correct the program.
|
||||
* @param limit - Timeout-derived maximum tool-call attempts for one run_code invocation.
|
||||
* @param callNumber - One-based attempt rejected without invoking the tool.
|
||||
* @param callsPerMinute - Attempts granted per minute of run_code timeout.
|
||||
*/
|
||||
constructor(limit: number, callNumber: number, callsPerMinute: number) {
|
||||
super(`run_code exceeded its limit of ${limit} tool calls (${callsPerMinute} per minute of its timeout); `
|
||||
+ `call ${callNumber} was not dispatched. The run_code program may contain an infinite loop. `
|
||||
+ 'Check loop termination or split the work into smaller run_code calls.')
|
||||
this.name = 'ToolCallLimitExceededError'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown by `run_code` when the program run itself failed — a program
|
||||
* exception, a budget expiry, a runtime-local abort, or substrate death. Caller
|
||||
|
||||
@@ -20,11 +20,11 @@ const SDK_RENDERERS = {
|
||||
export class PtcExecutor implements ToolExecutor {
|
||||
readonly sections: readonly ToolPromptSection[]
|
||||
private readonly pipeline: ToolExecutionPipeline
|
||||
private readonly maxParallelSubCalls: number
|
||||
private readonly config: Config
|
||||
private ptcTransport: ToolDefinition | undefined
|
||||
|
||||
constructor(private readonly ctx: Context, config: Partial<Config> = {}, private readonly maxParallel: () => number = () => 10) {
|
||||
this.maxParallelSubCalls = Config(config).maxParallelSubCalls
|
||||
this.config = Config(config)
|
||||
const tools = ctx.tools
|
||||
this.sections = [
|
||||
{ name: 'tools:ptc-only', order: ctx.systemPrompt.getSectionOrder('PTC_ONLY'), text: PTC_ONLY_INSTRUCTION, interpolate: false },
|
||||
@@ -57,7 +57,7 @@ export class PtcExecutor implements ToolExecutor {
|
||||
|
||||
/** Keep the reserved transport outside registered capabilities and their restrictions. */
|
||||
private requirePtcTransport(): ToolDefinition {
|
||||
this.ptcTransport ??= createRunCodeTool(this.ctx, this.pipeline, this.maxParallelSubCalls)
|
||||
this.ptcTransport ??= createRunCodeTool(this.ctx, this.pipeline, this.config)
|
||||
return this.ptcTransport
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ export const inject = ['tools', 'systemPrompt', 'ptcRuntime']
|
||||
/**
|
||||
* Select PTC execution in the mounting scope.
|
||||
* @param ctx - context owning this executor's selection and lifetime.
|
||||
* @param config - program subcall concurrency limits.
|
||||
* @param config - timeout-based subcall allowance and concurrency limits.
|
||||
*/
|
||||
export function apply(ctx: Context, config: Config): void {
|
||||
ctx.tools.setExecutor(new PtcExecutor(ctx, config))
|
||||
|
||||
@@ -24,7 +24,8 @@ import type { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline'
|
||||
import { toolErrorMessage } from '@deepseek-ai/dsh-tools/pipeline'
|
||||
import { scopeTarget } from '@deepseek-ai/dsh-scope'
|
||||
import { PtcCalls } from './ptc-calls.ts'
|
||||
import { CodeRunFailedError } from './errors.ts'
|
||||
import { CodeRunFailedError, ToolCallLimitExceededError } from './errors.ts'
|
||||
import type { Config } from './config.ts'
|
||||
import type { ToolDefinition, ToolExecutionResult, ToolRunContext } from '@deepseek-ai/dsh-tools'
|
||||
import type {} from './types.ts'
|
||||
|
||||
@@ -267,14 +268,15 @@ function resolveSandboxPolicy(ctx: Context, exec: ToolRunContext): SandboxExecut
|
||||
* Build the reserved `run_code` transport outside registered capabilities.
|
||||
* @param ctx - executor context owning policy listeners and active program cleanup.
|
||||
* @param executor - shared invocation pipeline for nested calls.
|
||||
* @param maxParallel - maximum overlapping concurrency-safe subcalls.
|
||||
* @param config - validated timeout-based tool-call allowance and concurrency limits.
|
||||
* @returns the transport definition with runtime-dependent schema text.
|
||||
*/
|
||||
export function createRunCodeTool(
|
||||
ctx: Context,
|
||||
executor: ToolExecutionPipeline,
|
||||
maxParallel: number,
|
||||
config: Config,
|
||||
): ToolDefinition {
|
||||
const { maxSubCallsPerMinute, maxParallelSubCalls: maxParallel } = config
|
||||
// Retain services for log settlement after the executor context becomes inactive.
|
||||
const tools = ctx.tools
|
||||
const logger = ctx.logger
|
||||
@@ -377,7 +379,8 @@ export function createRunCodeTool(
|
||||
const onOuterAbort = (): void => { runController.abort(callSignal.reason) }
|
||||
callSignal.addEventListener('abort', onOuterAbort, { once: true })
|
||||
|
||||
let dispatches = 0
|
||||
let toolCallAttempts = 0
|
||||
let maxSubCalls: number
|
||||
// The per-run scheduler uses the registry's staged interface and follows
|
||||
// the same concurrency rules as the native loop. It also follows the
|
||||
// native loop's SEQUENCING: every ordered stage (the dispatch-start
|
||||
@@ -504,8 +507,14 @@ export function createRunCodeTool(
|
||||
if (runOver()) {
|
||||
throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} not dispatched`)
|
||||
}
|
||||
// Count before validation and queuing so repeated failed calls also exhaust the allowance.
|
||||
const n = ++toolCallAttempts
|
||||
if (n > maxSubCalls) {
|
||||
const error = new ToolCallLimitExceededError(maxSubCalls, n, maxSubCallsPerMinute)
|
||||
runController.abort(error)
|
||||
throw error
|
||||
}
|
||||
const normalized = jsonNormalizeArgs(rawArgs)
|
||||
const n = ++dispatches
|
||||
const subCallId = brandString<ToolCallId>(`${String(exec.callId)}:ptc:${n}`)
|
||||
const input = {
|
||||
callId: subCallId,
|
||||
@@ -672,7 +681,7 @@ export function createRunCodeTool(
|
||||
runtimes.add(provider)
|
||||
}
|
||||
}
|
||||
result = await runtime.run(runtime.resolve({
|
||||
const spec = runtime.resolve({
|
||||
program: args.code,
|
||||
...exec.agent === undefined || policy?.mode !== standingPolicy?.mode ? {} : { stateOwner: exec.agent },
|
||||
bindings: [{
|
||||
@@ -684,7 +693,10 @@ export function createRunCodeTool(
|
||||
...cwd !== undefined ? { cwd } : {},
|
||||
...policy !== undefined ? { sandboxPolicy: policy } : {},
|
||||
...args.timeoutMs !== undefined ? { timeoutMs: args.timeoutMs } : {},
|
||||
}))
|
||||
})
|
||||
maxSubCalls = spec.timeoutMs === null ? Infinity
|
||||
: Math.floor(spec.timeoutMs / 60_000 * maxSubCallsPerMinute)
|
||||
result = await runtime.run(spec)
|
||||
} finally {
|
||||
// Abort sub-dispatches and drain every in-flight dispatch before
|
||||
// closing the turn (queued-unstarted ones are abandoned unlogged).
|
||||
@@ -694,7 +706,9 @@ export function createRunCodeTool(
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
if (result.error.kind === 'abort' && exec.signal.aborted) {
|
||||
const limitError = runController.signal.reason instanceof ToolCallLimitExceededError
|
||||
? runController.signal.reason : undefined
|
||||
if (result.error.kind === 'abort' && exec.signal.aborted && limitError === undefined) {
|
||||
const error = new HarnessError('tool call aborted', TOOL_ABORTED)
|
||||
error.name = 'AbortError'
|
||||
throw error
|
||||
@@ -702,7 +716,9 @@ export function createRunCodeTool(
|
||||
const logsText = result.logs.length > 0 ? `\nCaptured output:\n${result.logs.join('\n')}` : ''
|
||||
const sandboxText = result.sandbox === undefined ? ''
|
||||
: `\nFile sandbox: ${result.sandbox.mode}${result.sandbox.enforcement === undefined ? '' : `; enforcement: ${result.sandbox.enforcement}`}${result.sandbox.denied ? '; operation denied' : ''}.`
|
||||
throw new CodeRunFailedError(`code run failed (${result.error.kind}): ${result.error.message}${logsText}${sandboxText}${result.sandbox?.denied ? escalationGuidance(runtime.sandboxMode) : ''}`)
|
||||
const failureText = limitError === undefined || limitError.message === result.error.message
|
||||
? result.error.message : `${limitError.message}\n${result.error.message}`
|
||||
throw new CodeRunFailedError(`code run failed (${result.error.kind}): ${failureText}${logsText}${sandboxText}${result.sandbox?.denied ? escalationGuidance(runtime.sandboxMode) : ''}`)
|
||||
}
|
||||
return {
|
||||
logs: result.logs,
|
||||
|
||||
@@ -65,6 +65,7 @@ class FakeRuntime extends PtcRuntime {
|
||||
|
||||
interface SetupOptions {
|
||||
mode?: 'native' | 'ptc'
|
||||
maxSubCallsPerMinute?: number
|
||||
maxParallelSubCalls?: number
|
||||
runtime?: false | { language?: string }
|
||||
toolOrder?: string[]
|
||||
@@ -75,7 +76,10 @@ async function setup(options: SetupOptions = {}) {
|
||||
provideWorkingDirectoryFixture(ctx)
|
||||
await ctx.plugin(SystemPrompt, { ...options.toolOrder ? { toolOrder: options.toolOrder } : {} })
|
||||
await ctx.plugin(ToolRuntime)
|
||||
const executor = options.mode === 'native' ? new NativeToolExecutor(ctx) : new PtcExecutor(ctx, { ...options.maxParallelSubCalls !== undefined ? { maxParallelSubCalls: options.maxParallelSubCalls } : {} })
|
||||
const executor = options.mode === 'native' ? new NativeToolExecutor(ctx) : new PtcExecutor(ctx, {
|
||||
...options.maxSubCallsPerMinute !== undefined ? { maxSubCallsPerMinute: options.maxSubCallsPerMinute } : {},
|
||||
...options.maxParallelSubCalls !== undefined ? { maxParallelSubCalls: options.maxParallelSubCalls } : {},
|
||||
})
|
||||
ctx.tools.setExecutor(executor)
|
||||
let runtime: FakeRuntime | undefined
|
||||
if (options.runtime !== false) {
|
||||
@@ -150,13 +154,16 @@ async function executeCall(ctx: Context, input: ToolExecutionInput): Promise<Too
|
||||
async function runCode(
|
||||
ctx: Context,
|
||||
code: string,
|
||||
extras: { agent?: Agent; signal?: AbortSignal; description?: string } = {},
|
||||
extras: { agent?: Agent; signal?: AbortSignal; description?: string; timeoutMs?: number } = {},
|
||||
): Promise<ToolExecutionResult> {
|
||||
return executeCall(ctx, {
|
||||
signal: testToolSignal,
|
||||
callId: ToolCallId('call-1'),
|
||||
name: RUN_CODE_NAME,
|
||||
arguments: { code, description: extras.description ?? 'Run the test program' },
|
||||
arguments: {
|
||||
code, description: extras.description ?? 'Run the test program',
|
||||
...extras.timeoutMs !== undefined ? { timeoutMs: extras.timeoutMs } : {},
|
||||
},
|
||||
...extras.agent ? { agent: extras.agent } : {},
|
||||
...extras.signal ? { signal: extras.signal } : {},
|
||||
})
|
||||
@@ -663,6 +670,171 @@ describe('mode-aware wire contribution', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('the per-program tool-call limit', () => {
|
||||
it.each([0, -1, 1.5, Number.NaN, Number.POSITIVE_INFINITY])('rejects maxSubCallsPerMinute=%s in configuration', (maxSubCallsPerMinute) => {
|
||||
expect(() => PtcToolExecutor.Config({ maxSubCallsPerMinute })).toThrow('maxSubCallsPerMinute')
|
||||
})
|
||||
|
||||
it('allows 2048 calls with the default two-minute timeout, rejects later attempts, and resets for the next program', async () => {
|
||||
const { ctx, runtime } = await setup()
|
||||
onTestFinished(() => ctx.fiber.dispose())
|
||||
const calls = registerEcho(ctx)
|
||||
const { agent, events } = fakeAgent()
|
||||
runtime.behavior = async (request) => {
|
||||
const echo = request.bindings[0]!.functions.echo!
|
||||
for (let n = 1; n <= 2048; n++) await echo({ value: String(n) })
|
||||
const message = 'run_code exceeded its limit of 2048 tool calls (1024 per minute of its timeout); call 2049 was not dispatched. '
|
||||
+ 'The run_code program may contain an infinite loop. Check loop termination or split the work into smaller run_code calls.'
|
||||
await expect(echo({ value: 'rejected' })).rejects.toThrow(message)
|
||||
expect(request.signal?.aborted).toBe(true)
|
||||
await expect(echo({ value: 'caught the error' })).rejects.toThrow('run_code run is over')
|
||||
return { logs: [], error: { kind: 'abort', message } }
|
||||
}
|
||||
const result = await runCode(ctx, 'program', { agent })
|
||||
expect(result.error).toMatchObject({ info: { code: 'CODE_RUN_FAILED' } })
|
||||
expect(result.error?.message).toContain('call 2049 was not dispatched')
|
||||
expect(calls).toHaveLength(2048)
|
||||
expect(events.filter(event => event.type === 'tool/ptc-dispatch-start')).toHaveLength(2048)
|
||||
expect(events.filter(event => event.type === 'tool/ptc-dispatch')).toHaveLength(2048)
|
||||
|
||||
runtime.behavior = async request => ({ logs: [], value: await request.bindings[0]!.functions.echo!({ value: 'next run' }) })
|
||||
expect((await runCode(ctx, 'next program', { agent })).isError).toBe(false)
|
||||
expect(calls).toHaveLength(2049)
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ requestedMs: 30_000, resolvedMs: 30_000, limit: 2 },
|
||||
{ requestedMs: 90_000, resolvedMs: 90_000, limit: 6 },
|
||||
{ requestedMs: 10_000, resolvedMs: 10_000, limit: 0 },
|
||||
{ requestedMs: 600_000, resolvedMs: 90_000, limit: 6 },
|
||||
])('uses the resolved timeout $resolvedMs ms for an override of $requestedMs ms', async ({ requestedMs, resolvedMs, limit }) => {
|
||||
const { ctx, runtime } = await setup({ maxSubCallsPerMinute: 4 })
|
||||
onTestFinished(() => ctx.fiber.dispose())
|
||||
vi.spyOn(runtime, 'timeout', 'get').mockReturnValue({ defaultMs: 120_000, maxMs: 600_000 })
|
||||
const resolver = vi.spyOn(runtime, 'resolve').mockImplementation(request => ({
|
||||
...request, cwd: process.cwd(), timeoutMs: resolvedMs,
|
||||
}))
|
||||
const calls = registerEcho(ctx)
|
||||
runtime.behavior = async (request) => {
|
||||
expect(request.timeoutMs).toBe(resolvedMs)
|
||||
const echo = request.bindings[0]!.functions.echo!
|
||||
for (let n = 0; n < limit; n++) await echo({ value: String(n) })
|
||||
await expect(echo({ value: 'rejected' })).rejects.toThrow(`limit of ${limit} tool calls (4 per minute of its timeout); call ${limit + 1} was not dispatched`)
|
||||
expect(request.signal?.aborted).toBe(true)
|
||||
return { logs: [], error: { kind: 'abort', message: String(request.signal?.reason) } }
|
||||
}
|
||||
expect((await runCode(ctx, 'program', { timeoutMs: requestedMs })).isError).toBe(true)
|
||||
expect(calls).toHaveLength(limit)
|
||||
expect(resolver).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ timeoutMs: requestedMs }))
|
||||
})
|
||||
|
||||
it('does not impose a finite call allowance when the runtime resolves no deadline', async () => {
|
||||
const { ctx, runtime } = await setup({ maxSubCallsPerMinute: 1 })
|
||||
onTestFinished(() => ctx.fiber.dispose())
|
||||
vi.spyOn(runtime, 'resolve').mockImplementation(request => ({
|
||||
...request, cwd: process.cwd(), timeoutMs: null,
|
||||
}))
|
||||
const calls = registerEcho(ctx)
|
||||
runtime.behavior = async (request) => {
|
||||
for (let n = 0; n < 3; n++) await request.bindings[0]!.functions.echo!({ value: String(n) })
|
||||
return { logs: [] }
|
||||
}
|
||||
expect((await runCode(ctx, 'program')).isError).toBe(false)
|
||||
expect(calls).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('aborts active calls across tool names, abandons queued calls, and drains their logs before returning', async () => {
|
||||
const { ctx, runtime } = await setup({ maxSubCallsPerMinute: 1, maxParallelSubCalls: 1 })
|
||||
onTestFinished(() => ctx.fiber.dispose())
|
||||
const { agent, events } = fakeAgent()
|
||||
const entered = Promise.withResolvers<undefined>()
|
||||
const aborted = Promise.withResolvers<undefined>()
|
||||
const queued = registerEcho(ctx, 'queued')
|
||||
let cleaned = false
|
||||
ctx.tools.register(defineContentToolFixture({
|
||||
name: 'active', description: 'Wait for cancellation.', parameters: {},
|
||||
async execute(_args, exec) {
|
||||
exec.signal.addEventListener('abort', () => { aborted.resolve(undefined) }, { once: true })
|
||||
entered.resolve(undefined)
|
||||
await aborted.promise
|
||||
cleaned = true
|
||||
return []
|
||||
},
|
||||
}))
|
||||
const outcomes: string[] = []
|
||||
runtime.behavior = async (request) => {
|
||||
const functions = request.bindings[0]!.functions
|
||||
void functions.active!({}).catch(() => { outcomes.push('active canceled') })
|
||||
await entered.promise
|
||||
void functions.queued!({ value: 'queued' }).catch(() => { outcomes.push('queued abandoned') })
|
||||
await expect(functions.queued!({ value: 'excess' })).rejects.toThrow('limit of 2 tool calls (1 per minute of its timeout); call 3 was not dispatched')
|
||||
expect(request.signal?.aborted).toBe(true)
|
||||
return { logs: [], error: { kind: 'abort', message: 'execution canceled' } }
|
||||
}
|
||||
const result = await runCode(ctx, 'program', { agent })
|
||||
expect(result.error).toMatchObject({ info: { code: 'CODE_RUN_FAILED' } })
|
||||
expect(cleaned).toBe(true)
|
||||
expect(outcomes.sort()).toEqual(['active canceled', 'queued abandoned'])
|
||||
expect(queued).toEqual([])
|
||||
expect(events.filter(event => event.type === 'tool/ptc-dispatch-start').map(event => event.data))
|
||||
.toMatchObject([{ name: 'active', subCallId: 'call-1:ptc:1' }])
|
||||
expect(events.filter(event => event.type === 'tool/ptc-dispatch').map(event => event.data))
|
||||
.toMatchObject([{ name: 'active', subCallId: 'call-1:ptc:1', isError: true }])
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ failure: { kind: 'abort', message: 'execution canceled; retained variables were cleared' }, cancelCaller: false },
|
||||
{ failure: { kind: 'worker-exit', message: 'managed process cleanup failed: fixture failure' }, cancelCaller: false },
|
||||
{ failure: { kind: 'abort', message: 'execution canceled' }, cancelCaller: true },
|
||||
] as const)('preserves the call limit and runtime $failure.kind diagnostics with caller cancellation $cancelCaller', async ({ failure, cancelCaller }) => {
|
||||
const { ctx, runtime } = await setup({ maxSubCallsPerMinute: 1 })
|
||||
onTestFinished(() => ctx.fiber.dispose())
|
||||
const controller = new AbortController()
|
||||
registerEcho(ctx)
|
||||
runtime.behavior = async (request) => {
|
||||
const echo = request.bindings[0]!.functions.echo!
|
||||
await echo({ value: 'one' })
|
||||
await echo({ value: 'two' })
|
||||
await expect(echo({ value: 'excess' })).rejects.toThrow('limit of 2 tool calls')
|
||||
expect(request.signal?.aborted).toBe(true)
|
||||
if (cancelCaller) controller.abort('later caller cancellation')
|
||||
return { logs: ['before termination'], error: failure }
|
||||
}
|
||||
const result = await runCode(ctx, 'program', { signal: controller.signal })
|
||||
expect(result.error).toMatchObject({ info: { code: 'CODE_RUN_FAILED' } })
|
||||
expect(result.error?.message).toContain(`code run failed (${failure.kind}): run_code exceeded its limit of 2 tool calls`)
|
||||
expect(result.error?.message).toContain(failure.message)
|
||||
expect(result.error?.message).toContain('before termination')
|
||||
})
|
||||
|
||||
it('counts failed argument validation and failed tool executions', async () => {
|
||||
const { ctx, runtime } = await setup({ maxSubCallsPerMinute: 3 })
|
||||
onTestFinished(() => ctx.fiber.dispose())
|
||||
vi.spyOn(runtime, 'resolve').mockImplementation(request => ({
|
||||
...request, cwd: process.cwd(), timeoutMs: 60_000,
|
||||
}))
|
||||
const calls = registerEcho(ctx)
|
||||
const failure = vi.fn(async () => { throw new Error('tool failed') })
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'fail', description: 'Fail a tool call.', parameters: {},
|
||||
output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: value }] },
|
||||
execute: failure,
|
||||
}))
|
||||
runtime.behavior = async (request) => {
|
||||
const functions = request.bindings[0]!.functions
|
||||
await expect(functions.echo!(undefined)).rejects.toThrow('lossless JSON')
|
||||
await expect(functions.echo!({})).rejects.toThrow()
|
||||
await expect(functions.fail!({})).rejects.toThrow('tool failed')
|
||||
await expect(functions.echo!({ value: 'rejected' })).rejects.toThrow('limit of 3 tool calls (3 per minute of its timeout); call 4 was not dispatched')
|
||||
expect(request.signal?.aborted).toBe(true)
|
||||
return { logs: [], error: { kind: 'abort', message: String(request.signal?.reason) } }
|
||||
}
|
||||
expect((await runCode(ctx, 'program')).isError).toBe(true)
|
||||
expect(calls).toEqual([])
|
||||
expect(failure).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('the sub-dispatch scheduler (native concurrency contract)', () => {
|
||||
/** Register a tool whose calls resolve only when the test releases them; returns live-call telemetry. */
|
||||
function registerGated(ctx: Context, name: string, concurrencySafe: boolean) {
|
||||
|
||||
@@ -296,7 +296,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
|
||||
await ctx.plugin(NodePtcRuntime)
|
||||
},
|
||||
note:
|
||||
'The PTC executor exposes only run_code to the model and declares other visible capabilities in a generated SDK in the runtime’s language. Program bindings enter the shared guarded tool pipeline in submission order; concurrency-safe bodies overlap up to maxParallelSubCalls, and nested results remain linked to the outer program.',
|
||||
'The PTC executor exposes only run_code to the model and declares other visible capabilities in a generated SDK in the runtime’s language. Program bindings enter the shared guarded tool pipeline in submission order; concurrency-safe bodies overlap up to maxParallelSubCalls, and nested results remain linked to the outer program. The resolved timeout and maxSubCallsPerMinute set the total call allowance; exceeding it terminates the program.',
|
||||
},
|
||||
{
|
||||
pkg: '@deepseek-ai/dsh-plan-mode',
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# One call per minute with the default two-minute timeout allows two calls.
|
||||
- id: tool-executor-ptc
|
||||
config:
|
||||
maxSubCallsPerMinute: 1
|
||||
@@ -0,0 +1,36 @@
|
||||
{"type":"session","version":4,"id":"{{session:1}}","createdAt":1785014439563,"cwd":"{{cwd}}","isSeeded":false,"delegationDepth":0}
|
||||
{"type":"permission/preset","data":{"preset":"danger-full-access"}}
|
||||
{"type":"sandbox/mode","data":{"mode":"danger-full-access"}}
|
||||
{"type":"approval/policy","data":{"policy":"never"}}
|
||||
{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"With one tool call per timeout minute and a two-minute timeout, try three appends to calls.txt inside a try/catch that spins forever after an error. Then use a fresh run_code to append next. Report the terminated program and the successful next run."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"}]}}
|
||||
{"type":"turn/start","data":{"turn":1}}
|
||||
{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
|
||||
{"type":"step/start","data":{"turn":1,"step":1}}
|
||||
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"With one tool call per timeout minute and a two-minute timeout, try three appends to calls.txt inside a try/catch that spins forever after an error. Then use a fresh run_code to append next. Report the terminated program and the successful next run."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).\n\nYou are the root agent. Your agent id is \"{{session:1}}\".\n\nTo message other agents, use send_message({ agent_id: \"<agent-id>\", message: \"...\" }).\nUse list_agents({}) to find your collaborator agents and their ids."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."},{"name":"subagent:identity","text":"You are the root agent. Your agent id is \"{{session:1}}\".\n\nTo message other agents, use send_message({ agent_id: \"<agent-id>\", message: \"...\" }).\nUse list_agents({}) to find your collaborator agents and their ids."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
|
||||
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
|
||||
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
|
||||
{"type":"session/title","data":{"title":"With one tool call per","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_limit","name":"run_code","arguments":"{\"code\":\"try { for (let n = 0; n < 3; n++) await tools.bash({ command: \\\"printf 'call\\\\n' >> calls.txt\\\", description: 'Append one completed call' }); } catch (error) { while (true) {} }\",\"description\":\"Verify the call limit terminates a caught loop\"}"},{"type":"tool-call","id":"call_next","name":"run_code","arguments":"{\"code\":\"await tools.bash({ command: \\\"printf 'next\\\\n' >> calls.txt\\\", description: 'Append from a fresh program' }); return 'next run succeeded';\",\"description\":\"Verify the next program has fresh allowance\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1793676779236,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"chunk","time":1793676779236,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_limit","name":"run_code","arguments":"{\"code\":\"try { for (let n = 0; n < 3; n++) await tools.bash({ command: \\\"printf 'call\\\\n' >> calls.txt\\\", description: 'Append one completed call' }); } catch (error) { while (true) {} }\",\"description\":\"Verify the call limit terminates a caught loop\"}"}}},{"type":"chunk","time":1793676779236,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"chunk","time":1793676779236,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_next","name":"run_code","arguments":"{\"code\":\"await tools.bash({ command: \\\"printf 'next\\\\n' >> calls.txt\\\", description: 'Append from a fresh program' }); return 'next run succeeded';\",\"description\":\"Verify the next program has fresh allowance\"}"}}},{"type":"chunk","time":1793676779236,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1793676779236,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_limit","name":"run_code","arguments":"{\"code\":\"try { for (let n = 0; n < 3; n++) await tools.bash({ command: \\\"printf 'call\\\\n' >> calls.txt\\\", description: 'Append one completed call' }); } catch (error) { while (true) {} }\",\"description\":\"Verify the call limit terminates a caught loop\"}"}}
|
||||
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_limit","parentCallId":"call_limit","subCallId":"call_limit:ptc:1","name":"bash","arguments":{"command":"printf 'call\n' >> calls.txt","description":"Append one completed call"}}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf 'call\n' >> calls.txt","startedAt":1793676779550,"toolCall":{"callId":"call_limit:ptc:1","rootCallId":"call_limit"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf 'call\n' >> calls.txt","startedAt":1793676779550,"toolCall":{"callId":"call_limit:ptc:1","rootCallId":"call_limit"},"finishedAt":1793676779661,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_limit","parentCallId":"call_limit","subCallId":"call_limit:ptc:1","name":"bash","arguments":{"command":"printf 'call\n' >> calls.txt","description":"Append one completed call"},"isError":false,"content":[{"type":"text","text":"(no output)"}],"meta":{"cwd":"{{cwd}}"}}}
|
||||
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_limit","parentCallId":"call_limit","subCallId":"call_limit:ptc:2","name":"bash","arguments":{"command":"printf 'call\n' >> calls.txt","description":"Append one completed call"}}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"printf 'call\n' >> calls.txt","startedAt":1793676779672,"toolCall":{"callId":"call_limit:ptc:2","rootCallId":"call_limit"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"printf 'call\n' >> calls.txt","startedAt":1793676779672,"toolCall":{"callId":"call_limit:ptc:2","rootCallId":"call_limit"},"finishedAt":1793676779794,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_limit","parentCallId":"call_limit","subCallId":"call_limit:ptc:2","name":"bash","arguments":{"command":"printf 'call\n' >> calls.txt","description":"Append one completed call"},"isError":false,"content":[{"type":"text","text":"(no output)"}],"meta":{"cwd":"{{cwd}}"}}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_limit"},"toolCallId":"call_limit","content":[{"type":"text","text":"Error: code run failed (abort): run_code exceeded its limit of 2 tool calls (1 per minute of its timeout); call 3 was not dispatched. The run_code program may contain an infinite loop. Check loop termination or split the work into smaller run_code calls.\nFile sandbox: danger-full-access."}],"isError":true,"id":"{{message:5}}"},"error":{"name":"CodeRunFailedError","code":"CODE_RUN_FAILED"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_next","name":"run_code","arguments":"{\"code\":\"await tools.bash({ command: \\\"printf 'next\\\\n' >> calls.txt\\\", description: 'Append from a fresh program' }); return 'next run succeeded';\",\"description\":\"Verify the next program has fresh allowance\"}"}}
|
||||
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_next","parentCallId":"call_next","subCallId":"call_next:ptc:1","name":"bash","arguments":{"command":"printf 'next\n' >> calls.txt","description":"Append from a fresh program"}}}
|
||||
{"type":"job/registered","data":{"jobId":"{{id:3}}","owner":"{{session:1}}","kind":"bash","label":"printf 'next\n' >> calls.txt","startedAt":1793676780083,"toolCall":{"callId":"call_next:ptc:1","rootCallId":"call_next"}}}
|
||||
{"type":"job/settled","data":{"jobId":"{{id:3}}","owner":"{{session:1}}","kind":"bash","label":"printf 'next\n' >> calls.txt","startedAt":1793676780083,"toolCall":{"callId":"call_next:ptc:1","rootCallId":"call_next"},"finishedAt":1793676780195,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
|
||||
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_next","parentCallId":"call_next","subCallId":"call_next:ptc:1","name":"bash","arguments":{"command":"printf 'next\n' >> calls.txt","description":"Append from a fresh program"},"isError":false,"content":[{"type":"text","text":"(no output)"}],"meta":{"cwd":"{{cwd}}"}}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_next"},"toolCallId":"call_next","content":[{"type":"text","text":"next run succeeded"}],"isError":false,"id":"{{message:6}}"}},"sourceEventSeqs":[24],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"The third tool call exceeded the limit of two and terminated the entire program, including its catch loop. Check the program for an infinite loop. The next run_code succeeded with a fresh allowance."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:7}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1793676780358,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"chunk","time":1793676780358,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"The third tool call exceeded the limit of two and terminated the entire program, including its catch loop. Check the program for an infinite loop. The next run_code succeeded with a fresh allowance."}}},{"type":"chunk","time":1793676780358,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1793676780358,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":2}}
|
||||
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
@@ -0,0 +1,9 @@
|
||||
version: 1
|
||||
scenario: ptc-tool-call-limit
|
||||
profile: headless
|
||||
composition: ptc
|
||||
recording: authored
|
||||
header:
|
||||
class: ptc
|
||||
workspace:
|
||||
final: true
|
||||
@@ -0,0 +1,3 @@
|
||||
call
|
||||
call
|
||||
next
|
||||
Reference in New Issue
Block a user