@deepseek-ai/dsh-user-questions 0.1.1-rc.2 → 0.1.2-alpha.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/README.i18n.yaml +2 -2
- package/README.md +40 -9
- package/README.zh.md +40 -9
- package/lib/index.js +33 -27
- package/lib/types/index.d.ts +10 -28
- package/lib/types/index.js +41 -30
- package/lib/types/types.d.ts +24 -6
- package/lib/types/types.js +1 -6
- package/package.json +11 -9
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/interaction/user-questions/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: f5b8c8f6d9d2d376a60d32d096cacf36fede5a7b
|
|
6
|
+
README.zh.md: cbabec7f551ab2257e860c45bbd27b99321c6e05
|
package/README.md
CHANGED
|
@@ -1,15 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Waterfall-based question and answer service for tools, permission plugins, local answerers, and Agent-scoped Web interactions."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# @deepseek-ai/dsh-user-questions
|
|
2
7
|
|
|
3
8
|
English | [中文](README.zh.md)
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
User-interaction Service Definition. It owns `ctx.userQuestions`, the service a model-facing tool or permission plugin uses when it needs to pause work and ask the human for a decision. Use it when a consumer must suspend an operation until the user answers.
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
6
15
|
|
|
16
|
+
- [Service: `UserQuestionService` (ctx key: `userQuestions`)](#service-userquestionservice-ctx-key-userquestions)
|
|
17
|
+
- [Role](#role)
|
|
18
|
+
- [Model Experience](#model-experience)
|
|
19
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
20
|
+
- [Dev Note](#dev-note)
|
|
21
|
+
|
|
22
|
+
-----
|
|
23
|
+
|
|
24
|
+
<a id="service-userquestionservice-ctx-key-userquestions"></a>
|
|
7
25
|
## Service: `UserQuestionService` (ctx key: `userQuestions`)
|
|
8
26
|
|
|
9
27
|
### Public API
|
|
10
28
|
|
|
11
|
-
- `ctx.userQuestions.
|
|
12
|
-
- `ctx.userQuestions.ask(request): Promise<AskUserQuestionAnswer>` Ask the active provider and wait for the answer.
|
|
29
|
+
- `ctx.userQuestions.ask(request): Promise<AskUserQuestionAnswer>` Dispatch the answerer waterfall and wait for the first accepted answer.
|
|
13
30
|
|
|
14
31
|
### Key Types
|
|
15
32
|
|
|
@@ -17,24 +34,25 @@ User-interaction Service Definition. It owns `ctx.userQuestions`, the service a
|
|
|
17
34
|
- `AskUserQuestionOption` — `{ label, description? }`.
|
|
18
35
|
- `AskUserQuestionIntent` — `{ kind: 'plan-review', approve }`; the tagged presentation intent below.
|
|
19
36
|
- `AskUserQuestionAnswer` — `{ answers: [{ id, selected, custom? }] }`.
|
|
20
|
-
- `
|
|
21
|
-
- `UserQuestionError` — `HarnessError` subclass with codes such as `EMPTY_QUESTIONS`, `BAD_INTENT`, `NO_PROVIDER`, `DUPLICATE_PROVIDER`, `ASK_ABORTED`, `CALLER_NOT_LIVE`, and `DELEGATED_CALLER`.
|
|
37
|
+
- `UserQuestionError` — `HarnessError` subclass with codes such as `EMPTY_QUESTIONS`, `BAD_INTENT`, `NO_PROVIDER`, `ASK_ABORTED`, `CALLER_NOT_LIVE`, and `DELEGATED_CALLER`.
|
|
22
38
|
|
|
23
39
|
For a single-select question, `custom` overrides the selected choice and `selected` is empty. For a multi-select question, `custom` may supplement the labels in `selected`. A UI may preserve a skipped item as `{ id, selected: [] }`, keeping the existing answer shape while retaining other answers in the batch.
|
|
24
40
|
|
|
25
|
-
When a request carries an agent, `ask()` authenticates its exact identity through the live `AgentRegistry` and admits only a runtime root. Durable lineage is not authority: a session with historical delegation depth may ask after it is resumed as a new runtime root, while a live child owned by another agent is rejected even if its durable depth is zero.
|
|
41
|
+
When a request carries an agent, `ask()` authenticates its exact identity through the live `AgentRegistry` and admits only a runtime root. Durable lineage is not authority: a session with historical delegation depth may ask after it is resumed as a new runtime root, while a live child owned by another agent is rejected even if its durable depth is zero. The Web answerer receives only Agent-scoped requests; an agentless programmatic request remains available to unscoped local waterfall listeners and fails with `NO_PROVIDER` when none accepts it.
|
|
26
42
|
|
|
27
43
|
### Presentation intent
|
|
28
44
|
|
|
29
45
|
`intent` declares that a question IS a known kind of decision, so a UI that recognises the tag may present it as such — `plan-review` says `detail` is a plan under review, and `dsh-plan-mode` sets it on the `exit_plan_mode` question. An intent changes presentation only: a UI honouring it answers with the same option labels a generic UI would send, and a UI that does not know the tag renders the generic option list, so callers read the same answer fields either way. `approve` names the label that approves rather than relying on option order. `ask()` rejects with `BAD_INTENT` the two assertions no type can carry: an `approve` naming none of that question's own options, and an intent on a question with no `detail` — the thing it declares itself a review of.
|
|
30
46
|
|
|
47
|
+
<a id="role"></a>
|
|
31
48
|
## Role
|
|
32
49
|
|
|
33
|
-
This is the Service Definition package. Consumers such as `@deepseek-ai/dsh-tool-ask-user` depend on this service; the Web
|
|
50
|
+
This is the Service Definition package. Consumers such as `@deepseek-ai/dsh-tool-ask-user` depend on this service; the Web client contributes an Agent-scoped answerer through Remote Events. The loop stays unchanged: a tool call awaits the waterfall result, and that result resumes the normal agent loop.
|
|
34
51
|
|
|
52
|
+
<a id="model-experience"></a>
|
|
35
53
|
## Model Experience
|
|
36
54
|
|
|
37
|
-
Indirectly, through `dsh-tool-ask-user`, which retains a successful
|
|
55
|
+
Indirectly, through `dsh-tool-ask-user`, which retains a successful answer as compact JSON or one of these failures: `Error: ask_user_question was aborted before the user answered`, `Error: ask_user_question requires at least one question`, `Error: human interaction requires the exact live calling agent when an agent is supplied`, `Error: human interaction is unavailable while the calling agent is owned by another live agent; include the unresolved question or decision in the child agent's final result`, `Error: no user-questions answerer accepted the request`, or `Error: <message>`. Waiting for the human adds no tokens.
|
|
38
56
|
|
|
39
57
|
#### KV Cache effect
|
|
40
58
|
|
|
@@ -42,5 +60,18 @@ No direct invalidation; the named consumer owns any request-prefix changes.
|
|
|
42
60
|
|
|
43
61
|
## Known Limitations and Deferred Work
|
|
44
62
|
|
|
45
|
-
|
|
63
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
64
|
+
|
|
65
|
+
- **Agent-scoped Web answering** — Remote Events route the shipped Web answerer only when the request carries a live Agent scope; agentless callers need an unscoped local waterfall listener.
|
|
46
66
|
- **The vocabulary is the question-form shape only** — selectable options plus optional custom text; richer interaction shapes (file pickers, diff-preview confirmations) have no seam vocabulary yet.
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
<a id="dev-note"></a>
|
|
70
|
+
### Dev Note
|
|
71
|
+
|
|
72
|
+
<details>
|
|
73
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
74
|
+
|
|
75
|
+
None.
|
|
76
|
+
|
|
77
|
+
</details>
|
package/README.zh.md
CHANGED
|
@@ -1,15 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "基于 waterfall 的问答服务,用于工具、权限插件、本地 answerer 与 Agent-scoped Web 交互。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# @deepseek-ai/dsh-user-questions
|
|
2
7
|
|
|
3
8
|
[English](README.md) | 中文
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
用户交互 Service Definition。它定义 `ctx.userQuestions`,供面向模型的工具或权限插件在需要暂停工作并询问人类决定时使用。当消费方必须暂停操作并等待用户回答时,请使用它。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
6
15
|
|
|
16
|
+
- [服务:`UserQuestionService`(ctx 键:`userQuestions`)](#service-userquestionservice-ctx-key-userquestions)
|
|
17
|
+
- [职责](#role)
|
|
18
|
+
- [模型体验](#model-experience)
|
|
19
|
+
- [已知限制与暂缓事项](#known-limitations-and-deferred-work)
|
|
20
|
+
- [开发备注](#dev-note)
|
|
21
|
+
|
|
22
|
+
-----
|
|
23
|
+
|
|
24
|
+
<a id="service-userquestionservice-ctx-key-userquestions"></a>
|
|
7
25
|
## 服务:`UserQuestionService`(ctx 键:`userQuestions`)
|
|
8
26
|
|
|
9
27
|
### 公开 API
|
|
10
28
|
|
|
11
|
-
- `ctx.userQuestions.
|
|
12
|
-
- `ctx.userQuestions.ask(request): Promise<AskUserQuestionAnswer>` 向活跃提供方提问并等待回答。
|
|
29
|
+
- `ctx.userQuestions.ask(request): Promise<AskUserQuestionAnswer>` 派发回答者 waterfall,并等待第一个接受请求的回答。
|
|
13
30
|
|
|
14
31
|
### 关键类型
|
|
15
32
|
|
|
@@ -17,24 +34,25 @@
|
|
|
17
34
|
- `AskUserQuestionOption`:`{ label, description? }`。
|
|
18
35
|
- `AskUserQuestionIntent`:`{ kind: 'plan-review', approve }`;即下文的带标签呈现意图。
|
|
19
36
|
- `AskUserQuestionAnswer`:`{ answers: [{ id, selected, custom? }] }`。
|
|
20
|
-
- `
|
|
21
|
-
- `UserQuestionError`:`HarnessError` 的子类,包含 `EMPTY_QUESTIONS`、`BAD_INTENT`、`NO_PROVIDER`、`DUPLICATE_PROVIDER`、`ASK_ABORTED`、`CALLER_NOT_LIVE` 和 `DELEGATED_CALLER` 等代码。
|
|
37
|
+
- `UserQuestionError`:`HarnessError` 的子类,包含 `EMPTY_QUESTIONS`、`BAD_INTENT`、`NO_PROVIDER`、`ASK_ABORTED`、`CALLER_NOT_LIVE` 和 `DELEGATED_CALLER` 等代码。
|
|
22
38
|
|
|
23
39
|
对于单选题,`custom` 会覆盖选中的选项,且 `selected` 为空。对于多选题,`custom` 可以补充 `selected` 中的标签。UI 可以把跳过的条目保留为 `{ id, selected: [] }`,既维持现有回答形态,也保留该批次中的其他回答。
|
|
24
40
|
|
|
25
|
-
请求包含 agent 时,`ask()` 会通过当前 `AgentRegistry` 验证该 agent 与注册表中的存活实例是同一对象,并且只允许运行时根调用。持久谱系不构成权限依据:带有历史委托深度的会话恢复为新的运行时根后可以提问;归属于另一个 agent
|
|
41
|
+
请求包含 agent 时,`ask()` 会通过当前 `AgentRegistry` 验证该 agent 与注册表中的存活实例是同一对象,并且只允许运行时根调用。持久谱系不构成权限依据:带有历史委托深度的会话恢复为新的运行时根后可以提问;归属于另一个 agent 的存活子级即使持久化记录的委托深度为零也会被拒绝。Web 回答者只接收带 Agent scope 的请求;不含 agent 的程序化请求仍会交给本地未限定 scope 的 waterfall listener,若无人接受则以 `NO_PROVIDER` 失败。
|
|
26
42
|
|
|
27
43
|
### 呈现意图
|
|
28
44
|
|
|
29
45
|
`intent` 声明某个问题本身就是一种已知决策,因此认识该标签的 UI 可以照此呈现——`plan-review` 表示 `detail` 是一份待审阅的计划,`dsh-plan-mode` 会在 `exit_plan_mode` 的问题上设置它。意图只改变呈现:遵循它的 UI 回答的仍是通用 UI 会发送的那些选项标签,不认识该标签的 UI 渲染通用选项列表,因此调用方两种情况下读到的回答字段相同。`approve` 指名表示批准的标签,而不依赖选项顺序。有两项断言无法通过类型表达,`ask()` 会以 `BAD_INTENT` 拒绝它们:`approve` 未命中该问题自身的任一选项,以及意图落在没有 `detail` 的问题上——而 `detail` 正是它自称在审阅的东西。
|
|
30
46
|
|
|
47
|
+
<a id="role"></a>
|
|
31
48
|
## 职责
|
|
32
49
|
|
|
33
|
-
这是 Service Definition 包。`@deepseek-ai/dsh-tool-ask-user` 等 Consumer 依赖此服务;Web
|
|
50
|
+
这是 Service Definition 包。`@deepseek-ai/dsh-tool-ask-user` 等 Consumer 依赖此服务;Web Client 通过 Remote Events 贡献带 Agent scope 的回答者。循环保持不变:工具调用等待 waterfall 结果,该结果随后恢复正常的 agent loop(智能体循环)。
|
|
34
51
|
|
|
52
|
+
<a id="model-experience"></a>
|
|
35
53
|
## 模型体验
|
|
36
54
|
|
|
37
|
-
间接地,通过 `dsh-tool-ask-user
|
|
55
|
+
间接地,通过 `dsh-tool-ask-user`:它会将成功回答保留为紧凑 JSON,或返回以下失败之一:`Error: ask_user_question was aborted before the user answered`、`Error: ask_user_question requires at least one question`、`Error: human interaction requires the exact live calling agent when an agent is supplied`、`Error: human interaction is unavailable while the calling agent is owned by another live agent; include the unresolved question or decision in the child agent's final result`、`Error: no user-questions answerer accepted the request` 或 `Error: <message>`。等待人类回答不会增加 token。
|
|
38
56
|
|
|
39
57
|
#### KV Cache 影响
|
|
40
58
|
|
|
@@ -42,5 +60,18 @@
|
|
|
42
60
|
|
|
43
61
|
## 已知限制与暂缓事项
|
|
44
62
|
|
|
45
|
-
-
|
|
63
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
64
|
+
|
|
65
|
+
- **带 Agent scope 的 Web 回答**:Remote Events 仅在请求带有存活 Agent scope 时路由随产品交付的 Web 回答者;agentless 调用方需要本地未限定 scope 的 waterfall listener。
|
|
46
66
|
- **词汇仅包含问题表单形态**:可供选择的选项加可选的自定义文本;更丰富的交互形态(文件选择器、diff 预览确认)尚无 seam 词汇。
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
<a id="dev-note"></a>
|
|
70
|
+
### 开发备注
|
|
71
|
+
|
|
72
|
+
<details>
|
|
73
|
+
<summary>维护者工作上下文——点击展开</summary>
|
|
74
|
+
|
|
75
|
+
无。
|
|
76
|
+
|
|
77
|
+
</details>
|
package/lib/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Service } from "@deepseek-ai/cordis";
|
|
2
2
|
import { HarnessError } from "@deepseek-ai/dsh-llm";
|
|
3
|
+
import { scopeTarget } from "@deepseek-ai/dsh-scope";
|
|
3
4
|
//#region lib/types/index.js
|
|
4
5
|
/**
|
|
5
6
|
* Service Definition for the user-questions capability seam (`ctx.userQuestions`): a UI-backed service for
|
|
6
7
|
* pausing an agent tool call until the human answers a question. The model-
|
|
7
|
-
* facing tool lives in `@deepseek-ai/dsh-tool-ask-user`; UI packages
|
|
8
|
-
* the
|
|
8
|
+
* facing tool lives in `@deepseek-ai/dsh-tool-ask-user`; UI packages compose
|
|
9
|
+
* answerers on the Agent-scoped Cordis waterfall.
|
|
9
10
|
*
|
|
10
11
|
* @module @deepseek-ai/dsh-user-questions
|
|
11
12
|
*/
|
|
@@ -16,30 +17,24 @@ var UserQuestionError = class extends HarnessError {
|
|
|
16
17
|
this.name = "UserQuestionError";
|
|
17
18
|
}
|
|
18
19
|
};
|
|
19
|
-
|
|
20
|
+
function abortedQuestion(cause) {
|
|
21
|
+
return new UserQuestionError("ask_user_question was aborted before the user answered", "ASK_ABORTED", cause === void 0 ? void 0 : { cause });
|
|
22
|
+
}
|
|
23
|
+
function isRecord(value) {
|
|
24
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
function restoreUserQuestionError(reason) {
|
|
27
|
+
if (reason instanceof UserQuestionError) return reason;
|
|
28
|
+
if (isRecord(reason) && reason.name === "UserQuestionError" && typeof reason.message === "string" && typeof reason.code === "string") return new UserQuestionError(reason.message, reason.code, { cause: reason });
|
|
29
|
+
return reason;
|
|
30
|
+
}
|
|
31
|
+
/** `ctx.userQuestions`: validation plus the scoped answerer waterfall. */
|
|
20
32
|
var UserQuestionService = class extends Service {
|
|
21
|
-
provider;
|
|
22
33
|
constructor(ctx) {
|
|
23
34
|
super(ctx, "userQuestions");
|
|
24
35
|
}
|
|
25
36
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* @param provider UI-side implementation that collects answers.
|
|
29
|
-
* @returns Disposer that unregisters this provider.
|
|
30
|
-
*/
|
|
31
|
-
registerProvider(provider) {
|
|
32
|
-
const dispose = this.ctx.effect(function* () {
|
|
33
|
-
if (this.provider !== void 0) throw new UserQuestionError("a user-questions provider is already registered", "DUPLICATE_PROVIDER");
|
|
34
|
-
this.provider = provider;
|
|
35
|
-
yield () => {
|
|
36
|
-
this.provider = void 0;
|
|
37
|
-
};
|
|
38
|
-
}.bind(this), "userInteraction.registerProvider()");
|
|
39
|
-
return () => void dispose();
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Ask the active UI provider and wait for the user's answer.
|
|
37
|
+
* Ask the scoped answerer waterfall and wait for the user's answer.
|
|
43
38
|
*
|
|
44
39
|
* When a caller supplies an agent, human interaction is valid only for the
|
|
45
40
|
* exact live runtime root. Runtime ownership, not durable session lineage,
|
|
@@ -49,12 +44,13 @@ var UserQuestionService = class extends Service {
|
|
|
49
44
|
*
|
|
50
45
|
* @param request Questions, owner agent, and abort signal.
|
|
51
46
|
* @returns The answer chosen or typed by the human.
|
|
52
|
-
* @throws {UserQuestionError} code `
|
|
53
|
-
*
|
|
54
|
-
*
|
|
47
|
+
* @throws {UserQuestionError} code `ASK_ABORTED` when the supplied signal
|
|
48
|
+
* is already or becomes aborted, `CALLER_NOT_LIVE` when a supplied agent
|
|
49
|
+
* is not the registry's exact live instance, or `DELEGATED_CALLER` when
|
|
50
|
+
* that live agent is owned by another agent.
|
|
55
51
|
*/
|
|
56
52
|
async ask(request) {
|
|
57
|
-
if (request.signal?.aborted) throw
|
|
53
|
+
if (request.signal?.aborted) throw abortedQuestion();
|
|
58
54
|
if (request.questions.length === 0) throw new UserQuestionError("ask_user_question requires at least one question", "EMPTY_QUESTIONS");
|
|
59
55
|
const agent = request.agent;
|
|
60
56
|
if (agent !== void 0) {
|
|
@@ -68,8 +64,18 @@ var UserQuestionService = class extends Service {
|
|
|
68
64
|
if (!(question.options ?? []).some((option) => option.label === intent.approve)) throw new UserQuestionError(`question ${question.id} declares intent ${intent.kind} whose approve label ${JSON.stringify(intent.approve)} names none of its options`, "BAD_INTENT");
|
|
69
65
|
if (question.detail === void 0) throw new UserQuestionError(`question ${question.id} declares intent ${intent.kind} without the detail it reviews`, "BAD_INTENT");
|
|
70
66
|
}
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
const noAnswerer = () => Promise.reject(new UserQuestionError("no user-questions answerer accepted the request", "NO_PROVIDER"));
|
|
68
|
+
try {
|
|
69
|
+
return await (agent === void 0 ? this.ctx.waterfall("user-questions/request", request, noAnswerer) : this.ctx.waterfall(scopeTarget(agent, agent), "user-questions/request", {
|
|
70
|
+
...request,
|
|
71
|
+
agent
|
|
72
|
+
}, noAnswerer));
|
|
73
|
+
} catch (error) {
|
|
74
|
+
const restored = restoreUserQuestionError(error);
|
|
75
|
+
if (restored instanceof UserQuestionError) throw restored;
|
|
76
|
+
if (request.signal?.aborted) throw abortedQuestion(error);
|
|
77
|
+
throw restored;
|
|
78
|
+
}
|
|
73
79
|
}
|
|
74
80
|
};
|
|
75
81
|
//#endregion
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,51 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Service Definition for the user-questions capability seam (`ctx.userQuestions`): a UI-backed service for
|
|
3
3
|
* pausing an agent tool call until the human answers a question. The model-
|
|
4
|
-
* facing tool lives in `@deepseek-ai/dsh-tool-ask-user`; UI packages
|
|
5
|
-
* the
|
|
4
|
+
* facing tool lives in `@deepseek-ai/dsh-tool-ask-user`; UI packages compose
|
|
5
|
+
* answerers on the Agent-scoped Cordis waterfall.
|
|
6
6
|
*
|
|
7
7
|
* @module @deepseek-ai/dsh-user-questions
|
|
8
8
|
*/
|
|
9
9
|
import { Context, Service } from '@deepseek-ai/cordis';
|
|
10
|
-
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
11
10
|
import { HarnessError } from '@deepseek-ai/dsh-llm';
|
|
12
11
|
declare module '@deepseek-ai/cordis' {
|
|
13
12
|
interface Context {
|
|
14
13
|
userQuestions: UserQuestionService;
|
|
15
14
|
}
|
|
16
15
|
}
|
|
17
|
-
import type { AskUserQuestionAnswer,
|
|
16
|
+
import type { AskUserQuestionAnswer, AskUserQuestionRequestEvent } from './types.ts';
|
|
18
17
|
export type { AskUserQuestionAnswer, AskUserQuestionAnswerItem, AskUserQuestionIntent, AskUserQuestionItem, AskUserQuestionOption, } from './types.ts';
|
|
19
18
|
/** Request for a human answer. */
|
|
20
|
-
export interface AskUserQuestionRequest {
|
|
21
|
-
/** Questions to display. */
|
|
22
|
-
questions: AskUserQuestionItem[];
|
|
23
|
-
/** Exact live calling agent, when the request came from an agent tool call. */
|
|
24
|
-
agent?: Agent;
|
|
25
|
-
/** Abort signal for the owning tool/step. */
|
|
26
|
-
signal?: AbortSignal;
|
|
27
|
-
}
|
|
28
|
-
/** UI-side provider for user questions. */
|
|
29
|
-
export interface UserQuestionProvider {
|
|
30
|
-
ask(request: AskUserQuestionRequest): Promise<AskUserQuestionAnswer>;
|
|
19
|
+
export interface AskUserQuestionRequest extends AskUserQuestionRequestEvent {
|
|
31
20
|
}
|
|
32
21
|
/** Stable error taxonomy for user-questions failures. */
|
|
33
22
|
export declare class UserQuestionError extends HarnessError {
|
|
34
23
|
constructor(message: string, code: string, options?: ErrorOptions);
|
|
35
24
|
}
|
|
36
|
-
/** `ctx.userQuestions`:
|
|
25
|
+
/** `ctx.userQuestions`: validation plus the scoped answerer waterfall. */
|
|
37
26
|
export declare class UserQuestionService extends Service {
|
|
38
|
-
private provider;
|
|
39
27
|
constructor(ctx: Context);
|
|
40
28
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* @param provider UI-side implementation that collects answers.
|
|
44
|
-
* @returns Disposer that unregisters this provider.
|
|
45
|
-
*/
|
|
46
|
-
registerProvider(provider: UserQuestionProvider): () => void;
|
|
47
|
-
/**
|
|
48
|
-
* Ask the active UI provider and wait for the user's answer.
|
|
29
|
+
* Ask the scoped answerer waterfall and wait for the user's answer.
|
|
49
30
|
*
|
|
50
31
|
* When a caller supplies an agent, human interaction is valid only for the
|
|
51
32
|
* exact live runtime root. Runtime ownership, not durable session lineage,
|
|
@@ -55,9 +36,10 @@ export declare class UserQuestionService extends Service {
|
|
|
55
36
|
*
|
|
56
37
|
* @param request Questions, owner agent, and abort signal.
|
|
57
38
|
* @returns The answer chosen or typed by the human.
|
|
58
|
-
* @throws {UserQuestionError} code `
|
|
59
|
-
*
|
|
60
|
-
*
|
|
39
|
+
* @throws {UserQuestionError} code `ASK_ABORTED` when the supplied signal
|
|
40
|
+
* is already or becomes aborted, `CALLER_NOT_LIVE` when a supplied agent
|
|
41
|
+
* is not the registry's exact live instance, or `DELEGATED_CALLER` when
|
|
42
|
+
* that live agent is owned by another agent.
|
|
61
43
|
*/
|
|
62
44
|
ask(request: AskUserQuestionRequest): Promise<AskUserQuestionAnswer>;
|
|
63
45
|
}
|
package/lib/types/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Service Definition for the user-questions capability seam (`ctx.userQuestions`): a UI-backed service for
|
|
3
3
|
* pausing an agent tool call until the human answers a question. The model-
|
|
4
|
-
* facing tool lives in `@deepseek-ai/dsh-tool-ask-user`; UI packages
|
|
5
|
-
* the
|
|
4
|
+
* facing tool lives in `@deepseek-ai/dsh-tool-ask-user`; UI packages compose
|
|
5
|
+
* answerers on the Agent-scoped Cordis waterfall.
|
|
6
6
|
*
|
|
7
7
|
* @module @deepseek-ai/dsh-user-questions
|
|
8
8
|
*/
|
|
9
9
|
import { Service } from '@deepseek-ai/cordis';
|
|
10
10
|
import { HarnessError } from '@deepseek-ai/dsh-llm';
|
|
11
|
+
import { scopeTarget } from '@deepseek-ai/dsh-scope';
|
|
11
12
|
/** Stable error taxonomy for user-questions failures. */
|
|
12
13
|
export class UserQuestionError extends HarnessError {
|
|
13
14
|
constructor(message, code, options) {
|
|
@@ -15,32 +16,30 @@ export class UserQuestionError extends HarnessError {
|
|
|
15
16
|
this.name = 'UserQuestionError';
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
|
-
|
|
19
|
+
function abortedQuestion(cause) {
|
|
20
|
+
return new UserQuestionError('ask_user_question was aborted before the user answered', 'ASK_ABORTED', cause === undefined ? undefined : { cause });
|
|
21
|
+
}
|
|
22
|
+
function isRecord(value) {
|
|
23
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
function restoreUserQuestionError(reason) {
|
|
26
|
+
if (reason instanceof UserQuestionError)
|
|
27
|
+
return reason;
|
|
28
|
+
if (isRecord(reason)
|
|
29
|
+
&& reason.name === 'UserQuestionError'
|
|
30
|
+
&& typeof reason.message === 'string'
|
|
31
|
+
&& typeof reason.code === 'string') {
|
|
32
|
+
return new UserQuestionError(reason.message, reason.code, { cause: reason });
|
|
33
|
+
}
|
|
34
|
+
return reason;
|
|
35
|
+
}
|
|
36
|
+
/** `ctx.userQuestions`: validation plus the scoped answerer waterfall. */
|
|
19
37
|
export class UserQuestionService extends Service {
|
|
20
|
-
provider;
|
|
21
38
|
constructor(ctx) {
|
|
22
39
|
super(ctx, 'userQuestions');
|
|
23
40
|
}
|
|
24
41
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* @param provider UI-side implementation that collects answers.
|
|
28
|
-
* @returns Disposer that unregisters this provider.
|
|
29
|
-
*/
|
|
30
|
-
registerProvider(provider) {
|
|
31
|
-
const dispose = this.ctx.effect(function* () {
|
|
32
|
-
if (this.provider !== undefined) {
|
|
33
|
-
throw new UserQuestionError('a user-questions provider is already registered', 'DUPLICATE_PROVIDER');
|
|
34
|
-
}
|
|
35
|
-
this.provider = provider;
|
|
36
|
-
yield () => {
|
|
37
|
-
this.provider = undefined;
|
|
38
|
-
};
|
|
39
|
-
}.bind(this), 'userInteraction.registerProvider()');
|
|
40
|
-
return () => void dispose();
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Ask the active UI provider and wait for the user's answer.
|
|
42
|
+
* Ask the scoped answerer waterfall and wait for the user's answer.
|
|
44
43
|
*
|
|
45
44
|
* When a caller supplies an agent, human interaction is valid only for the
|
|
46
45
|
* exact live runtime root. Runtime ownership, not durable session lineage,
|
|
@@ -50,13 +49,14 @@ export class UserQuestionService extends Service {
|
|
|
50
49
|
*
|
|
51
50
|
* @param request Questions, owner agent, and abort signal.
|
|
52
51
|
* @returns The answer chosen or typed by the human.
|
|
53
|
-
* @throws {UserQuestionError} code `
|
|
54
|
-
*
|
|
55
|
-
*
|
|
52
|
+
* @throws {UserQuestionError} code `ASK_ABORTED` when the supplied signal
|
|
53
|
+
* is already or becomes aborted, `CALLER_NOT_LIVE` when a supplied agent
|
|
54
|
+
* is not the registry's exact live instance, or `DELEGATED_CALLER` when
|
|
55
|
+
* that live agent is owned by another agent.
|
|
56
56
|
*/
|
|
57
57
|
async ask(request) {
|
|
58
58
|
if (request.signal?.aborted) {
|
|
59
|
-
throw
|
|
59
|
+
throw abortedQuestion();
|
|
60
60
|
}
|
|
61
61
|
if (request.questions.length === 0) {
|
|
62
62
|
throw new UserQuestionError('ask_user_question requires at least one question', 'EMPTY_QUESTIONS');
|
|
@@ -91,10 +91,21 @@ export class UserQuestionService extends Service {
|
|
|
91
91
|
throw new UserQuestionError(`question ${question.id} declares intent ${intent.kind} without the detail it reviews`, 'BAD_INTENT');
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
const noAnswerer = () => Promise.reject(new UserQuestionError('no user-questions answerer accepted the request', 'NO_PROVIDER'));
|
|
95
|
+
try {
|
|
96
|
+
return await (agent === undefined
|
|
97
|
+
? this.ctx.waterfall('user-questions/request', request, noAnswerer)
|
|
98
|
+
: this.ctx.waterfall(scopeTarget(agent, agent), 'user-questions/request', { ...request, agent }, noAnswerer));
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
const restored = restoreUserQuestionError(error);
|
|
102
|
+
if (restored instanceof UserQuestionError)
|
|
103
|
+
throw restored;
|
|
104
|
+
if (request.signal?.aborted) {
|
|
105
|
+
throw abortedQuestion(error);
|
|
106
|
+
}
|
|
107
|
+
throw restored;
|
|
96
108
|
}
|
|
97
|
-
return this.provider.ask(request);
|
|
98
109
|
}
|
|
99
110
|
}
|
|
100
111
|
export default UserQuestionService;
|
package/lib/types/types.d.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* package's Context augmentation.
|
|
5
|
-
* @module @deepseek-ai/dsh-user-questions/types
|
|
6
|
-
*/
|
|
1
|
+
/** Client-safe question, answer, and event types. @module @deepseek-ai/dsh-user-questions/types */
|
|
2
|
+
import type { Scoped } from '@deepseek-ai/dsh-scope';
|
|
3
|
+
import type { Agent } from '@deepseek-ai/dsh-agent/types';
|
|
7
4
|
/** One selectable answer offered to the user. */
|
|
8
5
|
export interface AskUserQuestionOption {
|
|
9
6
|
/** User-facing label. */
|
|
@@ -59,4 +56,25 @@ export interface AskUserQuestionAnswer {
|
|
|
59
56
|
/** Structured answers keyed by question id. */
|
|
60
57
|
answers: AskUserQuestionAnswerItem[];
|
|
61
58
|
}
|
|
59
|
+
/** Client-safe payload declared for the user-question answerer waterfall. */
|
|
60
|
+
export interface AskUserQuestionRequestEvent {
|
|
61
|
+
/** Questions to display. */
|
|
62
|
+
questions: AskUserQuestionItem[];
|
|
63
|
+
/** Agent identity projected to the corresponding Client Context in transit. */
|
|
64
|
+
agent?: Agent;
|
|
65
|
+
/** Cancellation lifetime of the pending request. */
|
|
66
|
+
signal?: AbortSignal;
|
|
67
|
+
}
|
|
68
|
+
declare module '@deepseek-ai/cordis' {
|
|
69
|
+
interface Events {
|
|
70
|
+
/**
|
|
71
|
+
* Ask composed answerers for structured user input. Return an answer to
|
|
72
|
+
* claim the request or call `next()` to delegate. Scope-filtered dispatch
|
|
73
|
+
* (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
|
|
74
|
+
* @param request - pending user-question request.
|
|
75
|
+
* @mode waterfall
|
|
76
|
+
*/
|
|
77
|
+
'user-questions/request'(this: Scoped<Agent>, request: AskUserQuestionRequestEvent, next: () => Promise<AskUserQuestionAnswer>): Promise<AskUserQuestionAnswer>;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
62
80
|
//# sourceMappingURL=types.d.ts.map
|
package/lib/types/types.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wire-safe question and answer types, free of cordis/service imports so browser
|
|
3
|
-
* type chains (apiproxy api → client) can consume them without loading this
|
|
4
|
-
* package's Context augmentation.
|
|
5
|
-
* @module @deepseek-ai/dsh-user-questions/types
|
|
6
|
-
*/
|
|
1
|
+
/** Client-safe question, answer, and event types. @module @deepseek-ai/dsh-user-questions/types */
|
|
7
2
|
export {};
|
|
8
3
|
//# sourceMappingURL=types.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-user-questions",
|
|
3
3
|
"description": "Abstract user-questions seam (ctx.userQuestions) for asking the human during agent runs",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2-alpha.2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -37,15 +37,17 @@
|
|
|
37
37
|
],
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"@deepseek-ai/dsh-agent": "^0.1.
|
|
41
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
42
|
-
"@deepseek-ai/
|
|
43
|
-
"@deepseek-ai/dsh-invariants": "^0.1.
|
|
40
|
+
"@deepseek-ai/dsh-agent": "^0.1.2-alpha.2",
|
|
41
|
+
"@deepseek-ai/dsh-llm": "^0.1.2-alpha.2",
|
|
42
|
+
"@deepseek-ai/dsh-scope": "^0.1.2-alpha.2",
|
|
43
|
+
"@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
|
|
44
|
+
"@deepseek-ai/cordis": "^4.0.2"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
|
-
"@deepseek-ai/dsh-agent": "^0.1.
|
|
47
|
-
"@deepseek-ai/dsh-invariants": "^0.1.
|
|
48
|
-
"@deepseek-ai/
|
|
49
|
-
"@deepseek-ai/dsh-
|
|
47
|
+
"@deepseek-ai/dsh-agent": "^0.1.2-alpha.2",
|
|
48
|
+
"@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
|
|
49
|
+
"@deepseek-ai/dsh-llm": "^0.1.2-alpha.2",
|
|
50
|
+
"@deepseek-ai/dsh-scope": "^0.1.2-alpha.2",
|
|
51
|
+
"@deepseek-ai/cordis": "^4.0.2"
|
|
50
52
|
}
|
|
51
53
|
}
|