@adminforth/agent 1.28.0 → 1.30.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.
@@ -0,0 +1,79 @@
1
+ import { logger } from "adminforth";
2
+ import type { PluginOptions } from "../types.js";
3
+
4
+ export type UserLanguage = {
5
+ language: string;
6
+ code: string;
7
+ };
8
+
9
+ const USER_LANGUAGE_OUTPUT_SCHEMA = {
10
+ name: "user_language",
11
+ strict: true,
12
+ schema: {
13
+ type: "object",
14
+ additionalProperties: false,
15
+ properties: {
16
+ language: {
17
+ type: "string",
18
+ description: "Full English language name, for example English, Ukrainian, French.",
19
+ },
20
+ code: {
21
+ type: "string",
22
+ description: "Uppercase two-letter language code, for example EN, UA, FR.",
23
+ },
24
+ },
25
+ required: ["language", "code"],
26
+ },
27
+ } as const;
28
+
29
+ export function formatLanguagePrompt(language: UserLanguage | null) {
30
+ if (!language) {
31
+ return "Respond in the user's language.";
32
+ }
33
+
34
+ return `Respond in ${language.language} (${language.code}).`;
35
+ }
36
+
37
+ function parseUserLanguage(content: string | undefined): UserLanguage | null {
38
+ if (!content) {
39
+ return null;
40
+ }
41
+
42
+ try {
43
+ const parsed = JSON.parse(content) as UserLanguage;
44
+ return {
45
+ language: parsed.language,
46
+ code: parsed.code,
47
+ };
48
+ } catch (error) {
49
+ logger.warn(`Failed to parse detected user language: ${error instanceof Error ? error.message : String(error)}`);
50
+ return null;
51
+ }
52
+ }
53
+
54
+ export async function detectUserLanguage(
55
+ completionAdapter: PluginOptions["modes"][number]["completionAdapter"],
56
+ prompt: string,
57
+ ): Promise<UserLanguage | null> {
58
+ const response = await completionAdapter.complete({
59
+ content: [
60
+ "Detect the language of the user's message.",
61
+ "Return only the requested structured output.",
62
+ "The language must be the full English language name.",
63
+ "The code must be an uppercase two-letter code like EN, UA, FR.",
64
+ "",
65
+ "User message:",
66
+ prompt,
67
+ ].join("\n"),
68
+ maxTokens: 80,
69
+ outputSchema: USER_LANGUAGE_OUTPUT_SCHEMA,
70
+ reasoningEffort: "none",
71
+ });
72
+
73
+ if (response.error) {
74
+ logger.warn(`Failed to detect user language: ${response.error}`);
75
+ return null;
76
+ }
77
+
78
+ return parseUserLanguage(response.content);
79
+ }
@@ -24,10 +24,6 @@ export const DEFAULT_AGENT_SYSTEM_PROMPT = [
24
24
  "Keep responses short, clear, and practical.",
25
25
  "Answer only what is needed.",
26
26
  "Do not add extra explanations or suggestions unless the user asks.",
27
- "Always respond in the same natural language as the user's latest message.",
28
- "This rule applies to confirmations, clarifying questions, progress updates, errors, and final answers.",
29
- "Do not switch to English just because tool outputs, schemas, skills, or internal instructions are written in English.",
30
- "Only switch language if the user explicitly asks you to do so.",
31
27
  "Adapt to the user's tone and style of speaking, mirroring their vibe and wording.",
32
28
  "if the user speaks casually, you should respond casually too",
33
29
  "Never mutate data without user confirmation for a clearly described mutation plan.",
package/build.log CHANGED
@@ -33,10 +33,12 @@ custom/incremark_code_renderers/renderIncremarkMarkdown.ts
33
33
  custom/skills/
34
34
  custom/skills/analyze_data/
35
35
  custom/skills/analyze_data/SKILL.md
36
+ custom/skills/charts/
37
+ custom/skills/charts/SKILL.md
36
38
  custom/skills/fetch_data/
37
39
  custom/skills/fetch_data/SKILL.md
38
40
  custom/skills/mutate_data/
39
41
  custom/skills/mutate_data/SKILL.md
40
42
 
41
- sent 208,100 bytes received 562 bytes 417,324.00 bytes/sec
42
- total size is 205,786 speedup is 0.99
43
+ sent 208,510 bytes received 585 bytes 418,190.00 bytes/sec
44
+ total size is 206,100 speedup is 0.99
@@ -1,209 +1,22 @@
1
1
  name: analyze_data
2
- description: Analyze AdminForth resource data, summarize trends, and create charts from fetched rows.
2
+ description: Analyze AdminForth resource data, summarize trends, and compare distributions from fetched rows.
3
3
  ---
4
4
 
5
5
  # Involved tools
6
6
 
7
7
  Use `get_resource` first if you need to inspect resource structure and column names.
8
8
 
9
- Use `get_resource_data` to fetch data for this skill. This is the main tool for loading rows for analytics, comparisons, distributions, and trend analysis.
9
+ Use `aggregate` to fetch data for analytics whenever possible. This is the main tool for fast server-side aggregations, including filtered data, grouped metrics, and date buckets such as day, week, or month.
10
+
11
+ Use `get_resource_data` only when the requested analysis cannot be answered with `aggregate`. This is heavier because it returns original rows with all fields, but it allows complex calculations, comparisons, and custom groupings in-memory.
10
12
 
11
13
  # Instructions
12
14
 
13
15
  When the user asks for analytics, reports, trends, comparisons, or distributions:
14
16
 
15
- - Fetch the requested data using `aggregate` tool. This tool is capable of performing fast server-side aggregations on filtered data, groupings by date including grouping by day/week/month etc.
16
- - if it is not possible to get the required aggregates using `aggregate`, fetch the underlying rows with `get_resource_data`. This is much heavier since returns original rows with all fields, but allows you to perform complex calculations, comparisons, and custom groupings in-memory. Always prefer `aggregate` when possible.
17
+ - Fetch the requested data using `aggregate` whenever possible.
18
+ - If it is not possible to get the required aggregates using `aggregate`, fetch the underlying rows with `get_resource_data`.
17
19
  - Prefer narrow requests: use filters, sorting, pagination, and date ranges whenever possible.
18
20
  - If the request is ambiguous, clarify the resource, metric, grouping, or date range before fetching data.
19
21
  - Return a short written summary with the key finding and most important numbers.
20
- - If a chart would help, produce a Vega-Lite spec.
21
-
22
- # Charts
23
-
24
- Use Vega-Lite syntax for charts.
25
-
26
- Return every chart as valid JSON inside a `vega-lite` fenced code block.
27
-
28
- Every chart spec should include:
29
- - `title.text`
30
- - `title.subtitle`
31
- - explicit axis titles when axes are used
32
- - tooltips for the key fields
33
-
34
- ### Line chart
35
-
36
- ```vega-lite
37
- {
38
- "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
39
- "title": {
40
- "text": "Orders by Day",
41
- "subtitle": "Daily order count for the selected date range"
42
- },
43
- "data": {
44
- "values": [
45
- { "date": "2026-04-01", "orders": 18 },
46
- { "date": "2026-04-02", "orders": 25 },
47
- { "date": "2026-04-03", "orders": 21 },
48
- { "date": "2026-04-04", "orders": 29 }
49
- ]
50
- },
51
- "mark": { "type": "line", "point": true },
52
- "encoding": {
53
- "x": { "field": "date", "type": "temporal", "title": "Date" },
54
- "y": { "field": "orders", "type": "quantitative", "title": "Orders" },
55
- "tooltip": [
56
- { "field": "date", "type": "temporal", "title": "Date" },
57
- { "field": "orders", "type": "quantitative", "title": "Orders" }
58
- ]
59
- }
60
- }
61
- ```
62
-
63
- ### Bar chart
64
-
65
- ```vega-lite
66
- {
67
- "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
68
- "title": {
69
- "text": "Revenue by Category",
70
- "subtitle": "Top categories in the current filtered dataset"
71
- },
72
- "data": {
73
- "values": [
74
- { "category": "Hardware", "revenue": 42000 },
75
- { "category": "Software", "revenue": 31500 },
76
- { "category": "Services", "revenue": 22750 }
77
- ]
78
- },
79
- "mark": "bar",
80
- "encoding": {
81
- "x": { "field": "category", "type": "nominal", "title": "Category", "sort": "-y" },
82
- "y": { "field": "revenue", "type": "quantitative", "title": "Revenue" },
83
- "tooltip": [
84
- { "field": "category", "type": "nominal", "title": "Category" },
85
- { "field": "revenue", "type": "quantitative", "title": "Revenue" }
86
- ]
87
- }
88
- }
89
- ```
90
-
91
- ### Area chart
92
-
93
- ```vega-lite
94
- {
95
- "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
96
- "title": {
97
- "text": "Monthly Signups",
98
- "subtitle": "New users accumulated across the last six months"
99
- },
100
- "data": {
101
- "values": [
102
- { "month": "2025-11-01", "signups": 120 },
103
- { "month": "2025-12-01", "signups": 155 },
104
- { "month": "2026-01-01", "signups": 168 },
105
- { "month": "2026-02-01", "signups": 190 },
106
- { "month": "2026-03-01", "signups": 214 },
107
- { "month": "2026-04-01", "signups": 238 }
108
- ]
109
- },
110
- "mark": { "type": "area", "line": true, "point": true },
111
- "encoding": {
112
- "x": { "field": "month", "type": "temporal", "title": "Month" },
113
- "y": { "field": "signups", "type": "quantitative", "title": "Signups" },
114
- "tooltip": [
115
- { "field": "month", "type": "temporal", "title": "Month" },
116
- { "field": "signups", "type": "quantitative", "title": "Signups" }
117
- ]
118
- }
119
- }
120
- ```
121
-
122
- ### Scatter plot
123
-
124
- ```vega-lite
125
- {
126
- "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
127
- "title": {
128
- "text": "Ad Spend vs Revenue",
129
- "subtitle": "Campaign-level correlation for the selected month"
130
- },
131
- "data": {
132
- "values": [
133
- { "campaign": "Search", "spend": 1200, "revenue": 5400 },
134
- { "campaign": "Social", "spend": 900, "revenue": 3100 },
135
- { "campaign": "Email", "spend": 350, "revenue": 2200 },
136
- { "campaign": "Affiliates", "spend": 700, "revenue": 3600 }
137
- ]
138
- },
139
- "mark": { "type": "point", "filled": true, "size": 120 },
140
- "encoding": {
141
- "x": { "field": "spend", "type": "quantitative", "title": "Ad Spend" },
142
- "y": { "field": "revenue", "type": "quantitative", "title": "Revenue" },
143
- "tooltip": [
144
- { "field": "campaign", "type": "nominal", "title": "Campaign" },
145
- { "field": "spend", "type": "quantitative", "title": "Ad Spend" },
146
- { "field": "revenue", "type": "quantitative", "title": "Revenue" }
147
- ]
148
- }
149
- }
150
- ```
151
-
152
- ### Pie chart
153
-
154
- ```vega-lite
155
- {
156
- "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
157
- "title": {
158
- "text": "Tickets by Status",
159
- "subtitle": "Share of tickets in each workflow state"
160
- },
161
- "data": {
162
- "values": [
163
- { "status": "Open", "count": 42 },
164
- { "status": "In Progress", "count": 27 },
165
- { "status": "Resolved", "count": 58 }
166
- ]
167
- },
168
- "mark": { "type": "arc", "innerRadius": 40 },
169
- "encoding": {
170
- "theta": { "field": "count", "type": "quantitative", "title": "Tickets" },
171
- "color": { "field": "status", "type": "nominal", "title": "Status" },
172
- "tooltip": [
173
- { "field": "status", "type": "nominal", "title": "Status" },
174
- { "field": "count", "type": "quantitative", "title": "Tickets" }
175
- ]
176
- }
177
- }
178
- ```
179
-
180
- ### Histogram
181
-
182
- ```vega-lite
183
- {
184
- "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
185
- "title": {
186
- "text": "Order Value Distribution",
187
- "subtitle": "Histogram of order totals in the filtered result set"
188
- },
189
- "data": {
190
- "values": [
191
- { "order_total": 24 },
192
- { "order_total": 31 },
193
- { "order_total": 39 },
194
- { "order_total": 42 },
195
- { "order_total": 63 },
196
- { "order_total": 78 },
197
- { "order_total": 95 }
198
- ]
199
- },
200
- "mark": "bar",
201
- "encoding": {
202
- "x": { "bin": true, "field": "order_total", "type": "quantitative", "title": "Order Total" },
203
- "y": { "aggregate": "count", "type": "quantitative", "title": "Count" },
204
- "tooltip": [
205
- { "aggregate": "count", "type": "quantitative", "title": "Count" }
206
- ]
207
- }
208
- }
209
- ```
22
+ - If the user asks for a chart, or if a chart would help and you decide to produce one, invoke the `charts` skill for chart formatting and Vega-Lite requirements.
@@ -0,0 +1,193 @@
1
+ name: charts
2
+ description: Create Vega-Lite chart specs for AdminForth analytics responses. Always use this skill when the user asks for a chart or when a chart would help illustrate the insights from an analytics query.
3
+ ---
4
+
5
+ # Instructions
6
+
7
+ Use Vega-Lite syntax for charts.
8
+
9
+ Return every chart as valid JSON inside a `vega-lite` fenced code block.
10
+
11
+ Every chart spec should include:
12
+
13
+ - `title.text`
14
+ - `title.subtitle`
15
+ - explicit axis titles when axes are used
16
+ - tooltips for the key fields
17
+
18
+ ### Line chart
19
+
20
+ ```vega-lite
21
+ {
22
+ "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
23
+ "title": {
24
+ "text": "Orders by Day",
25
+ "subtitle": "Daily order count for the selected date range"
26
+ },
27
+ "data": {
28
+ "values": [
29
+ { "date": "2026-04-01", "orders": 18 },
30
+ { "date": "2026-04-02", "orders": 25 },
31
+ { "date": "2026-04-03", "orders": 21 },
32
+ { "date": "2026-04-04", "orders": 29 }
33
+ ]
34
+ },
35
+ "mark": { "type": "line", "point": true },
36
+ "encoding": {
37
+ "x": { "field": "date", "type": "temporal", "title": "Date" },
38
+ "y": { "field": "orders", "type": "quantitative", "title": "Orders" },
39
+ "tooltip": [
40
+ { "field": "date", "type": "temporal", "title": "Date" },
41
+ { "field": "orders", "type": "quantitative", "title": "Orders" }
42
+ ]
43
+ }
44
+ }
45
+ ```
46
+
47
+ ### Bar chart
48
+
49
+ ```vega-lite
50
+ {
51
+ "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
52
+ "title": {
53
+ "text": "Revenue by Category",
54
+ "subtitle": "Top categories in the current filtered dataset"
55
+ },
56
+ "data": {
57
+ "values": [
58
+ { "category": "Hardware", "revenue": 42000 },
59
+ { "category": "Software", "revenue": 31500 },
60
+ { "category": "Services", "revenue": 22750 }
61
+ ]
62
+ },
63
+ "mark": "bar",
64
+ "encoding": {
65
+ "x": { "field": "category", "type": "nominal", "title": "Category", "sort": "-y" },
66
+ "y": { "field": "revenue", "type": "quantitative", "title": "Revenue" },
67
+ "tooltip": [
68
+ { "field": "category", "type": "nominal", "title": "Category" },
69
+ { "field": "revenue", "type": "quantitative", "title": "Revenue" }
70
+ ]
71
+ }
72
+ }
73
+ ```
74
+
75
+ ### Area chart
76
+
77
+ ```vega-lite
78
+ {
79
+ "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
80
+ "title": {
81
+ "text": "Monthly Signups",
82
+ "subtitle": "New users accumulated across the last six months"
83
+ },
84
+ "data": {
85
+ "values": [
86
+ { "month": "2025-11-01", "signups": 120 },
87
+ { "month": "2025-12-01", "signups": 155 },
88
+ { "month": "2026-01-01", "signups": 168 },
89
+ { "month": "2026-02-01", "signups": 190 },
90
+ { "month": "2026-03-01", "signups": 214 },
91
+ { "month": "2026-04-01", "signups": 238 }
92
+ ]
93
+ },
94
+ "mark": { "type": "area", "line": true, "point": true },
95
+ "encoding": {
96
+ "x": { "field": "month", "type": "temporal", "title": "Month" },
97
+ "y": { "field": "signups", "type": "quantitative", "title": "Signups" },
98
+ "tooltip": [
99
+ { "field": "month", "type": "temporal", "title": "Month" },
100
+ { "field": "signups", "type": "quantitative", "title": "Signups" }
101
+ ]
102
+ }
103
+ }
104
+ ```
105
+
106
+ ### Scatter plot
107
+
108
+ ```vega-lite
109
+ {
110
+ "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
111
+ "title": {
112
+ "text": "Ad Spend vs Revenue",
113
+ "subtitle": "Campaign-level correlation for the selected month"
114
+ },
115
+ "data": {
116
+ "values": [
117
+ { "campaign": "Search", "spend": 1200, "revenue": 5400 },
118
+ { "campaign": "Social", "spend": 900, "revenue": 3100 },
119
+ { "campaign": "Email", "spend": 350, "revenue": 2200 },
120
+ { "campaign": "Affiliates", "spend": 700, "revenue": 3600 }
121
+ ]
122
+ },
123
+ "mark": { "type": "point", "filled": true, "size": 120 },
124
+ "encoding": {
125
+ "x": { "field": "spend", "type": "quantitative", "title": "Ad Spend" },
126
+ "y": { "field": "revenue", "type": "quantitative", "title": "Revenue" },
127
+ "tooltip": [
128
+ { "field": "campaign", "type": "nominal", "title": "Campaign" },
129
+ { "field": "spend", "type": "quantitative", "title": "Ad Spend" },
130
+ { "field": "revenue", "type": "quantitative", "title": "Revenue" }
131
+ ]
132
+ }
133
+ }
134
+ ```
135
+
136
+ ### Pie chart
137
+
138
+ ```vega-lite
139
+ {
140
+ "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
141
+ "title": {
142
+ "text": "Tickets by Status",
143
+ "subtitle": "Share of tickets in each workflow state"
144
+ },
145
+ "data": {
146
+ "values": [
147
+ { "status": "Open", "count": 42 },
148
+ { "status": "In Progress", "count": 27 },
149
+ { "status": "Resolved", "count": 58 }
150
+ ]
151
+ },
152
+ "mark": { "type": "arc", "innerRadius": 40 },
153
+ "encoding": {
154
+ "theta": { "field": "count", "type": "quantitative", "title": "Tickets" },
155
+ "color": { "field": "status", "type": "nominal", "title": "Status" },
156
+ "tooltip": [
157
+ { "field": "status", "type": "nominal", "title": "Status" },
158
+ { "field": "count", "type": "quantitative", "title": "Tickets" }
159
+ ]
160
+ }
161
+ }
162
+ ```
163
+
164
+ ### Histogram
165
+
166
+ ```vega-lite
167
+ {
168
+ "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
169
+ "title": {
170
+ "text": "Order Value Distribution",
171
+ "subtitle": "Histogram of order totals in the filtered result set"
172
+ },
173
+ "data": {
174
+ "values": [
175
+ { "order_total": 24 },
176
+ { "order_total": 31 },
177
+ { "order_total": 39 },
178
+ { "order_total": 42 },
179
+ { "order_total": 63 },
180
+ { "order_total": 78 },
181
+ { "order_total": 95 }
182
+ ]
183
+ },
184
+ "mark": "bar",
185
+ "encoding": {
186
+ "x": { "bin": true, "field": "order_total", "type": "quantitative", "title": "Order Total" },
187
+ "y": { "aggregate": "count", "type": "quantitative", "title": "Count" },
188
+ "tooltip": [
189
+ { "aggregate": "count", "type": "quantitative", "title": "Count" }
190
+ ]
191
+ }
192
+ }
193
+ ```
@@ -0,0 +1,74 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { logger } from "adminforth";
11
+ const USER_LANGUAGE_OUTPUT_SCHEMA = {
12
+ name: "user_language",
13
+ strict: true,
14
+ schema: {
15
+ type: "object",
16
+ additionalProperties: false,
17
+ properties: {
18
+ language: {
19
+ type: "string",
20
+ description: "Full English language name, for example English, Ukrainian, French.",
21
+ },
22
+ code: {
23
+ type: "string",
24
+ description: "Uppercase two-letter language code, for example EN, UA, FR.",
25
+ },
26
+ },
27
+ required: ["language", "code"],
28
+ },
29
+ };
30
+ export function formatLanguagePrompt(language) {
31
+ if (!language) {
32
+ return "Respond in the user's language.";
33
+ }
34
+ return `Respond in ${language.language} (${language.code}).`;
35
+ }
36
+ function parseUserLanguage(content) {
37
+ if (!content) {
38
+ return null;
39
+ }
40
+ try {
41
+ const parsed = JSON.parse(content);
42
+ return {
43
+ language: parsed.language,
44
+ code: parsed.code,
45
+ };
46
+ }
47
+ catch (error) {
48
+ logger.warn(`Failed to parse detected user language: ${error instanceof Error ? error.message : String(error)}`);
49
+ return null;
50
+ }
51
+ }
52
+ export function detectUserLanguage(completionAdapter, prompt) {
53
+ return __awaiter(this, void 0, void 0, function* () {
54
+ const response = yield completionAdapter.complete({
55
+ content: [
56
+ "Detect the language of the user's message.",
57
+ "Return only the requested structured output.",
58
+ "The language must be the full English language name.",
59
+ "The code must be an uppercase two-letter code like EN, UA, FR.",
60
+ "",
61
+ "User message:",
62
+ prompt,
63
+ ].join("\n"),
64
+ maxTokens: 80,
65
+ outputSchema: USER_LANGUAGE_OUTPUT_SCHEMA,
66
+ reasoningEffort: "none",
67
+ });
68
+ if (response.error) {
69
+ logger.warn(`Failed to detect user language: ${response.error}`);
70
+ return null;
71
+ }
72
+ return parseUserLanguage(response.content);
73
+ });
74
+ }
@@ -23,10 +23,6 @@ export const DEFAULT_AGENT_SYSTEM_PROMPT = [
23
23
  "Keep responses short, clear, and practical.",
24
24
  "Answer only what is needed.",
25
25
  "Do not add extra explanations or suggestions unless the user asks.",
26
- "Always respond in the same natural language as the user's latest message.",
27
- "This rule applies to confirmations, clarifying questions, progress updates, errors, and final answers.",
28
- "Do not switch to English just because tool outputs, schemas, skills, or internal instructions are written in English.",
29
- "Only switch language if the user explicitly asks you to do so.",
30
26
  "Adapt to the user's tone and style of speaking, mirroring their vibe and wording.",
31
27
  "if the user speaks casually, you should respond casually too",
32
28
  "Never mutate data without user confirmation for a clearly described mutation plan.",
@@ -1,209 +1,22 @@
1
1
  name: analyze_data
2
- description: Analyze AdminForth resource data, summarize trends, and create charts from fetched rows.
2
+ description: Analyze AdminForth resource data, summarize trends, and compare distributions from fetched rows.
3
3
  ---
4
4
 
5
5
  # Involved tools
6
6
 
7
7
  Use `get_resource` first if you need to inspect resource structure and column names.
8
8
 
9
- Use `get_resource_data` to fetch data for this skill. This is the main tool for loading rows for analytics, comparisons, distributions, and trend analysis.
9
+ Use `aggregate` to fetch data for analytics whenever possible. This is the main tool for fast server-side aggregations, including filtered data, grouped metrics, and date buckets such as day, week, or month.
10
+
11
+ Use `get_resource_data` only when the requested analysis cannot be answered with `aggregate`. This is heavier because it returns original rows with all fields, but it allows complex calculations, comparisons, and custom groupings in-memory.
10
12
 
11
13
  # Instructions
12
14
 
13
15
  When the user asks for analytics, reports, trends, comparisons, or distributions:
14
16
 
15
- - Fetch the requested data using `aggregate` tool. This tool is capable of performing fast server-side aggregations on filtered data, groupings by date including grouping by day/week/month etc.
16
- - if it is not possible to get the required aggregates using `aggregate`, fetch the underlying rows with `get_resource_data`. This is much heavier since returns original rows with all fields, but allows you to perform complex calculations, comparisons, and custom groupings in-memory. Always prefer `aggregate` when possible.
17
+ - Fetch the requested data using `aggregate` whenever possible.
18
+ - If it is not possible to get the required aggregates using `aggregate`, fetch the underlying rows with `get_resource_data`.
17
19
  - Prefer narrow requests: use filters, sorting, pagination, and date ranges whenever possible.
18
20
  - If the request is ambiguous, clarify the resource, metric, grouping, or date range before fetching data.
19
21
  - Return a short written summary with the key finding and most important numbers.
20
- - If a chart would help, produce a Vega-Lite spec.
21
-
22
- # Charts
23
-
24
- Use Vega-Lite syntax for charts.
25
-
26
- Return every chart as valid JSON inside a `vega-lite` fenced code block.
27
-
28
- Every chart spec should include:
29
- - `title.text`
30
- - `title.subtitle`
31
- - explicit axis titles when axes are used
32
- - tooltips for the key fields
33
-
34
- ### Line chart
35
-
36
- ```vega-lite
37
- {
38
- "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
39
- "title": {
40
- "text": "Orders by Day",
41
- "subtitle": "Daily order count for the selected date range"
42
- },
43
- "data": {
44
- "values": [
45
- { "date": "2026-04-01", "orders": 18 },
46
- { "date": "2026-04-02", "orders": 25 },
47
- { "date": "2026-04-03", "orders": 21 },
48
- { "date": "2026-04-04", "orders": 29 }
49
- ]
50
- },
51
- "mark": { "type": "line", "point": true },
52
- "encoding": {
53
- "x": { "field": "date", "type": "temporal", "title": "Date" },
54
- "y": { "field": "orders", "type": "quantitative", "title": "Orders" },
55
- "tooltip": [
56
- { "field": "date", "type": "temporal", "title": "Date" },
57
- { "field": "orders", "type": "quantitative", "title": "Orders" }
58
- ]
59
- }
60
- }
61
- ```
62
-
63
- ### Bar chart
64
-
65
- ```vega-lite
66
- {
67
- "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
68
- "title": {
69
- "text": "Revenue by Category",
70
- "subtitle": "Top categories in the current filtered dataset"
71
- },
72
- "data": {
73
- "values": [
74
- { "category": "Hardware", "revenue": 42000 },
75
- { "category": "Software", "revenue": 31500 },
76
- { "category": "Services", "revenue": 22750 }
77
- ]
78
- },
79
- "mark": "bar",
80
- "encoding": {
81
- "x": { "field": "category", "type": "nominal", "title": "Category", "sort": "-y" },
82
- "y": { "field": "revenue", "type": "quantitative", "title": "Revenue" },
83
- "tooltip": [
84
- { "field": "category", "type": "nominal", "title": "Category" },
85
- { "field": "revenue", "type": "quantitative", "title": "Revenue" }
86
- ]
87
- }
88
- }
89
- ```
90
-
91
- ### Area chart
92
-
93
- ```vega-lite
94
- {
95
- "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
96
- "title": {
97
- "text": "Monthly Signups",
98
- "subtitle": "New users accumulated across the last six months"
99
- },
100
- "data": {
101
- "values": [
102
- { "month": "2025-11-01", "signups": 120 },
103
- { "month": "2025-12-01", "signups": 155 },
104
- { "month": "2026-01-01", "signups": 168 },
105
- { "month": "2026-02-01", "signups": 190 },
106
- { "month": "2026-03-01", "signups": 214 },
107
- { "month": "2026-04-01", "signups": 238 }
108
- ]
109
- },
110
- "mark": { "type": "area", "line": true, "point": true },
111
- "encoding": {
112
- "x": { "field": "month", "type": "temporal", "title": "Month" },
113
- "y": { "field": "signups", "type": "quantitative", "title": "Signups" },
114
- "tooltip": [
115
- { "field": "month", "type": "temporal", "title": "Month" },
116
- { "field": "signups", "type": "quantitative", "title": "Signups" }
117
- ]
118
- }
119
- }
120
- ```
121
-
122
- ### Scatter plot
123
-
124
- ```vega-lite
125
- {
126
- "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
127
- "title": {
128
- "text": "Ad Spend vs Revenue",
129
- "subtitle": "Campaign-level correlation for the selected month"
130
- },
131
- "data": {
132
- "values": [
133
- { "campaign": "Search", "spend": 1200, "revenue": 5400 },
134
- { "campaign": "Social", "spend": 900, "revenue": 3100 },
135
- { "campaign": "Email", "spend": 350, "revenue": 2200 },
136
- { "campaign": "Affiliates", "spend": 700, "revenue": 3600 }
137
- ]
138
- },
139
- "mark": { "type": "point", "filled": true, "size": 120 },
140
- "encoding": {
141
- "x": { "field": "spend", "type": "quantitative", "title": "Ad Spend" },
142
- "y": { "field": "revenue", "type": "quantitative", "title": "Revenue" },
143
- "tooltip": [
144
- { "field": "campaign", "type": "nominal", "title": "Campaign" },
145
- { "field": "spend", "type": "quantitative", "title": "Ad Spend" },
146
- { "field": "revenue", "type": "quantitative", "title": "Revenue" }
147
- ]
148
- }
149
- }
150
- ```
151
-
152
- ### Pie chart
153
-
154
- ```vega-lite
155
- {
156
- "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
157
- "title": {
158
- "text": "Tickets by Status",
159
- "subtitle": "Share of tickets in each workflow state"
160
- },
161
- "data": {
162
- "values": [
163
- { "status": "Open", "count": 42 },
164
- { "status": "In Progress", "count": 27 },
165
- { "status": "Resolved", "count": 58 }
166
- ]
167
- },
168
- "mark": { "type": "arc", "innerRadius": 40 },
169
- "encoding": {
170
- "theta": { "field": "count", "type": "quantitative", "title": "Tickets" },
171
- "color": { "field": "status", "type": "nominal", "title": "Status" },
172
- "tooltip": [
173
- { "field": "status", "type": "nominal", "title": "Status" },
174
- { "field": "count", "type": "quantitative", "title": "Tickets" }
175
- ]
176
- }
177
- }
178
- ```
179
-
180
- ### Histogram
181
-
182
- ```vega-lite
183
- {
184
- "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
185
- "title": {
186
- "text": "Order Value Distribution",
187
- "subtitle": "Histogram of order totals in the filtered result set"
188
- },
189
- "data": {
190
- "values": [
191
- { "order_total": 24 },
192
- { "order_total": 31 },
193
- { "order_total": 39 },
194
- { "order_total": 42 },
195
- { "order_total": 63 },
196
- { "order_total": 78 },
197
- { "order_total": 95 }
198
- ]
199
- },
200
- "mark": "bar",
201
- "encoding": {
202
- "x": { "bin": true, "field": "order_total", "type": "quantitative", "title": "Order Total" },
203
- "y": { "aggregate": "count", "type": "quantitative", "title": "Count" },
204
- "tooltip": [
205
- { "aggregate": "count", "type": "quantitative", "title": "Count" }
206
- ]
207
- }
208
- }
209
- ```
22
+ - If the user asks for a chart, or if a chart would help and you decide to produce one, invoke the `charts` skill for chart formatting and Vega-Lite requirements.
@@ -0,0 +1,193 @@
1
+ name: charts
2
+ description: Create Vega-Lite chart specs for AdminForth analytics responses. Always use this skill when the user asks for a chart or when a chart would help illustrate the insights from an analytics query.
3
+ ---
4
+
5
+ # Instructions
6
+
7
+ Use Vega-Lite syntax for charts.
8
+
9
+ Return every chart as valid JSON inside a `vega-lite` fenced code block.
10
+
11
+ Every chart spec should include:
12
+
13
+ - `title.text`
14
+ - `title.subtitle`
15
+ - explicit axis titles when axes are used
16
+ - tooltips for the key fields
17
+
18
+ ### Line chart
19
+
20
+ ```vega-lite
21
+ {
22
+ "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
23
+ "title": {
24
+ "text": "Orders by Day",
25
+ "subtitle": "Daily order count for the selected date range"
26
+ },
27
+ "data": {
28
+ "values": [
29
+ { "date": "2026-04-01", "orders": 18 },
30
+ { "date": "2026-04-02", "orders": 25 },
31
+ { "date": "2026-04-03", "orders": 21 },
32
+ { "date": "2026-04-04", "orders": 29 }
33
+ ]
34
+ },
35
+ "mark": { "type": "line", "point": true },
36
+ "encoding": {
37
+ "x": { "field": "date", "type": "temporal", "title": "Date" },
38
+ "y": { "field": "orders", "type": "quantitative", "title": "Orders" },
39
+ "tooltip": [
40
+ { "field": "date", "type": "temporal", "title": "Date" },
41
+ { "field": "orders", "type": "quantitative", "title": "Orders" }
42
+ ]
43
+ }
44
+ }
45
+ ```
46
+
47
+ ### Bar chart
48
+
49
+ ```vega-lite
50
+ {
51
+ "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
52
+ "title": {
53
+ "text": "Revenue by Category",
54
+ "subtitle": "Top categories in the current filtered dataset"
55
+ },
56
+ "data": {
57
+ "values": [
58
+ { "category": "Hardware", "revenue": 42000 },
59
+ { "category": "Software", "revenue": 31500 },
60
+ { "category": "Services", "revenue": 22750 }
61
+ ]
62
+ },
63
+ "mark": "bar",
64
+ "encoding": {
65
+ "x": { "field": "category", "type": "nominal", "title": "Category", "sort": "-y" },
66
+ "y": { "field": "revenue", "type": "quantitative", "title": "Revenue" },
67
+ "tooltip": [
68
+ { "field": "category", "type": "nominal", "title": "Category" },
69
+ { "field": "revenue", "type": "quantitative", "title": "Revenue" }
70
+ ]
71
+ }
72
+ }
73
+ ```
74
+
75
+ ### Area chart
76
+
77
+ ```vega-lite
78
+ {
79
+ "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
80
+ "title": {
81
+ "text": "Monthly Signups",
82
+ "subtitle": "New users accumulated across the last six months"
83
+ },
84
+ "data": {
85
+ "values": [
86
+ { "month": "2025-11-01", "signups": 120 },
87
+ { "month": "2025-12-01", "signups": 155 },
88
+ { "month": "2026-01-01", "signups": 168 },
89
+ { "month": "2026-02-01", "signups": 190 },
90
+ { "month": "2026-03-01", "signups": 214 },
91
+ { "month": "2026-04-01", "signups": 238 }
92
+ ]
93
+ },
94
+ "mark": { "type": "area", "line": true, "point": true },
95
+ "encoding": {
96
+ "x": { "field": "month", "type": "temporal", "title": "Month" },
97
+ "y": { "field": "signups", "type": "quantitative", "title": "Signups" },
98
+ "tooltip": [
99
+ { "field": "month", "type": "temporal", "title": "Month" },
100
+ { "field": "signups", "type": "quantitative", "title": "Signups" }
101
+ ]
102
+ }
103
+ }
104
+ ```
105
+
106
+ ### Scatter plot
107
+
108
+ ```vega-lite
109
+ {
110
+ "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
111
+ "title": {
112
+ "text": "Ad Spend vs Revenue",
113
+ "subtitle": "Campaign-level correlation for the selected month"
114
+ },
115
+ "data": {
116
+ "values": [
117
+ { "campaign": "Search", "spend": 1200, "revenue": 5400 },
118
+ { "campaign": "Social", "spend": 900, "revenue": 3100 },
119
+ { "campaign": "Email", "spend": 350, "revenue": 2200 },
120
+ { "campaign": "Affiliates", "spend": 700, "revenue": 3600 }
121
+ ]
122
+ },
123
+ "mark": { "type": "point", "filled": true, "size": 120 },
124
+ "encoding": {
125
+ "x": { "field": "spend", "type": "quantitative", "title": "Ad Spend" },
126
+ "y": { "field": "revenue", "type": "quantitative", "title": "Revenue" },
127
+ "tooltip": [
128
+ { "field": "campaign", "type": "nominal", "title": "Campaign" },
129
+ { "field": "spend", "type": "quantitative", "title": "Ad Spend" },
130
+ { "field": "revenue", "type": "quantitative", "title": "Revenue" }
131
+ ]
132
+ }
133
+ }
134
+ ```
135
+
136
+ ### Pie chart
137
+
138
+ ```vega-lite
139
+ {
140
+ "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
141
+ "title": {
142
+ "text": "Tickets by Status",
143
+ "subtitle": "Share of tickets in each workflow state"
144
+ },
145
+ "data": {
146
+ "values": [
147
+ { "status": "Open", "count": 42 },
148
+ { "status": "In Progress", "count": 27 },
149
+ { "status": "Resolved", "count": 58 }
150
+ ]
151
+ },
152
+ "mark": { "type": "arc", "innerRadius": 40 },
153
+ "encoding": {
154
+ "theta": { "field": "count", "type": "quantitative", "title": "Tickets" },
155
+ "color": { "field": "status", "type": "nominal", "title": "Status" },
156
+ "tooltip": [
157
+ { "field": "status", "type": "nominal", "title": "Status" },
158
+ { "field": "count", "type": "quantitative", "title": "Tickets" }
159
+ ]
160
+ }
161
+ }
162
+ ```
163
+
164
+ ### Histogram
165
+
166
+ ```vega-lite
167
+ {
168
+ "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
169
+ "title": {
170
+ "text": "Order Value Distribution",
171
+ "subtitle": "Histogram of order totals in the filtered result set"
172
+ },
173
+ "data": {
174
+ "values": [
175
+ { "order_total": 24 },
176
+ { "order_total": 31 },
177
+ { "order_total": 39 },
178
+ { "order_total": 42 },
179
+ { "order_total": 63 },
180
+ { "order_total": 78 },
181
+ { "order_total": 95 }
182
+ ]
183
+ },
184
+ "mark": "bar",
185
+ "encoding": {
186
+ "x": { "bin": true, "field": "order_total", "type": "quantitative", "title": "Order Total" },
187
+ "y": { "aggregate": "count", "type": "quantitative", "title": "Count" },
188
+ "tooltip": [
189
+ { "aggregate": "count", "type": "quantitative", "title": "Count" }
190
+ ]
191
+ }
192
+ }
193
+ ```
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ import { MemorySaver } from "@langchain/langgraph";
21
21
  import { createAgentChatModel, callAgent, } from "./agent/simpleAgent.js";
22
22
  import { AdminForthCheckpointSaver } from "./agent/checkpointer.js";
23
23
  import { createSequenceDebugCollector } from "./agent/middleware/sequenceDebug.js";
24
+ import { detectUserLanguage, formatLanguagePrompt, } from "./agent/languageDetect.js";
24
25
  import { prepareApiBasedTools as buildApiBasedTools, } from './apiBasedTools.js';
25
26
  import { appendCustomSystemPrompt, buildAgentSystemPrompt, DEFAULT_AGENT_SYSTEM_PROMPT, } from "./agent/systemPrompt.js";
26
27
  import { ALWAYS_AVAILABLE_API_TOOL_NAMES } from "./agent/tools/index.js";
@@ -59,7 +60,6 @@ function formatAdminUserPrompt(adminUser, usernameField) {
59
60
  ].join("\n");
60
61
  }
61
62
  function formatCurrentPagePrompt(currentPage) {
62
- console.log("Current page context:", currentPage);
63
63
  if (!currentPage) {
64
64
  return null;
65
65
  }
@@ -306,9 +306,15 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
306
306
  const maxTokens = (_f = this.options.maxTokens) !== null && _f !== void 0 ? _f : 10000;
307
307
  const selectedMode = (_g = this.options.modes.find((mode) => mode.name === body.mode)) !== null && _g !== void 0 ? _g : this.options.modes[0];
308
308
  const { model, summaryModel, modelMiddleware } = yield this.getModeModels(selectedMode, maxTokens);
309
+ const userLanguage = yield detectUserLanguage(selectedMode.completionAdapter, prompt)
310
+ .catch((error) => {
311
+ logger.warn(`Failed to detect user language: ${error instanceof Error ? error.message : String(error)}`);
312
+ return null;
313
+ });
309
314
  const systemPrompt = [
310
315
  yield this.agentSystemPromptPromise,
311
316
  formatAdminUserPrompt(adminUser, this.adminforth.config.auth.usernameField),
317
+ formatLanguagePrompt(userLanguage),
312
318
  ].join("\n\n");
313
319
  const apiBasedTools = buildApiBasedTools(this.adminforth);
314
320
  for (const toolName of ALWAYS_AVAILABLE_API_TOOL_NAMES) {
package/index.ts CHANGED
@@ -18,6 +18,10 @@ import {
18
18
  } from "./agent/simpleAgent.js";
19
19
  import { AdminForthCheckpointSaver } from "./agent/checkpointer.js";
20
20
  import { createSequenceDebugCollector } from "./agent/middleware/sequenceDebug.js";
21
+ import {
22
+ detectUserLanguage,
23
+ formatLanguagePrompt,
24
+ } from "./agent/languageDetect.js";
21
25
  import {
22
26
  prepareApiBasedTools as buildApiBasedTools,
23
27
  } from './apiBasedTools.js';
@@ -84,7 +88,6 @@ function formatAdminUserPrompt(adminUser: AdminUser, usernameField: string) {
84
88
  }
85
89
 
86
90
  function formatCurrentPagePrompt(currentPage: CurrentPageContext | undefined) {
87
- console.log("Current page context:", currentPage);
88
91
  if (!currentPage) {
89
92
  return null;
90
93
  }
@@ -379,9 +382,15 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
379
382
  const selectedMode = this.options.modes.find((mode) => mode.name === body.mode) ?? this.options.modes[0];
380
383
  const { model, summaryModel, modelMiddleware } =
381
384
  await this.getModeModels(selectedMode, maxTokens);
385
+ const userLanguage = await detectUserLanguage(selectedMode.completionAdapter, prompt)
386
+ .catch((error) => {
387
+ logger.warn(`Failed to detect user language: ${error instanceof Error ? error.message : String(error)}`);
388
+ return null;
389
+ });
382
390
  const systemPrompt = [
383
391
  await this.agentSystemPromptPromise,
384
392
  formatAdminUserPrompt(adminUser, this.adminforth.config.auth.usernameField),
393
+ formatLanguagePrompt(userLanguage),
385
394
  ].join("\n\n");
386
395
  const apiBasedTools = buildApiBasedTools(this.adminforth);
387
396
  for (const toolName of ALWAYS_AVAILABLE_API_TOOL_NAMES) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/agent",
3
- "version": "1.28.0",
3
+ "version": "1.30.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",