@cognipeer/agent-sdk 0.6.5 → 0.6.6
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.md +7 -2
- package/dist/index.cjs +306 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +110 -2
- package/dist/index.d.ts +110 -2
- package/dist/index.mjs +305 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,7 +37,8 @@ Highlights:
|
|
|
37
37
|
- **Unified reasoning surface** – one `reasoning` config controls provider-native reasoning and post-tool plain-text reflections.
|
|
38
38
|
- **Structured output** – provide a Zod schema and the agent injects a finalize tool to capture JSON deterministically.
|
|
39
39
|
- **Multi-agent and handoffs** – wrap agents as tools or transfer control mid-run with `asTool` / `asHandoff`.
|
|
40
|
-
- **
|
|
40
|
+
- **Human-in-the-loop** – `needsApproval` tools pause for review; `humanInTheLoop.askUser` enables a built-in `ask_user_question` tool that pauses with a structured multi-choice prompt and resumes from the user's answer.
|
|
41
|
+
- **Usage + events** – normalize provider usage, surface `tool_call`, `tool_approval`, `user_question`, `plan`, `summarization`, `reflection`, `metadata`, and `handoff` events.
|
|
41
42
|
- **Structured tracing** – optional per-invoke JSON traces with metadata, payload capture, and pluggable sinks (file, HTTP, Cognipeer, custom).
|
|
42
43
|
|
|
43
44
|
## What’s inside
|
|
@@ -267,6 +268,7 @@ console.log(res.content);
|
|
|
267
268
|
- **Planning discipline** – when planning is enabled the system prompt distinguishes full plan writes from incremental plan updates and emits `plan` events as todos change.
|
|
268
269
|
- **Structured output** – supply `outputSchema` and the framework adds a hidden `response` finalize tool; parsed JSON is returned as `result.output`.
|
|
269
270
|
- **Multi-agent orchestration** – reuse agents via `agent.asTool({ toolName })` or perform handoffs that swap runtimes mid-execution.
|
|
271
|
+
- **Ask the user** – opt in with `humanInTheLoop: { askUser: true }` to give the model an `ask_user_question` tool that pauses the run; resume by calling `agent.resolveUserQuestion(state, { id, answers })`. The global `allowFreeText` flag controls whether "Other" / typed answers are allowed.
|
|
270
272
|
- **MCP + LangChain tools** – any object satisfying the minimal tool interface works; LangChain’s `Tool` implementations plug in directly.
|
|
271
273
|
- **Vision input** – message parts accept base64 or URL images for multimodal requests.
|
|
272
274
|
- **Observability hooks** – `config.onEvent` surfaces tool lifecycle, summarization, reflection, metadata, and final answer events for streaming UIs or CLIs.
|
|
@@ -285,6 +287,7 @@ Examples live under `examples/` with per-folder READMEs. Build the package first
|
|
|
285
287
|
| `summarize-context/` | Summaries + `get_tool_response` raw retrieval. |
|
|
286
288
|
| `structured-output/` | Zod schema finalize tool and parsed outputs. |
|
|
287
289
|
| `rewrite-summary/` | Continue conversations after summaries are injected. |
|
|
290
|
+
| `ask-user/` | Built-in `ask_user_question` tool, structured multi-choice prompt, resume with user's answer. |
|
|
288
291
|
| `multi-agent/` | Delegating between agents via `asTool`. |
|
|
289
292
|
| `handoff/` | Explicit runtime handoffs. |
|
|
290
293
|
| `mcp-tavily/` | MCP remote tool discovery. |
|
|
@@ -349,7 +352,9 @@ Exported helpers (`agent-sdk/src/index.ts`):
|
|
|
349
352
|
**Utilities:**
|
|
350
353
|
- `buildSystemPrompt(extra?, planning?, name?)`
|
|
351
354
|
- `setTokenCounter`, `getTokenCounter`, `defaultTokenCounter` – swap in a real tokenizer (tiktoken, etc.) globally or per-invoke
|
|
352
|
-
-
|
|
355
|
+
- `resolveToolApprovalState`, `resolveUserQuestionState` – pure helpers behind `agent.resolveToolApproval` / `agent.resolveUserQuestion` for callers that want to apply a resolution outside the agent factory.
|
|
356
|
+
- `createAskUserQuestionTool(stateRef, { allowFreeText, promptOverride })` – exposed for advanced users that want to attach the human-in-the-loop tool manually instead of via `humanInTheLoop.askUser`.
|
|
357
|
+
- Node factories (`nodes/*`), context helpers, token utilities, and full TypeScript types (`SmartAgentOptions`, `SmartState`, `AgentInvokeResult`, `PendingUserQuestion`, `UserQuestionResolution`, etc.).
|
|
353
358
|
|
|
354
359
|
`SmartAgentOptions` accepts the usual suspects (`model`, `tools`, `limits`, `runtimeProfile`, `customProfile`, `useTodoList`, `summarization`, `reasoning`, `usageConverter`, `tracing`, `tokenCounter`, `costEstimator`). See `docs/api/` for detailed type references.
|
|
355
360
|
|
package/dist/index.cjs
CHANGED
|
@@ -753,6 +753,7 @@ function createResolverNode() {
|
|
|
753
753
|
toolHistory: state.toolHistory || [],
|
|
754
754
|
toolHistoryArchived: state.toolHistoryArchived || [],
|
|
755
755
|
pendingApprovals: state.pendingApprovals || [],
|
|
756
|
+
pendingUserQuestions: state.pendingUserQuestions || [],
|
|
756
757
|
plan: state.plan || null,
|
|
757
758
|
planVersion: state.planVersion || 0
|
|
758
759
|
};
|
|
@@ -2594,13 +2595,16 @@ function createToolsNode(initialTools, opts) {
|
|
|
2594
2595
|
const traceSession = state.ctx?.__traceSession;
|
|
2595
2596
|
const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals.map((entry) => ({ ...entry })) : [];
|
|
2596
2597
|
const pendingByCallId = new Map(pendingApprovals.map((entry) => [entry.toolCallId, entry]));
|
|
2598
|
+
const pendingUserQuestions = Array.isArray(state.pendingUserQuestions) ? state.pendingUserQuestions.map((entry) => ({ ...entry })) : [];
|
|
2597
2599
|
let awaitingApproval = false;
|
|
2600
|
+
let awaitingUserQuestion = false;
|
|
2598
2601
|
for (const tool of toolByName.values()) {
|
|
2599
2602
|
const anyTool = tool;
|
|
2600
2603
|
if (anyTool._stateRef && typeof anyTool._stateRef === "object") {
|
|
2601
2604
|
anyTool._stateRef.toolHistory = state.toolHistory;
|
|
2602
2605
|
anyTool._stateRef.toolHistoryArchived = state.toolHistoryArchived;
|
|
2603
2606
|
anyTool._stateRef.pendingApprovals = pendingApprovals;
|
|
2607
|
+
anyTool._stateRef.pendingUserQuestions = pendingUserQuestions;
|
|
2604
2608
|
anyTool._stateRef.messages = state.messages;
|
|
2605
2609
|
anyTool._stateRef.ctx = state.ctx || (state.ctx = {});
|
|
2606
2610
|
anyTool._stateRef.__onEvent = onEvent;
|
|
@@ -2674,6 +2678,7 @@ function createToolsNode(initialTools, opts) {
|
|
|
2674
2678
|
const toolStateRef = tool._stateRef && typeof tool._stateRef === "object" ? tool._stateRef : null;
|
|
2675
2679
|
if (toolStateRef) {
|
|
2676
2680
|
toolStateRef.pendingApprovals = pendingApprovals;
|
|
2681
|
+
toolStateRef.pendingUserQuestions = pendingUserQuestions;
|
|
2677
2682
|
toolStateRef.ctx = state.ctx || (state.ctx = {});
|
|
2678
2683
|
toolStateRef.__currentToolCallId = toolCallId;
|
|
2679
2684
|
delete toolStateRef.__awaitingApproval;
|
|
@@ -2885,6 +2890,22 @@ function createToolsNode(initialTools, opts) {
|
|
|
2885
2890
|
approval: pendingApproval
|
|
2886
2891
|
};
|
|
2887
2892
|
}
|
|
2893
|
+
if (output && typeof output === "object" && output.__awaitingUserQuestion) {
|
|
2894
|
+
if (toolStateRef?.ctx && typeof toolStateRef.ctx === "object") {
|
|
2895
|
+
state.ctx = toolStateRef.ctx;
|
|
2896
|
+
}
|
|
2897
|
+
const pendingQuestion = pendingUserQuestions.find(
|
|
2898
|
+
(entry) => entry.toolCallId === output.toolCallId || entry.id === output.id
|
|
2899
|
+
);
|
|
2900
|
+
if (!pendingQuestion) {
|
|
2901
|
+
throw new Error(`Tool ${toolName} requested a user question but no pending question entry was recorded.`);
|
|
2902
|
+
}
|
|
2903
|
+
awaitingUserQuestion = true;
|
|
2904
|
+
return {
|
|
2905
|
+
status: "awaiting_user_question",
|
|
2906
|
+
userQuestion: pendingQuestion
|
|
2907
|
+
};
|
|
2908
|
+
}
|
|
2888
2909
|
if (output && typeof output === "object" && output.__finalStructuredOutput) {
|
|
2889
2910
|
if (!state.ctx) state.ctx = {};
|
|
2890
2911
|
state.ctx.__structuredOutputParsed = output.data;
|
|
@@ -3022,15 +3043,19 @@ function createToolsNode(initialTools, opts) {
|
|
|
3022
3043
|
return false;
|
|
3023
3044
|
};
|
|
3024
3045
|
for (const idx of approvalIndices) {
|
|
3025
|
-
if (awaitingApproval || shouldStop) break;
|
|
3046
|
+
if (awaitingApproval || awaitingUserQuestion || shouldStop) break;
|
|
3026
3047
|
if (checkCancellationStop()) break;
|
|
3027
3048
|
const result = await runOne(planned[idx], orderedSlots[idx]);
|
|
3028
3049
|
if (result.status === "awaiting_approval") {
|
|
3029
3050
|
awaitingApproval = true;
|
|
3030
3051
|
break;
|
|
3031
3052
|
}
|
|
3053
|
+
if (result.status === "awaiting_user_question") {
|
|
3054
|
+
awaitingUserQuestion = true;
|
|
3055
|
+
break;
|
|
3056
|
+
}
|
|
3032
3057
|
}
|
|
3033
|
-
if (!awaitingApproval && !shouldStop && parallelIndices.length > 0) {
|
|
3058
|
+
if (!awaitingApproval && !awaitingUserQuestion && !shouldStop && parallelIndices.length > 0) {
|
|
3034
3059
|
const concurrency = Math.max(1, Math.min(limits.maxParallelTools, parallelIndices.length));
|
|
3035
3060
|
let cursor = 0;
|
|
3036
3061
|
const workerError = {};
|
|
@@ -3038,7 +3063,7 @@ function createToolsNode(initialTools, opts) {
|
|
|
3038
3063
|
for (let w = 0; w < concurrency; w++) {
|
|
3039
3064
|
workers.push((async () => {
|
|
3040
3065
|
while (true) {
|
|
3041
|
-
if (workerError.err || shouldStop || awaitingApproval) return;
|
|
3066
|
+
if (workerError.err || shouldStop || awaitingApproval || awaitingUserQuestion) return;
|
|
3042
3067
|
const i = cursor++;
|
|
3043
3068
|
if (i >= parallelIndices.length) return;
|
|
3044
3069
|
if (checkCancellationStop()) return;
|
|
@@ -3049,6 +3074,10 @@ function createToolsNode(initialTools, opts) {
|
|
|
3049
3074
|
awaitingApproval = true;
|
|
3050
3075
|
return;
|
|
3051
3076
|
}
|
|
3077
|
+
if (result.status === "awaiting_user_question") {
|
|
3078
|
+
awaitingUserQuestion = true;
|
|
3079
|
+
return;
|
|
3080
|
+
}
|
|
3052
3081
|
} catch (err) {
|
|
3053
3082
|
workerError.err = err;
|
|
3054
3083
|
return;
|
|
@@ -3111,11 +3140,12 @@ function createToolsNode(initialTools, opts) {
|
|
|
3111
3140
|
toolCache: state.toolCache,
|
|
3112
3141
|
// Explicitly propagate ctx so callers using `{ ...state, ...delta }`
|
|
3113
3142
|
// do not silently capture a stale ctx reference. Mutations to
|
|
3114
|
-
// __summarizationExhausted / __awaitingApproval /
|
|
3115
|
-
// are made on this object during execution.
|
|
3143
|
+
// __summarizationExhausted / __awaitingApproval / __awaitingUserQuestion /
|
|
3144
|
+
// __structuredOutputParsed are made on this object during execution.
|
|
3116
3145
|
ctx: state.ctx,
|
|
3117
3146
|
agent: state.agent,
|
|
3118
|
-
pendingApprovals
|
|
3147
|
+
pendingApprovals,
|
|
3148
|
+
pendingUserQuestions
|
|
3119
3149
|
};
|
|
3120
3150
|
};
|
|
3121
3151
|
}
|
|
@@ -3581,6 +3611,226 @@ function resolveToolApprovalState(state, resolution) {
|
|
|
3581
3611
|
};
|
|
3582
3612
|
}
|
|
3583
3613
|
|
|
3614
|
+
// src/utils/userQuestions.ts
|
|
3615
|
+
var cloneQuestions = (questions) => Array.isArray(questions) ? questions.map((entry) => ({ ...entry })) : [];
|
|
3616
|
+
function validateAnswers(entry, answers, cancelled) {
|
|
3617
|
+
if (cancelled) {
|
|
3618
|
+
return answers || {};
|
|
3619
|
+
}
|
|
3620
|
+
const allowFreeText = entry.allowFreeText !== false;
|
|
3621
|
+
const out = {};
|
|
3622
|
+
const provided = answers || {};
|
|
3623
|
+
for (const question of entry.questions) {
|
|
3624
|
+
const required = question.required !== false;
|
|
3625
|
+
const key = question.question;
|
|
3626
|
+
const answer = provided[key] ?? provided[question.header || ""];
|
|
3627
|
+
if (!answer) {
|
|
3628
|
+
if (required) {
|
|
3629
|
+
throw new Error(`Missing answer for required question: "${key}".`);
|
|
3630
|
+
}
|
|
3631
|
+
continue;
|
|
3632
|
+
}
|
|
3633
|
+
const values = Array.isArray(answer.values) ? answer.values.filter((v) => typeof v === "string") : [];
|
|
3634
|
+
const freeText = typeof answer.freeText === "string" ? answer.freeText.trim() : "";
|
|
3635
|
+
const notes = typeof answer.notes === "string" ? answer.notes : void 0;
|
|
3636
|
+
if (freeText && !allowFreeText) {
|
|
3637
|
+
throw new Error(
|
|
3638
|
+
`Free-text answers are disabled for this agent (question: "${key}"). Pick one of the provided options.`
|
|
3639
|
+
);
|
|
3640
|
+
}
|
|
3641
|
+
if (values.length === 0 && !freeText) {
|
|
3642
|
+
if (required) {
|
|
3643
|
+
throw new Error(`Question "${key}" requires either a selected option or free-text answer.`);
|
|
3644
|
+
}
|
|
3645
|
+
continue;
|
|
3646
|
+
}
|
|
3647
|
+
if (values.length > 1 && !question.multiSelect) {
|
|
3648
|
+
throw new Error(`Question "${key}" is single-select but received multiple values.`);
|
|
3649
|
+
}
|
|
3650
|
+
if (Array.isArray(question.options) && question.options.length > 0 && values.length > 0) {
|
|
3651
|
+
const allowedValues = new Set(
|
|
3652
|
+
question.options.map((option) => option.value ?? option.label)
|
|
3653
|
+
);
|
|
3654
|
+
for (const value of values) {
|
|
3655
|
+
if (!allowedValues.has(value)) {
|
|
3656
|
+
if (!allowFreeText) {
|
|
3657
|
+
throw new Error(
|
|
3658
|
+
`Value "${value}" is not one of the allowed options for question "${key}".`
|
|
3659
|
+
);
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
out[key] = { values, ...freeText ? { freeText } : {}, ...notes ? { notes } : {} };
|
|
3665
|
+
}
|
|
3666
|
+
return out;
|
|
3667
|
+
}
|
|
3668
|
+
function buildToolResponseContent(entry, answers, cancelled, notes) {
|
|
3669
|
+
if (cancelled) {
|
|
3670
|
+
return JSON.stringify({
|
|
3671
|
+
status: "cancelled",
|
|
3672
|
+
message: notes || "User dismissed the question without answering."
|
|
3673
|
+
});
|
|
3674
|
+
}
|
|
3675
|
+
const payload = {
|
|
3676
|
+
status: "answered",
|
|
3677
|
+
answers: entry.questions.map((question) => {
|
|
3678
|
+
const key = question.question;
|
|
3679
|
+
const ans = answers[key];
|
|
3680
|
+
return {
|
|
3681
|
+
question: key,
|
|
3682
|
+
header: question.header,
|
|
3683
|
+
values: ans?.values ?? [],
|
|
3684
|
+
freeText: ans?.freeText,
|
|
3685
|
+
notes: ans?.notes
|
|
3686
|
+
};
|
|
3687
|
+
}),
|
|
3688
|
+
...notes ? { reviewerNotes: notes } : {}
|
|
3689
|
+
};
|
|
3690
|
+
return JSON.stringify(payload);
|
|
3691
|
+
}
|
|
3692
|
+
function resolveUserQuestionState(state, resolution) {
|
|
3693
|
+
const pending = cloneQuestions(state.pendingUserQuestions);
|
|
3694
|
+
const matchIndex = pending.findIndex(
|
|
3695
|
+
(entry2) => entry2.id === resolution.id || entry2.toolCallId === resolution.id
|
|
3696
|
+
);
|
|
3697
|
+
if (matchIndex === -1) {
|
|
3698
|
+
throw new Error(`Pending user question not found for id: ${resolution.id}`);
|
|
3699
|
+
}
|
|
3700
|
+
const entry = { ...pending[matchIndex] };
|
|
3701
|
+
if (entry.status === "answered" || entry.status === "executed" || entry.status === "cancelled") {
|
|
3702
|
+
throw new Error(`User question ${entry.id} already resolved (status: ${entry.status}).`);
|
|
3703
|
+
}
|
|
3704
|
+
const cancelled = resolution.cancelled === true;
|
|
3705
|
+
const validated = validateAnswers(entry, resolution.answers, cancelled);
|
|
3706
|
+
entry.status = cancelled ? "cancelled" : "answered";
|
|
3707
|
+
entry.answers = validated;
|
|
3708
|
+
entry.cancelled = cancelled || void 0;
|
|
3709
|
+
entry.answeredBy = resolution.answeredBy;
|
|
3710
|
+
entry.notes = resolution.notes;
|
|
3711
|
+
entry.answeredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3712
|
+
pending[matchIndex] = entry;
|
|
3713
|
+
const ctx = { ...state.ctx || {} };
|
|
3714
|
+
delete ctx.__awaitingUserQuestion;
|
|
3715
|
+
if (ctx.__resumeStage === "tools") {
|
|
3716
|
+
delete ctx.__resumeStage;
|
|
3717
|
+
}
|
|
3718
|
+
ctx.__userQuestionResolved = { id: entry.id, status: entry.status };
|
|
3719
|
+
const toolMessage = {
|
|
3720
|
+
role: "tool",
|
|
3721
|
+
name: "ask_user_question",
|
|
3722
|
+
tool_call_id: entry.toolCallId,
|
|
3723
|
+
content: buildToolResponseContent(entry, validated, cancelled, resolution.notes)
|
|
3724
|
+
};
|
|
3725
|
+
return {
|
|
3726
|
+
...state,
|
|
3727
|
+
pendingUserQuestions: pending,
|
|
3728
|
+
messages: [...state.messages || [], toolMessage],
|
|
3729
|
+
ctx
|
|
3730
|
+
};
|
|
3731
|
+
}
|
|
3732
|
+
var DEFAULT_DESCRIPTION_WITH_FREETEXT = "Pause the agent and surface a structured prompt to the human user when you genuinely cannot proceed without their input. Group up to 4 closely related questions into a single call. Prefer concrete options the user can pick over open-ended free-text \u2014 list `options` whenever you can anticipate the choices. The user may type a custom answer for any question. Do not use this tool for things you can deduce from the conversation, the system prompt, or another tool. After the user answers, their selections come back as the tool result.";
|
|
3733
|
+
var DEFAULT_DESCRIPTION_NO_FREETEXT = "Pause the agent and surface a structured multi-choice prompt to the human user when you genuinely cannot proceed without their input. Group up to 4 closely related questions into a single call. Every question MUST include at least two `options` \u2014 free-text answers are disabled for this agent. Do not use this tool for things you can deduce from the conversation, the system prompt, or another tool. After the user picks, their selections come back as the tool result.";
|
|
3734
|
+
function buildOptionSchema() {
|
|
3735
|
+
return zod.z.object({
|
|
3736
|
+
label: zod.z.string().min(1).describe("Display text the user sees"),
|
|
3737
|
+
value: zod.z.string().min(1).optional().describe("Value returned in the answer; defaults to label"),
|
|
3738
|
+
description: zod.z.string().optional(),
|
|
3739
|
+
preview: zod.z.string().optional().describe("Optional code / ASCII preview rendered when this option is focused")
|
|
3740
|
+
}).strict();
|
|
3741
|
+
}
|
|
3742
|
+
function buildQuestionSchema(allowFreeText) {
|
|
3743
|
+
const optionSchema = buildOptionSchema();
|
|
3744
|
+
const baseShape = {
|
|
3745
|
+
question: zod.z.string().min(1).describe("Full question shown to the user"),
|
|
3746
|
+
header: zod.z.string().max(40).optional().describe("Short chip / tag rendered next to the question (max ~12 chars recommended)"),
|
|
3747
|
+
multiSelect: zod.z.boolean().optional().describe("Allow multiple selections. Default: false"),
|
|
3748
|
+
placeholder: zod.z.string().optional(),
|
|
3749
|
+
required: zod.z.boolean().optional().describe("Default: true")
|
|
3750
|
+
};
|
|
3751
|
+
if (allowFreeText) {
|
|
3752
|
+
return zod.z.object({
|
|
3753
|
+
...baseShape,
|
|
3754
|
+
options: zod.z.array(optionSchema).min(2).max(12).optional()
|
|
3755
|
+
}).strict();
|
|
3756
|
+
}
|
|
3757
|
+
return zod.z.object({
|
|
3758
|
+
...baseShape,
|
|
3759
|
+
options: zod.z.array(optionSchema).min(2).max(12).describe("Required: free-text answers are disabled for this agent")
|
|
3760
|
+
}).strict();
|
|
3761
|
+
}
|
|
3762
|
+
function createAskUserQuestionTool(stateRef, config) {
|
|
3763
|
+
const allowFreeText = config.allowFreeText !== false;
|
|
3764
|
+
const description = config.promptOverride ?? (allowFreeText ? DEFAULT_DESCRIPTION_WITH_FREETEXT : DEFAULT_DESCRIPTION_NO_FREETEXT);
|
|
3765
|
+
const questionSchema = buildQuestionSchema(allowFreeText);
|
|
3766
|
+
const askUser = createTool({
|
|
3767
|
+
name: "ask_user_question",
|
|
3768
|
+
description,
|
|
3769
|
+
schema: zod.z.object({
|
|
3770
|
+
questions: zod.z.array(questionSchema).min(1).max(4).describe("1-4 related questions to ask in a single pause.")
|
|
3771
|
+
}).strict(),
|
|
3772
|
+
func: async ({ questions }) => {
|
|
3773
|
+
const ref = askUser._stateRef || (askUser._stateRef = {});
|
|
3774
|
+
const onEvent = ref.__onEvent;
|
|
3775
|
+
if (!ref.ctx) ref.ctx = {};
|
|
3776
|
+
const ctx = ref.ctx;
|
|
3777
|
+
const toolCallId = ref.__currentToolCallId || `ask_user_${nanoid.nanoid(6)}`;
|
|
3778
|
+
if (!ref.pendingUserQuestions) ref.pendingUserQuestions = [];
|
|
3779
|
+
const pendingList = ref.pendingUserQuestions;
|
|
3780
|
+
const entry = {
|
|
3781
|
+
id: nanoid.nanoid(),
|
|
3782
|
+
toolCallId,
|
|
3783
|
+
toolName: "ask_user_question",
|
|
3784
|
+
questions,
|
|
3785
|
+
status: "pending",
|
|
3786
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3787
|
+
allowFreeText
|
|
3788
|
+
};
|
|
3789
|
+
pendingList.push(entry);
|
|
3790
|
+
ctx.__awaitingUserQuestion = {
|
|
3791
|
+
id: entry.id,
|
|
3792
|
+
toolCallId: entry.toolCallId,
|
|
3793
|
+
requestedAt: entry.requestedAt
|
|
3794
|
+
};
|
|
3795
|
+
ctx.__resumeStage = "tools";
|
|
3796
|
+
onEvent?.({
|
|
3797
|
+
type: "user_question",
|
|
3798
|
+
status: "pending",
|
|
3799
|
+
id: entry.id,
|
|
3800
|
+
toolCallId: entry.toolCallId,
|
|
3801
|
+
questions: entry.questions,
|
|
3802
|
+
allowFreeText
|
|
3803
|
+
});
|
|
3804
|
+
recordTraceEvent(ctx.__traceSession, {
|
|
3805
|
+
type: "user_question",
|
|
3806
|
+
label: "Ask User",
|
|
3807
|
+
actor: { scope: "agent", name: "ask_user_question", role: "user_interaction" },
|
|
3808
|
+
status: "success",
|
|
3809
|
+
sections: [
|
|
3810
|
+
{
|
|
3811
|
+
kind: "metadata",
|
|
3812
|
+
label: "Pending Question",
|
|
3813
|
+
data: {
|
|
3814
|
+
id: entry.id,
|
|
3815
|
+
toolCallId: entry.toolCallId,
|
|
3816
|
+
questions: entry.questions,
|
|
3817
|
+
allowFreeText
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
]
|
|
3821
|
+
});
|
|
3822
|
+
return {
|
|
3823
|
+
__awaitingUserQuestion: true,
|
|
3824
|
+
id: entry.id,
|
|
3825
|
+
toolCallId: entry.toolCallId
|
|
3826
|
+
};
|
|
3827
|
+
}
|
|
3828
|
+
});
|
|
3829
|
+
askUser._stateRef = stateRef;
|
|
3830
|
+
askUser.__source = "human-in-the-loop";
|
|
3831
|
+
return askUser;
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3584
3834
|
// src/agent.ts
|
|
3585
3835
|
init_utilTokens();
|
|
3586
3836
|
|
|
@@ -3792,6 +4042,21 @@ function createAgent(opts) {
|
|
|
3792
4042
|
const resolver = createResolverNode();
|
|
3793
4043
|
const agentCore = createAgentCoreNode(opts);
|
|
3794
4044
|
const toolsBase = [...opts.tools ?? []];
|
|
4045
|
+
const askUserConfig = (() => {
|
|
4046
|
+
const raw = opts.humanInTheLoop?.askUser;
|
|
4047
|
+
if (!raw) return void 0;
|
|
4048
|
+
if (raw === true) return { enabled: true, allowFreeText: true };
|
|
4049
|
+
return {
|
|
4050
|
+
enabled: true,
|
|
4051
|
+
allowFreeText: raw.allowFreeText !== false,
|
|
4052
|
+
promptOverride: raw.promptOverride
|
|
4053
|
+
};
|
|
4054
|
+
})();
|
|
4055
|
+
const askUserOnQuestion = typeof opts.humanInTheLoop?.askUser === "object" ? opts.humanInTheLoop?.askUser?.onQuestion : void 0;
|
|
4056
|
+
const askUserStateRef = askUserConfig ? { pendingUserQuestions: void 0, ctx: void 0, __onEvent: void 0, __currentToolCallId: void 0 } : void 0;
|
|
4057
|
+
if (askUserConfig && askUserStateRef) {
|
|
4058
|
+
toolsBase.push(createAskUserQuestionTool(askUserStateRef, askUserConfig));
|
|
4059
|
+
}
|
|
3795
4060
|
const soManager = opts.outputSchema ? new StructuredOutputManager(opts.outputSchema, resolveStrategy(opts.model)) : void 0;
|
|
3796
4061
|
if (soManager) {
|
|
3797
4062
|
const modelCapabilities = getModelCapabilities(opts.model);
|
|
@@ -3836,7 +4101,8 @@ function createAgent(opts) {
|
|
|
3836
4101
|
useTodoList: void 0,
|
|
3837
4102
|
outputSchema: opts.outputSchema,
|
|
3838
4103
|
responseFormat: soManager?.getResponseFormat(),
|
|
3839
|
-
tracing: opts.tracing
|
|
4104
|
+
tracing: opts.tracing,
|
|
4105
|
+
humanInTheLoop: askUserConfig ? { askUser: askUserConfig } : void 0
|
|
3840
4106
|
};
|
|
3841
4107
|
const summarizationThreshold = getActiveSummarizationThreshold(opts);
|
|
3842
4108
|
async function runLoop(initial, config, emit) {
|
|
@@ -4126,6 +4392,7 @@ function createAgent(opts) {
|
|
|
4126
4392
|
state = { ...state, ...await toolsNode(state) };
|
|
4127
4393
|
onProgress?.({ stage: "tools", message: "Tools finished" });
|
|
4128
4394
|
if (state.ctx?.__awaitingApproval) break;
|
|
4395
|
+
if (state.ctx?.__awaitingUserQuestion) break;
|
|
4129
4396
|
if (checkpointIfRequested("after_tools")) break;
|
|
4130
4397
|
if (state.ctx?.__finalizedDueToStructuredOutput) break;
|
|
4131
4398
|
if (reflectNode && resolvedReasoning?.reflection.enabled) {
|
|
@@ -4196,7 +4463,7 @@ function createAgent(opts) {
|
|
|
4196
4463
|
}
|
|
4197
4464
|
const lastAfterLoop = state.messages[state.messages.length - 1];
|
|
4198
4465
|
if (lastAfterLoop?.role === "tool" && !pausedStage) {
|
|
4199
|
-
const isExpectedExit = state.ctx?.__awaitingApproval || state.ctx?.__cancelled || state.ctx?.__finalizedDueToStructuredOutput || state.ctx?.__finalizedDueToToolLimit || state.ctx?.__needsSummarization || state.ctx?.__limitBreached;
|
|
4466
|
+
const isExpectedExit = state.ctx?.__awaitingApproval || state.ctx?.__awaitingUserQuestion || state.ctx?.__cancelled || state.ctx?.__finalizedDueToStructuredOutput || state.ctx?.__finalizedDueToToolLimit || state.ctx?.__needsSummarization || state.ctx?.__limitBreached;
|
|
4200
4467
|
if (!isExpectedExit) {
|
|
4201
4468
|
throw new Error(
|
|
4202
4469
|
"Agent loop terminated with a pending tool response but no subsequent model invocation. This usually indicates a model provider error or exhausted iteration budget."
|
|
@@ -4214,7 +4481,17 @@ function createAgent(opts) {
|
|
|
4214
4481
|
const { setTokenCounter: setTokenCounter2 } = await Promise.resolve().then(() => (init_utilTokens(), utilTokens_exports));
|
|
4215
4482
|
setTokenCounter2(userTokenCounter);
|
|
4216
4483
|
}
|
|
4217
|
-
const
|
|
4484
|
+
const callerOnEvent = config?.onEvent;
|
|
4485
|
+
const onEvent = askUserOnQuestion ? (event) => {
|
|
4486
|
+
if (event && event.type === "user_question") {
|
|
4487
|
+
try {
|
|
4488
|
+
askUserOnQuestion(event);
|
|
4489
|
+
} catch (err) {
|
|
4490
|
+
console.warn("[agent-sdk] humanInTheLoop.askUser.onQuestion error:", err);
|
|
4491
|
+
}
|
|
4492
|
+
}
|
|
4493
|
+
callerOnEvent?.(event);
|
|
4494
|
+
} : callerOnEvent;
|
|
4218
4495
|
const onProgress = config?.onProgress;
|
|
4219
4496
|
const onStream = config?.onStream;
|
|
4220
4497
|
const streamEnabled = config?.stream === true;
|
|
@@ -4258,6 +4535,7 @@ function createAgent(opts) {
|
|
|
4258
4535
|
metadata: input.metadata,
|
|
4259
4536
|
ctx,
|
|
4260
4537
|
pendingApprovals: input.pendingApprovals || [],
|
|
4538
|
+
pendingUserQuestions: input.pendingUserQuestions || [],
|
|
4261
4539
|
agent: input.agent || runtimeWithInvokeLimits,
|
|
4262
4540
|
usage: input.usage || { perRequest: [], totals: {} }
|
|
4263
4541
|
};
|
|
@@ -4334,11 +4612,13 @@ function createAgent(opts) {
|
|
|
4334
4612
|
return invokeAgent(restoredState, config);
|
|
4335
4613
|
};
|
|
4336
4614
|
const resolveToolApproval = (state, resolution) => resolveToolApprovalState(state, resolution);
|
|
4615
|
+
const resolveUserQuestion = (state, resolution) => resolveUserQuestionState(state, resolution);
|
|
4337
4616
|
const instance = {
|
|
4338
4617
|
invoke: invokeAgent,
|
|
4339
4618
|
snapshot: snapshotState,
|
|
4340
4619
|
resume: resumeAgent,
|
|
4341
4620
|
resolveToolApproval,
|
|
4621
|
+
resolveUserQuestion,
|
|
4342
4622
|
asTool: ({ toolName, description, inputDescription }) => {
|
|
4343
4623
|
const schema = zod.z.object({ input: zod.z.string().describe(inputDescription || "Input for delegated agent") });
|
|
4344
4624
|
const delegatedTool = createTool({
|
|
@@ -5655,7 +5935,20 @@ function createSmartAgent(opts) {
|
|
|
5655
5935
|
const userTools = opts.tools ?? [];
|
|
5656
5936
|
const factoryStateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0, planVersion: 0, adherenceScore: 0 };
|
|
5657
5937
|
const factoryContextTools = createContextTools(factoryStateRef, { planningEnabled, includeGetToolResponse: false });
|
|
5938
|
+
const askUserConfig = (() => {
|
|
5939
|
+
const raw = opts.humanInTheLoop?.askUser;
|
|
5940
|
+
if (!raw) return void 0;
|
|
5941
|
+
if (raw === true) return { enabled: true, allowFreeText: true };
|
|
5942
|
+
return {
|
|
5943
|
+
enabled: true,
|
|
5944
|
+
allowFreeText: raw.allowFreeText !== false,
|
|
5945
|
+
promptOverride: raw.promptOverride
|
|
5946
|
+
};
|
|
5947
|
+
})();
|
|
5658
5948
|
const factoryToolsWithoutRecovery = [...userTools, ...factoryContextTools];
|
|
5949
|
+
if (askUserConfig) {
|
|
5950
|
+
factoryToolsWithoutRecovery.push(createAskUserQuestionTool(factoryStateRef, askUserConfig));
|
|
5951
|
+
}
|
|
5659
5952
|
const soManagerFactory = opts.outputSchema ? new StructuredOutputManager(opts.outputSchema, resolveStrategy(opts.model)) : void 0;
|
|
5660
5953
|
const modelCapabilities = opts.outputSchema ? getModelCapabilities(opts.model) : void 0;
|
|
5661
5954
|
const responseFinalizeTool = soManagerFactory && modelCapabilities?.structuredOutput !== "native" ? soManagerFactory.getResponseTool() : void 0;
|
|
@@ -5665,6 +5958,7 @@ function createSmartAgent(opts) {
|
|
|
5665
5958
|
const recoveryTool = createGetToolResponseTool(ref);
|
|
5666
5959
|
const base2 = [...userTools, ...ctxTools];
|
|
5667
5960
|
if (responseFinalizeTool) base2.push(responseFinalizeTool);
|
|
5961
|
+
if (askUserConfig) base2.push(createAskUserQuestionTool(ref, askUserConfig));
|
|
5668
5962
|
const without = base2;
|
|
5669
5963
|
const withRec = [...without, recoveryTool];
|
|
5670
5964
|
return { stateRef: ref, toolsWithoutRecovery: without, toolsWithRecovery: withRec };
|
|
@@ -5849,6 +6143,7 @@ function createSmartAgent(opts) {
|
|
|
5849
6143
|
snapshot: base.snapshot,
|
|
5850
6144
|
resume: base.resume,
|
|
5851
6145
|
resolveToolApproval: base.resolveToolApproval,
|
|
6146
|
+
resolveUserQuestion: base.resolveUserQuestion,
|
|
5852
6147
|
asTool: base.asTool,
|
|
5853
6148
|
asHandoff: base.asHandoff,
|
|
5854
6149
|
__runtime: base.__runtime
|
|
@@ -8174,6 +8469,7 @@ exports.countApproxTokens = countApproxTokens;
|
|
|
8174
8469
|
exports.countMessagesTokens = countMessagesTokens;
|
|
8175
8470
|
exports.createAgent = createAgent;
|
|
8176
8471
|
exports.createAgentNode = createAgentNode;
|
|
8472
|
+
exports.createAskUserQuestionTool = createAskUserQuestionTool;
|
|
8177
8473
|
exports.createCodeGuardrail = createCodeGuardrail;
|
|
8178
8474
|
exports.createContextSummarizeNode = createContextSummarizeNode;
|
|
8179
8475
|
exports.createContextTools = createContextTools;
|
|
@@ -8215,6 +8511,7 @@ exports.regexRule = regexRule;
|
|
|
8215
8511
|
exports.resolveMemoryStore = resolveMemoryStore;
|
|
8216
8512
|
exports.resolveStrategy = resolveStrategy;
|
|
8217
8513
|
exports.resolveToolApprovalState = resolveToolApprovalState;
|
|
8514
|
+
exports.resolveUserQuestionState = resolveUserQuestionState;
|
|
8218
8515
|
exports.restoreSnapshot = restoreSnapshot;
|
|
8219
8516
|
exports.runSmartAgentEvalHarness = runSmartAgentEvalHarness;
|
|
8220
8517
|
exports.setTokenCounter = setTokenCounter;
|