@adminforth/agent 1.1.1 → 1.2.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/agent/middleware/openAiResponsesContinuation.ts +92 -0
- package/agent/middleware/sequenceDebug.ts +26 -0
- package/agent/simpleAgent.ts +11 -1
- package/build.log +4 -2
- package/custom/Message.vue +7 -3
- package/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +78 -3
- package/custom/package.json +2 -1
- package/custom/pnpm-lock.yaml +721 -0
- package/custom/skills/data-analytics/SKILL.md +209 -0
- package/dist/agent/middleware/openAiResponsesContinuation.js +66 -0
- package/dist/agent/middleware/sequenceDebug.js +9 -0
- package/dist/agent/simpleAgent.js +6 -2
- package/dist/custom/Message.vue +7 -3
- package/dist/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +78 -3
- package/dist/custom/package.json +2 -1
- package/dist/custom/pnpm-lock.yaml +721 -0
- package/dist/custom/skills/data-analytics/SKILL.md +209 -0
- package/package.json +1 -1
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { AIMessage } from "@langchain/core/messages";
|
|
2
|
+
import { createMiddleware } from "langchain";
|
|
3
|
+
|
|
4
|
+
type OpenAiResponsesMetadata = {
|
|
5
|
+
id?: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type OpenAiResponsesContext = {
|
|
9
|
+
sessionId: string;
|
|
10
|
+
turnId: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function getTurnKey(context: OpenAiResponsesContext) {
|
|
14
|
+
return `${context.sessionId}:${context.turnId}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getResponseId(message: AIMessage) {
|
|
18
|
+
const metadata = message.response_metadata as OpenAiResponsesMetadata | undefined;
|
|
19
|
+
return metadata?.id ?? null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getPreviousResponseId(modelSettings?: Record<string, unknown>) {
|
|
23
|
+
return (modelSettings as { previous_response_id?: string } | undefined)
|
|
24
|
+
?.previous_response_id;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getContinuationMessages<T extends { response_metadata?: unknown }>(
|
|
28
|
+
messages: T[],
|
|
29
|
+
previousResponseId: string,
|
|
30
|
+
) {
|
|
31
|
+
let continuationStartIndex: number | null = null;
|
|
32
|
+
|
|
33
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
34
|
+
const message = messages[index];
|
|
35
|
+
|
|
36
|
+
if (
|
|
37
|
+
AIMessage.isInstance(message) &&
|
|
38
|
+
(message.response_metadata as OpenAiResponsesMetadata | undefined)?.id ===
|
|
39
|
+
previousResponseId
|
|
40
|
+
) {
|
|
41
|
+
continuationStartIndex = index + 1;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (continuationStartIndex === null) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return messages.slice(continuationStartIndex);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function createOpenAiResponsesContinuationMiddleware() {
|
|
54
|
+
const responseIdsByTurn = new Map<string, string>();
|
|
55
|
+
|
|
56
|
+
return createMiddleware({
|
|
57
|
+
name: "OpenAiResponsesContinuationMiddleware",
|
|
58
|
+
async wrapModelCall(request, handler) {
|
|
59
|
+
const context = request.runtime.context as OpenAiResponsesContext;
|
|
60
|
+
const turnKey = getTurnKey(context);
|
|
61
|
+
const previousResponseId =
|
|
62
|
+
getPreviousResponseId(request.modelSettings) ??
|
|
63
|
+
responseIdsByTurn.get(turnKey);
|
|
64
|
+
const continuationMessages = previousResponseId
|
|
65
|
+
? getContinuationMessages(request.messages, previousResponseId)
|
|
66
|
+
: null;
|
|
67
|
+
|
|
68
|
+
const response = await handler(
|
|
69
|
+
previousResponseId && continuationMessages
|
|
70
|
+
? {
|
|
71
|
+
...request,
|
|
72
|
+
messages: continuationMessages,
|
|
73
|
+
modelSettings: {
|
|
74
|
+
...request.modelSettings,
|
|
75
|
+
previous_response_id: previousResponseId,
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
: request,
|
|
79
|
+
) as AIMessage;
|
|
80
|
+
|
|
81
|
+
const responseId = getResponseId(response);
|
|
82
|
+
|
|
83
|
+
if (responseId) {
|
|
84
|
+
responseIdsByTurn.set(turnKey, responseId);
|
|
85
|
+
} else {
|
|
86
|
+
responseIdsByTurn.delete(turnKey);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return response;
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -23,6 +23,8 @@ export type SequenceDebug = {
|
|
|
23
23
|
prompt: string;
|
|
24
24
|
reasoning: string;
|
|
25
25
|
text: string;
|
|
26
|
+
cachedTokens: number;
|
|
27
|
+
responseId: string | null;
|
|
26
28
|
toolCalls: SequenceDebugToolCall[];
|
|
27
29
|
endedAt: string;
|
|
28
30
|
resultType: SequenceDebugResultType;
|
|
@@ -37,9 +39,21 @@ type PendingSequenceDebug = Omit<SequenceDebug, "toolCalls" | "endedAt" | "resul
|
|
|
37
39
|
type SequenceDebugModelCall = {
|
|
38
40
|
reasoning: string;
|
|
39
41
|
text: string;
|
|
42
|
+
cachedTokens: number;
|
|
43
|
+
responseId: string | null;
|
|
40
44
|
resultType: SequenceDebugResultType;
|
|
41
45
|
};
|
|
42
46
|
|
|
47
|
+
type OpenAiUsageMetadata = {
|
|
48
|
+
input_token_details?: {
|
|
49
|
+
cache_read?: number;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type OpenAiResponseMetadata = {
|
|
54
|
+
id?: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
43
57
|
export type SequenceDebugModelCallSink = {
|
|
44
58
|
handleModelCallStart: (prompt: string) => void;
|
|
45
59
|
handleModelCallComplete: (params: SequenceDebugModelCall) => void;
|
|
@@ -58,6 +72,8 @@ function createPendingSequenceDebug(sequenceId: number): PendingSequenceDebug {
|
|
|
58
72
|
prompt: "",
|
|
59
73
|
reasoning: "",
|
|
60
74
|
text: "",
|
|
75
|
+
cachedTokens: 0,
|
|
76
|
+
responseId: null,
|
|
61
77
|
toolCalls: [],
|
|
62
78
|
pendingToolCalls: 0,
|
|
63
79
|
resultType: null,
|
|
@@ -83,6 +99,8 @@ function finalizeSequenceDebug(sequence: PendingSequenceDebug): SequenceDebug {
|
|
|
83
99
|
prompt: sequence.prompt,
|
|
84
100
|
reasoning: sequence.reasoning,
|
|
85
101
|
text: sequence.text,
|
|
102
|
+
cachedTokens: sequence.cachedTokens,
|
|
103
|
+
responseId: sequence.responseId,
|
|
86
104
|
toolCalls: sequence.toolCalls.map(({ completed: _completed, ...toolCall }) => toolCall),
|
|
87
105
|
endedAt: new Date().toISOString(),
|
|
88
106
|
resultType: sequence.resultType ?? "final_text",
|
|
@@ -172,6 +190,12 @@ function extractSequenceResponseDebug(message: AIMessage): SequenceDebugModelCal
|
|
|
172
190
|
return {
|
|
173
191
|
reasoning,
|
|
174
192
|
text: textFromBlocks || (typeof message.content === "string" ? message.content : ""),
|
|
193
|
+
cachedTokens:
|
|
194
|
+
(message.usage_metadata as OpenAiUsageMetadata | undefined)
|
|
195
|
+
?.input_token_details?.cache_read ?? 0,
|
|
196
|
+
responseId:
|
|
197
|
+
(message.response_metadata as OpenAiResponseMetadata | undefined)?.id ??
|
|
198
|
+
null,
|
|
175
199
|
resultType: hasToolCallSignal(message) ? "tool_calls" : "final_text",
|
|
176
200
|
};
|
|
177
201
|
}
|
|
@@ -216,6 +240,8 @@ export function createSequenceDebugCollector(): SequenceDebugCollector {
|
|
|
216
240
|
const sequenceDebug = ensureSequenceDebug();
|
|
217
241
|
sequenceDebug.reasoning = params.reasoning;
|
|
218
242
|
sequenceDebug.text = params.text;
|
|
243
|
+
sequenceDebug.cachedTokens = params.cachedTokens;
|
|
244
|
+
sequenceDebug.responseId = params.responseId;
|
|
219
245
|
sequenceDebug.resultType = params.resultType;
|
|
220
246
|
|
|
221
247
|
if (
|
package/agent/simpleAgent.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
createSequenceDebugMiddleware,
|
|
12
12
|
type SequenceDebugModelCallSink,
|
|
13
13
|
} from "./middleware/sequenceDebug.js";
|
|
14
|
+
import { createOpenAiResponsesContinuationMiddleware } from "./middleware/openAiResponsesContinuation.js";
|
|
14
15
|
import type { ApiBasedTool } from "../apiBasedTools.js";
|
|
15
16
|
import type { ToolCallEventSink } from "./toolCallEvents.js";
|
|
16
17
|
|
|
@@ -199,12 +200,18 @@ export function createAgentChatModel(params: {
|
|
|
199
200
|
const baseURL = options.baseURL ?? options.baseUrl;
|
|
200
201
|
const reasoning = normalizeReasoning(params.reasoning);
|
|
201
202
|
|
|
203
|
+
// @ts-ignore
|
|
202
204
|
return new ChatOpenAI({
|
|
203
205
|
apiKey: options.openAiApiKey,
|
|
204
206
|
model,
|
|
205
207
|
maxTokens: params.maxTokens,
|
|
206
208
|
useResponsesApi: true,
|
|
207
209
|
outputVersion: "v1",
|
|
210
|
+
|
|
211
|
+
promptCacheKey: `adminforth-agent:${model}:system-v1:tools-v1`,
|
|
212
|
+
|
|
213
|
+
promptCacheRetention: "in_memory",
|
|
214
|
+
|
|
208
215
|
...(reasoning ? { reasoning } : {}),
|
|
209
216
|
...(typeof options.timeoutMs === "number"
|
|
210
217
|
? { timeout: options.timeoutMs }
|
|
@@ -250,16 +257,19 @@ export async function callAgent(params: {
|
|
|
250
257
|
|
|
251
258
|
const tools = await createAgentTools(customComponentsDir, apiBasedTools);
|
|
252
259
|
const apiBasedToolsMiddleware = createApiBasedToolsMiddleware(apiBasedTools);
|
|
260
|
+
const openAiResponsesContinuationMiddleware =
|
|
261
|
+
createOpenAiResponsesContinuationMiddleware();
|
|
253
262
|
const sequenceDebugMiddleware = createSequenceDebugMiddleware(
|
|
254
263
|
sequenceDebugSink,
|
|
255
264
|
);
|
|
256
265
|
|
|
257
266
|
const middleware = [
|
|
258
267
|
apiBasedToolsMiddleware,
|
|
268
|
+
openAiResponsesContinuationMiddleware,
|
|
259
269
|
sequenceDebugMiddleware,
|
|
260
270
|
summarizationMiddleware({
|
|
261
271
|
model: summaryModel,
|
|
262
|
-
trigger: { tokens: 1024 *
|
|
272
|
+
trigger: { tokens: 1024 * 128 },
|
|
263
273
|
keep: { messages: 10 },
|
|
264
274
|
}),
|
|
265
275
|
] as const;
|
package/build.log
CHANGED
|
@@ -21,10 +21,12 @@ custom/incremark_code_renderers/incremarkCodeHighlight.ts
|
|
|
21
21
|
custom/incremark_code_renderers/incremarkRenderer.ts
|
|
22
22
|
custom/incremark_code_renderers/renderIncremarkMarkdown.ts
|
|
23
23
|
custom/skills/
|
|
24
|
+
custom/skills/data-analytics/
|
|
25
|
+
custom/skills/data-analytics/SKILL.md
|
|
24
26
|
custom/skills/fetch_data/
|
|
25
27
|
custom/skills/fetch_data/SKILL.md
|
|
26
28
|
custom/skills/mutate_data/
|
|
27
29
|
custom/skills/mutate_data/SKILL.md
|
|
28
30
|
|
|
29
|
-
sent
|
|
30
|
-
total size is
|
|
31
|
+
sent 166,886 bytes received 394 bytes 334,560.00 bytes/sec
|
|
32
|
+
total size is 165,300 speedup is 0.99
|
package/custom/Message.vue
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
<div
|
|
3
3
|
class="max-w-[80%] flex px-4 m-2 rounded-xl border border-gray-200 dark:border-gray-700"
|
|
4
4
|
@click="handleMarkdownLinkClick"
|
|
5
|
-
:class="
|
|
5
|
+
:class="[
|
|
6
|
+
hasVegaLite ? 'w-full' : '',
|
|
7
|
+
props.role === 'user' ? 'bg-lightListTableHeading dark:bg-darkListTableHeading self-end'
|
|
6
8
|
: isTypeReasoning || isTypeToolCall ? 'bg-transparent border-none self-start'
|
|
7
|
-
: 'bg-blue-100 dark:bg-blue-700/10 self-start'
|
|
9
|
+
: 'bg-blue-100 dark:bg-blue-700/10 self-start'
|
|
10
|
+
]"
|
|
8
11
|
>
|
|
9
12
|
<IncremarkContent
|
|
10
|
-
class="text-wrap break-words max-w-full"
|
|
13
|
+
class="text-wrap break-words w-full max-w-full"
|
|
11
14
|
v-if="content && props.type === 'text'"
|
|
12
15
|
:content="content"
|
|
13
16
|
:is-finished="isFinished"
|
|
@@ -88,6 +91,7 @@
|
|
|
88
91
|
const content = computed(() => props.message)
|
|
89
92
|
const isFinished = computed(() => props.state === 'done')
|
|
90
93
|
const isThoughtsExpanded = ref(false)
|
|
94
|
+
const hasVegaLite = computed(() => props.type === 'text' && props.message.includes('```vega-lite'))
|
|
91
95
|
|
|
92
96
|
const isTypeReasoning = computed(() => props.type === 'reasoning')
|
|
93
97
|
const isTypeToolCall = computed(() => props.type === 'data-tool-call')
|
|
@@ -18,7 +18,13 @@
|
|
|
18
18
|
|
|
19
19
|
<div class="incremark-shiki-body">
|
|
20
20
|
<div
|
|
21
|
-
v-if="renderedHtml"
|
|
21
|
+
v-if="shouldRenderVega && !renderedHtml"
|
|
22
|
+
ref="vegaContainer"
|
|
23
|
+
class="incremark-vega"
|
|
24
|
+
/>
|
|
25
|
+
|
|
26
|
+
<div
|
|
27
|
+
v-else-if="renderedHtml"
|
|
22
28
|
class="incremark-shiki-html"
|
|
23
29
|
v-html="renderedHtml"
|
|
24
30
|
/>
|
|
@@ -31,6 +37,7 @@
|
|
|
31
37
|
<script setup lang="ts">
|
|
32
38
|
import type { Code } from 'mdast';
|
|
33
39
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|
40
|
+
import embed from 'vega-embed';
|
|
34
41
|
|
|
35
42
|
import { highlightCodeSnippetHtml, type IncremarkCodeTheme } from './incremarkCodeHighlight';
|
|
36
43
|
|
|
@@ -53,15 +60,18 @@ const props = withDefaults(defineProps<{
|
|
|
53
60
|
const renderedHtml = ref('');
|
|
54
61
|
const copied = ref(false);
|
|
55
62
|
const prefersDarkMode = ref(isDarkDocument());
|
|
63
|
+
const vegaContainer = ref<HTMLDivElement | null>(null);
|
|
56
64
|
|
|
57
65
|
let copyResetTimeout: number | null = null;
|
|
58
66
|
let renderRequestId = 0;
|
|
59
67
|
let scheduledFrameId: number | null = null;
|
|
60
68
|
let themeObserver: MutationObserver | null = null;
|
|
69
|
+
let vegaResult: { finalize: () => void } | null = null;
|
|
61
70
|
|
|
62
71
|
const sourceCode = computed(() => props.node.value ?? '');
|
|
63
72
|
const language = computed(() => props.node.lang?.trim().toLowerCase() || 'text');
|
|
64
|
-
const languageLabel = computed(() => props.node.lang?.trim() || 'text');
|
|
73
|
+
const languageLabel = computed(() => language.value === 'vega-lite' ? '' : props.node.lang?.trim() || 'text');
|
|
74
|
+
const shouldRenderVega = computed(() => language.value === 'vega-lite' && props.blockStatus === 'completed');
|
|
65
75
|
const codeTheme = computed<IncremarkCodeTheme>(() => {
|
|
66
76
|
const requestedTheme = props.theme ?? (prefersDarkMode.value ? props.darkTheme : props.lightTheme);
|
|
67
77
|
|
|
@@ -77,7 +87,7 @@ const codeTheme = computed<IncremarkCodeTheme>(() => {
|
|
|
77
87
|
});
|
|
78
88
|
|
|
79
89
|
watch(
|
|
80
|
-
[sourceCode, language, codeTheme, () => props.disableHighlight],
|
|
90
|
+
[sourceCode, language, codeTheme, () => props.disableHighlight, () => props.blockStatus],
|
|
81
91
|
() => {
|
|
82
92
|
scheduleHighlight();
|
|
83
93
|
},
|
|
@@ -86,6 +96,7 @@ watch(
|
|
|
86
96
|
|
|
87
97
|
onMounted(() => {
|
|
88
98
|
if (typeof MutationObserver === 'undefined' || typeof document === 'undefined') {
|
|
99
|
+
scheduleHighlight();
|
|
89
100
|
return;
|
|
90
101
|
}
|
|
91
102
|
|
|
@@ -97,10 +108,13 @@ onMounted(() => {
|
|
|
97
108
|
attributes: true,
|
|
98
109
|
attributeFilter: ['class'],
|
|
99
110
|
});
|
|
111
|
+
|
|
112
|
+
scheduleHighlight();
|
|
100
113
|
});
|
|
101
114
|
|
|
102
115
|
onBeforeUnmount(() => {
|
|
103
116
|
renderRequestId += 1;
|
|
117
|
+
clearVega();
|
|
104
118
|
|
|
105
119
|
if (copyResetTimeout !== null) {
|
|
106
120
|
window.clearTimeout(copyResetTimeout);
|
|
@@ -154,6 +168,45 @@ function scheduleHighlight() {
|
|
|
154
168
|
async function renderHighlight() {
|
|
155
169
|
const requestId = ++renderRequestId;
|
|
156
170
|
|
|
171
|
+
if (shouldRenderVega.value) {
|
|
172
|
+
renderedHtml.value = '';
|
|
173
|
+
|
|
174
|
+
if (!sourceCode.value || !vegaContainer.value) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
clearVega();
|
|
180
|
+
const spec = JSON.parse(sourceCode.value);
|
|
181
|
+
|
|
182
|
+
if (spec.width == null) {
|
|
183
|
+
spec.width = 'container';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (spec.autosize == null) {
|
|
187
|
+
spec.autosize = { type: 'fit-x', contains: 'padding' };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const result = await embed(vegaContainer.value, spec, {
|
|
191
|
+
actions: false,
|
|
192
|
+
renderer: 'svg',
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
if (requestId !== renderRequestId) {
|
|
196
|
+
result.finalize();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
vegaResult = result;
|
|
201
|
+
return;
|
|
202
|
+
} catch (error) {
|
|
203
|
+
clearVega();
|
|
204
|
+
console.error('Failed to render Vega-Lite block', error);
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
clearVega();
|
|
208
|
+
}
|
|
209
|
+
|
|
157
210
|
if (!sourceCode.value || props.disableHighlight) {
|
|
158
211
|
renderedHtml.value = '';
|
|
159
212
|
return;
|
|
@@ -177,6 +230,15 @@ async function renderHighlight() {
|
|
|
177
230
|
function isDarkDocument(): boolean {
|
|
178
231
|
return typeof document !== 'undefined' && document.documentElement.classList.contains('dark');
|
|
179
232
|
}
|
|
233
|
+
|
|
234
|
+
function clearVega() {
|
|
235
|
+
vegaResult?.finalize();
|
|
236
|
+
vegaResult = null;
|
|
237
|
+
|
|
238
|
+
if (vegaContainer.value) {
|
|
239
|
+
vegaContainer.value.innerHTML = '';
|
|
240
|
+
}
|
|
241
|
+
}
|
|
180
242
|
</script>
|
|
181
243
|
|
|
182
244
|
<style scoped>
|
|
@@ -265,6 +327,11 @@ function isDarkDocument(): boolean {
|
|
|
265
327
|
overflow-x: auto;
|
|
266
328
|
}
|
|
267
329
|
|
|
330
|
+
.incremark-vega {
|
|
331
|
+
padding: 18px;
|
|
332
|
+
width: 100%;
|
|
333
|
+
}
|
|
334
|
+
|
|
268
335
|
.incremark-shiki-fallback {
|
|
269
336
|
margin: 0;
|
|
270
337
|
padding: 18px;
|
|
@@ -298,4 +365,12 @@ function isDarkDocument(): boolean {
|
|
|
298
365
|
:deep(.incremark-shiki-html .line) {
|
|
299
366
|
min-height: 1.65em;
|
|
300
367
|
}
|
|
368
|
+
|
|
369
|
+
:deep(.incremark-vega .vega-embed) {
|
|
370
|
+
width: 100%;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
:deep(.incremark-vega){
|
|
374
|
+
padding: 0;
|
|
375
|
+
}
|
|
301
376
|
</style>
|