@dudousxd/nestjs-agent 1.12.0 → 1.13.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 +5 -3
- package/dist/{agent-deps.factory-pqv_qTmJ.d.cts → agent-deps.factory-DkWvoWbD.d.cts} +13 -2
- package/dist/{agent-deps.factory-pqv_qTmJ.d.ts → agent-deps.factory-DkWvoWbD.d.ts} +13 -2
- package/dist/durable/index.cjs +45 -10
- package/dist/durable/index.cjs.map +1 -1
- package/dist/durable/index.d.cts +1 -1
- package/dist/durable/index.d.ts +1 -1
- package/dist/durable/index.js +46 -11
- package/dist/durable/index.js.map +1 -1
- package/dist/index.cjs +45 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -5
- package/dist/index.d.ts +13 -5
- package/dist/index.js +46 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -297,7 +297,8 @@ through `AgentService.subscribeAs(actor, runId)`.
|
|
|
297
297
|
### Resuming a dropped stream
|
|
298
298
|
|
|
299
299
|
Every event frame of `POST /agent/chat` and `GET /agent/chat/:runId/stream` carries an SSE `id:` —
|
|
300
|
-
its 1-based position in the run, the same on every attach
|
|
300
|
+
its 1-based position in the run, the same on every attach (a runner of your own may number with gaps,
|
|
301
|
+
as long as ids only increase — see *Numbering a stream you rebuild* in the protocol doc). `GET …/stream?after=<n>` (or the
|
|
301
302
|
`Last-Event-ID` header) skips what a reconnecting client already has; the React transport uses it to
|
|
302
303
|
continue a message after a network drop. See docs/stream-protocol.md.
|
|
303
304
|
|
|
@@ -346,8 +347,9 @@ turn rather than answering on another. `staticModelCatalog(view)` wraps a fixed
|
|
|
346
347
|
|
|
347
348
|
### Budgets: `GET /agent/quota`
|
|
348
349
|
|
|
349
|
-
`GET /agent/quota` reports `{ windows: [{ period: 'day' | 'month', usedTokens
|
|
350
|
-
usedUsd, limitUsd?, resetsAt? }], blocked? }` from a `QuotaProvider
|
|
350
|
+
`GET /agent/quota` reports `{ windows: [{ period: 'day' | 'month', usedTokens?, limitTokens?,
|
|
351
|
+
usedUsd, limitUsd?, resetsAt?, warnAt? }], blocked?, warning? }` from a `QuotaProvider` (a USD-only
|
|
352
|
+
budget leaves `usedTokens` out; `quota: { limits, warnAt: 0.8 }` adds a soft-limit `warning`). The default,
|
|
351
353
|
`LedgerQuotaProvider`, reads the usage ledger (day, plus month when the store has `usageBetween`)
|
|
352
354
|
and only reports. Configure it and it also gates sends — a `blocked` report refuses `POST
|
|
353
355
|
/agent/chat` with `429 { code: 'quota_exceeded', period }` before the turn starts:
|
|
@@ -103,6 +103,14 @@ interface QuotaLimits {
|
|
|
103
103
|
day?: QuotaWindowLimits;
|
|
104
104
|
month?: QuotaWindowLimits;
|
|
105
105
|
}
|
|
106
|
+
/** {@link LedgerQuotaProvider}'s tuning beyond the ceilings. */
|
|
107
|
+
interface LedgerQuotaOptions {
|
|
108
|
+
/**
|
|
109
|
+
* The soft limit: the share of a ceiling (`0..1`) past which the report carries a `warning`.
|
|
110
|
+
* Stamped on every window that has a ceiling. Omit → no warnings.
|
|
111
|
+
*/
|
|
112
|
+
warnAt?: number;
|
|
113
|
+
}
|
|
106
114
|
/**
|
|
107
115
|
* The default {@link QuotaProvider}: windows read off the usage ledger the loop already writes.
|
|
108
116
|
*
|
|
@@ -115,7 +123,8 @@ interface QuotaLimits {
|
|
|
115
123
|
declare class LedgerQuotaProvider implements QuotaProvider {
|
|
116
124
|
private readonly store;
|
|
117
125
|
private readonly limits;
|
|
118
|
-
|
|
126
|
+
private readonly options;
|
|
127
|
+
constructor(store: AgentStore, limits?: QuotaLimits, options?: LedgerQuotaOptions);
|
|
119
128
|
report(query: QuotaQuery): Promise<QuotaReport>;
|
|
120
129
|
private dayWindow;
|
|
121
130
|
private window;
|
|
@@ -233,7 +242,8 @@ interface AgentModuleOptions {
|
|
|
233
242
|
sink?: TokenStreamSink;
|
|
234
243
|
/**
|
|
235
244
|
* The caller's budget. `{ limits: { day?: { tokens?, usd? }, month?: { tokens?, usd? } } }` puts
|
|
236
|
-
* ceilings on the built-in ledger provider
|
|
245
|
+
* ceilings on the built-in ledger provider, and `warnAt` (`0..1`, e.g. `0.8`) a soft limit past
|
|
246
|
+
* which the report carries a `warning`; a {@link QuotaProvider} of your own replaces it. Either
|
|
237
247
|
* way `GET <base>/quota` reports it and a send whose report comes back `blocked` is refused with
|
|
238
248
|
* `429` before the turn starts. Omit → reported (usage off the ledger), never enforced — for a
|
|
239
249
|
* public (anonymous) deployment that means unbounded model spend, so set limits there; they apply
|
|
@@ -241,6 +251,7 @@ interface AgentModuleOptions {
|
|
|
241
251
|
*/
|
|
242
252
|
quota?: {
|
|
243
253
|
limits: QuotaLimits;
|
|
254
|
+
warnAt?: number;
|
|
244
255
|
} | QuotaProvider;
|
|
245
256
|
/**
|
|
246
257
|
* Which models a caller may pick — `GET <base>/models`, a send's `model`, a thread's pinned model
|
|
@@ -103,6 +103,14 @@ interface QuotaLimits {
|
|
|
103
103
|
day?: QuotaWindowLimits;
|
|
104
104
|
month?: QuotaWindowLimits;
|
|
105
105
|
}
|
|
106
|
+
/** {@link LedgerQuotaProvider}'s tuning beyond the ceilings. */
|
|
107
|
+
interface LedgerQuotaOptions {
|
|
108
|
+
/**
|
|
109
|
+
* The soft limit: the share of a ceiling (`0..1`) past which the report carries a `warning`.
|
|
110
|
+
* Stamped on every window that has a ceiling. Omit → no warnings.
|
|
111
|
+
*/
|
|
112
|
+
warnAt?: number;
|
|
113
|
+
}
|
|
106
114
|
/**
|
|
107
115
|
* The default {@link QuotaProvider}: windows read off the usage ledger the loop already writes.
|
|
108
116
|
*
|
|
@@ -115,7 +123,8 @@ interface QuotaLimits {
|
|
|
115
123
|
declare class LedgerQuotaProvider implements QuotaProvider {
|
|
116
124
|
private readonly store;
|
|
117
125
|
private readonly limits;
|
|
118
|
-
|
|
126
|
+
private readonly options;
|
|
127
|
+
constructor(store: AgentStore, limits?: QuotaLimits, options?: LedgerQuotaOptions);
|
|
119
128
|
report(query: QuotaQuery): Promise<QuotaReport>;
|
|
120
129
|
private dayWindow;
|
|
121
130
|
private window;
|
|
@@ -233,7 +242,8 @@ interface AgentModuleOptions {
|
|
|
233
242
|
sink?: TokenStreamSink;
|
|
234
243
|
/**
|
|
235
244
|
* The caller's budget. `{ limits: { day?: { tokens?, usd? }, month?: { tokens?, usd? } } }` puts
|
|
236
|
-
* ceilings on the built-in ledger provider
|
|
245
|
+
* ceilings on the built-in ledger provider, and `warnAt` (`0..1`, e.g. `0.8`) a soft limit past
|
|
246
|
+
* which the report carries a `warning`; a {@link QuotaProvider} of your own replaces it. Either
|
|
237
247
|
* way `GET <base>/quota` reports it and a send whose report comes back `blocked` is refused with
|
|
238
248
|
* `429` before the turn starts. Omit → reported (usage off the ledger), never enforced — for a
|
|
239
249
|
* public (anonymous) deployment that means unbounded model spend, so set limits there; they apply
|
|
@@ -241,6 +251,7 @@ interface AgentModuleOptions {
|
|
|
241
251
|
*/
|
|
242
252
|
quota?: {
|
|
243
253
|
limits: QuotaLimits;
|
|
254
|
+
warnAt?: number;
|
|
244
255
|
} | QuotaProvider;
|
|
245
256
|
/**
|
|
246
257
|
* Which models a caller may pick — `GET <base>/models`, a send's `model`, a thread's pinned model
|
package/dist/durable/index.cjs
CHANGED
|
@@ -1219,8 +1219,22 @@ var AgentService = class {
|
|
|
1219
1219
|
}
|
|
1220
1220
|
return entry.id;
|
|
1221
1221
|
}
|
|
1222
|
-
/**
|
|
1222
|
+
/**
|
|
1223
|
+
* The model a turn runs on: the send's own (that turn only — it is never stored on the thread),
|
|
1224
|
+
* else the thread's pinned one, else none. An agent the catalog locks to one model runs on it
|
|
1225
|
+
* whatever was sent or pinned, and a send naming another model is refused.
|
|
1226
|
+
*/
|
|
1223
1227
|
async resolveModel(actor, agent, requested, threadId) {
|
|
1228
|
+
const locked = this.models !== void 0 ? (await this.models.list({
|
|
1229
|
+
actor,
|
|
1230
|
+
agent
|
|
1231
|
+
})).locked : void 0;
|
|
1232
|
+
if (locked !== void 0) {
|
|
1233
|
+
if (requested !== void 0 && requested !== locked.model) {
|
|
1234
|
+
throw new import_common6.BadRequestException(`model "${requested}" cannot be selected: ${locked.reason ?? `this agent always uses "${locked.model}"`}`);
|
|
1235
|
+
}
|
|
1236
|
+
return locked.model;
|
|
1237
|
+
}
|
|
1224
1238
|
const model = requested ?? (threadId !== void 0 ? await this.threadModel(threadId) : null);
|
|
1225
1239
|
if (model === null || model === void 0) {
|
|
1226
1240
|
return void 0;
|
|
@@ -1369,12 +1383,15 @@ var AgentService = class {
|
|
|
1369
1383
|
* "the user picked exactly the defaults" persist as the same answers, and no client has to
|
|
1370
1384
|
* re-send what it was shown.
|
|
1371
1385
|
*/
|
|
1372
|
-
async answer(actor, toolCallId2, answers2 = {}) {
|
|
1386
|
+
async answer(actor, toolCallId2, answers2 = {}, opts = {}) {
|
|
1373
1387
|
await this.assertOwnsToolCall(actor, toolCallId2);
|
|
1374
1388
|
await this.assertAnswersFit(toolCallId2, answers2);
|
|
1375
1389
|
const reply = {
|
|
1376
1390
|
answers: answers2,
|
|
1377
|
-
answeredByRef: actor.id
|
|
1391
|
+
answeredByRef: actor.id,
|
|
1392
|
+
...opts.via !== void 0 ? {
|
|
1393
|
+
answeredVia: opts.via
|
|
1394
|
+
} : {}
|
|
1378
1395
|
};
|
|
1379
1396
|
return this.signalToolCall(toolCallId2, reply);
|
|
1380
1397
|
}
|
|
@@ -1383,12 +1400,15 @@ var AgentService = class {
|
|
|
1383
1400
|
* them: the run records this as a rejection, so a reader auditing what the agent was told can
|
|
1384
1401
|
* tell a choice the user made from one they refused to make.
|
|
1385
1402
|
*/
|
|
1386
|
-
async skip(actor, toolCallId2) {
|
|
1403
|
+
async skip(actor, toolCallId2, opts = {}) {
|
|
1387
1404
|
await this.assertOwnsToolCall(actor, toolCallId2);
|
|
1388
1405
|
const reply = {
|
|
1389
1406
|
answers: {},
|
|
1390
1407
|
skipped: true,
|
|
1391
|
-
answeredByRef: actor.id
|
|
1408
|
+
answeredByRef: actor.id,
|
|
1409
|
+
...opts.via !== void 0 ? {
|
|
1410
|
+
answeredVia: opts.via
|
|
1411
|
+
} : {}
|
|
1392
1412
|
};
|
|
1393
1413
|
return this.signalToolCall(toolCallId2, reply);
|
|
1394
1414
|
}
|
|
@@ -3250,7 +3270,9 @@ var ToolCallController = class {
|
|
|
3250
3270
|
*/
|
|
3251
3271
|
async answer(req, body) {
|
|
3252
3272
|
const actor = await this.actorResolver.resolve(req);
|
|
3253
|
-
await this.agent.answer(actor, toolCallId(body.toolCallId), answers(body.answers)
|
|
3273
|
+
await this.agent.answer(actor, toolCallId(body.toolCallId), answers(body.answers), {
|
|
3274
|
+
via: via(body.via)
|
|
3275
|
+
});
|
|
3254
3276
|
return {
|
|
3255
3277
|
ok: true
|
|
3256
3278
|
};
|
|
@@ -3258,7 +3280,9 @@ var ToolCallController = class {
|
|
|
3258
3280
|
/** Decline to answer and let the agent proceed on the answers it pre-picked. */
|
|
3259
3281
|
async skip(req, body) {
|
|
3260
3282
|
const actor = await this.actorResolver.resolve(req);
|
|
3261
|
-
await this.agent.skip(actor, toolCallId(body.toolCallId)
|
|
3283
|
+
await this.agent.skip(actor, toolCallId(body.toolCallId), {
|
|
3284
|
+
via: via(body.via)
|
|
3285
|
+
});
|
|
3262
3286
|
return {
|
|
3263
3287
|
ok: true
|
|
3264
3288
|
};
|
|
@@ -3894,9 +3918,11 @@ var LedgerQuotaProvider = class {
|
|
|
3894
3918
|
}
|
|
3895
3919
|
store;
|
|
3896
3920
|
limits;
|
|
3897
|
-
|
|
3921
|
+
options;
|
|
3922
|
+
constructor(store, limits = {}, options = {}) {
|
|
3898
3923
|
this.store = store;
|
|
3899
3924
|
this.limits = limits;
|
|
3925
|
+
this.options = options;
|
|
3900
3926
|
}
|
|
3901
3927
|
async report(query) {
|
|
3902
3928
|
const now = query.now ?? /* @__PURE__ */ new Date();
|
|
@@ -3910,10 +3936,14 @@ var LedgerQuotaProvider = class {
|
|
|
3910
3936
|
windows.push(this.window("month", used.usedTokens, used.costUsd, range.resetsAt));
|
|
3911
3937
|
}
|
|
3912
3938
|
const blocked = (0, import_nestjs_agent_core25.exhaustedWindow)(windows);
|
|
3939
|
+
const warning = (0, import_nestjs_agent_core25.quotaWarning)(windows);
|
|
3913
3940
|
return {
|
|
3914
3941
|
windows,
|
|
3915
3942
|
...blocked !== void 0 ? {
|
|
3916
3943
|
blocked
|
|
3944
|
+
} : {},
|
|
3945
|
+
...warning !== void 0 ? {
|
|
3946
|
+
warning
|
|
3917
3947
|
} : {}
|
|
3918
3948
|
};
|
|
3919
3949
|
}
|
|
@@ -3934,7 +3964,10 @@ var LedgerQuotaProvider = class {
|
|
|
3934
3964
|
...limits.usd !== void 0 ? {
|
|
3935
3965
|
limitUsd: limits.usd
|
|
3936
3966
|
} : {},
|
|
3937
|
-
resetsAt
|
|
3967
|
+
resetsAt,
|
|
3968
|
+
...this.options.warnAt !== void 0 && (limits.tokens !== void 0 || limits.usd !== void 0) ? {
|
|
3969
|
+
warnAt: this.options.warnAt
|
|
3970
|
+
} : {}
|
|
3938
3971
|
};
|
|
3939
3972
|
}
|
|
3940
3973
|
};
|
|
@@ -4539,7 +4572,9 @@ function sharedProviders(durable) {
|
|
|
4539
4572
|
// `quota` is either a provider of the host's own or ceilings for the built-in ledger one;
|
|
4540
4573
|
// omitted, the ledger provider still reports usage, with no ceiling to block on.
|
|
4541
4574
|
provide: import_nestjs_agent_core28.AGENT_QUOTA_PROVIDER,
|
|
4542
|
-
useFactory: /* @__PURE__ */ __name((o, store) => isQuotaProvider(o.quota) ? o.quota : new LedgerQuotaProvider(store, o.quota?.limits
|
|
4575
|
+
useFactory: /* @__PURE__ */ __name((o, store) => isQuotaProvider(o.quota) ? o.quota : new LedgerQuotaProvider(store, o.quota?.limits, o.quota?.warnAt !== void 0 ? {
|
|
4576
|
+
warnAt: o.quota.warnAt
|
|
4577
|
+
} : {}), "useFactory"),
|
|
4543
4578
|
inject: [
|
|
4544
4579
|
import_nestjs_agent_core28.AGENT_OPTIONS,
|
|
4545
4580
|
import_nestjs_agent_core28.AGENT_STORE
|