@dudousxd/nestjs-agent-core 0.21.0 → 0.22.0
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 +2 -1
- package/dist/guardrails/index.d.cts +1 -1
- package/dist/guardrails/index.d.ts +1 -1
- package/dist/index.cjs +156 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -3
- package/dist/index.d.ts +41 -3
- package/dist/index.js +152 -21
- package/dist/index.js.map +1 -1
- package/dist/{tool-CwXibnce.d.cts → tool-BQqFt06V.d.cts} +45 -1
- package/dist/{tool-CwXibnce.d.ts → tool-BQqFt06V.d.ts} +45 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,8 @@ import type { ModelProvider, AgentStore, ToolSpec, RolesPolicy } from '@dudousxd
|
|
|
18
18
|
|
|
19
19
|
## Key types
|
|
20
20
|
|
|
21
|
-
- `ToolSpec` — `{ name, kind: 'read' | 'action' | 'agent', description, inputSchema, roles?, ability?, targetAgent?, detached? }`. (`ToolKind` has three further members — `'ask'`, `'skill'` and `'memory'` — which no `ToolSpec` carries: they belong to the built-in tools the loop serves itself rather than from a handler. `ask` settles against a person, `skill` against the turn's journaled catalog, `remember` against the turn's journaled memory digest.) `inputSchema` is a [Standard Schema](https://standardschema.dev) (Zod, Valibot, ArkType); the loop validates via `~standard.validate`, throwing `ToolInputInvalidError` on failure.
|
|
21
|
+
- `ToolSpec` — `{ name, kind: 'read' | 'action' | 'agent', description, inputSchema, roles?, ability?, targetAgent?, detached?, terminal? }`. (`ToolKind` has three further members — `'ask'`, `'skill'` and `'memory'` — which no `ToolSpec` carries: they belong to the built-in tools the loop serves itself rather than from a handler. `ask` settles against a person, `skill` against the turn's journaled catalog, `remember` against the turn's journaled memory digest.) `inputSchema` is a [Standard Schema](https://standardschema.dev) (Zod, Valibot, ArkType); the loop validates via `~standard.validate`, throwing `ToolInputInvalidError` on failure.
|
|
22
|
+
- `AiToolCtx.emitUi(component, props, { id?, version? })` — **a tool pushes generative UI.** The `ui` frame streams live (inline, durable, and from a dispatched tool step's worker) and the component is persisted on the assistant message through the optional `AgentStore.setMessageUi`, once per step, after the step's tools settle. `id` defaults to `<toolCallId>:ui:<n>`, so a retried call replaces what it pushed. The pushes ride the tool step's journaled result (`wrapToolStepOutput` / `unwrapToolStepOutput`; the wrapper is only used when something was pushed), so a replay neither re-streams nor re-persists them. `ToolSpec.terminal` ends the turn after a successful call, settled in `persist:toolcall` like `detached`.
|
|
22
23
|
- `AgentDefinition` — a named agent (`systemPrompt` string | `PromptBuilder`, `tools`, `delegatesTo`, `personas`, …) for multi-agent setups. `delegatesTo` holds `AgentDelegation` entries: a bare target name for the delegation that waits, `{ agent, detached: true }` for one that does not.
|
|
23
24
|
- `detachedStarted` / `detachedDelivered` / `settleUnsettledDelegation` / `AgentLoopHooks.startAgent` / `AgentRunInput.deliverTo` — **delegation that does not block the chat.** An `agent`-kind call whose spec says `detached` is STARTED rather than awaited (`hooks.startAgent`, which the durable runner maps to `ctx.startChild` and the inline one to a loop nobody awaits), and the turn ends with a `DetachedDelegationReceipt` as the call's result instead of an answer. The started run carries a `deliverTo` address — the delegating thread and the call that started it — and posts its answer there as a message of its own, stamped with its own `runId` and `agentName`, so a client renders "the research agent finished" rather than the assistant's next reply. Whether a call detaches is settled INSIDE `persist:toolcall` alongside its kind and target, never from a live registry lookup, so a replay reads the branch back rather than re-deciding it; the loop writes the same checkpoint names either way, and only the runner's own positions (`spawn:` versus the awaited child's `signal:child:`) differ. A detached run streams into its OWN sink and its `action` tools park on its OWN run, so its approval reaches the pending-approvals surface instead of an inline card in whatever turn happens to be open. `settleUnsettledDelegation` is the runner's half: a run that crashed or was stopped posts a message saying so, because "started" is the one state a reader can neither wait on nor act on. `AgentRunInput.parentRunId` / `RecordRunStartInput.parentRunId` record the edge, so a delegation is not a run row with nothing pointing at it.
|
|
24
25
|
- `RolesPolicy.can(actor, tool): boolean | Promise<boolean>` — the tool authorization seam
|
package/dist/index.cjs
CHANGED
|
@@ -97,6 +97,7 @@ __export(src_exports, {
|
|
|
97
97
|
compositeSkillProvider: () => compositeSkillProvider,
|
|
98
98
|
createFrameBuffer: () => createFrameBuffer,
|
|
99
99
|
createIncrementalGate: () => createIncrementalGate,
|
|
100
|
+
createUiCollector: () => createUiCollector,
|
|
100
101
|
dayBoundsUtc: () => dayBoundsUtc,
|
|
101
102
|
decodeStreamEvent: () => decodeStreamEvent,
|
|
102
103
|
defaultCanDecide: () => defaultCanDecide,
|
|
@@ -124,6 +125,7 @@ __export(src_exports, {
|
|
|
124
125
|
mayDecideApproval: () => mayDecideApproval,
|
|
125
126
|
memoryForgetVerdict: () => memoryForgetVerdict,
|
|
126
127
|
memoryWriteVerdict: () => memoryWriteVerdict,
|
|
128
|
+
mergeUi: () => mergeUi,
|
|
127
129
|
normalizeDelegation: () => normalizeDelegation,
|
|
128
130
|
normalizeElicitationReply: () => normalizeElicitationReply,
|
|
129
131
|
observeTurnFrames: () => observeTurnFrames,
|
|
@@ -174,6 +176,7 @@ __export(src_exports, {
|
|
|
174
176
|
traceLlmTurn: () => traceLlmTurn,
|
|
175
177
|
traceToolExecution: () => traceToolExecution,
|
|
176
178
|
truncateDetailContent: () => truncateDetailContent,
|
|
179
|
+
unwrapToolStepOutput: () => unwrapToolStepOutput,
|
|
177
180
|
validateElicitationAnswer: () => validateElicitationAnswer,
|
|
178
181
|
validateElicitationValue: () => validateElicitationValue,
|
|
179
182
|
validateStructured: () => validateStructured,
|
|
@@ -183,6 +186,7 @@ __export(src_exports, {
|
|
|
183
186
|
withSkillTool: () => withSkillTool,
|
|
184
187
|
withToolTimeout: () => withToolTimeout,
|
|
185
188
|
withTurnFrames: () => withTurnFrames,
|
|
189
|
+
wrapToolStepOutput: () => wrapToolStepOutput,
|
|
186
190
|
writeMemory: () => writeMemory
|
|
187
191
|
});
|
|
188
192
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -343,6 +347,95 @@ function withTurnFrames(result, frames) {
|
|
|
343
347
|
}
|
|
344
348
|
__name(withTurnFrames, "withTurnFrames");
|
|
345
349
|
|
|
350
|
+
// src/tool-ui.ts
|
|
351
|
+
var TOOL_STEP_UI = "@@nestjs-agent/tool-step-ui";
|
|
352
|
+
function wrapToolStepOutput(output, ui) {
|
|
353
|
+
if (ui.length === 0) {
|
|
354
|
+
return output;
|
|
355
|
+
}
|
|
356
|
+
const wrapped = {
|
|
357
|
+
[TOOL_STEP_UI]: 1,
|
|
358
|
+
output,
|
|
359
|
+
ui: [
|
|
360
|
+
...ui
|
|
361
|
+
]
|
|
362
|
+
};
|
|
363
|
+
return wrapped;
|
|
364
|
+
}
|
|
365
|
+
__name(wrapToolStepOutput, "wrapToolStepOutput");
|
|
366
|
+
function unwrapToolStepOutput(raw) {
|
|
367
|
+
if (typeof raw === "object" && raw !== null && raw[TOOL_STEP_UI] === 1) {
|
|
368
|
+
const wrapped = raw;
|
|
369
|
+
return {
|
|
370
|
+
output: wrapped.output,
|
|
371
|
+
ui: Array.isArray(wrapped.ui) ? wrapped.ui : []
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
output: raw,
|
|
376
|
+
ui: []
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
__name(unwrapToolStepOutput, "unwrapToolStepOutput");
|
|
380
|
+
function createUiCollector(toolCallId, write) {
|
|
381
|
+
const pushed = /* @__PURE__ */ new Map();
|
|
382
|
+
let next = 0;
|
|
383
|
+
const emit2 = /* @__PURE__ */ __name(async (component, props, options = {}) => {
|
|
384
|
+
if (typeof component !== "string" || component.length === 0) {
|
|
385
|
+
throw new Error("emitUi: component must be a non-empty string");
|
|
386
|
+
}
|
|
387
|
+
if (typeof props !== "object" || props === null || Array.isArray(props)) {
|
|
388
|
+
throw new Error("emitUi: props must be a JSON object");
|
|
389
|
+
}
|
|
390
|
+
let id = options.id;
|
|
391
|
+
if (id === void 0) {
|
|
392
|
+
id = `${toolCallId}:ui:${next}`;
|
|
393
|
+
next += 1;
|
|
394
|
+
}
|
|
395
|
+
const entry = {
|
|
396
|
+
id,
|
|
397
|
+
component,
|
|
398
|
+
// Snapshot: the frame and the persisted value are what the tool pushed at THIS moment, not
|
|
399
|
+
// whatever the object it handed over looks like when the step settles.
|
|
400
|
+
props: JSON.parse(JSON.stringify(props)),
|
|
401
|
+
...options.version !== void 0 ? {
|
|
402
|
+
version: options.version
|
|
403
|
+
} : {},
|
|
404
|
+
toolCallId
|
|
405
|
+
};
|
|
406
|
+
pushed.set(id, entry);
|
|
407
|
+
await write?.({
|
|
408
|
+
kind: "ui",
|
|
409
|
+
...entry
|
|
410
|
+
});
|
|
411
|
+
return {
|
|
412
|
+
id
|
|
413
|
+
};
|
|
414
|
+
}, "emit");
|
|
415
|
+
return {
|
|
416
|
+
emit: emit2,
|
|
417
|
+
components: /* @__PURE__ */ __name(() => [
|
|
418
|
+
...pushed.values()
|
|
419
|
+
], "components"),
|
|
420
|
+
restart: /* @__PURE__ */ __name(() => {
|
|
421
|
+
next = 0;
|
|
422
|
+
}, "restart")
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
__name(createUiCollector, "createUiCollector");
|
|
426
|
+
function mergeUi(...lists) {
|
|
427
|
+
const merged = /* @__PURE__ */ new Map();
|
|
428
|
+
for (const list of lists) {
|
|
429
|
+
for (const component of list ?? []) {
|
|
430
|
+
merged.set(component.id, component);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return [
|
|
434
|
+
...merged.values()
|
|
435
|
+
];
|
|
436
|
+
}
|
|
437
|
+
__name(mergeUi, "mergeUi");
|
|
438
|
+
|
|
346
439
|
// src/spi/pricing-store.ts
|
|
347
440
|
async function seedModelPrices(store, prices) {
|
|
348
441
|
for (const price of prices) {
|
|
@@ -3357,6 +3450,11 @@ async function claimToolCall(turn, call) {
|
|
|
3357
3450
|
...spec?.detached === true ? {
|
|
3358
3451
|
detached: true
|
|
3359
3452
|
} : {},
|
|
3453
|
+
// Same for `terminal`: absent unless declared, so a deployment with no terminal tool writes
|
|
3454
|
+
// the bytes it always has, and a replay reads the branch back instead of re-deciding it.
|
|
3455
|
+
...spec?.terminal === true ? {
|
|
3456
|
+
terminal: true
|
|
3457
|
+
} : {},
|
|
3360
3458
|
...approval !== void 0 ? {
|
|
3361
3459
|
approval
|
|
3362
3460
|
} : {}
|
|
@@ -3378,6 +3476,9 @@ async function claimToolCall(turn, call) {
|
|
|
3378
3476
|
...persisted?.detached === true ? {
|
|
3379
3477
|
detached: true
|
|
3380
3478
|
} : {},
|
|
3479
|
+
...persisted?.terminal === true ? {
|
|
3480
|
+
terminal: true
|
|
3481
|
+
} : {},
|
|
3381
3482
|
...toolType === "action" && persisted?.approval !== void 0 ? {
|
|
3382
3483
|
approval: persisted.approval
|
|
3383
3484
|
} : {},
|
|
@@ -3544,7 +3645,7 @@ async function invokeClaimedTool(turn, claimed) {
|
|
|
3544
3645
|
if (claimed.toolType === "memory") {
|
|
3545
3646
|
return await rememberIntoTurn(turn, claimed, startedAt);
|
|
3546
3647
|
}
|
|
3547
|
-
let
|
|
3648
|
+
let raw;
|
|
3548
3649
|
if (hooks.dispatchTool) {
|
|
3549
3650
|
const stepCtx = {
|
|
3550
3651
|
actor: input.actor,
|
|
@@ -3567,28 +3668,47 @@ async function invokeClaimedTool(turn, claimed) {
|
|
|
3567
3668
|
} : {},
|
|
3568
3669
|
// Numeric-only: the handler applies withToolTimeout AND its own local `classify` — see
|
|
3569
3670
|
// ToolStepEnvelope.transientRetry.
|
|
3570
|
-
transientRetry: resolveToolTransientRetryNumbers(deps.toolTransientRetry)
|
|
3671
|
+
transientRetry: resolveToolTransientRetryNumbers(deps.toolTransientRetry),
|
|
3672
|
+
// The handler hands back what the tool pushed through `ctx.emitUi` with its output.
|
|
3673
|
+
collectUi: true
|
|
3571
3674
|
};
|
|
3572
|
-
|
|
3675
|
+
raw = await hooks.dispatchTool(resolvedCall, envelope);
|
|
3573
3676
|
} else {
|
|
3574
|
-
const invocation = hooks.step(`tool:${call.id}`, () =>
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3677
|
+
const invocation = hooks.step(`tool:${call.id}`, async () => {
|
|
3678
|
+
const ui2 = createUiCollector(call.id, (event) => turn.writer.write(encodeStreamEvent(event)));
|
|
3679
|
+
const output2 = await traceToolExecution(hooks.runId, {
|
|
3680
|
+
toolCallId: call.id,
|
|
3681
|
+
toolName: call.name,
|
|
3682
|
+
toolType
|
|
3683
|
+
}, () => invokeWithTransientRetry(() => {
|
|
3684
|
+
ui2.restart();
|
|
3685
|
+
return deps.registry.invoke(call.name, call.input, {
|
|
3686
|
+
...ctx,
|
|
3687
|
+
emitUi: ui2.emit
|
|
3688
|
+
}, deps.rolesPolicy);
|
|
3689
|
+
}, deps.toolTransientRetry ?? {}, {
|
|
3690
|
+
...hooks.isControlFlowError !== void 0 ? {
|
|
3691
|
+
isControlFlowError: hooks.isControlFlowError
|
|
3692
|
+
} : {},
|
|
3693
|
+
onRetry: /* @__PURE__ */ __name((attempt, retryError) => {
|
|
3694
|
+
publishAgentToolRetry({
|
|
3695
|
+
toolName: call.name,
|
|
3696
|
+
toolCallId: call.id,
|
|
3697
|
+
attempt,
|
|
3698
|
+
message: retryError instanceof Error ? retryError.message : String(retryError)
|
|
3699
|
+
});
|
|
3700
|
+
}, "onRetry")
|
|
3701
|
+
}));
|
|
3702
|
+
return wrapToolStepOutput(output2, ui2.components());
|
|
3703
|
+
});
|
|
3704
|
+
raw = deps.toolTimeoutMs !== void 0 ? await withToolTimeout(invocation, deps.toolTimeoutMs, call.name) : await invocation;
|
|
3705
|
+
}
|
|
3706
|
+
const { output, ui } = unwrapToolStepOutput(raw);
|
|
3707
|
+
if (ui.length > 0) {
|
|
3708
|
+
turn.toolUi.set(call.id, ui);
|
|
3709
|
+
}
|
|
3710
|
+
if (claimed.terminal === true) {
|
|
3711
|
+
turn.halt.terminal = true;
|
|
3592
3712
|
}
|
|
3593
3713
|
return {
|
|
3594
3714
|
status: "executed",
|
|
@@ -4486,6 +4606,10 @@ ${block}`;
|
|
|
4486
4606
|
hooks,
|
|
4487
4607
|
messageId: assistant.id,
|
|
4488
4608
|
writer,
|
|
4609
|
+
toolUi: /* @__PURE__ */ new Map(),
|
|
4610
|
+
halt: {
|
|
4611
|
+
terminal: false
|
|
4612
|
+
},
|
|
4489
4613
|
...skillOffer !== void 0 ? {
|
|
4490
4614
|
skills: skillOffer
|
|
4491
4615
|
} : {},
|
|
@@ -4518,6 +4642,10 @@ ${block}`;
|
|
|
4518
4642
|
assistantMessage.toolResults = settledResults;
|
|
4519
4643
|
await hooks.step(`stream:tool-outputs:${i}`, async () => {
|
|
4520
4644
|
await deps.store.setMessageToolResults(assistant.id, settledResults);
|
|
4645
|
+
const toolUi = toolCallsWithKind.flatMap((call) => turnCalls.toolUi.get(call.id) ?? []);
|
|
4646
|
+
if (toolUi.length > 0) {
|
|
4647
|
+
await deps.store.setMessageUi?.(assistant.id, mergeUi(turn.ui, toolUi));
|
|
4648
|
+
}
|
|
4521
4649
|
for (const result of results) {
|
|
4522
4650
|
await writer.write(encodeStreamEvent(outputFrame(result)));
|
|
4523
4651
|
}
|
|
@@ -4532,6 +4660,9 @@ ${block}`;
|
|
|
4532
4660
|
} : {}
|
|
4533
4661
|
}));
|
|
4534
4662
|
});
|
|
4663
|
+
if (turnCalls.halt.terminal) {
|
|
4664
|
+
break;
|
|
4665
|
+
}
|
|
4535
4666
|
}
|
|
4536
4667
|
if (history.title === "" || history.title === "New chat") {
|
|
4537
4668
|
await hooks.step("persist:title", () => deps.store.setTitle(input.threadId, deriveTitle(input.userText)));
|
|
@@ -4662,6 +4793,7 @@ __name(runAgentLoop, "runAgentLoop");
|
|
|
4662
4793
|
compositeSkillProvider,
|
|
4663
4794
|
createFrameBuffer,
|
|
4664
4795
|
createIncrementalGate,
|
|
4796
|
+
createUiCollector,
|
|
4665
4797
|
dayBoundsUtc,
|
|
4666
4798
|
decodeStreamEvent,
|
|
4667
4799
|
defaultCanDecide,
|
|
@@ -4689,6 +4821,7 @@ __name(runAgentLoop, "runAgentLoop");
|
|
|
4689
4821
|
mayDecideApproval,
|
|
4690
4822
|
memoryForgetVerdict,
|
|
4691
4823
|
memoryWriteVerdict,
|
|
4824
|
+
mergeUi,
|
|
4692
4825
|
normalizeDelegation,
|
|
4693
4826
|
normalizeElicitationReply,
|
|
4694
4827
|
observeTurnFrames,
|
|
@@ -4739,6 +4872,7 @@ __name(runAgentLoop, "runAgentLoop");
|
|
|
4739
4872
|
traceLlmTurn,
|
|
4740
4873
|
traceToolExecution,
|
|
4741
4874
|
truncateDetailContent,
|
|
4875
|
+
unwrapToolStepOutput,
|
|
4742
4876
|
validateElicitationAnswer,
|
|
4743
4877
|
validateElicitationValue,
|
|
4744
4878
|
validateStructured,
|
|
@@ -4748,6 +4882,7 @@ __name(runAgentLoop, "runAgentLoop");
|
|
|
4748
4882
|
withSkillTool,
|
|
4749
4883
|
withToolTimeout,
|
|
4750
4884
|
withTurnFrames,
|
|
4885
|
+
wrapToolStepOutput,
|
|
4751
4886
|
writeMemory
|
|
4752
4887
|
});
|
|
4753
4888
|
//# sourceMappingURL=index.cjs.map
|