@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.
Files changed (66) hide show
  1. package/.woodpecker/buildRelease.sh +13 -0
  2. package/.woodpecker/buildSlackNotify.sh +46 -0
  3. package/.woodpecker/release.yml +57 -0
  4. package/agent/middleware/apiBasedTools.ts +109 -0
  5. package/agent/middleware/sequenceDebug.ts +302 -0
  6. package/agent/simpleAgent.ts +291 -0
  7. package/agent/skills/registry.ts +135 -0
  8. package/agent/systemPrompt.ts +69 -0
  9. package/agent/toolCallEvents.ts +17 -0
  10. package/agent/tools/apiTool.ts +99 -0
  11. package/agent/tools/fetchSkill.ts +58 -0
  12. package/agent/tools/fetchToolSchema.ts +50 -0
  13. package/agent/tools/index.ts +26 -0
  14. package/apiBasedTools.ts +625 -0
  15. package/build.log +30 -0
  16. package/custom/ChatSurface.vue +184 -0
  17. package/custom/ConversationArea.vue +175 -0
  18. package/custom/Message.vue +206 -0
  19. package/custom/SessionsHistory.vue +93 -0
  20. package/custom/ToolRenderer.vue +131 -0
  21. package/custom/ToolsGroup.vue +67 -0
  22. package/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +301 -0
  23. package/custom/incremark_code_renderers/incremarkCodeHighlight.ts +285 -0
  24. package/custom/incremark_code_renderers/incremarkRenderer.ts +653 -0
  25. package/custom/incremark_code_renderers/renderIncremarkMarkdown.ts +118 -0
  26. package/custom/package.json +26 -0
  27. package/custom/pnpm-lock.yaml +1467 -0
  28. package/custom/skills/fetch_data/SKILL.md +15 -0
  29. package/custom/skills/mutate_data/SKILL.md +108 -0
  30. package/custom/tsconfig.json +16 -0
  31. package/custom/types.ts +34 -0
  32. package/custom/useAgentStore.ts +349 -0
  33. package/dist/agent/middleware/apiBasedTools.js +91 -0
  34. package/dist/agent/middleware/sequenceDebug.js +210 -0
  35. package/dist/agent/simpleAgent.js +173 -0
  36. package/dist/agent/skills/registry.js +108 -0
  37. package/dist/agent/systemPrompt.js +64 -0
  38. package/dist/agent/toolCallEvents.js +1 -0
  39. package/dist/agent/tools/apiTool.js +93 -0
  40. package/dist/agent/tools/fetchSkill.js +51 -0
  41. package/dist/agent/tools/fetchToolSchema.js +36 -0
  42. package/dist/agent/tools/index.js +28 -0
  43. package/dist/apiBasedTools.js +412 -0
  44. package/dist/custom/ChatSurface.vue +184 -0
  45. package/dist/custom/ConversationArea.vue +175 -0
  46. package/dist/custom/Message.vue +206 -0
  47. package/dist/custom/SessionsHistory.vue +93 -0
  48. package/dist/custom/ToolRenderer.vue +131 -0
  49. package/dist/custom/ToolsGroup.vue +67 -0
  50. package/dist/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +301 -0
  51. package/dist/custom/incremark_code_renderers/incremarkCodeHighlight.ts +285 -0
  52. package/dist/custom/incremark_code_renderers/incremarkRenderer.ts +653 -0
  53. package/dist/custom/incremark_code_renderers/renderIncremarkMarkdown.ts +118 -0
  54. package/dist/custom/package.json +26 -0
  55. package/dist/custom/pnpm-lock.yaml +1467 -0
  56. package/dist/custom/skills/fetch_data/SKILL.md +15 -0
  57. package/dist/custom/skills/mutate_data/SKILL.md +108 -0
  58. package/dist/custom/tsconfig.json +16 -0
  59. package/dist/custom/types.ts +34 -0
  60. package/dist/custom/useAgentStore.ts +349 -0
  61. package/dist/index.js +415 -0
  62. package/dist/types.js +1 -0
  63. package/index.ts +457 -0
  64. package/package.json +58 -0
  65. package/tsconfig.json +13 -0
  66. package/types.ts +45 -0
@@ -0,0 +1,13 @@
1
+
2
+ #!/bin/bash
3
+
4
+ # write npm run output both to console and to build.log
5
+ npm run build 2>&1 | tee build.log
6
+ build_status=${PIPESTATUS[0]}
7
+
8
+ # if exist status from the npm run build is not 0
9
+ # then exit with the status code from the npm run build
10
+ if [ $build_status -ne 0 ]; then
11
+ echo "Build failed. Exiting with status code $build_status"
12
+ exit $build_status
13
+ fi
@@ -0,0 +1,46 @@
1
+ #!/bin/sh
2
+
3
+ set -x
4
+
5
+ COMMIT_SHORT_SHA=$(echo $CI_COMMIT_SHA | cut -c1-8)
6
+
7
+ STATUS=${1}
8
+
9
+
10
+ if [ "$STATUS" = "success" ]; then
11
+ MESSAGE="Did a build without issues on \`$CI_REPO_NAME/$CI_COMMIT_BRANCH\`. Commit: _${CI_COMMIT_MESSAGE}_ (<$CI_COMMIT_URL|$COMMIT_SHORT_SHA>)"
12
+
13
+ curl -s -X POST -H "Content-Type: application/json" -d '{
14
+ "username": "'"$CI_COMMIT_AUTHOR"'",
15
+ "icon_url": "'"$CI_COMMIT_AUTHOR_AVATAR"'",
16
+ "attachments": [
17
+ {
18
+ "mrkdwn_in": ["text", "pretext"],
19
+ "color": "#36a64f",
20
+ "text": "'"$MESSAGE"'"
21
+ }
22
+ ]
23
+ }' "$DEVELOPERS_SLACK_WEBHOOK"
24
+ exit 0
25
+ fi
26
+ export BUILD_LOG=$(cat ./build.log)
27
+
28
+ BUILD_LOG=$(echo $BUILD_LOG | sed 's/"/\\"/g')
29
+
30
+ MESSAGE="Broke \`$CI_REPO_NAME/$CI_COMMIT_BRANCH\` with commit _${CI_COMMIT_MESSAGE}_ (<$CI_COMMIT_URL|$COMMIT_SHORT_SHA>)"
31
+ CODE_BLOCK="\`\`\`$BUILD_LOG\n\`\`\`"
32
+
33
+ echo "Sending slack message to developers $MESSAGE"
34
+ # Send the message
35
+ curl -sS -X POST -H "Content-Type: application/json" -d '{
36
+ "username": "'"$CI_COMMIT_AUTHOR"'",
37
+ "icon_url": "'"$CI_COMMIT_AUTHOR_AVATAR"'",
38
+ "attachments": [
39
+ {
40
+ "mrkdwn_in": ["text", "pretext"],
41
+ "color": "#8A1C12",
42
+ "text": "'"$CODE_BLOCK"'",
43
+ "pretext": "'"$MESSAGE"'"
44
+ }
45
+ ]
46
+ }' "$DEVELOPERS_SLACK_WEBHOOK" 2>&1
@@ -0,0 +1,57 @@
1
+ clone:
2
+ git:
3
+ image: woodpeckerci/plugin-git
4
+ settings:
5
+ partial: false
6
+ depth: 5
7
+
8
+ steps:
9
+ init-secrets:
10
+ when:
11
+ - event: push
12
+ image: infisical/cli
13
+ environment:
14
+ INFISICAL_TOKEN:
15
+ from_secret: VAULT_TOKEN
16
+ commands:
17
+ - infisical export --domain https://vault.devforth.io/api --format=dotenv-export --env="prod" > /woodpecker/deploy.vault.env
18
+
19
+ build:
20
+ image: devforth/node20-pnpm:latest
21
+ when:
22
+ - event: push
23
+ commands:
24
+ - apt update && apt install -y rsync
25
+ - . /woodpecker/deploy.vault.env
26
+ - pnpm install
27
+ - /bin/bash ./.woodpecker/buildRelease.sh
28
+ - npm audit signatures
29
+
30
+ release:
31
+ image: devforth/node20-pnpm:latest
32
+ when:
33
+ - event:
34
+ - push
35
+ branch:
36
+ - main
37
+ commands:
38
+ - . /woodpecker/deploy.vault.env
39
+ - pnpm exec semantic-release
40
+
41
+ slack-on-failure:
42
+ image: curlimages/curl
43
+ when:
44
+ - event: push
45
+ status: [failure]
46
+ commands:
47
+ - . /woodpecker/deploy.vault.env
48
+ - /bin/sh ./.woodpecker/buildSlackNotify.sh failure
49
+
50
+ slack-on-success:
51
+ image: curlimages/curl
52
+ when:
53
+ - event: push
54
+ status: [success]
55
+ commands:
56
+ - . /woodpecker/deploy.vault.env
57
+ - /bin/sh ./.woodpecker/buildSlackNotify.sh success
@@ -0,0 +1,109 @@
1
+ import { ToolMessage } from "@langchain/core/messages";
2
+ import { createMiddleware } from "langchain";
3
+ import { logger } from "adminforth";
4
+ import type { ApiBasedTool } from "../../apiBasedTools.js";
5
+ import { ALWAYS_AVAILABLE_API_TOOL_NAMES } from "../tools/index.js";
6
+ import { createApiTool } from "../tools/apiTool.js";
7
+
8
+ function getEnabledApiToolNames(messages: unknown[]) {
9
+ const enabledToolNames = new Set<string>();
10
+
11
+ for (const message of messages) {
12
+ if (!ToolMessage.isInstance(message) || message.name !== "fetch_tool_schema") {
13
+ continue;
14
+ }
15
+
16
+ const content =
17
+ typeof message.content === "string"
18
+ ? message.content
19
+ : Array.isArray(message.content)
20
+ ? message.content
21
+ .map((block) =>
22
+ typeof block === "string"
23
+ ? block
24
+ : "text" in block
25
+ ? block.text
26
+ : "",
27
+ )
28
+ .join("")
29
+ : "";
30
+
31
+ try {
32
+ const parsed = JSON.parse(content) as { status?: number; name?: string };
33
+
34
+ if (parsed.status === 200 && parsed.name) {
35
+ enabledToolNames.add(parsed.name);
36
+ }
37
+ } catch {}
38
+ }
39
+
40
+ return enabledToolNames;
41
+ }
42
+
43
+ export function createApiBasedToolsMiddleware(
44
+ apiBasedTools: Record<string, ApiBasedTool>,
45
+ ) {
46
+ const alwaysAvailableApiToolNames = new Set<string>(ALWAYS_AVAILABLE_API_TOOL_NAMES);
47
+ const dynamicTools = Object.fromEntries(
48
+ Object.entries(apiBasedTools).map(([toolName, apiBasedTool]) => [
49
+ toolName,
50
+ createApiTool(toolName, apiBasedTool),
51
+ ]),
52
+ );
53
+
54
+ return createMiddleware({
55
+ name: "ApiBasedToolsMiddleware",
56
+ async wrapModelCall(request, handler) {
57
+ const enabledApiToolNames = getEnabledApiToolNames(request.state.messages);
58
+ const tools = [...enabledApiToolNames]
59
+ .filter((toolName) => !alwaysAvailableApiToolNames.has(toolName))
60
+ .map((toolName) => dynamicTools[toolName]);
61
+
62
+ return handler({
63
+ ...request,
64
+ tools: [...request.tools, ...tools],
65
+ });
66
+ },
67
+ async wrapToolCall(request, handler) {
68
+ const startedAt = Date.now();
69
+ const toolInput = JSON.stringify(request.toolCall.args ?? {});
70
+ logger.info(
71
+ `Invoking tool "${request.toolCall.name}" with input: ${toolInput}`,
72
+ );
73
+
74
+ try {
75
+ if (request.tool) {
76
+ return await handler(request);
77
+ }
78
+
79
+ const enabledApiToolNames = getEnabledApiToolNames(request.state.messages);
80
+
81
+ if (enabledApiToolNames.has(request.toolCall.name)) {
82
+ return await handler({
83
+ ...request,
84
+ tool: dynamicTools[request.toolCall.name],
85
+ });
86
+ }
87
+
88
+ return new ToolMessage({
89
+ content: `Tool "${request.toolCall.name}" is not loaded. Call fetch_tool_schema first.`,
90
+ tool_call_id: request.toolCall.id ?? "",
91
+ name: request.toolCall.name,
92
+ status: "error",
93
+ });
94
+ } catch (error) {
95
+ const errorDetails =
96
+ error instanceof Error ? error.stack ?? error.message : String(error);
97
+
98
+ logger.error(
99
+ `Tool "${request.toolCall.name}" failed after ${Date.now() - startedAt}ms with input: ${toolInput}\n${errorDetails}`,
100
+ );
101
+ throw error;
102
+ } finally {
103
+ logger.info(
104
+ `Tool "${request.toolCall.name}" finished in ${Date.now() - startedAt}ms`,
105
+ );
106
+ }
107
+ },
108
+ });
109
+ }
@@ -0,0 +1,302 @@
1
+ import { AIMessage } from "@langchain/core/messages";
2
+ import { createMiddleware } from "langchain";
3
+ import YAML from "yaml";
4
+ import type { ToolCallEvent } from "../toolCallEvents.js";
5
+
6
+ export type SequenceDebugResultType = "tool_calls" | "final_text";
7
+
8
+ type SequenceDebugToolCall = {
9
+ toolCallId: string;
10
+ toolName: string;
11
+ input: string;
12
+ output: string | null;
13
+ error: string | null;
14
+ };
15
+
16
+ type PendingSequenceDebugToolCall = SequenceDebugToolCall & {
17
+ completed: boolean;
18
+ };
19
+
20
+ export type SequenceDebug = {
21
+ sequenceId: number;
22
+ startedAt: string;
23
+ prompt: string;
24
+ reasoning: string;
25
+ text: string;
26
+ toolCalls: SequenceDebugToolCall[];
27
+ endedAt: string;
28
+ resultType: SequenceDebugResultType;
29
+ };
30
+
31
+ type PendingSequenceDebug = Omit<SequenceDebug, "toolCalls" | "endedAt" | "resultType"> & {
32
+ toolCalls: PendingSequenceDebugToolCall[];
33
+ pendingToolCalls: number;
34
+ resultType: SequenceDebugResultType | null;
35
+ };
36
+
37
+ type SequenceDebugModelCall = {
38
+ reasoning: string;
39
+ text: string;
40
+ resultType: SequenceDebugResultType;
41
+ };
42
+
43
+ export type SequenceDebugModelCallSink = {
44
+ handleModelCallStart: (prompt: string) => void;
45
+ handleModelCallComplete: (params: SequenceDebugModelCall) => void;
46
+ };
47
+
48
+ export type SequenceDebugCollector = SequenceDebugModelCallSink & {
49
+ handleToolCallEvent: (event: ToolCallEvent) => void;
50
+ flush: () => void;
51
+ getHistory: () => SequenceDebug[];
52
+ };
53
+
54
+ function createPendingSequenceDebug(sequenceId: number): PendingSequenceDebug {
55
+ return {
56
+ sequenceId,
57
+ startedAt: new Date().toISOString(),
58
+ prompt: "",
59
+ reasoning: "",
60
+ text: "",
61
+ toolCalls: [],
62
+ pendingToolCalls: 0,
63
+ resultType: null,
64
+ };
65
+ }
66
+
67
+ function hasSequenceDebugContent(sequence: PendingSequenceDebug | null): sequence is PendingSequenceDebug {
68
+ return Boolean(
69
+ sequence &&
70
+ (
71
+ sequence.prompt ||
72
+ sequence.reasoning ||
73
+ sequence.text ||
74
+ sequence.toolCalls.length > 0
75
+ ),
76
+ );
77
+ }
78
+
79
+ function finalizeSequenceDebug(sequence: PendingSequenceDebug): SequenceDebug {
80
+ return {
81
+ sequenceId: sequence.sequenceId,
82
+ startedAt: sequence.startedAt,
83
+ prompt: sequence.prompt,
84
+ reasoning: sequence.reasoning,
85
+ text: sequence.text,
86
+ toolCalls: sequence.toolCalls.map(({ completed: _completed, ...toolCall }) => toolCall),
87
+ endedAt: new Date().toISOString(),
88
+ resultType: sequence.resultType ?? "final_text",
89
+ };
90
+ }
91
+
92
+ function stringifyPromptForDebug(params: {
93
+ systemMessage: { toDict(): unknown };
94
+ messages: Array<{ toDict(): unknown }>;
95
+ tools: unknown[];
96
+ modelSettings?: Record<string, unknown>;
97
+ }) {
98
+ const { systemMessage, messages, tools, modelSettings } = params;
99
+
100
+ return YAML.stringify({
101
+ systemMessage: systemMessage.toDict(),
102
+ messages: messages.map((message) => message.toDict()),
103
+ tools: tools.map((tool) => {
104
+ if (
105
+ typeof tool === "object" &&
106
+ tool !== null &&
107
+ "name" in tool &&
108
+ typeof tool.name === "string"
109
+ ) {
110
+ return tool.name;
111
+ }
112
+
113
+ if (
114
+ typeof tool === "object" &&
115
+ tool !== null &&
116
+ "schema" in tool &&
117
+ typeof tool.schema === "object" &&
118
+ tool.schema !== null &&
119
+ "name" in tool.schema &&
120
+ typeof tool.schema.name === "string"
121
+ ) {
122
+ return tool.schema.name;
123
+ }
124
+
125
+ return "";
126
+ }),
127
+ ...(modelSettings && Object.keys(modelSettings).length > 0
128
+ ? { modelSettings }
129
+ : {}),
130
+ });
131
+ }
132
+
133
+ function getMessageBlocks(message: {
134
+ contentBlocks?: unknown;
135
+ content?: unknown;
136
+ }) {
137
+ if (Array.isArray(message.contentBlocks)) {
138
+ return message.contentBlocks;
139
+ }
140
+
141
+ if (Array.isArray(message.content)) {
142
+ return message.content;
143
+ }
144
+
145
+ return [];
146
+ }
147
+
148
+ function hasToolCallSignal(message: {
149
+ tool_calls?: unknown;
150
+ tool_call_chunks?: unknown;
151
+ additional_kwargs?: { tool_calls?: unknown };
152
+ }) {
153
+ return Boolean(
154
+ (Array.isArray(message.tool_calls) && message.tool_calls.length > 0) ||
155
+ (Array.isArray(message.tool_call_chunks) && message.tool_call_chunks.length > 0) ||
156
+ (Array.isArray(message.additional_kwargs?.tool_calls) &&
157
+ message.additional_kwargs.tool_calls.length > 0),
158
+ );
159
+ }
160
+
161
+ function extractSequenceResponseDebug(message: AIMessage): SequenceDebugModelCall {
162
+ const blocks = getMessageBlocks(message);
163
+ const reasoning = blocks
164
+ .filter((block: any) => block?.type === "reasoning")
165
+ .map((block: any) => String(block.reasoning ?? ""))
166
+ .join("");
167
+ const textFromBlocks = blocks
168
+ .filter((block: any) => block?.type === "text")
169
+ .map((block: any) => String(block.text ?? ""))
170
+ .join("");
171
+
172
+ return {
173
+ reasoning,
174
+ text: textFromBlocks || (typeof message.content === "string" ? message.content : ""),
175
+ resultType: hasToolCallSignal(message) ? "tool_calls" : "final_text",
176
+ };
177
+ }
178
+
179
+ export function createSequenceDebugCollector(): SequenceDebugCollector {
180
+ let nextSequenceId = 1;
181
+ let currentSequenceDebug: PendingSequenceDebug | null = null;
182
+ const history: SequenceDebug[] = [];
183
+
184
+ const ensureSequenceDebug = () => {
185
+ if (!currentSequenceDebug) {
186
+ currentSequenceDebug = createPendingSequenceDebug(nextSequenceId++);
187
+ }
188
+
189
+ return currentSequenceDebug;
190
+ };
191
+
192
+ const flush = () => {
193
+ if (!hasSequenceDebugContent(currentSequenceDebug)) {
194
+ currentSequenceDebug = null;
195
+ return;
196
+ }
197
+
198
+ const finalizedSequenceDebug = finalizeSequenceDebug(currentSequenceDebug);
199
+ history.push(finalizedSequenceDebug);
200
+ currentSequenceDebug = null;
201
+ };
202
+
203
+ return {
204
+ handleModelCallStart(prompt) {
205
+ if (
206
+ currentSequenceDebug?.resultType &&
207
+ currentSequenceDebug.pendingToolCalls === 0
208
+ ) {
209
+ flush();
210
+ }
211
+
212
+ const sequenceDebug = ensureSequenceDebug();
213
+ sequenceDebug.prompt = prompt;
214
+ },
215
+ handleModelCallComplete(params) {
216
+ const sequenceDebug = ensureSequenceDebug();
217
+ sequenceDebug.reasoning = params.reasoning;
218
+ sequenceDebug.text = params.text;
219
+ sequenceDebug.resultType = params.resultType;
220
+
221
+ if (
222
+ sequenceDebug.resultType === "final_text" &&
223
+ sequenceDebug.pendingToolCalls === 0
224
+ ) {
225
+ flush();
226
+ }
227
+ },
228
+ handleToolCallEvent(event) {
229
+ const sequenceDebug = ensureSequenceDebug();
230
+
231
+ if (event.phase === "start") {
232
+ sequenceDebug.toolCalls.push({
233
+ toolCallId: event.toolCallId,
234
+ toolName: event.toolName,
235
+ input: event.input,
236
+ output: null,
237
+ error: null,
238
+ completed: false,
239
+ });
240
+ sequenceDebug.pendingToolCalls += 1;
241
+ return;
242
+ }
243
+
244
+ const pendingToolCall = sequenceDebug.toolCalls.find(
245
+ (toolCall) => toolCall.toolCallId === event.toolCallId && !toolCall.completed,
246
+ );
247
+
248
+ if (pendingToolCall) {
249
+ pendingToolCall.output = event.output;
250
+ pendingToolCall.error = event.error;
251
+ pendingToolCall.completed = true;
252
+ } else {
253
+ sequenceDebug.toolCalls.push({
254
+ toolCallId: event.toolCallId,
255
+ toolName: event.toolName,
256
+ input: "",
257
+ output: event.output,
258
+ error: event.error,
259
+ completed: true,
260
+ });
261
+ }
262
+
263
+ sequenceDebug.pendingToolCalls = Math.max(
264
+ 0,
265
+ sequenceDebug.pendingToolCalls - 1,
266
+ );
267
+
268
+ if (
269
+ sequenceDebug.resultType === "tool_calls" &&
270
+ sequenceDebug.pendingToolCalls === 0
271
+ ) {
272
+ flush();
273
+ }
274
+ },
275
+ flush,
276
+ getHistory() {
277
+ return history;
278
+ },
279
+ };
280
+ }
281
+
282
+ export function createSequenceDebugMiddleware(
283
+ sink: SequenceDebugModelCallSink,
284
+ ) {
285
+ return createMiddleware({
286
+ name: "SequenceDebugMiddleware",
287
+ async wrapModelCall(request, handler) {
288
+ sink.handleModelCallStart(
289
+ stringifyPromptForDebug({
290
+ systemMessage: request.systemMessage,
291
+ messages: request.messages,
292
+ tools: request.tools,
293
+ modelSettings: request.modelSettings,
294
+ }),
295
+ );
296
+
297
+ const response = await handler(request) as AIMessage;
298
+ sink.handleModelCallComplete(extractSequenceResponseDebug(response));
299
+ return response;
300
+ },
301
+ });
302
+ }