@dudousxd/nestjs-agent-core 0.36.0 → 0.37.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/dist/index.js CHANGED
@@ -27,6 +27,7 @@ var AGENT_APPROVAL_PORT = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent:app
27
27
  var AGENT_SKILLS = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent:skills");
28
28
  var AGENT_MEMORY = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent:memory");
29
29
  var AGENT_SKILL_SOURCES = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent:skill-sources");
30
+ var AGENT_CONFIRM_TOKEN_STORE = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent:confirm-token-store");
30
31
 
31
32
  // src/spi/model-catalog.ts
32
33
  function staticModelCatalog(view) {
@@ -5787,11 +5788,321 @@ var InMemoryAgentStore = class {
5787
5788
  };
5788
5789
  }
5789
5790
  };
5791
+
5792
+ // src/confirm-token.ts
5793
+ import { createHash as createHash2, createHmac, timingSafeEqual } from "crypto";
5794
+ var DEFAULT_CONFIRM_TTL_MS = 15 * 60 * 1e3;
5795
+ function canonicalJson(value) {
5796
+ if (value === void 0) return "null";
5797
+ if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
5798
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
5799
+ if (typeof value.toJSON === "function") {
5800
+ return canonicalJson(value.toJSON());
5801
+ }
5802
+ const entries = Object.entries(value).filter(([, entry]) => entry !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
5803
+ return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`).join(",")}}`;
5804
+ }
5805
+ __name(canonicalJson, "canonicalJson");
5806
+ function signature(subject, expiresAt, secret) {
5807
+ if (secret === "") {
5808
+ throw new Error("confirm token: the secret is empty");
5809
+ }
5810
+ return createHmac("sha256", secret).update(canonicalJson([
5811
+ "agent-confirm",
5812
+ subject.tool,
5813
+ subject.actorId,
5814
+ subject.tenantRef ?? "",
5815
+ expiresAt,
5816
+ subject.args
5817
+ ])).digest("base64url");
5818
+ }
5819
+ __name(signature, "signature");
5820
+ function signConfirmToken(subject, options) {
5821
+ return `${options.expiresAt}.${signature(subject, options.expiresAt, options.secret)}`;
5822
+ }
5823
+ __name(signConfirmToken, "signConfirmToken");
5824
+ function confirmTokenExpiry(token) {
5825
+ const separator = token.indexOf(".");
5826
+ if (separator <= 0) return void 0;
5827
+ const expiresAt = Number(token.slice(0, separator));
5828
+ return Number.isSafeInteger(expiresAt) && expiresAt > 0 ? expiresAt : void 0;
5829
+ }
5830
+ __name(confirmTokenExpiry, "confirmTokenExpiry");
5831
+ function verifyConfirmToken(token, subject, options) {
5832
+ if (typeof token !== "string") return false;
5833
+ const expiresAt = confirmTokenExpiry(token);
5834
+ if (expiresAt === void 0 || expiresAt < (options.now ?? Date.now())) return false;
5835
+ const expected = Buffer.from(signature(subject, expiresAt, options.secret));
5836
+ const given = Buffer.from(token.slice(token.indexOf(".") + 1));
5837
+ return expected.length === given.length && timingSafeEqual(expected, given);
5838
+ }
5839
+ __name(verifyConfirmToken, "verifyConfirmToken");
5840
+ function hashConfirmToken(token) {
5841
+ return createHash2("sha256").update(token).digest("hex");
5842
+ }
5843
+ __name(hashConfirmToken, "hashConfirmToken");
5844
+ var InMemoryConfirmTokenStore = class {
5845
+ static {
5846
+ __name(this, "InMemoryConfirmTokenStore");
5847
+ }
5848
+ claims = /* @__PURE__ */ new Map();
5849
+ async claim(input) {
5850
+ await this.purgeExpired();
5851
+ if (this.claims.has(input.hash)) return false;
5852
+ this.claims.set(input.hash, {
5853
+ ...input
5854
+ });
5855
+ return true;
5856
+ }
5857
+ async release(hash) {
5858
+ this.claims.delete(hash);
5859
+ }
5860
+ async purgeExpired(now = Date.now()) {
5861
+ let purged = 0;
5862
+ for (const [hash, claim] of this.claims) {
5863
+ if (claim.expiresAt < now) {
5864
+ this.claims.delete(hash);
5865
+ purged += 1;
5866
+ }
5867
+ }
5868
+ return purged;
5869
+ }
5870
+ /** How many tokens are currently marked as spent. */
5871
+ get size() {
5872
+ return this.claims.size;
5873
+ }
5874
+ };
5875
+
5876
+ // src/confirmed-tool.ts
5877
+ var DEFAULT_MESSAGES = {
5878
+ confirm: 'Nothing was written. Show this preview to the user and ask them to confirm. To commit, call this tool again with the SAME arguments plus "confirm": true and this "confirmToken" (valid for {minutes} minutes).',
5879
+ invalid: 'Invalid or expired confirmation: nothing was written. Call the tool without "confirm" to get a fresh preview, then confirm with the "confirmToken" it returns, without changing the arguments.',
5880
+ used: "This preview was already confirmed: nothing was written again. To repeat the operation, ask for a new preview."
5881
+ };
5882
+ var ConfirmTokenError = class extends Error {
5883
+ static {
5884
+ __name(this, "ConfirmTokenError");
5885
+ }
5886
+ reason;
5887
+ constructor(reason, message) {
5888
+ super(message), this.reason = reason;
5889
+ this.name = "ConfirmTokenError";
5890
+ }
5891
+ };
5892
+ var CONFIRM_JSON_SCHEMA_PROPERTIES = {
5893
+ confirm: {
5894
+ type: "boolean",
5895
+ description: "Omit to get a preview (nothing is written). Send true, with confirmToken, to commit."
5896
+ },
5897
+ confirmToken: {
5898
+ type: "string",
5899
+ description: "The confirmToken the preview returned. Required when confirm is true."
5900
+ }
5901
+ };
5902
+ var SCHEMA_EXTENSION = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent:schema-extension");
5903
+ function schemaExtensionOf(schema) {
5904
+ const extension = schema[SCHEMA_EXTENSION];
5905
+ return typeof extension === "object" && extension !== null ? extension : void 0;
5906
+ }
5907
+ __name(schemaExtensionOf, "schemaExtensionOf");
5908
+ function isRecord(value) {
5909
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5910
+ }
5911
+ __name(isRecord, "isRecord");
5912
+ function splitConfirm(input) {
5913
+ if (!isRecord(input)) return {
5914
+ args: input,
5915
+ fields: {}
5916
+ };
5917
+ const { confirm, confirmToken, ...args } = input;
5918
+ return {
5919
+ args,
5920
+ fields: {
5921
+ ...typeof confirm === "boolean" ? {
5922
+ confirm
5923
+ } : {},
5924
+ ...typeof confirmToken === "string" ? {
5925
+ confirmToken
5926
+ } : {}
5927
+ }
5928
+ };
5929
+ }
5930
+ __name(splitConfirm, "splitConfirm");
5931
+ function jsonSchemaInputOf(schema) {
5932
+ const standard = schema["~standard"];
5933
+ const input = standard.jsonSchema?.input;
5934
+ return typeof input === "function" ? (options) => input.call(standard.jsonSchema, options) : void 0;
5935
+ }
5936
+ __name(jsonSchemaInputOf, "jsonSchemaInputOf");
5937
+ function withConfirmFields(schema) {
5938
+ const inner = schema["~standard"];
5939
+ const innerJsonSchema = jsonSchemaInputOf(schema);
5940
+ const describe = /* @__PURE__ */ __name((options) => {
5941
+ const base = innerJsonSchema?.(options) ?? {
5942
+ additionalProperties: true
5943
+ };
5944
+ return {
5945
+ ...base,
5946
+ type: "object",
5947
+ properties: {
5948
+ ...isRecord(base.properties) ? base.properties : {},
5949
+ ...CONFIRM_JSON_SCHEMA_PROPERTIES
5950
+ }
5951
+ };
5952
+ }, "describe");
5953
+ const wrapped = {
5954
+ [SCHEMA_EXTENSION]: {
5955
+ base: schema,
5956
+ properties: CONFIRM_JSON_SCHEMA_PROPERTIES
5957
+ },
5958
+ "~standard": {
5959
+ version: 1,
5960
+ vendor: "@dudousxd/nestjs-agent",
5961
+ validate: /* @__PURE__ */ __name(async (value) => {
5962
+ if (!isRecord(value)) {
5963
+ return {
5964
+ issues: [
5965
+ {
5966
+ message: "expected an object"
5967
+ }
5968
+ ]
5969
+ };
5970
+ }
5971
+ const issues = [];
5972
+ if (value.confirm != null && typeof value.confirm !== "boolean") {
5973
+ issues.push({
5974
+ message: "expected a boolean",
5975
+ path: [
5976
+ "confirm"
5977
+ ]
5978
+ });
5979
+ }
5980
+ if (value.confirmToken != null && typeof value.confirmToken !== "string") {
5981
+ issues.push({
5982
+ message: "expected a string",
5983
+ path: [
5984
+ "confirmToken"
5985
+ ]
5986
+ });
5987
+ }
5988
+ const { args, fields } = splitConfirm(value);
5989
+ const result = await inner.validate(args);
5990
+ if (result.issues !== void 0) {
5991
+ return {
5992
+ issues: [
5993
+ ...issues,
5994
+ ...result.issues
5995
+ ]
5996
+ };
5997
+ }
5998
+ if (issues.length > 0) {
5999
+ return {
6000
+ issues
6001
+ };
6002
+ }
6003
+ return {
6004
+ value: isRecord(result.value) ? {
6005
+ ...result.value,
6006
+ ...fields
6007
+ } : result.value
6008
+ };
6009
+ }, "validate"),
6010
+ jsonSchema: {
6011
+ input: describe,
6012
+ output: describe
6013
+ }
6014
+ }
6015
+ };
6016
+ return wrapped;
6017
+ }
6018
+ __name(withConfirmFields, "withConfirmFields");
6019
+ function defineConfirmedTool(options, steps) {
6020
+ const { secret, ttlMs = DEFAULT_CONFIRM_TTL_MS, store, messages, input, ...specFields } = options;
6021
+ const text = {
6022
+ ...DEFAULT_MESSAGES,
6023
+ ...messages
6024
+ };
6025
+ const howToConfirm = text.confirm.replaceAll("{minutes}", String(Math.round(ttlMs / 6e4)));
6026
+ const secretNow = /* @__PURE__ */ __name(() => typeof secret === "function" ? secret() : secret, "secretNow");
6027
+ const handler = {
6028
+ async execute(value, ctx) {
6029
+ const { args, fields } = splitConfirm(value);
6030
+ const subject = {
6031
+ tool: options.name,
6032
+ actorId: ctx.actor.id,
6033
+ ...ctx.actor.tenantRef !== void 0 ? {
6034
+ tenantRef: ctx.actor.tenantRef
6035
+ } : {},
6036
+ args
6037
+ };
6038
+ const confirming = fields.confirm === true;
6039
+ const token = fields.confirmToken;
6040
+ if (confirming && !verifyConfirmToken(token, subject, {
6041
+ secret: secretNow()
6042
+ })) {
6043
+ throw new ConfirmTokenError("invalid", text.invalid);
6044
+ }
6045
+ const prepared = await steps.prepare(args, ctx);
6046
+ if (!confirming || token === void 0) {
6047
+ const preview = await steps.preview(prepared, ctx);
6048
+ const expiresAt = Date.now() + ttlMs;
6049
+ return {
6050
+ status: "preview",
6051
+ summary: preview.summary,
6052
+ ...preview.data !== void 0 ? {
6053
+ data: preview.data
6054
+ } : {},
6055
+ confirmToken: signConfirmToken(subject, {
6056
+ secret: secretNow(),
6057
+ expiresAt
6058
+ }),
6059
+ expiresAt: new Date(expiresAt).toISOString(),
6060
+ confirm: howToConfirm
6061
+ };
6062
+ }
6063
+ const hash = hashConfirmToken(token);
6064
+ if (store !== void 0) {
6065
+ const claimed = await store.claim({
6066
+ hash,
6067
+ actorRef: ctx.actor.id,
6068
+ tool: options.name,
6069
+ expiresAt: confirmTokenExpiry(token) ?? Date.now() + ttlMs
6070
+ });
6071
+ if (!claimed) {
6072
+ throw new ConfirmTokenError("used", text.used);
6073
+ }
6074
+ }
6075
+ try {
6076
+ const done = await steps.commit(prepared, ctx);
6077
+ return {
6078
+ status: "done",
6079
+ summary: done.summary,
6080
+ ...done.data !== void 0 ? {
6081
+ data: done.data
6082
+ } : {}
6083
+ };
6084
+ } catch (error) {
6085
+ await store?.release(hash).catch(() => void 0);
6086
+ throw error;
6087
+ }
6088
+ }
6089
+ };
6090
+ return {
6091
+ spec: {
6092
+ ...specFields,
6093
+ kind: "read",
6094
+ inputSchema: withConfirmFields(input)
6095
+ },
6096
+ handler
6097
+ };
6098
+ }
6099
+ __name(defineConfirmedTool, "defineConfirmedTool");
5790
6100
  export {
5791
6101
  AGENT_ACTOR_DIRECTORY,
5792
6102
  AGENT_ACTOR_RESOLVER,
5793
6103
  AGENT_APPROVAL_PORT,
5794
6104
  AGENT_ATTACHMENT_STAGING,
6105
+ AGENT_CONFIRM_TOKEN_STORE,
5795
6106
  AGENT_DEPS_FACTORY,
5796
6107
  AGENT_DIAGNOSTIC_EVENTS,
5797
6108
  AGENT_DURABLE_RUNNER,
@@ -5821,7 +6132,10 @@ export {
5821
6132
  ASK_TOOL_NAME,
5822
6133
  AgentRegistry,
5823
6134
  AgentStreamError,
6135
+ CONFIRM_JSON_SCHEMA_PROPERTIES,
5824
6136
  ClosedRolesPolicy,
6137
+ ConfirmTokenError,
6138
+ DEFAULT_CONFIRM_TTL_MS,
5825
6139
  DEFAULT_HISTORY_SUMMARY_INSTRUCTION,
5826
6140
  DEFAULT_INCREMENTAL_LOOKBACK_CHARS,
5827
6141
  DEFAULT_INTAKE_PREAMBLE,
@@ -5837,6 +6151,7 @@ export {
5837
6151
  ELICITATION_INPUT_TYPES,
5838
6152
  GLOBAL_SCOPE,
5839
6153
  InMemoryAgentStore,
6154
+ InMemoryConfirmTokenStore,
5840
6155
  MAX_ASK_QUESTIONS,
5841
6156
  OutputRejectedError,
5842
6157
  ProcessorFailedError,
@@ -5850,6 +6165,7 @@ export {
5850
6165
  RUN_NOT_ACTIVE_MESSAGE,
5851
6166
  RUN_NO_LONGER_RUNNING,
5852
6167
  RunCancelledError,
6168
+ SCHEMA_EXTENSION,
5853
6169
  SKILL_TOOL_DESCRIPTION,
5854
6170
  SKILL_TOOL_NAME,
5855
6171
  StructuredOutputError,
@@ -5872,8 +6188,10 @@ export {
5872
6188
  buildMemoryBlock,
5873
6189
  buildSkillsBlock,
5874
6190
  canActorUseTool,
6191
+ canonicalJson,
5875
6192
  closeEmptyRoles,
5876
6193
  compositeSkillProvider,
6194
+ confirmTokenExpiry,
5877
6195
  createFrameBuffer,
5878
6196
  createIncrementalGate,
5879
6197
  createNoopEmitUi,
@@ -5883,6 +6201,7 @@ export {
5883
6201
  decodeStreamEvent,
5884
6202
  defaultCanDecide,
5885
6203
  defaultScopeResolver,
6204
+ defineConfirmedTool,
5886
6205
  detachedDelivered,
5887
6206
  detachedStarted,
5888
6207
  detachedUnsettled,
@@ -5899,6 +6218,7 @@ export {
5899
6218
  findCatalogModel,
5900
6219
  gateFollowUps,
5901
6220
  gateTail,
6221
+ hashConfirmToken,
5902
6222
  invokeWithTransientRetry,
5903
6223
  isChatQueueStore,
5904
6224
  isControlFlowSignal,
@@ -5951,12 +6271,14 @@ export {
5951
6271
  runAgentLoop,
5952
6272
  runInputProcessors,
5953
6273
  runOutputProcessors,
6274
+ schemaExtensionOf,
5954
6275
  seedModelPrices,
5955
6276
  settleAll,
5956
6277
  settleDanglingToolCalls,
5957
6278
  settleDeadRun,
5958
6279
  settleElicitation,
5959
6280
  settleUnsettledDelegation,
6281
+ signConfirmToken,
5960
6282
  skillInputSchema,
5961
6283
  skillToolDefinition,
5962
6284
  skillWriteVerdict,
@@ -5975,8 +6297,10 @@ export {
5975
6297
  validateElicitationAnswer,
5976
6298
  validateElicitationValue,
5977
6299
  validateStructured,
6300
+ verifyConfirmToken,
5978
6301
  windowHistory,
5979
6302
  withAskTool,
6303
+ withConfirmFields,
5980
6304
  withMemoryTool,
5981
6305
  withSelectedModel,
5982
6306
  withSkillTool,