@x1a0f3n9/dsh-tool-call-timeout-policy 0.1.5-rc.2
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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +135 -0
- package/README.zh.md +135 -0
- package/lib/index.js +144 -0
- package/lib/types/index.d.ts +32 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/guard/timeout-policy/README.md
|
|
5
|
+
README.md: a9381595b25adcdbf2c4bf5e2c7034580e7813e7
|
|
6
|
+
README.zh.md: 27938b1793bf75a3df809e16782857701d1b11cb
|
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Cooperative time limit for cancellation-aware tool calls, mapping a settled timeout to a clear model error for users and maintainers choosing or debugging the plugin."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @x1a0f3n9/dsh-tool-call-timeout-policy
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
Use this package to give tool calls their configured cooperative time limits and return a clear timeout error to the model after cancellation settles. Calls that finish in time are unchanged. A tool that ignores or slowly handles cancellation can keep the caller waiting because the package cannot hard-stop downstream work. Each tool supplies its own limit; the package has no configuration and is enabled in the `dsh` base bundle.
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Use this package](#use-this-package)
|
|
17
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
18
|
+
- [Further Exploration](#further-exploration)
|
|
19
|
+
- [Model Experience](#model-experience)
|
|
20
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
21
|
+
- [Dev Note](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## Use this package
|
|
27
|
+
|
|
28
|
+
The common path is one line: add the plugin to the composition — the `dsh` base bundle already has it. Tools that have a limit configured are protected automatically; every other tool is untouched.
|
|
29
|
+
|
|
30
|
+
### When to choose it
|
|
31
|
+
|
|
32
|
+
Choose it when the model calls tools that can take a long time, those tools honor `exec.signal`, and you want a predictable timed-out answer after cancellation settles. Avoid it when a tool must be hard-stopped at its limit — the plugin can only ask a tool to stop, so a tool that ignores cancellation keeps running and keeps the caller waiting — and when you want one default limit for every tool, because each tool's limit comes from that tool's own configuration.
|
|
33
|
+
|
|
34
|
+
### Setting it up
|
|
35
|
+
|
|
36
|
+
Mount the plugin with no configuration:
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
- name: '@x1a0f3n9/dsh-tool-call-timeout-policy'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The limit is set where the tool is configured. For example, `dsh-tool-web`'s `fetchTimeoutMs`/`searchTimeoutMs` settings (default 30,000 ms) put the limit on `web_fetch` and `web_search`. Tools without a limit — the shipped `bash`, `read`, `write`, and `edit` — are never cut off. The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-tool-web) lists the tool settings that produce limits.
|
|
43
|
+
|
|
44
|
+
### What you get
|
|
45
|
+
|
|
46
|
+
When the deadline fires, the plugin aborts the derived `exec.signal`. After downstream code honors cancellation and `next()` settles, the model receives `Error: tool call timed out after <ms>ms` as an error result, so it can decide to retry, adjust, or give up. A tool that ignores or slowly handles the signal keeps the caller waiting and produces no timeout result until it settles; calls that finish in time are unchanged.
|
|
47
|
+
|
|
48
|
+
-----
|
|
49
|
+
|
|
50
|
+
<a id="understand-the-implementation"></a>
|
|
51
|
+
## Understand the implementation
|
|
52
|
+
|
|
53
|
+
<details>
|
|
54
|
+
<summary>Implementation internals — click to expand</summary>
|
|
55
|
+
|
|
56
|
+
This section explains how the plugin arms a deadline around each dispatch and maps it to the `TOOL_TIMEOUT` result, and points at the code that realizes it; the observable behavior is fully covered in [Use this package](#use-this-package).
|
|
57
|
+
|
|
58
|
+
### Design philosophy
|
|
59
|
+
|
|
60
|
+
The wrapper is built on four commitments:
|
|
61
|
+
|
|
62
|
+
- **Enforcement home, not a library.** `dsh-timeout` owns timing and classification (`deadline`, `timeoutOf`); this plugin owns the per-call wiring over `tools/execute`; each capability owns termination. The split is recorded in the [timeout-deadline-library Agent Note](../../../.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md).
|
|
63
|
+
- **The tool declares its own budget.** `timeoutMs` lives on the tool's `ToolDefinition`, read from the registry (`ctx.tools.get(exec.name, exec.agent)?.timeoutMs`), so a mistyped tool name is impossible and undeclared tools delegate untouched.
|
|
64
|
+
- **Scoped classification.** `TOOL_TIMEOUT` serves as both the internal `deadline` classification code and the structured error `code`; scoping `timeoutOf` to it keeps a nested outer deadline (another wrapper's timer that fired first) from being misread as this plugin's timeout — it reads as an ordinary upstream cancel.
|
|
65
|
+
- **Signal swap, then restore.** Cordis `next()` ignores passed arguments, so the wrapper mutates the shared `exec` in place: it swaps the derived deadline signal onto `exec` for dispatch and restores the caller's signal in a `finally`, so `tools/post-execute` listeners never see this plugin's possibly-aborted signal.
|
|
66
|
+
|
|
67
|
+
### How a deadline is armed and mapped
|
|
68
|
+
|
|
69
|
+
One `tools/execute` listener reads the dispatched tool's declared limit from the registry (`ctx.tools.get(exec.name, exec.agent)?.timeoutMs`); a tool without a limit delegates untouched. For a limited tool, `deadline(exec.signal, timeoutMs, TOOL_TIMEOUT)` builds a fused signal that the wrapper swaps onto `exec` for dispatch and restores in a `finally`, so `tools/post-execute` listeners never see the derived signal. When the wrapper's own timer fired — `timeoutOf(d.signal, 'TOOL_TIMEOUT')` scoped by the code, so a nested outer deadline reads as an ordinary upstream cancel — the dispatched result, already normalized into an error result by dispatch, is replaced with the structured result: `isError: true`, content `Error: tool call timed out after <ms>ms`, and error info `{ name: 'ToolTimeoutError', code: 'TOOL_TIMEOUT' }`.
|
|
70
|
+
|
|
71
|
+
### Composing with other wrappers
|
|
72
|
+
|
|
73
|
+
Multiple `tools/execute` listeners compose by Cordis registration order, which chooses the semantics: the timeout registered outer covers a whole retry operation, the timeout registered inner covers each attempt.
|
|
74
|
+
|
|
75
|
+
### Source map
|
|
76
|
+
|
|
77
|
+
| File | Role |
|
|
78
|
+
|---|---|
|
|
79
|
+
| [`src/index.ts`](src/index.ts) | Plugin entry: `TOOL_TIMEOUT`, `name`/`inject`/`apply`, the `tools/execute` wrapper |
|
|
80
|
+
| — | No runtime invariant companion is published; this stateless policy plugin owns no package-local event history or mutable data relation beyond the seam it intercepts. |
|
|
81
|
+
|
|
82
|
+
</details>
|
|
83
|
+
|
|
84
|
+
-----
|
|
85
|
+
|
|
86
|
+
<a id="further-exploration"></a>
|
|
87
|
+
## Further Exploration
|
|
88
|
+
|
|
89
|
+
Read these pages when the package-level contract is not enough. They move from the tool-call pipeline to the timeout-library split, the enforced limits, and the guard group map.
|
|
90
|
+
|
|
91
|
+
- [Tools subsystem reference](../../../docs/subsystems/tools.md) — the `tools/execute` waterfall and decision shapes this wrapper hooks.
|
|
92
|
+
- [Timeout deadline library Agent Note](../../../.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md) — the timing/termination split and why the deadline only notifies.
|
|
93
|
+
- [Generated configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-tool-web) — `dsh-tool-web`'s `fetchTimeoutMs`/`searchTimeoutMs` budgets the policy enforces.
|
|
94
|
+
- [guard group map](../README.md) — the sibling guard packages and the loop-hygiene family.
|
|
95
|
+
|
|
96
|
+
-----
|
|
97
|
+
|
|
98
|
+
<a id="model-experience"></a>
|
|
99
|
+
## Model Experience
|
|
100
|
+
|
|
101
|
+
### Conditional tool result
|
|
102
|
+
|
|
103
|
+
#### What the model sees
|
|
104
|
+
|
|
105
|
+
This plugin adds no prompt or schema. If a declared deadline wins and downstream cancellation settles, it replaces the provider's outcome with `Error: tool call timed out after <ms>ms` plus the structured `TOOL_TIMEOUT` error; otherwise the original result passes through unchanged. A downstream call that never settles cannot produce a timeout result.
|
|
106
|
+
|
|
107
|
+
#### Token effect
|
|
108
|
+
|
|
109
|
+
Zero tokens on non-timeout calls. A timeout adds one small retained error result and can prevent a larger late provider result from entering context.
|
|
110
|
+
|
|
111
|
+
#### KV Cache effect
|
|
112
|
+
|
|
113
|
+
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV Cache entries.
|
|
114
|
+
|
|
115
|
+
## Known Limitations and Deferred Work
|
|
116
|
+
|
|
117
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
These limits define when the policy is a poor fit. They are current package constraints, not a task backlog.
|
|
121
|
+
|
|
122
|
+
- **Cooperative, never a hard kill** — the deadline only notifies via `exec.signal`; a tool that ignores the signal does not stop on timeout, the wrapper remains inside `await next()`, and the model receives no timeout result until downstream settles.
|
|
123
|
+
- **No blanket budget** — only tools that declare `timeoutMs` on their `ToolDefinition` get a deadline; undeclared tools (the shipped `bash`, `read`, `write`, and `edit` declare none) have no registry-wide default.
|
|
124
|
+
|
|
125
|
+
<a id="dev-note"></a>
|
|
126
|
+
### Dev Note
|
|
127
|
+
|
|
128
|
+
<details>
|
|
129
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
130
|
+
|
|
131
|
+
This Dev Note is working context for maintainers: open questions and directions that are not decided. It is explicitly non-authoritative — shipped behavior, limits, and accepted rationale live in the sections above, the package code, and the linked Agent Notes.
|
|
132
|
+
|
|
133
|
+
The `src/index.ts` FIXME asks to settle a `@x1a0f3n9/dsh-timeout-guard` rename; the [naming ledger](../../../.agents/notes/archived/architecture/2026-08-11-repository-naming-contract-and-rename-ledger.md) already records `@x1a0f3n9/dsh-tool-call-timeout-policy` as the decided name, so the FIXME is stale pending a code cleanup.
|
|
134
|
+
|
|
135
|
+
</details>
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "为配合取消的工具调用设置协作式时间上限,并在超时流程完成后映射为清晰的模型错误,供选择或排查此插件的用户与维护者阅读。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @x1a0f3n9/dsh-tool-call-timeout-policy
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
使用本包可为工具调用执行其配置的协作式时间上限,并在取消完成后向模型返回清晰的超时错误。按时完成的调用保持不变。忽略或缓慢处理取消的工具仍可能让调用方继续等待,因为本包无法硬性停止下游工作。每个工具分别提供自己的限时;本包无需配置,并随 `dsh` 基础组合包默认启用。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [使用本包](#use-this-package)
|
|
17
|
+
- [理解实现](#understand-the-implementation)
|
|
18
|
+
- [进一步探索](#further-exploration)
|
|
19
|
+
- [模型体验](#model-experience)
|
|
20
|
+
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
|
21
|
+
- [开发备注](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## 使用本包
|
|
27
|
+
|
|
28
|
+
常用路径只有一行:把插件加入组合——`dsh` 基础组合包已经包含它。配置了限时的工具会被自动保护;其余工具完全不受影响。
|
|
29
|
+
|
|
30
|
+
### 何时选择
|
|
31
|
+
|
|
32
|
+
当模型会调用耗时很长的工具、这些工具会遵守 `exec.signal`,且你希望在取消完成后得到可预期的超时答复时,选择它。当工具必须在到达限时后被硬性停止时——插件只能请求工具停止,因此忽略取消的工具会继续运行并让调用方继续等待——以及当你希望为所有工具设置一个统一默认限时时(因为每个工具的限时来自该工具自身的配置),避免使用它。
|
|
33
|
+
|
|
34
|
+
### 设置
|
|
35
|
+
|
|
36
|
+
无需任何配置即可挂载插件:
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
- name: '@x1a0f3n9/dsh-tool-call-timeout-policy'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
限时在配置工具的位置设置。例如,`dsh-tool-web` 的 `fetchTimeoutMs`/`searchTimeoutMs` 设置(默认 30,000 ms)把限时放到 `web_fetch` 与 `web_search` 上。没有限时的工具——随附的 `bash`、`read`、`write`、`edit`——绝不会被切断。生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-tool-web)列出会产生限时的工具设置。
|
|
43
|
+
|
|
44
|
+
### 你会得到什么
|
|
45
|
+
|
|
46
|
+
截止时间触发时,插件会中止派生的 `exec.signal`。下游代码遵守取消且 `next()` 完成后,模型会收到标记为错误的 `Error: tool call timed out after <ms>ms` 工具结果,从而决定重试、调整或放弃。忽略或缓慢处理该信号的工具会让调用方继续等待,并且在自身完成前不会产生超时结果;按时完成的调用保持不变。
|
|
47
|
+
|
|
48
|
+
-----
|
|
49
|
+
|
|
50
|
+
<a id="understand-the-implementation"></a>
|
|
51
|
+
## 理解实现
|
|
52
|
+
|
|
53
|
+
<details>
|
|
54
|
+
<summary>实现细节——点击展开</summary>
|
|
55
|
+
|
|
56
|
+
本节解释插件如何在每次分发周围设置截止时间并将其映射为 `TOOL_TIMEOUT` 结果,并指出实现它的代码位置;可观察行为已在[使用本包](#use-this-package)中完整说明。
|
|
57
|
+
|
|
58
|
+
### 设计理念
|
|
59
|
+
|
|
60
|
+
包装层建立在四项承诺之上:
|
|
61
|
+
|
|
62
|
+
- **强制执行归属,而非库。** `dsh-timeout` 负责时序与分类(`deadline`、`timeoutOf`);本插件负责 `tools/execute` 上的单次调用接线;各能力负责终止。该拆分记录在[超时截止时间库 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.zh.md) 中。
|
|
63
|
+
- **工具声明自己的预算。** `timeoutMs` 位于工具的 `ToolDefinition` 上,从注册表读取(`ctx.tools.get(exec.name, exec.agent)?.timeoutMs`),因此不可能拼错工具名,未声明工具原样委派。
|
|
64
|
+
- **作用域分类。** `TOOL_TIMEOUT` 同时用作内部 `deadline` 分类码与结构化错误 `code`;把 `timeoutOf` 限定到它,可避免嵌套的外层截止时间(先触发的另一包装层计时器)被误读为本插件的超时——它读作普通的上游取消。
|
|
65
|
+
- **先交换信号,再恢复。** Cordis `next()` 忽略传入参数,因此包装层原地修改共享 `exec`:分发时把派生的截止时间信号换到 `exec` 上,并在 `finally` 中恢复调用方信号,使 `tools/post-execute` 监听器永远看不到本插件可能已中止的信号。
|
|
66
|
+
|
|
67
|
+
### 截止时间如何设置与映射
|
|
68
|
+
|
|
69
|
+
一个 `tools/execute` 监听器从注册表读取已分发工具声明的限时(`ctx.tools.get(exec.name, exec.agent)?.timeoutMs`);没有限时的工具原样委派。对有限时的工具,`deadline(exec.signal, timeoutMs, TOOL_TIMEOUT)` 构建融合信号,包装层在分发时把它换到 `exec` 上并在 `finally` 中恢复,使 `tools/post-execute` 监听器永远看不到派生信号。当包装层自己的计时器触发时——`timeoutOf(d.signal, 'TOOL_TIMEOUT')` 以代码限定作用域,因此嵌套的外层截止时间读作普通的上游取消——已被分发规范化为错误结果的分发结果会被替换为结构化结果:`isError: true`、内容 `Error: tool call timed out after <ms>ms`、错误信息 `{ name: 'ToolTimeoutError', code: 'TOOL_TIMEOUT' }`。
|
|
70
|
+
|
|
71
|
+
### 与其他包装层组合
|
|
72
|
+
|
|
73
|
+
多个 `tools/execute` 监听器按 Cordis 注册顺序组合,注册顺序决定语义:超时注册在外层时覆盖整个重试操作,注册在内层时覆盖每次尝试。
|
|
74
|
+
|
|
75
|
+
### 源码地图
|
|
76
|
+
|
|
77
|
+
| 文件 | 职责 |
|
|
78
|
+
|---|---|
|
|
79
|
+
| [`src/index.ts`](src/index.ts) | 插件入口:`TOOL_TIMEOUT`、`name`/`inject`/`apply`、`tools/execute` 包装层 |
|
|
80
|
+
| — | 不发布运行时不变式伴生入口;此无状态策略插件不拥有包级事件历史,也不拥有所拦截 seam 之外的可变数据关系。 |
|
|
81
|
+
|
|
82
|
+
</details>
|
|
83
|
+
|
|
84
|
+
-----
|
|
85
|
+
|
|
86
|
+
<a id="further-exploration"></a>
|
|
87
|
+
## 进一步探索
|
|
88
|
+
|
|
89
|
+
当包级约定不够用时阅读以下页面。它们从工具调用流水线逐步进入超时库拆分、被执行的限时与 guard 组映射。
|
|
90
|
+
|
|
91
|
+
- [工具子系统参考](../../../docs/subsystems/tools.zh.md)——本包装层挂钩的 `tools/execute` waterfall(瀑布式事件)与决策形态。
|
|
92
|
+
- [超时截止时间库 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.zh.md)——时序/终止拆分以及截止时间为何只通知。
|
|
93
|
+
- [生成配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-tool-web)——策略所执行的 `dsh-tool-web` 的 `fetchTimeoutMs`/`searchTimeoutMs` 预算。
|
|
94
|
+
- [guard 组映射](../README.zh.md)——同组的 guard 包与循环卫生家族。
|
|
95
|
+
|
|
96
|
+
-----
|
|
97
|
+
|
|
98
|
+
<a id="model-experience"></a>
|
|
99
|
+
## 模型体验
|
|
100
|
+
|
|
101
|
+
### 条件工具结果
|
|
102
|
+
|
|
103
|
+
#### 模型看到什么
|
|
104
|
+
|
|
105
|
+
此插件不添加提示词或 schema。如果已声明的截止时间先到且下游取消完成,它会用 `Error: tool call timed out after <ms>ms` 与结构化 `TOOL_TIMEOUT` 错误替换提供方结果;否则原结果保持不变。永不完成的下游调用无法产生超时结果。
|
|
106
|
+
|
|
107
|
+
#### Token 影响
|
|
108
|
+
|
|
109
|
+
未超时的调用不会增加 token。超时会添加一条会被保留的简短错误结果,并可防止体积更大、较晚返回的提供方结果进入上下文。
|
|
110
|
+
|
|
111
|
+
#### KV Cache 影响
|
|
112
|
+
|
|
113
|
+
仅追加;新可见内容位于可复用请求前缀之后,不会使现有 KV Cache 条目失效。
|
|
114
|
+
|
|
115
|
+
## 已知限制与延期工作
|
|
116
|
+
|
|
117
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
这些限制说明策略何时不合适。它们是当前包约束,不是任务积压。
|
|
121
|
+
|
|
122
|
+
- **协作式,绝不是硬终止**——截止时间只通过 `exec.signal` 通知;忽略该信号的工具不会在超时时停止,包装层仍停留在 `await next()` 内,模型要等下游完成后才可能收到超时结果。
|
|
123
|
+
- **没有统一预算**——只有声明 `timeoutMs` 并将其放在 `ToolDefinition` 上的工具才会获得截止时间;未声明工具(随附的 `bash`、`read`、`write`、`edit` 有意不声明)没有注册表级默认值。
|
|
124
|
+
|
|
125
|
+
<a id="dev-note"></a>
|
|
126
|
+
### 开发备注
|
|
127
|
+
|
|
128
|
+
<details>
|
|
129
|
+
<summary>维护者的工作上下文——点击展开</summary>
|
|
130
|
+
|
|
131
|
+
本开发备注是维护者的工作上下文:开放问题与尚未决定的探索方向。它明确不具权威性——已交付的行为、限制与既定理由以上文、包代码和相关 Agent Note 为准。
|
|
132
|
+
|
|
133
|
+
`src/index.ts` 中的 FIXME 要求确定 `@x1a0f3n9/dsh-timeout-guard` 改名;[改名台账](../../../.agents/notes/archived/architecture/2026-08-11-repository-naming-contract-and-rename-ledger.md) 已把 `@x1a0f3n9/dsh-tool-call-timeout-policy` 记录为既定名称,因此该 FIXME 已陈旧,待代码清理。
|
|
134
|
+
|
|
135
|
+
</details>
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { deadline, timeoutOf } from "@x1a0f3n9/dsh-timeout";
|
|
2
|
+
//#region lib/types/index.js
|
|
3
|
+
/**
|
|
4
|
+
* Cooperative tool-call timeout enforcer. A tool declares `timeoutMs` and
|
|
5
|
+
* promises to honor `exec.signal`; this wrapper arms that deadline and maps its
|
|
6
|
+
* own expiry to `TOOL_TIMEOUT` without racing or abandoning the tool promise.
|
|
7
|
+
*
|
|
8
|
+
* FIXME: settle the intended `@x1a0f3n9/dsh-timeout-guard` rename before the
|
|
9
|
+
* first tagged release — suggestion only, aligning the name with its `guard/`
|
|
10
|
+
* home; decide at resolution time
|
|
11
|
+
* ([regrouping Agent Note](../../../../.agents/notes/archived/architecture/2026-07-29-package-regrouping.md)).
|
|
12
|
+
*
|
|
13
|
+
* @module @x1a0f3n9/dsh-tool-call-timeout-policy
|
|
14
|
+
*/
|
|
15
|
+
var __addDisposableResource = function(env, value, async) {
|
|
16
|
+
if (value !== null && value !== void 0) {
|
|
17
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
18
|
+
var dispose, inner;
|
|
19
|
+
if (async) {
|
|
20
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
21
|
+
dispose = value[Symbol.asyncDispose];
|
|
22
|
+
}
|
|
23
|
+
if (dispose === void 0) {
|
|
24
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
25
|
+
dispose = value[Symbol.dispose];
|
|
26
|
+
if (async) inner = dispose;
|
|
27
|
+
}
|
|
28
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
29
|
+
if (inner) dispose = function() {
|
|
30
|
+
try {
|
|
31
|
+
inner.call(this);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
return Promise.reject(e);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
env.stack.push({
|
|
37
|
+
value,
|
|
38
|
+
dispose,
|
|
39
|
+
async
|
|
40
|
+
});
|
|
41
|
+
} else if (async) env.stack.push({ async: true });
|
|
42
|
+
return value;
|
|
43
|
+
};
|
|
44
|
+
var __disposeResources = (function(SuppressedError) {
|
|
45
|
+
return function(env) {
|
|
46
|
+
function fail(e) {
|
|
47
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
48
|
+
env.hasError = true;
|
|
49
|
+
}
|
|
50
|
+
var r, s = 0;
|
|
51
|
+
function next() {
|
|
52
|
+
while (r = env.stack.pop()) try {
|
|
53
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
54
|
+
if (r.dispose) {
|
|
55
|
+
var result = r.dispose.call(r.value);
|
|
56
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
|
|
57
|
+
fail(e);
|
|
58
|
+
return next();
|
|
59
|
+
});
|
|
60
|
+
} else s |= 1;
|
|
61
|
+
} catch (e) {
|
|
62
|
+
fail(e);
|
|
63
|
+
}
|
|
64
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
65
|
+
if (env.hasError) throw env.error;
|
|
66
|
+
}
|
|
67
|
+
return next();
|
|
68
|
+
};
|
|
69
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
|
|
70
|
+
var e = new Error(message);
|
|
71
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
72
|
+
});
|
|
73
|
+
/**
|
|
74
|
+
* The code owned by this plugin, used BOTH as the internal {@link deadline}
|
|
75
|
+
* classification code AND as the structured error `code` on the replacement
|
|
76
|
+
* tool result. Scoping {@link timeoutOf} to it keeps a nested outer deadline
|
|
77
|
+
* (another `tools/execute` wrapper's timer that fired first) from being misread
|
|
78
|
+
* as this plugin's own timeout — it reads as an ordinary upstream cancel.
|
|
79
|
+
*/
|
|
80
|
+
const TOOL_TIMEOUT = "TOOL_TIMEOUT";
|
|
81
|
+
/** Cordis plugin name used by loader diagnostics. */
|
|
82
|
+
const name = "timeout-policy";
|
|
83
|
+
/** The tool registry service this plugin wraps (`tools/execute`) and reads (`get`). */
|
|
84
|
+
const inject = ["tools"];
|
|
85
|
+
/**
|
|
86
|
+
* The structured result substituted when this plugin's deadline wins. `content`
|
|
87
|
+
* is the model-facing message; `error.code` is the same {@link TOOL_TIMEOUT}
|
|
88
|
+
* this plugin owns, so a retry/sandbox plugin (and replay) can route on it.
|
|
89
|
+
*
|
|
90
|
+
* @param timeoutMs - the elapsed budget, rendered into the model-facing message.
|
|
91
|
+
* @returns the `isError` {@link ToolExecutionResult} with a `TOOL_TIMEOUT` error.
|
|
92
|
+
*/
|
|
93
|
+
function toolTimeoutResult(timeoutMs) {
|
|
94
|
+
const message = `tool call timed out after ${timeoutMs}ms`;
|
|
95
|
+
return {
|
|
96
|
+
content: [{
|
|
97
|
+
type: "text",
|
|
98
|
+
text: `Error: ${message}`
|
|
99
|
+
}],
|
|
100
|
+
isError: true,
|
|
101
|
+
error: {
|
|
102
|
+
message,
|
|
103
|
+
info: {
|
|
104
|
+
name: "ToolTimeoutError",
|
|
105
|
+
code: TOOL_TIMEOUT
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Register the timeout wrapper. It resolves the caller-visible tool definition,
|
|
112
|
+
* temporarily replaces `exec.signal`, delegates, restores the upstream signal,
|
|
113
|
+
* and replaces the result only when this wrapper's own timer fired.
|
|
114
|
+
*/
|
|
115
|
+
function apply(ctx) {
|
|
116
|
+
ctx.on("tools/execute", async (exec, next) => {
|
|
117
|
+
const env_1 = {
|
|
118
|
+
stack: [],
|
|
119
|
+
error: void 0,
|
|
120
|
+
hasError: false
|
|
121
|
+
};
|
|
122
|
+
try {
|
|
123
|
+
const timeoutMs = ctx.tools.get(exec.name, exec.agent)?.timeoutMs;
|
|
124
|
+
if (timeoutMs === void 0) return next();
|
|
125
|
+
const d = __addDisposableResource(env_1, deadline(exec.signal, timeoutMs, TOOL_TIMEOUT), false);
|
|
126
|
+
const upstream = exec.signal;
|
|
127
|
+
exec.signal = d.signal;
|
|
128
|
+
try {
|
|
129
|
+
const result = await next();
|
|
130
|
+
if (timeoutOf(d.signal, "TOOL_TIMEOUT") !== void 0) return toolTimeoutResult(timeoutMs);
|
|
131
|
+
return result;
|
|
132
|
+
} finally {
|
|
133
|
+
exec.signal = upstream;
|
|
134
|
+
}
|
|
135
|
+
} catch (e_1) {
|
|
136
|
+
env_1.error = e_1;
|
|
137
|
+
env_1.hasError = true;
|
|
138
|
+
} finally {
|
|
139
|
+
__disposeResources(env_1);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
//#endregion
|
|
144
|
+
export { TOOL_TIMEOUT, apply, inject, name };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cooperative tool-call timeout enforcer. A tool declares `timeoutMs` and
|
|
3
|
+
* promises to honor `exec.signal`; this wrapper arms that deadline and maps its
|
|
4
|
+
* own expiry to `TOOL_TIMEOUT` without racing or abandoning the tool promise.
|
|
5
|
+
*
|
|
6
|
+
* FIXME: settle the intended `@x1a0f3n9/dsh-timeout-guard` rename before the
|
|
7
|
+
* first tagged release — suggestion only, aligning the name with its `guard/`
|
|
8
|
+
* home; decide at resolution time
|
|
9
|
+
* ([regrouping Agent Note](../../../../.agents/notes/archived/architecture/2026-07-29-package-regrouping.md)).
|
|
10
|
+
*
|
|
11
|
+
* @module @x1a0f3n9/dsh-tool-call-timeout-policy
|
|
12
|
+
*/
|
|
13
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
14
|
+
/**
|
|
15
|
+
* The code owned by this plugin, used BOTH as the internal {@link deadline}
|
|
16
|
+
* classification code AND as the structured error `code` on the replacement
|
|
17
|
+
* tool result. Scoping {@link timeoutOf} to it keeps a nested outer deadline
|
|
18
|
+
* (another `tools/execute` wrapper's timer that fired first) from being misread
|
|
19
|
+
* as this plugin's own timeout — it reads as an ordinary upstream cancel.
|
|
20
|
+
*/
|
|
21
|
+
export declare const TOOL_TIMEOUT = "TOOL_TIMEOUT";
|
|
22
|
+
/** Cordis plugin name used by loader diagnostics. */
|
|
23
|
+
export declare const name = "timeout-policy";
|
|
24
|
+
/** The tool registry service this plugin wraps (`tools/execute`) and reads (`get`). */
|
|
25
|
+
export declare const inject: string[];
|
|
26
|
+
/**
|
|
27
|
+
* Register the timeout wrapper. It resolves the caller-visible tool definition,
|
|
28
|
+
* temporarily replaces `exec.signal`, delegates, restores the upstream signal,
|
|
29
|
+
* and replaces the result only when this wrapper's own timer fired.
|
|
30
|
+
*/
|
|
31
|
+
export declare function apply(ctx: Context): void;
|
|
32
|
+
//# sourceMappingURL=index.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@x1a0f3n9/dsh-tool-call-timeout-policy",
|
|
3
|
+
"description": "Tool-call timeout policy: a tools/execute wrapper that arms a per-tool deadline on exec.signal and returns TOOL_TIMEOUT when it wins",
|
|
4
|
+
"version": "0.1.5-rc.2",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/guard/timeout-policy"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./src/*": "./src/*",
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"lib/index.js",
|
|
26
|
+
"lib/types/**/*.d.ts"
|
|
27
|
+
],
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@x1a0f3n9/dsh-llm": "^0.1.5-rc.2",
|
|
31
|
+
"@x1a0f3n9/dsh-timeout": "^0.1.5-rc.2",
|
|
32
|
+
"@x1a0f3n9/dsh-tools": "^0.1.5-rc.2",
|
|
33
|
+
"@deepseek-ai/cordis": "^4.0.2"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@x1a0f3n9/dsh-llm": "^0.1.5-rc.2",
|
|
37
|
+
"@x1a0f3n9/dsh-tools": "^0.1.5-rc.2",
|
|
38
|
+
"@x1a0f3n9/dsh-timeout": "^0.1.5-rc.2",
|
|
39
|
+
"@deepseek-ai/cordis": "^4.0.2"
|
|
40
|
+
}
|
|
41
|
+
}
|