plugin-agent-orchestrator 1.0.20 → 1.0.21
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/client/hooks/useRunEventStream.d.ts +22 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -1
- package/dist/externalVersion.js +6 -6
- package/dist/server/collections/agent-execution-spans.js +24 -0
- package/dist/server/collections/agent-loop-runs.js +36 -0
- package/dist/server/collections/orchestrator-config.js +14 -0
- package/dist/server/migrations/20260601000000-add-token-fields.d.ts +7 -0
- package/dist/server/migrations/20260601000000-add-token-fields.js +101 -0
- package/dist/server/plugin.js +47 -0
- package/dist/server/resources/agent-loop.js +33 -25
- package/dist/server/resources/tracing.js +5 -8
- package/dist/server/services/AgentHarness.d.ts +2 -0
- package/dist/server/services/AgentHarness.js +56 -90
- package/dist/server/services/AgentLoopController.d.ts +33 -20
- package/dist/server/services/AgentLoopController.js +164 -125
- package/dist/server/services/AgentLoopRepository.js +16 -34
- package/dist/server/services/AgentLoopService.d.ts +28 -18
- package/dist/server/services/AgentLoopService.js +7 -1
- package/dist/server/services/AgentPlannerService.js +5 -25
- package/dist/server/services/AgentRegistryService.d.ts +8 -0
- package/dist/server/services/AgentRegistryService.js +34 -24
- package/dist/server/services/CircuitBreaker.d.ts +40 -0
- package/dist/server/services/CircuitBreaker.js +120 -0
- package/dist/server/services/ContextAggregator.d.ts +45 -0
- package/dist/server/services/ContextAggregator.js +201 -0
- package/dist/server/services/ExecutionSpanService.js +2 -5
- package/dist/server/services/RunEventBus.d.ts +9 -0
- package/dist/server/services/RunEventBus.js +73 -0
- package/dist/server/services/TokenTracker.d.ts +62 -0
- package/dist/server/services/TokenTracker.js +173 -0
- package/dist/server/tools/agent-loop.d.ts +8 -8
- package/dist/server/tools/agent-loop.js +30 -63
- package/dist/server/tools/delegate-task.js +14 -72
- package/dist/server/tools/orchestrator-plan.d.ts +6 -6
- package/dist/server/tools/orchestrator-plan.js +10 -47
- package/dist/server/types.d.ts +47 -0
- package/dist/server/types.js +24 -0
- package/dist/server/utils/ctx-utils.d.ts +30 -0
- package/dist/server/utils/ctx-utils.js +152 -0
- package/dist/server/utils/logging.d.ts +6 -0
- package/dist/server/utils/logging.js +86 -0
- package/package.json +44 -44
- package/src/client/AgentRunsTab.tsx +764 -764
- package/src/client/HarnessProfilesTab.tsx +247 -247
- package/src/client/OrchestratorSettings.tsx +106 -106
- package/src/client/RulesTab.tsx +716 -716
- package/src/client/hooks/useRunEventStream.ts +76 -0
- package/src/client/index.tsx +2 -1
- package/src/client/plugin.tsx +27 -27
- package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
- package/src/client/skill-hub/index.tsx +51 -51
- package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +99 -99
- package/src/client/skill-hub/tools/SkillHubCard.tsx +109 -109
- package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
- package/src/client/skill-hub/tools/registerSkillLoopCards.ts +58 -58
- package/src/client/tools/PlanApprovalCard.tsx +175 -175
- package/src/client/tools/registerOrchestratorCards.ts +7 -7
- package/src/server/__tests__/agent-loop-controller.test.ts +375 -0
- package/src/server/__tests__/circuit-breaker.test.ts +169 -0
- package/src/server/__tests__/context-aggregator.test.ts +222 -0
- package/src/server/__tests__/parallel-execution.test.ts +318 -0
- package/src/server/__tests__/smoke.test.ts +120 -0
- package/src/server/collections/agent-execution-spans.ts +24 -0
- package/src/server/collections/agent-harness-profiles.ts +59 -59
- package/src/server/collections/agent-loop-events.ts +71 -71
- package/src/server/collections/agent-loop-runs.ts +38 -1
- package/src/server/collections/agent-loop-steps.ts +144 -144
- package/src/server/collections/orchestrator-config.ts +14 -0
- package/src/server/collections/skill-executions.ts +106 -106
- package/src/server/collections/skill-loop-configs.ts +65 -65
- package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -30
- package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +142 -142
- package/src/server/migrations/20260601000000-add-token-fields.ts +89 -0
- package/src/server/plugin.ts +53 -0
- package/src/server/resources/agent-loop.ts +21 -12
- package/src/server/resources/tracing.ts +3 -7
- package/src/server/services/AgentHarness.ts +78 -116
- package/src/server/services/AgentLoopController.ts +197 -122
- package/src/server/services/AgentLoopRepository.ts +9 -25
- package/src/server/services/AgentLoopService.ts +13 -1
- package/src/server/services/AgentPlanValidator.ts +73 -73
- package/src/server/services/AgentPlannerService.ts +2 -25
- package/src/server/services/AgentRegistryService.ts +40 -31
- package/src/server/services/CircuitBreaker.ts +116 -0
- package/src/server/services/ContextAggregator.ts +239 -0
- package/src/server/services/ExecutionSpanService.ts +2 -4
- package/src/server/services/RunEventBus.ts +45 -0
- package/src/server/services/TokenTracker.ts +209 -0
- package/src/server/skill-hub/plugin.ts +898 -898
- package/src/server/skill-hub/tasks/SkillExecutionTask.ts +460 -460
- package/src/server/tools/agent-loop.ts +18 -57
- package/src/server/tools/delegate-task.ts +11 -93
- package/src/server/tools/orchestrator-plan.ts +26 -50
- package/src/server/tools/skill-execute.ts +160 -160
- package/src/server/types.ts +55 -0
- package/src/server/utils/ctx-utils.ts +118 -0
- package/src/server/utils/logging.ts +63 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
|
+
var ContextAggregator_exports = {};
|
|
28
|
+
__export(ContextAggregator_exports, {
|
|
29
|
+
ContextAggregator: () => ContextAggregator
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(ContextAggregator_exports);
|
|
32
|
+
function trimText(text, maxLen) {
|
|
33
|
+
if (text.length <= maxLen) return text;
|
|
34
|
+
return text.slice(0, maxLen) + "\n...[truncated]";
|
|
35
|
+
}
|
|
36
|
+
function estimateTokens(text) {
|
|
37
|
+
return Math.ceil(text.length / 4);
|
|
38
|
+
}
|
|
39
|
+
class ContextAggregator {
|
|
40
|
+
constructor(plugin) {
|
|
41
|
+
this.plugin = plugin;
|
|
42
|
+
}
|
|
43
|
+
get db() {
|
|
44
|
+
return this.plugin.db;
|
|
45
|
+
}
|
|
46
|
+
get app() {
|
|
47
|
+
return this.plugin.app;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Build a structured context string from completed steps of a run.
|
|
51
|
+
*
|
|
52
|
+
* @param runId - agentLoopRuns.id
|
|
53
|
+
* @param maxTokens - Maximum tokens for the context output (default 4000)
|
|
54
|
+
* @param options - Strategy options
|
|
55
|
+
*/
|
|
56
|
+
async buildStepContext(runId, maxTokens, options) {
|
|
57
|
+
var _a;
|
|
58
|
+
const effectiveMaxTokens = maxTokens || 4e3;
|
|
59
|
+
let steps;
|
|
60
|
+
try {
|
|
61
|
+
const repo = this.db.getRepository("agentLoopSteps");
|
|
62
|
+
if (!repo) return "";
|
|
63
|
+
steps = await repo.find({
|
|
64
|
+
filter: { runId },
|
|
65
|
+
sort: ["index", "createdAt"],
|
|
66
|
+
pageSize: 500
|
|
67
|
+
});
|
|
68
|
+
} catch {
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
if (!steps || steps.length === 0) return "";
|
|
72
|
+
const completedSteps = steps.filter((s) => s.status === "succeeded" || s.status === "failed");
|
|
73
|
+
if (completedSteps.length === 0) return "";
|
|
74
|
+
const config = {
|
|
75
|
+
strategy: (options == null ? void 0 : options.strategy) || "all",
|
|
76
|
+
includeToolResults: (options == null ? void 0 : options.includeToolResults) ?? false,
|
|
77
|
+
includeStepOutputs: (options == null ? void 0 : options.includeStepOutputs) ?? true
|
|
78
|
+
};
|
|
79
|
+
let candidates = completedSteps;
|
|
80
|
+
if (config.strategy === "last_n") {
|
|
81
|
+
const n = Math.min(10, completedSteps.length);
|
|
82
|
+
candidates = completedSteps.slice(-n);
|
|
83
|
+
}
|
|
84
|
+
const parts = [];
|
|
85
|
+
for (const step of candidates) {
|
|
86
|
+
const key = step.planKey || `step_${step.index || 0}`;
|
|
87
|
+
const type = step.type || "unknown";
|
|
88
|
+
const target = step.target || "";
|
|
89
|
+
const title = step.title || key;
|
|
90
|
+
const status = step.status || "unknown";
|
|
91
|
+
const lines = [];
|
|
92
|
+
lines.push(`<step key="${key}" type="${type}" target="${target}" status="${status}">`);
|
|
93
|
+
lines.push(` <title>${this.escapeXml(title)}</title>`);
|
|
94
|
+
if (step.description) {
|
|
95
|
+
lines.push(` <description>${this.escapeXml(trimText(step.description, 500))}</description>`);
|
|
96
|
+
}
|
|
97
|
+
if (config.includeStepOutputs && step.output) {
|
|
98
|
+
const outputStr = typeof step.output === "string" ? step.output : this.safeStringify(step.output);
|
|
99
|
+
lines.push(` <output>${this.escapeXml(trimText(outputStr, 2e3))}</output>`);
|
|
100
|
+
}
|
|
101
|
+
if (config.includeToolResults && ((_a = step.metadata) == null ? void 0 : _a.toolResults)) {
|
|
102
|
+
const toolStr = this.safeStringify(step.metadata.toolResults);
|
|
103
|
+
lines.push(` <tool_results>${this.escapeXml(trimText(toolStr, 1500))}</tool_results>`);
|
|
104
|
+
}
|
|
105
|
+
if (step.error) {
|
|
106
|
+
lines.push(` <error>${this.escapeXml(trimText(step.error, 1e3))}</error>`);
|
|
107
|
+
}
|
|
108
|
+
lines.push("</step>");
|
|
109
|
+
parts.push(lines.join("\n"));
|
|
110
|
+
}
|
|
111
|
+
let context = `<previous_steps>
|
|
112
|
+
${parts.join("\n\n")}
|
|
113
|
+
</previous_steps>`;
|
|
114
|
+
if (estimateTokens(context) > effectiveMaxTokens) {
|
|
115
|
+
context = this.truncateToTokenLimit(context, effectiveMaxTokens);
|
|
116
|
+
}
|
|
117
|
+
return context;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Enrich a base system prompt with step context from the run.
|
|
121
|
+
* Fetches the run from DB to access policy settings (maxContextTokens, etc.).
|
|
122
|
+
*
|
|
123
|
+
* @param basePrompt - The original system prompt to enrich
|
|
124
|
+
* @param runId - agentLoopRuns.id
|
|
125
|
+
* @param _stepId - agentLoopSteps.id (reserved for future per-step context)
|
|
126
|
+
*/
|
|
127
|
+
async enrichSystemPrompt(basePrompt, runId, _stepId, options) {
|
|
128
|
+
let run;
|
|
129
|
+
try {
|
|
130
|
+
const repo = this.db.getRepository("agentLoopRuns");
|
|
131
|
+
if (!repo) return basePrompt;
|
|
132
|
+
run = await repo.findOne({ filter: { id: runId } });
|
|
133
|
+
if (!run) return basePrompt;
|
|
134
|
+
} catch {
|
|
135
|
+
return basePrompt;
|
|
136
|
+
}
|
|
137
|
+
const policy = run.policy || {};
|
|
138
|
+
const maxCtxTokens = (options == null ? void 0 : options.maxContextTokens) ?? policy.maxContextTokens ?? 4e3;
|
|
139
|
+
const strategy = (options == null ? void 0 : options.contextSummaryStrategy) ?? policy.contextSummaryStrategy ?? "all";
|
|
140
|
+
const includeToolResults = (options == null ? void 0 : options.includeToolResults) ?? policy.includeToolResults ?? false;
|
|
141
|
+
const includeStepOutputs = (options == null ? void 0 : options.includeStepOutputs) ?? policy.includeStepOutputs ?? true;
|
|
142
|
+
const stepContext = await this.buildStepContext(runId, maxCtxTokens, {
|
|
143
|
+
strategy,
|
|
144
|
+
includeToolResults,
|
|
145
|
+
includeStepOutputs
|
|
146
|
+
});
|
|
147
|
+
if (!stepContext) return basePrompt;
|
|
148
|
+
return `${basePrompt}
|
|
149
|
+
|
|
150
|
+
<previous_steps_context>
|
|
151
|
+
${stepContext}
|
|
152
|
+
</previous_steps_context>`;
|
|
153
|
+
}
|
|
154
|
+
truncateToTokenLimit(text, maxTokens) {
|
|
155
|
+
const outerMatch = text.match(/<previous_steps>\n([\s\S]*)\n<\/previous_steps>/);
|
|
156
|
+
if (!outerMatch) return text;
|
|
157
|
+
const inner = outerMatch[1];
|
|
158
|
+
const stepBlocks = this.splitStepBlocks(inner);
|
|
159
|
+
if (stepBlocks.length <= 2) {
|
|
160
|
+
const maxChars2 = maxTokens * 4;
|
|
161
|
+
if (text.length <= maxChars2) return text;
|
|
162
|
+
return text.slice(0, maxChars2) + "\n...[truncated]\n</previous_steps>";
|
|
163
|
+
}
|
|
164
|
+
const keepFirst = stepBlocks.slice(0, 2);
|
|
165
|
+
const keepLast = stepBlocks.slice(-2);
|
|
166
|
+
const removed = stepBlocks.length - keepFirst.length - keepLast.length;
|
|
167
|
+
const rebuilt = [
|
|
168
|
+
"<previous_steps>",
|
|
169
|
+
...keepFirst,
|
|
170
|
+
` <!-- ... ${removed} intermediate step(s) omitted due to context limit ... -->`,
|
|
171
|
+
...keepLast,
|
|
172
|
+
"</previous_steps>"
|
|
173
|
+
].join("\n");
|
|
174
|
+
const maxChars = maxTokens * 4;
|
|
175
|
+
if (rebuilt.length <= maxChars) return rebuilt;
|
|
176
|
+
return rebuilt.slice(0, maxChars) + "\n...[truncated]\n</previous_steps>";
|
|
177
|
+
}
|
|
178
|
+
splitStepBlocks(text) {
|
|
179
|
+
const blocks = [];
|
|
180
|
+
const regex = /<step[\s\S]*?<\/step>/g;
|
|
181
|
+
let match;
|
|
182
|
+
while ((match = regex.exec(text)) !== null) {
|
|
183
|
+
blocks.push(match[0]);
|
|
184
|
+
}
|
|
185
|
+
return blocks;
|
|
186
|
+
}
|
|
187
|
+
escapeXml(value) {
|
|
188
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
189
|
+
}
|
|
190
|
+
safeStringify(value) {
|
|
191
|
+
try {
|
|
192
|
+
return JSON.stringify(value, null, 2);
|
|
193
|
+
} catch {
|
|
194
|
+
return String(value);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
199
|
+
0 && (module.exports = {
|
|
200
|
+
ContextAggregator
|
|
201
|
+
});
|
|
@@ -32,11 +32,8 @@ __export(ExecutionSpanService_exports, {
|
|
|
32
32
|
setOrchestratorTraceContext: () => setOrchestratorTraceContext
|
|
33
33
|
});
|
|
34
34
|
module.exports = __toCommonJS(ExecutionSpanService_exports);
|
|
35
|
+
var import_ctx_utils = require("../utils/ctx-utils");
|
|
35
36
|
const ORCHESTRATOR_TRACE_CONTEXT_KEY = "__orchestratorTraceContext";
|
|
36
|
-
function toPlain(record) {
|
|
37
|
-
var _a;
|
|
38
|
-
return ((_a = record == null ? void 0 : record.toJSON) == null ? void 0 : _a.call(record)) || record;
|
|
39
|
-
}
|
|
40
37
|
class ExecutionSpanService {
|
|
41
38
|
constructor(plugin) {
|
|
42
39
|
this.plugin = plugin;
|
|
@@ -53,7 +50,7 @@ class ExecutionSpanService {
|
|
|
53
50
|
createdAt: /* @__PURE__ */ new Date()
|
|
54
51
|
}
|
|
55
52
|
});
|
|
56
|
-
return toPlain(record);
|
|
53
|
+
return (0, import_ctx_utils.toPlain)(record);
|
|
57
54
|
} catch (error) {
|
|
58
55
|
(_b = (_a = this.plugin.app.log) == null ? void 0 : _a.warn) == null ? void 0 : _b.call(_a, "[AgentOrchestrator] Failed to create execution span", error);
|
|
59
56
|
return null;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type EventCallback = (event: any) => void;
|
|
2
|
+
declare class RunEventBusImpl {
|
|
3
|
+
private listeners;
|
|
4
|
+
subscribe(runId: string | number, callback: EventCallback): () => void;
|
|
5
|
+
emit(runId: string | number, event: any): void;
|
|
6
|
+
listenerCount(runId: string | number): number;
|
|
7
|
+
}
|
|
8
|
+
export declare function getRunEventBus(): RunEventBusImpl;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
|
+
var RunEventBus_exports = {};
|
|
28
|
+
__export(RunEventBus_exports, {
|
|
29
|
+
getRunEventBus: () => getRunEventBus
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(RunEventBus_exports);
|
|
32
|
+
class RunEventBusImpl {
|
|
33
|
+
listeners = /* @__PURE__ */ new Map();
|
|
34
|
+
subscribe(runId, callback) {
|
|
35
|
+
let set = this.listeners.get(runId);
|
|
36
|
+
if (!set) {
|
|
37
|
+
set = /* @__PURE__ */ new Set();
|
|
38
|
+
this.listeners.set(runId, set);
|
|
39
|
+
}
|
|
40
|
+
set.add(callback);
|
|
41
|
+
return () => {
|
|
42
|
+
set == null ? void 0 : set.delete(callback);
|
|
43
|
+
if ((set == null ? void 0 : set.size) === 0) {
|
|
44
|
+
this.listeners.delete(runId);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
emit(runId, event) {
|
|
49
|
+
const set = this.listeners.get(runId);
|
|
50
|
+
if (!set) return;
|
|
51
|
+
for (const callback of set) {
|
|
52
|
+
try {
|
|
53
|
+
callback(event);
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
listenerCount(runId) {
|
|
59
|
+
var _a;
|
|
60
|
+
return ((_a = this.listeners.get(runId)) == null ? void 0 : _a.size) || 0;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
let instance = null;
|
|
64
|
+
function getRunEventBus() {
|
|
65
|
+
if (!instance) {
|
|
66
|
+
instance = new RunEventBusImpl();
|
|
67
|
+
}
|
|
68
|
+
return instance;
|
|
69
|
+
}
|
|
70
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
71
|
+
0 && (module.exports = {
|
|
72
|
+
getRunEventBus
|
|
73
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export interface TokenUsage {
|
|
2
|
+
inputTokens: number;
|
|
3
|
+
outputTokens: number;
|
|
4
|
+
totalTokens: number;
|
|
5
|
+
cost: number;
|
|
6
|
+
}
|
|
7
|
+
export interface BudgetConfig {
|
|
8
|
+
budgetMaxTokens?: number;
|
|
9
|
+
budgetMaxCost?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface BudgetCheckResult {
|
|
12
|
+
allowed: boolean;
|
|
13
|
+
reason?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Extract usage_metadata from the final state returned by createReactAgent.invoke().
|
|
17
|
+
*
|
|
18
|
+
* LangChain's createReactAgent returns a final state object with a `messages` array.
|
|
19
|
+
* The last AIMessage in that array carries `usage_metadata` populated by the
|
|
20
|
+
* LLM provider after the final generation step. This is the standard LangChain
|
|
21
|
+
* approach — no private API access needed.
|
|
22
|
+
*
|
|
23
|
+
* Expected shape (from @langchain/core/messages/ai):
|
|
24
|
+
* AIMessage.usage_metadata = {
|
|
25
|
+
* input_tokens: number,
|
|
26
|
+
* output_tokens: number,
|
|
27
|
+
* total_tokens: number,
|
|
28
|
+
* }
|
|
29
|
+
*/
|
|
30
|
+
export declare function extractTokenUsage(finalState: any): TokenUsage | null;
|
|
31
|
+
/**
|
|
32
|
+
* Service for tracking token consumption across agent runs and spans.
|
|
33
|
+
*
|
|
34
|
+
* Responsibilities:
|
|
35
|
+
* 1. Parse token usage from LangGraph execution results
|
|
36
|
+
* 2. Persist token counts to agentExecutionSpans
|
|
37
|
+
* 3. Accumulate totals at the agentLoopRuns level
|
|
38
|
+
* 4. Enforce budget limits (max tokens / max cost per run)
|
|
39
|
+
*/
|
|
40
|
+
export declare class TokenTracker {
|
|
41
|
+
private readonly plugin;
|
|
42
|
+
constructor(plugin: any);
|
|
43
|
+
get db(): any;
|
|
44
|
+
get app(): any;
|
|
45
|
+
/**
|
|
46
|
+
* Track token usage for a single execution span.
|
|
47
|
+
* Updates the span record and accumulates into the parent run.
|
|
48
|
+
*
|
|
49
|
+
* @param spanId - The agentExecutionSpans.id to update
|
|
50
|
+
* @param usage - Parsed token usage from extractTokenUsage()
|
|
51
|
+
* @param runId - Optional agentLoopRuns.id to accumulate totals
|
|
52
|
+
*/
|
|
53
|
+
trackSpan(spanId: string | number | undefined, usage: TokenUsage, runId?: string | number): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Recalculate total token/cost for a run by summing all its spans.
|
|
56
|
+
*/
|
|
57
|
+
accumulateToRun(runId: string | number): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Check if a run has exceeded its budget limits.
|
|
60
|
+
*/
|
|
61
|
+
checkBudget(runId: string | number): Promise<BudgetCheckResult>;
|
|
62
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
|
+
var TokenTracker_exports = {};
|
|
28
|
+
__export(TokenTracker_exports, {
|
|
29
|
+
TokenTracker: () => TokenTracker,
|
|
30
|
+
extractTokenUsage: () => extractTokenUsage
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(TokenTracker_exports);
|
|
33
|
+
const PRICE_PER_1K_INPUT = Number(process.env.ORCHESTRATOR_PRICE_PER_1K_INPUT || 3e-3);
|
|
34
|
+
const PRICE_PER_1K_OUTPUT = Number(process.env.ORCHESTRATOR_PRICE_PER_1K_OUTPUT || 0.015);
|
|
35
|
+
function estimateCost(inputTokens, outputTokens) {
|
|
36
|
+
return inputTokens / 1e3 * PRICE_PER_1K_INPUT + outputTokens / 1e3 * PRICE_PER_1K_OUTPUT;
|
|
37
|
+
}
|
|
38
|
+
function extractTokenUsage(finalState) {
|
|
39
|
+
if (!(finalState == null ? void 0 : finalState.messages) || !Array.isArray(finalState.messages)) return null;
|
|
40
|
+
let totalInput = 0;
|
|
41
|
+
let totalOutput = 0;
|
|
42
|
+
let totalAll = 0;
|
|
43
|
+
for (const msg of finalState.messages) {
|
|
44
|
+
if (msg == null ? void 0 : msg.usage_metadata) {
|
|
45
|
+
const um = msg.usage_metadata;
|
|
46
|
+
totalInput += um.input_tokens || 0;
|
|
47
|
+
totalOutput += um.output_tokens || 0;
|
|
48
|
+
totalAll += um.total_tokens || 0;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (totalAll === 0) return null;
|
|
52
|
+
return {
|
|
53
|
+
inputTokens: totalInput,
|
|
54
|
+
outputTokens: totalOutput,
|
|
55
|
+
totalTokens: totalAll,
|
|
56
|
+
cost: estimateCost(totalInput, totalOutput)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
class TokenTracker {
|
|
60
|
+
constructor(plugin) {
|
|
61
|
+
this.plugin = plugin;
|
|
62
|
+
}
|
|
63
|
+
get db() {
|
|
64
|
+
return this.plugin.db;
|
|
65
|
+
}
|
|
66
|
+
get app() {
|
|
67
|
+
return this.plugin.app;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Track token usage for a single execution span.
|
|
71
|
+
* Updates the span record and accumulates into the parent run.
|
|
72
|
+
*
|
|
73
|
+
* @param spanId - The agentExecutionSpans.id to update
|
|
74
|
+
* @param usage - Parsed token usage from extractTokenUsage()
|
|
75
|
+
* @param runId - Optional agentLoopRuns.id to accumulate totals
|
|
76
|
+
*/
|
|
77
|
+
async trackSpan(spanId, usage, runId) {
|
|
78
|
+
var _a, _b;
|
|
79
|
+
if (!spanId) return;
|
|
80
|
+
try {
|
|
81
|
+
const repo = this.db.getRepository("agentExecutionSpans");
|
|
82
|
+
if (!repo) return;
|
|
83
|
+
await repo.update({
|
|
84
|
+
filterByTk: spanId,
|
|
85
|
+
values: {
|
|
86
|
+
inputTokens: usage.inputTokens,
|
|
87
|
+
outputTokens: usage.outputTokens,
|
|
88
|
+
totalTokens: usage.totalTokens,
|
|
89
|
+
cost: usage.cost
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
if (runId != null) {
|
|
93
|
+
await this.accumulateToRun(runId);
|
|
94
|
+
}
|
|
95
|
+
} catch (e) {
|
|
96
|
+
(_b = (_a = this.app.log) == null ? void 0 : _a.warn) == null ? void 0 : _b.call(_a, "[TokenTracker] Failed to track span tokens", e);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Recalculate total token/cost for a run by summing all its spans.
|
|
101
|
+
*/
|
|
102
|
+
async accumulateToRun(runId) {
|
|
103
|
+
var _a, _b, _c, _d, _e;
|
|
104
|
+
try {
|
|
105
|
+
const spansRepo = this.db.getRepository("agentExecutionSpans");
|
|
106
|
+
if (!spansRepo) return;
|
|
107
|
+
const spans = await spansRepo.find({
|
|
108
|
+
filter: { "metadata.agentLoopRunId": String(runId) }
|
|
109
|
+
});
|
|
110
|
+
let totalInput = 0;
|
|
111
|
+
let totalOutput = 0;
|
|
112
|
+
let totalCost = 0;
|
|
113
|
+
for (const span of spans) {
|
|
114
|
+
totalInput += Number(((_a = span.get) == null ? void 0 : _a.call(span, "inputTokens")) || span.inputTokens || 0);
|
|
115
|
+
totalOutput += Number(((_b = span.get) == null ? void 0 : _b.call(span, "outputTokens")) || span.outputTokens || 0);
|
|
116
|
+
totalCost += Number(((_c = span.get) == null ? void 0 : _c.call(span, "cost")) || span.cost || 0);
|
|
117
|
+
}
|
|
118
|
+
const runsRepo = this.db.getRepository("agentLoopRuns");
|
|
119
|
+
if (!runsRepo) return;
|
|
120
|
+
await runsRepo.update({
|
|
121
|
+
filterByTk: runId,
|
|
122
|
+
values: {
|
|
123
|
+
totalInputTokens: totalInput,
|
|
124
|
+
totalOutputTokens: totalOutput,
|
|
125
|
+
totalTokens: totalInput + totalOutput,
|
|
126
|
+
totalCost
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
} catch (e) {
|
|
130
|
+
(_e = (_d = this.app.log) == null ? void 0 : _d.warn) == null ? void 0 : _e.call(_d, "[TokenTracker] Failed to accumulate run totals", e);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Check if a run has exceeded its budget limits.
|
|
135
|
+
*/
|
|
136
|
+
async checkBudget(runId) {
|
|
137
|
+
var _a, _b, _c, _d, _e, _f;
|
|
138
|
+
try {
|
|
139
|
+
const repo = this.db.getRepository("agentLoopRuns");
|
|
140
|
+
if (!repo) return { allowed: true };
|
|
141
|
+
const run = await repo.findOne({ filter: { id: runId } });
|
|
142
|
+
if (!run) return { allowed: true };
|
|
143
|
+
const budgetMaxTokens = Number(((_a = run.get) == null ? void 0 : _a.call(run, "budgetMaxTokens")) ?? 0);
|
|
144
|
+
const budgetMaxCost = Number(((_b = run.get) == null ? void 0 : _b.call(run, "budgetMaxCost")) ?? 0);
|
|
145
|
+
if (budgetMaxTokens <= 0 && budgetMaxCost <= 0) return { allowed: true };
|
|
146
|
+
const totalTokens = Number(((_c = run.get) == null ? void 0 : _c.call(run, "totalTokens")) || 0);
|
|
147
|
+
const totalCost = Number(((_d = run.get) == null ? void 0 : _d.call(run, "totalCost")) || 0);
|
|
148
|
+
if (budgetMaxTokens > 0 && totalTokens >= budgetMaxTokens) {
|
|
149
|
+
return {
|
|
150
|
+
allowed: false,
|
|
151
|
+
reason: `Budget exceeded: ${totalTokens}/${budgetMaxTokens} tokens used. Maximum allowed tokens for this run: ${budgetMaxTokens}.`
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (budgetMaxCost > 0 && totalCost >= budgetMaxCost) {
|
|
155
|
+
return {
|
|
156
|
+
allowed: false,
|
|
157
|
+
reason: `Budget exceeded: $${totalCost.toFixed(4)}/$${budgetMaxCost.toFixed(
|
|
158
|
+
4
|
|
159
|
+
)} spent. Maximum allowed cost for this run: $${budgetMaxCost}.`
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return { allowed: true };
|
|
163
|
+
} catch (e) {
|
|
164
|
+
(_f = (_e = this.app.log) == null ? void 0 : _e.warn) == null ? void 0 : _f.call(_e, "[TokenTracker] Budget check failed, allowing", e);
|
|
165
|
+
return { allowed: true };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
170
|
+
0 && (module.exports = {
|
|
171
|
+
TokenTracker,
|
|
172
|
+
extractTokenUsage
|
|
173
|
+
});
|
|
@@ -56,10 +56,10 @@ export declare function createAgentLoopTools(plugin: any, service: AgentLoopServ
|
|
|
56
56
|
description?: string;
|
|
57
57
|
metadata?: any;
|
|
58
58
|
input?: any;
|
|
59
|
+
id?: string;
|
|
59
60
|
planKey?: string;
|
|
60
61
|
target?: string;
|
|
61
62
|
dependsOn?: string[];
|
|
62
|
-
id?: string;
|
|
63
63
|
parentStepId?: string | number;
|
|
64
64
|
maxAttempts?: number;
|
|
65
65
|
}, {
|
|
@@ -69,10 +69,10 @@ export declare function createAgentLoopTools(plugin: any, service: AgentLoopServ
|
|
|
69
69
|
description?: string;
|
|
70
70
|
metadata?: any;
|
|
71
71
|
input?: any;
|
|
72
|
+
id?: string;
|
|
72
73
|
planKey?: string;
|
|
73
74
|
target?: string;
|
|
74
75
|
dependsOn?: string[];
|
|
75
|
-
id?: string;
|
|
76
76
|
parentStepId?: string | number;
|
|
77
77
|
maxAttempts?: number;
|
|
78
78
|
}>, "many">>;
|
|
@@ -96,10 +96,10 @@ export declare function createAgentLoopTools(plugin: any, service: AgentLoopServ
|
|
|
96
96
|
description?: string;
|
|
97
97
|
metadata?: any;
|
|
98
98
|
input?: any;
|
|
99
|
+
id?: string;
|
|
99
100
|
planKey?: string;
|
|
100
101
|
target?: string;
|
|
101
102
|
dependsOn?: string[];
|
|
102
|
-
id?: string;
|
|
103
103
|
parentStepId?: string | number;
|
|
104
104
|
maxAttempts?: number;
|
|
105
105
|
}[];
|
|
@@ -123,10 +123,10 @@ export declare function createAgentLoopTools(plugin: any, service: AgentLoopServ
|
|
|
123
123
|
description?: string;
|
|
124
124
|
metadata?: any;
|
|
125
125
|
input?: any;
|
|
126
|
+
id?: string;
|
|
126
127
|
planKey?: string;
|
|
127
128
|
target?: string;
|
|
128
129
|
dependsOn?: string[];
|
|
129
|
-
id?: string;
|
|
130
130
|
parentStepId?: string | number;
|
|
131
131
|
maxAttempts?: number;
|
|
132
132
|
}[];
|
|
@@ -181,20 +181,20 @@ export declare function createAgentLoopTools(plugin: any, service: AgentLoopServ
|
|
|
181
181
|
metadata: z.ZodOptional<z.ZodAny>;
|
|
182
182
|
}, "strip", z.ZodTypeAny, {
|
|
183
183
|
error?: string;
|
|
184
|
-
status?: "
|
|
184
|
+
status?: "succeeded" | "failed" | "running" | "skipped";
|
|
185
|
+
reason?: string;
|
|
185
186
|
metadata?: any;
|
|
186
187
|
output?: any;
|
|
187
188
|
skillExecutionId?: string | number;
|
|
188
|
-
reason?: string;
|
|
189
189
|
stepId?: string | number;
|
|
190
190
|
agentExecutionSpanId?: string | number;
|
|
191
191
|
}, {
|
|
192
192
|
error?: string;
|
|
193
|
-
status?: "
|
|
193
|
+
status?: "succeeded" | "failed" | "running" | "skipped";
|
|
194
|
+
reason?: string;
|
|
194
195
|
metadata?: any;
|
|
195
196
|
output?: any;
|
|
196
197
|
skillExecutionId?: string | number;
|
|
197
|
-
reason?: string;
|
|
198
198
|
stepId?: string | number;
|
|
199
199
|
agentExecutionSpanId?: string | number;
|
|
200
200
|
}>;
|