dsh-better-ask-user 0.1.3 → 0.1.4
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/package.json +1 -1
- package/src/ask-tool.js +108 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-better-ask-user",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Independent ask_user_question plugin: configurable wait, input-extended countdown, markdown/mobile card, native or default timeout behavior, and PTC deadline exemption.",
|
|
5
5
|
"license": "GPL-3.0",
|
|
6
6
|
"repository": {
|
package/src/ask-tool.js
CHANGED
|
@@ -1,16 +1,113 @@
|
|
|
1
|
+
/** ask_user_question scope shadow: official contract plus the plugin-managed wait. */
|
|
2
|
+
import { ToolArgsError, validateJsonSchemaValue } from '@deepseek-ai/dsh-tools'
|
|
3
|
+
import { buildDefaultAnswer } from './default-answer.js'
|
|
4
|
+
|
|
5
|
+
/** Model-visible notice for the native pending result. */
|
|
6
|
+
export const PENDING_QUESTION_NOTICE = '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.'
|
|
7
|
+
|
|
8
|
+
function questionRequest(questions, exec) {
|
|
9
|
+
return {
|
|
10
|
+
questions: questions.map(question => ({
|
|
11
|
+
id: question.id,
|
|
12
|
+
question: question.question,
|
|
13
|
+
...(question.header !== undefined ? { header: question.header } : {}),
|
|
14
|
+
...(question.options !== undefined ? {
|
|
15
|
+
options: question.options.map(option => ({ ...option })),
|
|
16
|
+
} : {}),
|
|
17
|
+
...(question.multi_select !== undefined ? { multiSelect: question.multi_select } : {}),
|
|
18
|
+
})),
|
|
19
|
+
...(exec.agent !== undefined ? { agent: exec.agent } : {}),
|
|
20
|
+
signal: exec.signal,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function validateQuestionIds(questions) {
|
|
25
|
+
const ids = new Set()
|
|
26
|
+
for (const question of questions) {
|
|
27
|
+
if (ids.has(question.id)) throw new Error('question id ' + JSON.stringify(question.id) + ' must be unique within this call')
|
|
28
|
+
ids.add(question.id)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function answerResult(result) {
|
|
33
|
+
return {
|
|
34
|
+
answers: result.answers.map(answer => ({
|
|
35
|
+
id: answer.id,
|
|
36
|
+
selected: [...answer.selected],
|
|
37
|
+
...(answer.custom !== undefined ? { custom: answer.custom } : {}),
|
|
38
|
+
})),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The timed path can return the official pending shape; keep it valid. */
|
|
43
|
+
function outputWithPending(base) {
|
|
44
|
+
const output = { ...base.output }
|
|
45
|
+
const schema = structuredClone(base.output.schema)
|
|
46
|
+
if (Array.isArray(schema?.oneOf) && schema.oneOf.some(branch => branch?.properties?.pending !== undefined)) return output
|
|
47
|
+
output.schema = {
|
|
48
|
+
oneOf: [
|
|
49
|
+
schema,
|
|
50
|
+
{
|
|
51
|
+
type: 'object',
|
|
52
|
+
additionalProperties: false,
|
|
53
|
+
properties: {
|
|
54
|
+
pending: { type: 'boolean' },
|
|
55
|
+
callId: { type: 'string' },
|
|
56
|
+
message: { type: 'string' },
|
|
57
|
+
},
|
|
58
|
+
required: ['pending', 'callId', 'message'],
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
}
|
|
62
|
+
return output
|
|
63
|
+
}
|
|
64
|
+
|
|
1
65
|
/**
|
|
2
|
-
* ask_user_question
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
66
|
+
* Build the ask_user_question shadow from the currently registered base tool.
|
|
67
|
+
* @param ctx - root host context with userQuestions and optional goals.
|
|
68
|
+
* @param base - existing official ask_user_question definition.
|
|
69
|
+
* @param readConfig - live config reader.
|
|
70
|
+
* @returns a ToolDefinition with the official contract and the plugin wait.
|
|
6
71
|
*/
|
|
7
|
-
export function createBetterAskTool(
|
|
72
|
+
export function createBetterAskTool(ctx, base, readConfig) {
|
|
73
|
+
const output = outputWithPending(base)
|
|
74
|
+
|
|
8
75
|
const execute = async (args, exec) => {
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
76
|
+
const violations = validateJsonSchemaValue(base.parameters, args, '')
|
|
77
|
+
if (violations.length > 0) throw new ToolArgsError(violations)
|
|
78
|
+
validateQuestionIds(args.questions)
|
|
79
|
+
|
|
80
|
+
const userQuestions = ctx.get('userQuestions')
|
|
81
|
+
if (userQuestions === undefined) throw new Error('ask_user_question requires the userQuestions service')
|
|
82
|
+
|
|
83
|
+
const config = readConfig()
|
|
84
|
+
const goal = exec.agent === undefined ? undefined : ctx.get('goals')?.get?.(exec.agent)
|
|
85
|
+
const goalActive = goal?.phase === 'active' && goal.activation === 'armed'
|
|
86
|
+
const mode = goalActive ? config.goalTimeoutBehavior : config.timeoutBehavior
|
|
87
|
+
const seconds = config.questionTimeoutSeconds
|
|
88
|
+
const questions = questionRequest(args.questions, exec)
|
|
89
|
+
const defaultAnswer = mode === 'default' && seconds !== -1
|
|
90
|
+
? answerResult(buildDefaultAnswer(args.questions))
|
|
91
|
+
: undefined
|
|
92
|
+
|
|
93
|
+
if (seconds === -1) {
|
|
94
|
+
const result = await userQuestions.ask({ ...questions, wait: { callId: exec.callId } })
|
|
95
|
+
return answerResult(result)
|
|
96
|
+
}
|
|
97
|
+
if (exec.agent === undefined) throw new Error('timed questions require a live agent')
|
|
98
|
+
|
|
99
|
+
const waitMs = seconds * 1000
|
|
100
|
+
const result = await userQuestions.askTimed({
|
|
101
|
+
...questions,
|
|
102
|
+
agent: exec.agent,
|
|
103
|
+
better: { waitMs, timeoutMode: mode, defaultAnswer },
|
|
104
|
+
}, exec.callId, waitMs)
|
|
105
|
+
if ('pending' in result) {
|
|
106
|
+
if (mode === 'default' && defaultAnswer !== undefined) return answerResult(defaultAnswer)
|
|
107
|
+
return { ...result, message: PENDING_QUESTION_NOTICE }
|
|
108
|
+
}
|
|
109
|
+
return answerResult(result)
|
|
14
110
|
}
|
|
15
|
-
|
|
111
|
+
|
|
112
|
+
return { ...base, output, execute }
|
|
16
113
|
}
|