@qilin-agent/tool-ask-user 0.0.0-stage → 3.1.3
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 +76 -0
- package/README.md +151 -2
- package/README.zh.md +152 -0
- package/lib/index.js +324 -0
- package/lib/types/index.d.ts +22 -0
- package/lib/types/timed.d.ts +11 -0
- package/package.json +46 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kkutysllb
|
|
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,76 @@
|
|
|
1
|
+
# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
|
|
2
|
+
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
|
3
|
+
# After editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/interaction/tool-ask-user/README.md
|
|
5
|
+
/:
|
|
6
|
+
en: fd744e4beac0ee66
|
|
7
|
+
zh: 27288856740f5dbf
|
|
8
|
+
/qilin-tool-ask-user:
|
|
9
|
+
en: ebc5ef51f6f8f9fb
|
|
10
|
+
zh: fc0d76e83cbc056c
|
|
11
|
+
/qilin-tool-ask-user/summary:
|
|
12
|
+
en: 45bb29d463d1e8d2
|
|
13
|
+
zh: a462610ab6e8e61b
|
|
14
|
+
/qilin-tool-ask-user/table-of-contents:
|
|
15
|
+
en: d152484eb41ac6b4
|
|
16
|
+
zh: 09388d293f9be9cb
|
|
17
|
+
/qilin-tool-ask-user/use-this-package:
|
|
18
|
+
en: ed8ec46772509d66
|
|
19
|
+
zh: b06aad135f7a4fa3
|
|
20
|
+
/qilin-tool-ask-user/use-this-package/when-to-call-the-tool:
|
|
21
|
+
en: 5a142d88def1a4fd
|
|
22
|
+
zh: 2ae5573abf23d76a
|
|
23
|
+
/qilin-tool-ask-user/use-this-package/what-the-model-gets-back:
|
|
24
|
+
en: d0047cb9dda88606
|
|
25
|
+
zh: d3432561db23f65a
|
|
26
|
+
/qilin-tool-ask-user/use-this-package/when-the-call-fails:
|
|
27
|
+
en: dd379ed6282761b6
|
|
28
|
+
zh: fa7da19a52091403
|
|
29
|
+
/qilin-tool-ask-user/understand-the-implementation:
|
|
30
|
+
en: e9b9d46be0b45ca2
|
|
31
|
+
zh: c02403486473cd80
|
|
32
|
+
/qilin-tool-ask-user/understand-the-implementation/source-map:
|
|
33
|
+
en: 4533d1e8efbd438f
|
|
34
|
+
zh: 69a265056b455b96
|
|
35
|
+
/qilin-tool-ask-user/understand-the-implementation/consumer-role:
|
|
36
|
+
en: 30c2b751e1b04fe4
|
|
37
|
+
zh: 862c1087ad9c8f66
|
|
38
|
+
/qilin-tool-ask-user/understand-the-implementation/result-rendering:
|
|
39
|
+
en: 686176b2703dfa08
|
|
40
|
+
zh: 4ea4dab0da7f79f9
|
|
41
|
+
/qilin-tool-ask-user/further-exploration:
|
|
42
|
+
en: efbe254b9d4e0187
|
|
43
|
+
zh: ef516ca754cdd66a
|
|
44
|
+
/qilin-tool-ask-user/model-experience:
|
|
45
|
+
en: 215e7ba838619b7b
|
|
46
|
+
zh: a311da3843709f90
|
|
47
|
+
/qilin-tool-ask-user/model-experience/tool-schema:
|
|
48
|
+
en: e4c7376d4f29dbd5
|
|
49
|
+
zh: 6b7a9eaf69c9ea53
|
|
50
|
+
/qilin-tool-ask-user/model-experience/tool-schema/what-the-model-sees:
|
|
51
|
+
en: 4aca80567536d6b9
|
|
52
|
+
zh: 449630280ba405f5
|
|
53
|
+
/qilin-tool-ask-user/model-experience/tool-schema/token-effect:
|
|
54
|
+
en: 97cce067584eadff
|
|
55
|
+
zh: c376cab39ea9df68
|
|
56
|
+
/qilin-tool-ask-user/model-experience/tool-schema/kv-cache-effect:
|
|
57
|
+
en: b385b04c94496e5b
|
|
58
|
+
zh: 6bd8f2bc39e6f8c0
|
|
59
|
+
/qilin-tool-ask-user/model-experience/tool-call-history-and-result:
|
|
60
|
+
en: 9717b983b6a857e3
|
|
61
|
+
zh: 611a2dadf7c0c706
|
|
62
|
+
/qilin-tool-ask-user/model-experience/tool-call-history-and-result/what-the-model-sees:
|
|
63
|
+
en: d5ba86e2dbdb5b4b
|
|
64
|
+
zh: f5a2d6de0d985eed
|
|
65
|
+
/qilin-tool-ask-user/model-experience/tool-call-history-and-result/token-effect:
|
|
66
|
+
en: 3092d4afd4ba8678
|
|
67
|
+
zh: cb7a7bbefdbc3a1e
|
|
68
|
+
/qilin-tool-ask-user/model-experience/tool-call-history-and-result/kv-cache-effect:
|
|
69
|
+
en: f5d5f97a153f7c70
|
|
70
|
+
zh: 4db81881ebdcfcc4
|
|
71
|
+
/qilin-tool-ask-user/known-limitations-and-deferred-work:
|
|
72
|
+
en: a0f0f15f51d5affb
|
|
73
|
+
zh: 41f11351e852968c
|
|
74
|
+
/qilin-tool-ask-user/known-limitations-and-deferred-work/dev-note:
|
|
75
|
+
en: e67f633dc6fe7773
|
|
76
|
+
zh: 3bb0c20c93f16f98
|
package/README.md
CHANGED
|
@@ -1,3 +1,152 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
description: "The model-facing ask_user_question tool over the user-questions seam, for users and maintainers composing or debugging interactive agent surfaces."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
# @qilin-agent/tool-ask-user
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
`ask_user_question` lets a model pause work and ask the human for confirmation, a choice, or missing information. It accepts one or more questions and returns their answers as compact JSON. The call waits until an answer is accepted or the turn is cancelled; if no answer handler accepts it, the model receives an error. A live child agent owned by another agent cannot call this tool and must report unresolved questions in its final result. The package does not render or collect input, so callers must provide a compatible user interaction surface.
|
|
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
|
+
Compose this plugin wherever the model should be able to pause for a human decision: it provides the `ask_user_question` tool and needs the `ctx.userQuestions` seam with an answerer that accepts the scoped request. Without one, the tool call fails with an error instead of degrading.
|
|
29
|
+
|
|
30
|
+
### When to call the tool
|
|
31
|
+
|
|
32
|
+
The model calls `ask_user_question` when it needs confirmation, a choice, or missing information before proceeding. Send one or more questions, each with a stable `id` that is echoed in the answer; a recommended option goes first with `(Recommended)` appended to its label.
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"questions": [
|
|
37
|
+
{
|
|
38
|
+
"id": "cleanup",
|
|
39
|
+
"question": "Proceed with the destructive cleanup?",
|
|
40
|
+
"header": "Confirm",
|
|
41
|
+
"options": [
|
|
42
|
+
{ "label": "Yes, delete them (Recommended)", "description": "Removes the three stale files." },
|
|
43
|
+
{ "label": "No, keep them", "description": "Aborts the cleanup." }
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### What the model gets back
|
|
51
|
+
|
|
52
|
+
The tool returns one answer object per question: `selected` holds the chosen option labels, and `custom` carries a free-form answer — supplementing `selected` for a multi-select question and overriding it for a single-select question. The Native renderer preserves the compact JSON text shape.
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{ "answers": [{ "id": "cleanup", "selected": ["Yes, delete them (Recommended)"] }] }
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### When the call fails
|
|
59
|
+
|
|
60
|
+
The tool call blocks until the human answers and cancels only through the turn's signal. No accepting answerer, an aborted call, or a caller that is not the exact live runtime root each settles as an error the model sees in the tool result — most notably, a live child agent owned by another agent is rejected (`DELEGATED_CALLER`) and must include the unresolved question or decision in its final result.
|
|
61
|
+
|
|
62
|
+
-----
|
|
63
|
+
|
|
64
|
+
<a id="understand-the-implementation"></a>
|
|
65
|
+
## Understand the implementation
|
|
66
|
+
|
|
67
|
+
<details>
|
|
68
|
+
<summary>Implementation internals — click to expand</summary>
|
|
69
|
+
|
|
70
|
+
The observable behavior is covered in [Use this package](#use-this-package); this section explains the tool definition and its relationship to the seam.
|
|
71
|
+
|
|
72
|
+
### Source map
|
|
73
|
+
|
|
74
|
+
| File | Role |
|
|
75
|
+
|---|---|
|
|
76
|
+
| [`src/index.ts`](src/index.ts) | Tool registration: `ask_user_question` schema, execute path, result render |
|
|
77
|
+
|
|
78
|
+
### Consumer role
|
|
79
|
+
|
|
80
|
+
The plugin registers one `defineTool` entry on `ctx.tools` with injects `['tools', 'userQuestions']`. `execute` maps model arguments into an `AskUserQuestionRequest`, forwards the exact calling agent and the turn's signal, and maps the accepted answer back into the canonical `answers` array. The seam owns identity checks, intent validation, waterfall dispatch, and the error taxonomy; this package only translates.
|
|
81
|
+
|
|
82
|
+
### Result rendering
|
|
83
|
+
|
|
84
|
+
The `render` output projects the structured value to a single text block via `JSON.stringify`, which is why the model-facing result is compact JSON rather than a richer content-block vocabulary.
|
|
85
|
+
|
|
86
|
+
</details>
|
|
87
|
+
|
|
88
|
+
-----
|
|
89
|
+
|
|
90
|
+
<a id="further-exploration"></a>
|
|
91
|
+
## Further Exploration
|
|
92
|
+
|
|
93
|
+
Read these pages when the package-level contract is not enough. They move from the tool surface to the seam contract and its answerer waterfall.
|
|
94
|
+
|
|
95
|
+
- [User interaction subsystem reference](../../../docs/subsystems/user-questions.md) — the service contract, question vocabulary, and answerer waterfall behind this tool.
|
|
96
|
+
- [Tool catalog](../../../docs/tool-catalog.md#qilin-agenttool-ask-user) — the generated `ask_user_question` schema.
|
|
97
|
+
- [user-questions package](../user-questions/README.md) — the seam this tool consumes.
|
|
98
|
+
- [Interaction group map](../README.md) — adjacent approval and command surfaces.
|
|
99
|
+
|
|
100
|
+
-----
|
|
101
|
+
|
|
102
|
+
<a id="model-experience"></a>
|
|
103
|
+
## Model Experience
|
|
104
|
+
|
|
105
|
+
### Tool schema
|
|
106
|
+
|
|
107
|
+
#### What the model sees
|
|
108
|
+
|
|
109
|
+
The model sees the generated [`ask_user_question` schema](../../../docs/tool-catalog.md#qilin-agenttool-ask-user), including question ids, prompts, headings, options, and multi-select flags.
|
|
110
|
+
|
|
111
|
+
#### Token effect
|
|
112
|
+
|
|
113
|
+
Fixed schema cost on every request where the tool is visible.
|
|
114
|
+
|
|
115
|
+
#### KV Cache effect
|
|
116
|
+
|
|
117
|
+
Prefix-stable while the definition and visibility are unchanged. Plugin lifecycle or scoped restrictions may invalidate reuse from this schema.
|
|
118
|
+
|
|
119
|
+
### Tool-call history and result
|
|
120
|
+
|
|
121
|
+
#### What the model sees
|
|
122
|
+
|
|
123
|
+
The model's full questions remain in the assistant tool-call arguments. After the human answers, the next step sees compact JSON in the exact shape `{"answers":[{"id":"<id>","selected":["<label>"],"custom":"<text>"}]}`; `custom` is omitted when unused and `selected` can contain zero, one, or several labels. UI interaction while the call is pending is not model context.
|
|
124
|
+
|
|
125
|
+
#### Token effect
|
|
126
|
+
|
|
127
|
+
Arguments and answer JSON are data-dependent retained tokens; there is no token cost while waiting for the human.
|
|
128
|
+
|
|
129
|
+
#### KV Cache effect
|
|
130
|
+
|
|
131
|
+
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
|
|
132
|
+
|
|
133
|
+
## Known Limitations and Deferred Work
|
|
134
|
+
|
|
135
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
These limits define when the tool is a poor fit. They are current package constraints, not a UI backlog.
|
|
139
|
+
|
|
140
|
+
- **A pending question blocks the tool call until the human answers** — the tool declares no `timeout-policy` budget; cancellation rides the turn's `exec.signal` only.
|
|
141
|
+
- **Runtime-owned subagents cannot ask the user** — `ask_user_question` rejects a live child owned by another agent with `DELEGATED_CALLER`; the child must include the unresolved question or decision in its final result. Durable lineage does not decide this boundary, so a lineage-bearing session resumed as a runtime root may ask normally.
|
|
142
|
+
- **Native answers render as JSON text** — the canonical value remains structured, but the model-facing result uses compact JSON rather than a richer content-block vocabulary.
|
|
143
|
+
|
|
144
|
+
<a id="dev-note"></a>
|
|
145
|
+
### Dev Note
|
|
146
|
+
|
|
147
|
+
<details>
|
|
148
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
149
|
+
|
|
150
|
+
None.
|
|
151
|
+
|
|
152
|
+
</details>
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "基于 user-questions seam 的模型侧 ask_user_question 工具;供组合或排查交互式 agent(智能体)表面的用户与维护者阅读。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @qilin-agent/tool-ask-user
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
`ask_user_question` 让模型暂停工作,向用户请求确认、选择或缺失的信息。它接受一个或多个问题,并以紧凑 JSON 返回回答。调用会等待回答被接受或当前轮次被取消;如果没有回答处理器接受请求,模型会收到错误。归属于运行时其他 agent 的子级不能调用此工具,必须在最终结果中报告尚未解决的问题。本包不渲染界面或收集输入,因此调用方必须提供兼容的用户交互表面。
|
|
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
|
+
凡模型应当能够暂停等待人类决定的场景,都可组合此插件:它提供 `ask_user_question` 工具,并且需要带有接受作用域请求的 answerer 的 `ctx.userQuestions` seam。没有 answerer 接受时,工具调用会以错误失败,而不是降级。
|
|
29
|
+
|
|
30
|
+
### 何时调用该工具
|
|
31
|
+
|
|
32
|
+
当模型需要确认、选择结果或缺失的信息才能继续时,调用 `ask_user_question`。发送一个或多个问题,每个问题携带稳定的 `id`(回答中会原样包含);推荐选项放在首位,并在标签末尾追加 `(Recommended)`。
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"questions": [
|
|
37
|
+
{
|
|
38
|
+
"id": "cleanup",
|
|
39
|
+
"question": "Proceed with the destructive cleanup?",
|
|
40
|
+
"header": "Confirm",
|
|
41
|
+
"options": [
|
|
42
|
+
{ "label": "Yes, delete them (Recommended)", "description": "Removes the three stale files." },
|
|
43
|
+
{ "label": "No, keep them", "description": "Aborts the cleanup." }
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 模型得到什么
|
|
51
|
+
|
|
52
|
+
工具为每个问题返回一个回答对象:`selected` 保存选中的选项标签,`custom` 携带自由填写的回答——对多选题补充 `selected`,对单选题覆盖它。Native 渲染器保留紧凑的 JSON 文本形式。
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{ "answers": [{ "id": "cleanup", "selected": ["Yes, delete them (Recommended)"] }] }
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 调用何时失败
|
|
59
|
+
|
|
60
|
+
工具调用会阻塞到用户作答,并且只能通过当前轮次的信号取消。没有 answerer 接受、调用被中止、或调用方不是确切的存活运行时根,都会以模型在工具结果中看到的错误结算——最值得注意的是,归属于另一个 agent 的存活子级会被拒绝(`DELEGATED_CALLER`),必须在最终结果中包含尚未解决的问题或决定。
|
|
61
|
+
|
|
62
|
+
-----
|
|
63
|
+
|
|
64
|
+
<a id="understand-the-implementation"></a>
|
|
65
|
+
## 理解实现
|
|
66
|
+
|
|
67
|
+
<details>
|
|
68
|
+
<summary>实现细节——点击展开</summary>
|
|
69
|
+
|
|
70
|
+
可观察行为已在[使用本包](#use-this-package)中说明;本节解释工具定义及其与 seam 的关系。
|
|
71
|
+
|
|
72
|
+
### 源码地图
|
|
73
|
+
|
|
74
|
+
| 文件 | 职责 |
|
|
75
|
+
|---|---|
|
|
76
|
+
| [`src/index.ts`](src/index.ts) | 工具注册:`ask_user_question` schema、执行路径、结果渲染 |
|
|
77
|
+
|
|
78
|
+
### 消费方角色
|
|
79
|
+
|
|
80
|
+
该插件以 `['tools', 'userQuestions']` 注入,在 `ctx.tools` 上注册一个 `defineTool` 条目。`execute` 把模型参数映射为 `AskUserQuestionRequest`,转发确切的调用 agent 与当前轮次的信号,并把接受的回答映射回规范的 `answers` 数组。身份检查、意图校验、waterfall(瀑布式事件)分派与错误分类由 seam 拥有;本包只做转换。
|
|
81
|
+
|
|
82
|
+
### 结果渲染
|
|
83
|
+
|
|
84
|
+
`render` 输出把结构化值经 `JSON.stringify` 投影为单个文本块,因此模型侧结果是紧凑 JSON,而非更丰富的内容块词汇。
|
|
85
|
+
|
|
86
|
+
</details>
|
|
87
|
+
|
|
88
|
+
-----
|
|
89
|
+
|
|
90
|
+
<a id="further-exploration"></a>
|
|
91
|
+
## 进一步探索
|
|
92
|
+
|
|
93
|
+
当包级约定不够用时阅读以下页面。它们从工具表面逐步进入 seam 约定及其 answerer waterfall。
|
|
94
|
+
|
|
95
|
+
- [用户交互子系统参考](../../../docs/subsystems/user-questions.zh.md)——此工具背后的服务约定、问题词汇与 answerer waterfall。
|
|
96
|
+
- [工具目录](../../../docs/tool-catalog.zh.md#qilin-agenttool-ask-user)——生成的 `ask_user_question` schema。
|
|
97
|
+
- [user-questions 包](../user-questions/README.zh.md)——本工具消费的 seam。
|
|
98
|
+
- [交互组映射](../README.zh.md)——相邻的审批与命令表面。
|
|
99
|
+
|
|
100
|
+
-----
|
|
101
|
+
|
|
102
|
+
<a id="model-experience"></a>
|
|
103
|
+
## 模型体验
|
|
104
|
+
|
|
105
|
+
### 工具 schema
|
|
106
|
+
|
|
107
|
+
#### 模型看到的内容
|
|
108
|
+
|
|
109
|
+
模型会看到生成的 [`ask_user_question` schema](../../../docs/tool-catalog.zh.md#qilin-agenttool-ask-user),其中包含问题 id、提示语、标题、选项与多选标志。
|
|
110
|
+
|
|
111
|
+
#### Token 影响
|
|
112
|
+
|
|
113
|
+
工具可见时,每个请求都会产生固定的 schema token 开销。
|
|
114
|
+
|
|
115
|
+
#### KV Cache 影响
|
|
116
|
+
|
|
117
|
+
只要定义和可见性保持不变,前缀即可稳定复用。插件生命周期变化或作用域限制可能会使从此 schema 起的缓存复用失效。
|
|
118
|
+
|
|
119
|
+
### 工具调用历史与结果
|
|
120
|
+
|
|
121
|
+
#### 模型看到的内容
|
|
122
|
+
|
|
123
|
+
模型提出的完整问题保留在 assistant 工具调用参数中。用户回答后,下一步会看到精确采用 `{"answers":[{"id":"<id>","selected":["<label>"],"custom":"<text>"}]}` 形式的紧凑 JSON;不使用 `custom` 时会省略该字段,`selected` 可以包含零个、一个或多个标签。调用等待期间的 UI 交互不属于模型上下文。
|
|
124
|
+
|
|
125
|
+
#### Token 影响
|
|
126
|
+
|
|
127
|
+
参数和回答 JSON 是依数据而定的保留 token;等待用户时不会产生 token 开销。
|
|
128
|
+
|
|
129
|
+
#### KV Cache 影响
|
|
130
|
+
|
|
131
|
+
仅追加;新出现的可见内容位于可复用请求前缀之后,不会使现有 KV Cache 条目失效。
|
|
132
|
+
|
|
133
|
+
## 已知限制与延期工作
|
|
134
|
+
|
|
135
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
这些限制说明该工具何时不合适。它们是当前包约束,不是 UI 积压事项。
|
|
139
|
+
|
|
140
|
+
- **待处理问题会阻塞工具调用,直至用户作答**:该工具未声明 `timeout-policy` 预算;取消仅沿用当前轮次的 `exec.signal`。
|
|
141
|
+
- **运行时中归属于其他 agent 的 subagent 不能向用户提问**:`ask_user_question` 会以 `DELEGATED_CALLER` 拒绝归属于另一个 agent 的存活子级;该子级必须在最终结果中包含尚未解决的问题或决定。持久谱系不能决定这一边界,因此带有谱系的会话恢复为运行时根后可以正常提问。
|
|
142
|
+
- **Native 回答渲染为 JSON 文本**:规范值仍为结构化数据,但模型侧结果使用紧凑 JSON,而非更丰富的内容块词汇。
|
|
143
|
+
|
|
144
|
+
<a id="dev-note"></a>
|
|
145
|
+
### 开发备注
|
|
146
|
+
|
|
147
|
+
<details>
|
|
148
|
+
<summary>维护者的工作上下文——点击展开</summary>
|
|
149
|
+
|
|
150
|
+
无。
|
|
151
|
+
|
|
152
|
+
</details>
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { defineTool } from "@qilin-agent/tools";
|
|
2
|
+
import z from "@qilin-agent/schemastery";
|
|
3
|
+
import { TIMED_WAIT_PARAMETER } from "@qilin-agent/user-questions";
|
|
4
|
+
//#region lib/types/timed.js
|
|
5
|
+
/**
|
|
6
|
+
* Opt-in timed `ask_user_question` tool definition.
|
|
7
|
+
*/
|
|
8
|
+
function validateTimeout(timeout) {
|
|
9
|
+
if (timeout !== -1 && (!Number.isInteger(timeout) || timeout < 1 || timeout > 2147483)) throw new Error("timeout must be -1 or a positive integer up to 2147483 seconds");
|
|
10
|
+
return timeout;
|
|
11
|
+
}
|
|
12
|
+
function validateQuestionIds(questions) {
|
|
13
|
+
const ids = /* @__PURE__ */ new Set();
|
|
14
|
+
for (const question of questions) {
|
|
15
|
+
if (ids.has(question.id)) throw new Error(`question id ${JSON.stringify(question.id)} must be unique within this call`);
|
|
16
|
+
ids.add(question.id);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const description$1 = "Ask brief, direct, self-contained questions about missing information, preferences, or decisions. Use user-facing terms; assume no knowledge of background work or internal names. Use distinct stable question IDs. A submitted skipped question is an answer item with empty selected and no custom; pending instead means no answer batch arrived before the timeout and the user can still answer.";
|
|
20
|
+
/**
|
|
21
|
+
* Instruction the pending result carries in its `message` field. It is a field
|
|
22
|
+
* of the result value rather than prose beside it because the recorded result
|
|
23
|
+
* text is read back as one JSON object: the `userQuestions` projection decides
|
|
24
|
+
* from it that the call stays answerable, and the Client question row decides
|
|
25
|
+
* from it that the recorded result is the timeout, not an answer batch.
|
|
26
|
+
*/
|
|
27
|
+
const pendingNotice = "No answer batch arrived before the timeout. This is pending, not a skipped answer. Continue useful independent work. The user can still answer; their reply will be a user message identified as answer_to_pending_question with this callId and the original questions. Do not treat this as permission.";
|
|
28
|
+
/**
|
|
29
|
+
* Translate the model schema into the service request without changing ownership.
|
|
30
|
+
* @param questions - Model-supplied questions in tool-schema form.
|
|
31
|
+
* @param exec - Execution context that owns the agent and cancellation signal.
|
|
32
|
+
* @returns The corresponding user-question service request.
|
|
33
|
+
*/
|
|
34
|
+
function questionRequest(questions, exec) {
|
|
35
|
+
return {
|
|
36
|
+
questions: questions.map((question) => ({
|
|
37
|
+
id: question.id,
|
|
38
|
+
question: question.question,
|
|
39
|
+
...question.header !== void 0 ? { header: question.header } : {},
|
|
40
|
+
...question.options !== void 0 ? { options: question.options.map((option) => ({ ...option })) } : {},
|
|
41
|
+
...question.multi_select !== void 0 ? { multiSelect: question.multi_select } : {}
|
|
42
|
+
})),
|
|
43
|
+
...exec.agent !== void 0 ? { agent: exec.agent } : {},
|
|
44
|
+
signal: exec.signal
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Copy service-owned answer arrays into the model-facing result.
|
|
49
|
+
* @param result - Answer returned by the user-question service.
|
|
50
|
+
* @returns A detached model-facing answer payload.
|
|
51
|
+
*/
|
|
52
|
+
function answerResult(result) {
|
|
53
|
+
return { answers: result.answers.map((answer) => ({
|
|
54
|
+
id: answer.id,
|
|
55
|
+
selected: [...answer.selected],
|
|
56
|
+
...answer.custom !== void 0 ? { custom: answer.custom } : {}
|
|
57
|
+
})) };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Register the opt-in timed tool definition.
|
|
61
|
+
* @param ctx - Agent-scoped context receiving the tool.
|
|
62
|
+
* @param timeout - Default foreground wait in seconds.
|
|
63
|
+
*/
|
|
64
|
+
function registerTimedAskUser(ctx, timeout = 120) {
|
|
65
|
+
const defaultTimeout = validateTimeout(timeout);
|
|
66
|
+
ctx.tools.register(defineTool({
|
|
67
|
+
name: "ask_user_question",
|
|
68
|
+
description: description$1,
|
|
69
|
+
parameters: {
|
|
70
|
+
questions: {
|
|
71
|
+
type: "array",
|
|
72
|
+
required: true,
|
|
73
|
+
description: "Questions to ask the user.",
|
|
74
|
+
items: {
|
|
75
|
+
type: "object",
|
|
76
|
+
additionalProperties: true,
|
|
77
|
+
properties: {
|
|
78
|
+
id: {
|
|
79
|
+
type: "string",
|
|
80
|
+
required: true,
|
|
81
|
+
description: "Stable id for this question; echoed in the answer."
|
|
82
|
+
},
|
|
83
|
+
question: {
|
|
84
|
+
type: "string",
|
|
85
|
+
required: true,
|
|
86
|
+
description: "The specific question to ask the user."
|
|
87
|
+
},
|
|
88
|
+
header: {
|
|
89
|
+
type: "string",
|
|
90
|
+
description: "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"."
|
|
91
|
+
},
|
|
92
|
+
options: {
|
|
93
|
+
type: "array",
|
|
94
|
+
description: "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.",
|
|
95
|
+
items: {
|
|
96
|
+
type: "object",
|
|
97
|
+
additionalProperties: true,
|
|
98
|
+
properties: {
|
|
99
|
+
label: {
|
|
100
|
+
type: "string",
|
|
101
|
+
required: true,
|
|
102
|
+
description: "Short user-facing option label."
|
|
103
|
+
},
|
|
104
|
+
description: {
|
|
105
|
+
type: "string",
|
|
106
|
+
description: "One sentence explaining the tradeoff or impact."
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
multi_select: {
|
|
112
|
+
type: "boolean",
|
|
113
|
+
description: "Whether the user may select more than one option. Defaults to false."
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
[TIMED_WAIT_PARAMETER]: {
|
|
119
|
+
type: "integer",
|
|
120
|
+
description: `Wait seconds for the entire batch (default ${defaultTimeout}); omit unless the user specifies a duration. Use -1 only when an answer is required before proceeding.`
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
output: {
|
|
124
|
+
schema: { oneOf: [{
|
|
125
|
+
type: "object",
|
|
126
|
+
additionalProperties: false,
|
|
127
|
+
properties: {
|
|
128
|
+
pending: {
|
|
129
|
+
type: "boolean",
|
|
130
|
+
enum: [true],
|
|
131
|
+
required: true,
|
|
132
|
+
description: "True when the foreground wait expired before the user submitted an answer batch. The questions remain answerable; this is not a skipped answer."
|
|
133
|
+
},
|
|
134
|
+
callId: {
|
|
135
|
+
type: "string",
|
|
136
|
+
required: true,
|
|
137
|
+
description: "Tool call whose unanswered questions remain pending."
|
|
138
|
+
},
|
|
139
|
+
message: {
|
|
140
|
+
type: "string",
|
|
141
|
+
required: true,
|
|
142
|
+
description: "How to continue while the questions stay answerable."
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}, {
|
|
146
|
+
type: "object",
|
|
147
|
+
additionalProperties: false,
|
|
148
|
+
properties: { answers: {
|
|
149
|
+
type: "array",
|
|
150
|
+
required: true,
|
|
151
|
+
description: "Submitted answer batch with one item per question. A skipped question has empty selected and no custom; unlike pending, the user has completed the batch.",
|
|
152
|
+
items: {
|
|
153
|
+
type: "object",
|
|
154
|
+
additionalProperties: false,
|
|
155
|
+
properties: {
|
|
156
|
+
id: {
|
|
157
|
+
type: "string",
|
|
158
|
+
required: true,
|
|
159
|
+
description: "Stable question id echoed from the request."
|
|
160
|
+
},
|
|
161
|
+
selected: {
|
|
162
|
+
type: "array",
|
|
163
|
+
required: true,
|
|
164
|
+
items: { type: "string" },
|
|
165
|
+
description: "Selected option labels. Empty with no custom means the user explicitly skipped this question."
|
|
166
|
+
},
|
|
167
|
+
custom: {
|
|
168
|
+
type: "string",
|
|
169
|
+
description: "Optional free-form answer; omitted for a skipped question."
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} }
|
|
174
|
+
}] },
|
|
175
|
+
render: (_args, value) => [{
|
|
176
|
+
type: "text",
|
|
177
|
+
text: JSON.stringify(value)
|
|
178
|
+
}]
|
|
179
|
+
},
|
|
180
|
+
async execute(args, exec) {
|
|
181
|
+
const timeout = validateTimeout(args.timeout ?? defaultTimeout);
|
|
182
|
+
validateQuestionIds(args.questions);
|
|
183
|
+
const request = questionRequest(args.questions, exec);
|
|
184
|
+
if (timeout !== -1) {
|
|
185
|
+
if (exec.agent === void 0) throw new Error("timed questions require a live agent");
|
|
186
|
+
const result = await ctx.userQuestions.askTimed({
|
|
187
|
+
...request,
|
|
188
|
+
agent: exec.agent
|
|
189
|
+
}, exec.callId, timeout * 1e3);
|
|
190
|
+
return "pending" in result ? {
|
|
191
|
+
...result,
|
|
192
|
+
message: pendingNotice
|
|
193
|
+
} : answerResult(result);
|
|
194
|
+
}
|
|
195
|
+
return answerResult(await ctx.userQuestions.ask({
|
|
196
|
+
...request,
|
|
197
|
+
wait: { callId: exec.callId }
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
202
|
+
//#endregion
|
|
203
|
+
//#region lib/types/index.js
|
|
204
|
+
/**
|
|
205
|
+
* Model-facing Consumer of the `ctx.userQuestions` capability seam.
|
|
206
|
+
* The tool pauses until a UI provider returns a human answer, then feeds that
|
|
207
|
+
* answer back into the agent loop as an ordinary tool result.
|
|
208
|
+
*
|
|
209
|
+
* @module @qilin-agent/tool-ask-user
|
|
210
|
+
*/
|
|
211
|
+
const Config = z.object({
|
|
212
|
+
mode: z.union(["legacy", "timed"]).default("legacy"),
|
|
213
|
+
timeout: z.union([-1, z.number().step(1).min(1).max(2147483)]).default(120)
|
|
214
|
+
});
|
|
215
|
+
const name = "tool-ask-user";
|
|
216
|
+
const inject = ["tools", "userQuestions"];
|
|
217
|
+
const description = "Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding.";
|
|
218
|
+
function apply(ctx, config = {}) {
|
|
219
|
+
if (config.mode === "timed") {
|
|
220
|
+
registerTimedAskUser(ctx, config.timeout);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
ctx.tools.register(defineTool({
|
|
224
|
+
name: "ask_user_question",
|
|
225
|
+
description,
|
|
226
|
+
parameters: { questions: {
|
|
227
|
+
type: "array",
|
|
228
|
+
required: true,
|
|
229
|
+
description: "Questions to ask the user before continuing.",
|
|
230
|
+
items: {
|
|
231
|
+
type: "object",
|
|
232
|
+
additionalProperties: true,
|
|
233
|
+
properties: {
|
|
234
|
+
id: {
|
|
235
|
+
type: "string",
|
|
236
|
+
required: true,
|
|
237
|
+
description: "Stable id for this question; echoed in the answer."
|
|
238
|
+
},
|
|
239
|
+
question: {
|
|
240
|
+
type: "string",
|
|
241
|
+
required: true,
|
|
242
|
+
description: "The specific question to ask the user."
|
|
243
|
+
},
|
|
244
|
+
header: {
|
|
245
|
+
type: "string",
|
|
246
|
+
description: "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"."
|
|
247
|
+
},
|
|
248
|
+
options: {
|
|
249
|
+
type: "array",
|
|
250
|
+
description: "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.",
|
|
251
|
+
items: {
|
|
252
|
+
type: "object",
|
|
253
|
+
additionalProperties: true,
|
|
254
|
+
properties: {
|
|
255
|
+
label: {
|
|
256
|
+
type: "string",
|
|
257
|
+
required: true,
|
|
258
|
+
description: "Short user-facing option label."
|
|
259
|
+
},
|
|
260
|
+
description: {
|
|
261
|
+
type: "string",
|
|
262
|
+
description: "One sentence explaining the tradeoff or impact."
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
multi_select: {
|
|
268
|
+
type: "boolean",
|
|
269
|
+
description: "Whether the user may select more than one option. Defaults to false."
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
} },
|
|
274
|
+
output: {
|
|
275
|
+
schema: {
|
|
276
|
+
type: "object",
|
|
277
|
+
additionalProperties: false,
|
|
278
|
+
properties: { answers: {
|
|
279
|
+
type: "array",
|
|
280
|
+
required: true,
|
|
281
|
+
items: {
|
|
282
|
+
type: "object",
|
|
283
|
+
additionalProperties: false,
|
|
284
|
+
properties: {
|
|
285
|
+
id: {
|
|
286
|
+
type: "string",
|
|
287
|
+
required: true
|
|
288
|
+
},
|
|
289
|
+
selected: {
|
|
290
|
+
type: "array",
|
|
291
|
+
required: true,
|
|
292
|
+
items: { type: "string" }
|
|
293
|
+
},
|
|
294
|
+
custom: { type: "string" }
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
} }
|
|
298
|
+
},
|
|
299
|
+
render: (_args, value) => [{
|
|
300
|
+
type: "text",
|
|
301
|
+
text: JSON.stringify(value)
|
|
302
|
+
}]
|
|
303
|
+
},
|
|
304
|
+
async execute(args, exec) {
|
|
305
|
+
return { answers: (await ctx.userQuestions.ask({
|
|
306
|
+
questions: args.questions.map((question) => ({
|
|
307
|
+
id: question.id,
|
|
308
|
+
question: question.question,
|
|
309
|
+
...question.header !== void 0 ? { header: question.header } : {},
|
|
310
|
+
...question.options !== void 0 ? { options: question.options } : {},
|
|
311
|
+
...question.multi_select !== void 0 ? { multiSelect: question.multi_select } : {}
|
|
312
|
+
})),
|
|
313
|
+
...exec.agent !== void 0 ? { agent: exec.agent } : {},
|
|
314
|
+
signal: exec.signal
|
|
315
|
+
})).answers.map((answer) => ({
|
|
316
|
+
id: answer.id,
|
|
317
|
+
selected: [...answer.selected],
|
|
318
|
+
...answer.custom !== void 0 ? { custom: answer.custom } : {}
|
|
319
|
+
})) };
|
|
320
|
+
}
|
|
321
|
+
}));
|
|
322
|
+
}
|
|
323
|
+
//#endregion
|
|
324
|
+
export { Config, apply, inject, name };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-facing Consumer of the `ctx.userQuestions` capability seam.
|
|
3
|
+
* The tool pauses until a UI provider returns a human answer, then feeds that
|
|
4
|
+
* answer back into the agent loop as an ordinary tool result.
|
|
5
|
+
*
|
|
6
|
+
* @module @qilin-agent/tool-ask-user
|
|
7
|
+
*/
|
|
8
|
+
import type { Context } from '@qilin-agent/kylin';
|
|
9
|
+
import z from '@qilin-agent/schemastery';
|
|
10
|
+
import '@qilin-agent/user-questions';
|
|
11
|
+
/** Cordis row selecting the tool schema and its default foreground wait. */
|
|
12
|
+
export interface Config {
|
|
13
|
+
/** Tool definition selected by this Cordis row. Defaults to the blocking legacy tool. */
|
|
14
|
+
mode?: 'legacy' | 'timed';
|
|
15
|
+
/** Foreground wait before automatic continuation. Defaults to 120 seconds. */
|
|
16
|
+
timeout?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare const Config: z<Config>;
|
|
19
|
+
export declare const name = "tool-ask-user";
|
|
20
|
+
export declare const inject: string[];
|
|
21
|
+
export declare function apply(ctx: Context, config?: Config): void;
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt-in timed `ask_user_question` tool definition.
|
|
3
|
+
*/
|
|
4
|
+
import type { Context } from '@qilin-agent/kylin';
|
|
5
|
+
/**
|
|
6
|
+
* Register the opt-in timed tool definition.
|
|
7
|
+
* @param ctx - Agent-scoped context receiving the tool.
|
|
8
|
+
* @param timeout - Default foreground wait in seconds.
|
|
9
|
+
*/
|
|
10
|
+
export declare function registerTimedAskUser(ctx: Context, timeout?: number): void;
|
|
11
|
+
//# sourceMappingURL=timed.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qilin-agent/tool-ask-user",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"description": "Model-facing ask_user_question tool over the ctx.userQuestions seam",
|
|
4
|
+
"version": "3.1.3",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/kkutysllb/QiLin.git",
|
|
11
|
+
"directory": "packages/interaction/tool-ask-user"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
16
|
+
},
|
|
17
|
+
"main": "lib/index.js",
|
|
18
|
+
"types": "lib/types/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./lib/types/index.d.ts",
|
|
22
|
+
"default": "./lib/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./src/*": "./src/*",
|
|
25
|
+
"./package.json": "./package.json"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"lib/index.js",
|
|
29
|
+
"lib/types/**/*.d.ts"
|
|
30
|
+
],
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@qilin-agent/agent": "3.1.3",
|
|
34
|
+
"@qilin-agent/tools": "3.1.3",
|
|
35
|
+
"@qilin-agent/user-questions": "3.1.3",
|
|
36
|
+
"@qilin-agent/kylin": "~4.0.3"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@qilin-agent/agent": "3.1.3",
|
|
40
|
+
"@qilin-agent/llm": "3.1.3",
|
|
41
|
+
"@qilin-agent/tools": "3.1.3",
|
|
42
|
+
"@qilin-agent/system-prompt": "3.1.3",
|
|
43
|
+
"@qilin-agent/user-questions": "3.1.3",
|
|
44
|
+
"@qilin-agent/kylin": "~4.0.3"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@qilin-agent/schemastery": "~3.18.3"
|
|
48
|
+
}
|
|
6
49
|
}
|