@adminforth/agent 1.0.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/.woodpecker/buildRelease.sh +13 -0
- package/.woodpecker/buildSlackNotify.sh +46 -0
- package/.woodpecker/release.yml +57 -0
- package/agent/middleware/apiBasedTools.ts +109 -0
- package/agent/middleware/sequenceDebug.ts +302 -0
- package/agent/simpleAgent.ts +291 -0
- package/agent/skills/registry.ts +135 -0
- package/agent/systemPrompt.ts +69 -0
- package/agent/toolCallEvents.ts +17 -0
- package/agent/tools/apiTool.ts +99 -0
- package/agent/tools/fetchSkill.ts +58 -0
- package/agent/tools/fetchToolSchema.ts +50 -0
- package/agent/tools/index.ts +26 -0
- package/apiBasedTools.ts +625 -0
- package/build.log +30 -0
- package/custom/ChatSurface.vue +184 -0
- package/custom/ConversationArea.vue +175 -0
- package/custom/Message.vue +206 -0
- package/custom/SessionsHistory.vue +93 -0
- package/custom/ToolRenderer.vue +131 -0
- package/custom/ToolsGroup.vue +67 -0
- package/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +301 -0
- package/custom/incremark_code_renderers/incremarkCodeHighlight.ts +285 -0
- package/custom/incremark_code_renderers/incremarkRenderer.ts +653 -0
- package/custom/incremark_code_renderers/renderIncremarkMarkdown.ts +118 -0
- package/custom/package.json +26 -0
- package/custom/pnpm-lock.yaml +1467 -0
- package/custom/skills/fetch_data/SKILL.md +15 -0
- package/custom/skills/mutate_data/SKILL.md +108 -0
- package/custom/tsconfig.json +16 -0
- package/custom/types.ts +34 -0
- package/custom/useAgentStore.ts +349 -0
- package/dist/agent/middleware/apiBasedTools.js +91 -0
- package/dist/agent/middleware/sequenceDebug.js +210 -0
- package/dist/agent/simpleAgent.js +173 -0
- package/dist/agent/skills/registry.js +108 -0
- package/dist/agent/systemPrompt.js +64 -0
- package/dist/agent/toolCallEvents.js +1 -0
- package/dist/agent/tools/apiTool.js +93 -0
- package/dist/agent/tools/fetchSkill.js +51 -0
- package/dist/agent/tools/fetchToolSchema.js +36 -0
- package/dist/agent/tools/index.js +28 -0
- package/dist/apiBasedTools.js +412 -0
- package/dist/custom/ChatSurface.vue +184 -0
- package/dist/custom/ConversationArea.vue +175 -0
- package/dist/custom/Message.vue +206 -0
- package/dist/custom/SessionsHistory.vue +93 -0
- package/dist/custom/ToolRenderer.vue +131 -0
- package/dist/custom/ToolsGroup.vue +67 -0
- package/dist/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +301 -0
- package/dist/custom/incremark_code_renderers/incremarkCodeHighlight.ts +285 -0
- package/dist/custom/incremark_code_renderers/incremarkRenderer.ts +653 -0
- package/dist/custom/incremark_code_renderers/renderIncremarkMarkdown.ts +118 -0
- package/dist/custom/package.json +26 -0
- package/dist/custom/pnpm-lock.yaml +1467 -0
- package/dist/custom/skills/fetch_data/SKILL.md +15 -0
- package/dist/custom/skills/mutate_data/SKILL.md +108 -0
- package/dist/custom/tsconfig.json +16 -0
- package/dist/custom/types.ts +34 -0
- package/dist/custom/useAgentStore.ts +349 -0
- package/dist/index.js +415 -0
- package/dist/types.js +1 -0
- package/index.ts +457 -0
- package/package.json +58 -0
- package/tsconfig.json +13 -0
- package/types.ts +45 -0
package/index.ts
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AdminForthResource,
|
|
3
|
+
AdminUser,
|
|
4
|
+
IAdminForth,
|
|
5
|
+
IHttpServer
|
|
6
|
+
} from "adminforth";
|
|
7
|
+
|
|
8
|
+
import { AdminForthPlugin, logger, Filters, Sorts } from "adminforth";
|
|
9
|
+
|
|
10
|
+
import type { PluginOptions } from './types.js';
|
|
11
|
+
import { randomUUID } from 'crypto';
|
|
12
|
+
import { HumanMessage, SystemMessage } from "langchain";
|
|
13
|
+
import { createAgentChatModel, callAgent } from "./agent/simpleAgent.js";
|
|
14
|
+
import { createSequenceDebugCollector } from "./agent/middleware/sequenceDebug.js";
|
|
15
|
+
import {
|
|
16
|
+
prepareApiBasedTools as buildApiBasedTools,
|
|
17
|
+
} from './apiBasedTools.js';
|
|
18
|
+
import type { ApiBasedTool } from './apiBasedTools.js';
|
|
19
|
+
import {
|
|
20
|
+
buildAgentSystemPrompt,
|
|
21
|
+
DEFAULT_AGENT_SYSTEM_PROMPT,
|
|
22
|
+
} from "./agent/systemPrompt.js";
|
|
23
|
+
import { ALWAYS_AVAILABLE_API_TOOL_NAMES } from "./agent/tools/index.js";
|
|
24
|
+
import type { ToolCallEvent } from "./agent/toolCallEvents.js";
|
|
25
|
+
|
|
26
|
+
function isAggregateErrorLike(
|
|
27
|
+
error: unknown,
|
|
28
|
+
): error is { errors: unknown[]; message?: string; stack?: string } {
|
|
29
|
+
return typeof error === "object" && error !== null && Array.isArray((error as { errors?: unknown[] }).errors);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function formatAgentError(error: unknown) {
|
|
33
|
+
if (isAggregateErrorLike(error)) {
|
|
34
|
+
const nestedErrors = error.errors
|
|
35
|
+
.map((nestedError, index) => {
|
|
36
|
+
if (nestedError instanceof Error) {
|
|
37
|
+
return `${index + 1}. ${nestedError.stack ?? nestedError.message}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return `${index + 1}. ${String(nestedError)}`;
|
|
41
|
+
})
|
|
42
|
+
.join("\n");
|
|
43
|
+
|
|
44
|
+
return `${error.stack ?? error.message}\nNested errors:\n${nestedErrors}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (error instanceof Error) {
|
|
48
|
+
return error.stack ?? error.message;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return String(error);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function assertRequiredApiTool(
|
|
55
|
+
apiBasedTools: Record<string, ApiBasedTool>,
|
|
56
|
+
toolName: string,
|
|
57
|
+
) {
|
|
58
|
+
if (toolName in apiBasedTools) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const availableToolNames = Object.keys(apiBasedTools).sort().join(", ");
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Required API tool "${toolName}" is missing from AdminForth Agent tools. Available tools: ${availableToolNames}`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
69
|
+
options: PluginOptions;
|
|
70
|
+
apiBasedTools: Record<string, ApiBasedTool> = {};
|
|
71
|
+
agentSystemPromptPromise = Promise.resolve(DEFAULT_AGENT_SYSTEM_PROMPT);
|
|
72
|
+
|
|
73
|
+
private async createNewTurn(sessionId: string, prompt: string, response?: string) {
|
|
74
|
+
const turnId = randomUUID();
|
|
75
|
+
const turnRecord = {
|
|
76
|
+
[this.options.turnResource.idField]: turnId,
|
|
77
|
+
[this.options.turnResource.sessionIdField]: sessionId,
|
|
78
|
+
[this.options.turnResource.promptField]: prompt,
|
|
79
|
+
[this.options.turnResource.responseField]: response || "not_finished",
|
|
80
|
+
};
|
|
81
|
+
const newTurn = await this.adminforth.resource(this.options.turnResource.resourceId).create(turnRecord);
|
|
82
|
+
return newTurn.createdRecord[this.options.turnResource.idField];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private async updateTurn(turnId: string, updates: Record<string, unknown>) {
|
|
86
|
+
await this.adminforth.resource(this.options.turnResource.resourceId).update(turnId, updates);
|
|
87
|
+
return {ok: true};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private async getSessionTurns(sessionId: string) {
|
|
91
|
+
const turns = await this.adminforth.resource(this.options.turnResource.resourceId).list(
|
|
92
|
+
[Filters.EQ(this.options.turnResource.sessionIdField, sessionId)],
|
|
93
|
+
undefined,
|
|
94
|
+
undefined,
|
|
95
|
+
[Sorts.ASC(this.options.turnResource.createdAtField)]
|
|
96
|
+
);
|
|
97
|
+
return turns.map(turn => ({
|
|
98
|
+
prompt: turn[this.options.turnResource.promptField],
|
|
99
|
+
response: turn[this.options.turnResource.responseField],
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
constructor(options: PluginOptions) {
|
|
104
|
+
super(options, import.meta.url);
|
|
105
|
+
this.options = options;
|
|
106
|
+
this.shouldHaveSingleInstancePerWholeApp = () => false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
|
|
110
|
+
super.modifyResourceConfig(adminforth, resourceConfig);
|
|
111
|
+
if (!this.adminforth.config.customization.globalInjections.header) {
|
|
112
|
+
this.adminforth.config.customization.globalInjections.header = [];
|
|
113
|
+
}
|
|
114
|
+
this.adminforth.config.customization.globalInjections.header.push({
|
|
115
|
+
file: this.componentPath("ChatSurface.vue"),
|
|
116
|
+
meta: {
|
|
117
|
+
pluginInstanceId: this.pluginInstanceId,
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (!this.pluginOptions.completionAdapter) {
|
|
122
|
+
throw new Error("CompletionAdapter is required for AdminForthAgentPlugin");
|
|
123
|
+
}
|
|
124
|
+
if (!this.pluginOptions.sessionResource) {
|
|
125
|
+
throw new Error("sessionResource is required for AdminForthAgentPlugin");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
|
|
130
|
+
this.apiBasedTools = buildApiBasedTools(adminforth);
|
|
131
|
+
for (const toolName of ALWAYS_AVAILABLE_API_TOOL_NAMES) {
|
|
132
|
+
assertRequiredApiTool(this.apiBasedTools, toolName);
|
|
133
|
+
}
|
|
134
|
+
assertRequiredApiTool(this.apiBasedTools, "update_record");
|
|
135
|
+
this.agentSystemPromptPromise = buildAgentSystemPrompt(adminforth);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
instanceUniqueRepresentation(pluginOptions: any) : string {
|
|
139
|
+
return `single`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
setupEndpoints(server: IHttpServer) {
|
|
143
|
+
server.endpoint({
|
|
144
|
+
method: 'POST',
|
|
145
|
+
path: `/agent/response`,
|
|
146
|
+
handler: async ({ body, adminUser, _raw_express_res }) => {
|
|
147
|
+
const res = _raw_express_res;
|
|
148
|
+
const messageId = randomUUID();
|
|
149
|
+
const prompt = body.message;
|
|
150
|
+
const userTimeZone = (body.timeZone as string | undefined) ?? 'UTC';
|
|
151
|
+
const sessionId = body.sessionId || adminUser?.pk || adminUser?.username || 'default';
|
|
152
|
+
const turnId = await this.createNewTurn(sessionId, prompt);
|
|
153
|
+
let fullResponse = "";
|
|
154
|
+
let isStreamClosed = false;
|
|
155
|
+
const sequenceDebugCollector = createSequenceDebugCollector();
|
|
156
|
+
|
|
157
|
+
res.writeHead(200, {
|
|
158
|
+
'Content-Type': 'text/event-stream',
|
|
159
|
+
'Cache-Control': 'no-cache',
|
|
160
|
+
'Connection': 'keep-alive',
|
|
161
|
+
'x-vercel-ai-ui-message-stream': 'v1',
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const send = (obj: unknown) => {
|
|
165
|
+
if (isStreamClosed || res.writableEnded || res.destroyed) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
res.write(`data: ${JSON.stringify(obj)}\n\n`);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const emitToolCallEvent = (event: ToolCallEvent) => {
|
|
172
|
+
sequenceDebugCollector.handleToolCallEvent(event);
|
|
173
|
+
|
|
174
|
+
send({
|
|
175
|
+
type: "data-tool-call",
|
|
176
|
+
data: event,
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
let activeBlock: { type: 'text' | 'reasoning'; id: string } | null = null;
|
|
181
|
+
|
|
182
|
+
const endActiveBlock = () => {
|
|
183
|
+
if (!activeBlock) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
send({
|
|
188
|
+
type: `${activeBlock.type}-end`,
|
|
189
|
+
id: activeBlock.id,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
activeBlock = null;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const startBlock = (type: 'text' | 'reasoning') => {
|
|
196
|
+
if (activeBlock?.type === type) {
|
|
197
|
+
return activeBlock.id;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
endActiveBlock();
|
|
201
|
+
|
|
202
|
+
const id = randomUUID();
|
|
203
|
+
activeBlock = { type, id };
|
|
204
|
+
|
|
205
|
+
send({
|
|
206
|
+
type: `${type}-start`,
|
|
207
|
+
id,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
return id;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const endStream = () => {
|
|
214
|
+
if (isStreamClosed || res.writableEnded || res.destroyed) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
endActiveBlock();
|
|
218
|
+
|
|
219
|
+
send({
|
|
220
|
+
type: 'finish',
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
res.write(`data: [DONE]\n\n`);
|
|
224
|
+
isStreamClosed = true;
|
|
225
|
+
res.end();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
send({
|
|
230
|
+
type: 'start',
|
|
231
|
+
messageId,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const maxTokens = this.options.maxTokens ?? 10000;
|
|
235
|
+
const reasoning = this.options.reasoning ?? 'low';
|
|
236
|
+
const summaryReasoning = 'low';
|
|
237
|
+
|
|
238
|
+
const model = createAgentChatModel({
|
|
239
|
+
adapter: this.options.completionAdapter,
|
|
240
|
+
maxTokens,
|
|
241
|
+
reasoning,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const summaryModel = createAgentChatModel({
|
|
245
|
+
adapter: this.options.completionAdapter,
|
|
246
|
+
maxTokens,
|
|
247
|
+
reasoning: summaryReasoning,
|
|
248
|
+
});
|
|
249
|
+
const systemPrompt = await this.agentSystemPromptPromise;
|
|
250
|
+
const stream = await callAgent({
|
|
251
|
+
name: `adminforth-agent-${this.pluginInstanceId}`,
|
|
252
|
+
model,
|
|
253
|
+
summaryModel,
|
|
254
|
+
messages: [
|
|
255
|
+
new SystemMessage(systemPrompt),
|
|
256
|
+
new HumanMessage(prompt),
|
|
257
|
+
],
|
|
258
|
+
adminUser,
|
|
259
|
+
apiBasedTools: this.apiBasedTools,
|
|
260
|
+
customComponentsDir: this.adminforth.config.customization.customComponentsDir,
|
|
261
|
+
sessionId,
|
|
262
|
+
turnId,
|
|
263
|
+
userTimeZone,
|
|
264
|
+
emitToolCallEvent,
|
|
265
|
+
sequenceDebugSink: sequenceDebugCollector,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
for await (const rawChunk of stream as AsyncIterable<[any, any]>) {
|
|
269
|
+
const [token, metadata] = rawChunk;
|
|
270
|
+
|
|
271
|
+
const nodeName =
|
|
272
|
+
typeof metadata?.langgraph_node === "string"
|
|
273
|
+
? metadata.langgraph_node
|
|
274
|
+
: "";
|
|
275
|
+
|
|
276
|
+
if (nodeName && !["model", "model_request"].includes(nodeName)) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const blocks = Array.isArray(token?.contentBlocks)
|
|
281
|
+
? token.contentBlocks
|
|
282
|
+
: Array.isArray(token?.content)
|
|
283
|
+
? token.content
|
|
284
|
+
: [];
|
|
285
|
+
|
|
286
|
+
const reasoningDelta = blocks
|
|
287
|
+
.filter((b: any) => b?.type === "reasoning")
|
|
288
|
+
.map((b: any) => String(b.reasoning ?? ""))
|
|
289
|
+
.join("");
|
|
290
|
+
|
|
291
|
+
const textDelta = blocks
|
|
292
|
+
.filter((b: any) => b?.type === "text")
|
|
293
|
+
.map((b: any) => String(b.text ?? ""))
|
|
294
|
+
.join("");
|
|
295
|
+
|
|
296
|
+
if (reasoningDelta) {
|
|
297
|
+
const reasoningId = startBlock('reasoning');
|
|
298
|
+
send({
|
|
299
|
+
type: 'reasoning-delta',
|
|
300
|
+
id: reasoningId,
|
|
301
|
+
delta: reasoningDelta,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (textDelta) {
|
|
306
|
+
const textId = startBlock('text');
|
|
307
|
+
fullResponse += textDelta;
|
|
308
|
+
send({
|
|
309
|
+
type: 'text-delta',
|
|
310
|
+
id: textId,
|
|
311
|
+
delta: textDelta,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
} catch (error) {
|
|
316
|
+
logger.error(`Agent response streaming failed:\n${formatAgentError(error)}`);
|
|
317
|
+
sequenceDebugCollector.flush();
|
|
318
|
+
const textId = startBlock('text');
|
|
319
|
+
send({
|
|
320
|
+
type: 'text-delta',
|
|
321
|
+
id: textId,
|
|
322
|
+
delta: 'Agent response failed. Check server logs for details.',
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
sequenceDebugCollector.flush();
|
|
326
|
+
const turnUpdates: Record<string, unknown> = {
|
|
327
|
+
[this.options.turnResource.responseField]: fullResponse,
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
if (this.options.turnResource.debugField) {
|
|
331
|
+
turnUpdates[this.options.turnResource.debugField] = sequenceDebugCollector.getHistory();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
await this.updateTurn(turnId, turnUpdates);
|
|
335
|
+
endStream();
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
server.endpoint({
|
|
340
|
+
method: 'POST',
|
|
341
|
+
path: `/agent/get-sessions`,
|
|
342
|
+
handler: async ({body, adminUser }) => {
|
|
343
|
+
const userId = adminUser.pk;
|
|
344
|
+
const sessions = await this.adminforth.resource(this.pluginOptions.sessionResource.resourceId).list(
|
|
345
|
+
[Filters.EQ(this.pluginOptions.sessionResource.askerIdField, userId)], undefined, undefined, [Sorts.DESC(this.pluginOptions.sessionResource.createdAtField)]
|
|
346
|
+
);
|
|
347
|
+
const sessionsToReturn = [];
|
|
348
|
+
for (const session of sessions) {
|
|
349
|
+
sessionsToReturn.push({
|
|
350
|
+
sessionId: session[this.pluginOptions.sessionResource.idField],
|
|
351
|
+
title: session[this.pluginOptions.sessionResource.titleField],
|
|
352
|
+
timestamp: session[this.pluginOptions.sessionResource.createdAtField],
|
|
353
|
+
})
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
sessions: sessionsToReturn
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
server.endpoint({
|
|
361
|
+
method: 'POST',
|
|
362
|
+
path: `/agent/get-session-info`,
|
|
363
|
+
handler: async ({body, adminUser }) => {
|
|
364
|
+
const userId = adminUser.pk;
|
|
365
|
+
const sessionId = body.sessionId;
|
|
366
|
+
const session = await this.adminforth.resource(this.pluginOptions.sessionResource.resourceId).get(
|
|
367
|
+
[Filters.EQ(this.pluginOptions.sessionResource.idField, sessionId)]
|
|
368
|
+
);
|
|
369
|
+
if (!session) {
|
|
370
|
+
return {
|
|
371
|
+
error: 'Session not found'
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
if (session[this.pluginOptions.sessionResource.askerIdField] !== userId) {
|
|
375
|
+
return {
|
|
376
|
+
error: 'Unauthorized'
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
const turns = await this.getSessionTurns(sessionId);
|
|
380
|
+
const messagesToReturn = [];
|
|
381
|
+
for (const turn of turns) {
|
|
382
|
+
messagesToReturn.push({
|
|
383
|
+
text: turn.prompt,
|
|
384
|
+
role: 'user',
|
|
385
|
+
});
|
|
386
|
+
if (turn.response !== "not_finished") {
|
|
387
|
+
messagesToReturn.push({
|
|
388
|
+
text: turn.response,
|
|
389
|
+
role: 'assistant',
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const sessionToReturn = {
|
|
394
|
+
sessionId: session[this.pluginOptions.sessionResource.idField],
|
|
395
|
+
title: session[this.pluginOptions.sessionResource.titleField],
|
|
396
|
+
timestamp: session[this.pluginOptions.sessionResource.createdAtField],
|
|
397
|
+
messages: messagesToReturn
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
session: sessionToReturn
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
server.endpoint({
|
|
405
|
+
method: 'POST',
|
|
406
|
+
path: `/agent/create-session`,
|
|
407
|
+
handler: async ({body, adminUser }) => {
|
|
408
|
+
const triggerMessage = body.triggerMessage;
|
|
409
|
+
const userId = adminUser.pk;
|
|
410
|
+
const title = triggerMessage ? (triggerMessage.length > 40 ? triggerMessage.slice(0, 40) + '...' : triggerMessage) : 'New Session';
|
|
411
|
+
const newSession = {
|
|
412
|
+
[this.pluginOptions.sessionResource.idField]: randomUUID(),
|
|
413
|
+
[this.pluginOptions.sessionResource.titleField]: title,
|
|
414
|
+
[this.pluginOptions.sessionResource.askerIdField]: userId,
|
|
415
|
+
};
|
|
416
|
+
await this.adminforth.resource(this.pluginOptions.sessionResource.resourceId).create(newSession);
|
|
417
|
+
return {
|
|
418
|
+
sessionId: newSession[this.pluginOptions.sessionResource.idField],
|
|
419
|
+
title: newSession[this.pluginOptions.sessionResource.titleField],
|
|
420
|
+
timestamp: newSession[this.pluginOptions.sessionResource.createdAtField],
|
|
421
|
+
messages: []
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
server.endpoint({
|
|
426
|
+
method: 'POST',
|
|
427
|
+
path: `/agent/delete-session`,
|
|
428
|
+
handler: async ({body, adminUser }) => {
|
|
429
|
+
const sessionId = body.sessionId;
|
|
430
|
+
const userId = adminUser.pk;
|
|
431
|
+
const session = await this.adminforth.resource(this.pluginOptions.sessionResource.resourceId).get(
|
|
432
|
+
[Filters.EQ(this.pluginOptions.sessionResource.idField, sessionId)]
|
|
433
|
+
);
|
|
434
|
+
if (!session) {
|
|
435
|
+
return {
|
|
436
|
+
error: 'Session not found'
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
if (session[this.pluginOptions.sessionResource.askerIdField] !== userId) {
|
|
440
|
+
return {
|
|
441
|
+
error: 'Unauthorized'
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
await this.adminforth.resource(this.pluginOptions.sessionResource.resourceId).delete(sessionId);
|
|
445
|
+
const turns = await this.adminforth.resource(this.pluginOptions.turnResource.resourceId).list(
|
|
446
|
+
[Filters.EQ(this.pluginOptions.turnResource.sessionIdField, sessionId)]
|
|
447
|
+
);
|
|
448
|
+
for (const turn of turns) {
|
|
449
|
+
await this.adminforth.resource(this.pluginOptions.turnResource.resourceId).delete(turn[this.pluginOptions.turnResource.idField]);
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
ok: true
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adminforth/agent",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc && rsync -av --exclude 'node_modules' custom dist/"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [],
|
|
14
|
+
"author": "",
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"description": "",
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "latest",
|
|
19
|
+
"semantic-release": "^24.2.1",
|
|
20
|
+
"semantic-release-slack-bot": "^4.0.2",
|
|
21
|
+
"typescript": "^5.7.3"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@langchain/core": "^1.1.40",
|
|
25
|
+
"@langchain/langgraph": "^1.2.8",
|
|
26
|
+
"@langchain/openai": "^1.4.4",
|
|
27
|
+
"adminforth": "2.27.0-next.47",
|
|
28
|
+
"dayjs": "^1.11.20",
|
|
29
|
+
"langchain": "^1.3.3",
|
|
30
|
+
"yaml": "^2.8.3",
|
|
31
|
+
"zod": "^4.3.6"
|
|
32
|
+
},
|
|
33
|
+
"release": {
|
|
34
|
+
"plugins": [
|
|
35
|
+
"@semantic-release/commit-analyzer",
|
|
36
|
+
"@semantic-release/release-notes-generator",
|
|
37
|
+
"@semantic-release/npm",
|
|
38
|
+
"@semantic-release/github",
|
|
39
|
+
[
|
|
40
|
+
"semantic-release-slack-bot",
|
|
41
|
+
{
|
|
42
|
+
"packageName": "@adminforth/agent",
|
|
43
|
+
"notifyOnSuccess": true,
|
|
44
|
+
"notifyOnFail": true,
|
|
45
|
+
"slackIcon": ":package:",
|
|
46
|
+
"markdownReleaseNotes": true
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
"branches": [
|
|
52
|
+
"main",
|
|
53
|
+
{
|
|
54
|
+
"name": "next",
|
|
55
|
+
"prerelease": true
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include*/
|
|
4
|
+
"module": "node16", /* Specify what module code is generated. */
|
|
5
|
+
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
|
6
|
+
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. */
|
|
7
|
+
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
|
8
|
+
"strict": false, /* Enable all strict type-checking options. */
|
|
9
|
+
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
|
|
10
|
+
},
|
|
11
|
+
"exclude": ["node_modules", "dist", "custom"], /* Exclude files from compilation. */
|
|
12
|
+
}
|
|
13
|
+
|
package/types.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {type PluginsCommonOptions } from "adminforth";
|
|
2
|
+
import { type CompletionAdapter } from "adminforth";
|
|
3
|
+
|
|
4
|
+
interface ISessionResource {
|
|
5
|
+
resourceId: string;
|
|
6
|
+
idField: string;
|
|
7
|
+
titleField: string;
|
|
8
|
+
turnsField: string;
|
|
9
|
+
askerIdField: string;
|
|
10
|
+
createdAtField: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ITurnResource {
|
|
14
|
+
resourceId: string;
|
|
15
|
+
idField: string;
|
|
16
|
+
sessionIdField: string;
|
|
17
|
+
createdAtField: string;
|
|
18
|
+
promptField: string;
|
|
19
|
+
responseField: string;
|
|
20
|
+
debugField?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface PluginOptions extends PluginsCommonOptions {
|
|
24
|
+
/**
|
|
25
|
+
* Adapter instance that will be used to generate responses.
|
|
26
|
+
* You can use any adapter that implements the CompletionAdapter interface, for example the OpenAIAdapter included in adminforth,
|
|
27
|
+
* or create your own that fetches responses from your custom backend.
|
|
28
|
+
*/
|
|
29
|
+
completionAdapter: CompletionAdapter;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Max tokens for the generation.
|
|
33
|
+
* Default is 1000
|
|
34
|
+
*/
|
|
35
|
+
maxTokens?: number;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Response generation level.
|
|
39
|
+
* Default is low
|
|
40
|
+
*/
|
|
41
|
+
reasoning?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
42
|
+
|
|
43
|
+
sessionResource: ISessionResource;
|
|
44
|
+
turnResource: ITurnResource;
|
|
45
|
+
}
|