@adminforth/agent 1.50.1 → 1.51.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 (54) hide show
  1. package/agent/middleware/apiBasedTools.ts +13 -3
  2. package/agent/models/AgentModeResolver.ts +9 -0
  3. package/agent/models/AgentModelFactory.ts +28 -0
  4. package/agent/runtime/AgentContext.ts +30 -0
  5. package/agent/runtime/AgentRuntime.ts +68 -0
  6. package/agent/simpleAgent.ts +2 -129
  7. package/agent/speech/SpeechTurnService.ts +179 -0
  8. package/agent/tools/AgentToolProvider.ts +28 -0
  9. package/agent/tools/navigateUser.ts +2 -2
  10. package/agent/turn/TurnContextBuilder.ts +36 -0
  11. package/agent/turn/TurnLifecycleService.ts +29 -0
  12. package/agent/turn/TurnPersistenceService.ts +33 -0
  13. package/agent/turn/TurnPromptBuilder.ts +51 -0
  14. package/agent/turn/TurnStreamConsumer.ts +61 -0
  15. package/agent/turn/VegaLiteStreamBuffer.ts +90 -0
  16. package/agent/turn/turnTypes.ts +92 -0
  17. package/agentTurnService.ts +88 -461
  18. package/build.log +1 -1
  19. package/dist/agent/middleware/apiBasedTools.js +9 -2
  20. package/dist/agent/models/AgentModeResolver.d.ts +9 -0
  21. package/dist/agent/models/AgentModeResolver.js +9 -0
  22. package/dist/agent/models/AgentModelFactory.d.ts +7 -0
  23. package/dist/agent/models/AgentModelFactory.js +36 -0
  24. package/dist/agent/runtime/AgentContext.d.ts +28 -0
  25. package/dist/agent/runtime/AgentContext.js +17 -0
  26. package/dist/agent/runtime/AgentRuntime.d.ts +15 -0
  27. package/dist/agent/runtime/AgentRuntime.js +57 -0
  28. package/dist/agent/simpleAgent.d.ts +15 -45
  29. package/dist/agent/simpleAgent.js +1 -67
  30. package/dist/agent/speech/SpeechTurnService.d.ts +6 -0
  31. package/dist/agent/speech/SpeechTurnService.js +168 -0
  32. package/dist/agent/tools/AgentToolProvider.d.ts +9 -0
  33. package/dist/agent/tools/AgentToolProvider.js +27 -0
  34. package/dist/agent/tools/navigateUser.js +1 -1
  35. package/dist/agent/turn/TurnContextBuilder.d.ts +14 -0
  36. package/dist/agent/turn/TurnContextBuilder.js +31 -0
  37. package/dist/agent/turn/TurnLifecycleService.d.ts +17 -0
  38. package/dist/agent/turn/TurnLifecycleService.js +31 -0
  39. package/dist/agent/turn/TurnPersistenceService.d.ts +13 -0
  40. package/dist/agent/turn/TurnPersistenceService.js +35 -0
  41. package/dist/agent/turn/TurnPromptBuilder.d.ts +19 -0
  42. package/dist/agent/turn/TurnPromptBuilder.js +43 -0
  43. package/dist/agent/turn/TurnStreamConsumer.d.ts +10 -0
  44. package/dist/agent/turn/TurnStreamConsumer.js +78 -0
  45. package/dist/agent/turn/VegaLiteStreamBuffer.d.ts +7 -0
  46. package/dist/agent/turn/VegaLiteStreamBuffer.js +87 -0
  47. package/dist/agent/turn/turnTypes.d.ts +81 -0
  48. package/dist/agent/turn/turnTypes.js +1 -0
  49. package/dist/agentTurnService.d.ts +20 -69
  50. package/dist/agentTurnService.js +60 -373
  51. package/dist/index.d.ts +1 -0
  52. package/dist/index.js +22 -7
  53. package/index.ts +35 -7
  54. package/package.json +1 -1
@@ -0,0 +1,51 @@
1
+ import type { AdminUser, IAdminForth } from "adminforth";
2
+ import { logger } from "adminforth";
3
+ import { HumanMessage, SystemMessage } from "langchain";
4
+ import { detectUserLanguage, type PreviousUserMessage } from "../languageDetect.js";
5
+ import { buildAgentTurnSystemPrompt } from "../systemPrompt.js";
6
+ import { getErrorMessage, isAbortError } from "../../errors.js";
7
+ import type { AgentModeCompletionAdapter } from "../simpleAgent.js";
8
+
9
+ export class TurnPromptBuilder {
10
+ constructor(
11
+ private readonly options: {
12
+ getAgentSystemPrompt: () => Promise<string>;
13
+ getAdminforth: () => IAdminForth;
14
+ },
15
+ ) {}
16
+
17
+ async build(input: {
18
+ prompt: string;
19
+ previousUserMessages: PreviousUserMessage[];
20
+ adminUser: AdminUser;
21
+ completionAdapter: AgentModeCompletionAdapter;
22
+ chatSurface?: string;
23
+ abortSignal?: AbortSignal;
24
+ }) {
25
+ const adminforth = this.options.getAdminforth();
26
+ const userLanguage = await detectUserLanguage(
27
+ input.completionAdapter,
28
+ input.prompt,
29
+ input.previousUserMessages,
30
+ ).catch((error) => {
31
+ if (input.abortSignal?.aborted || isAbortError(error)) {
32
+ throw error;
33
+ }
34
+
35
+ logger.warn(`Failed to detect user language: ${getErrorMessage(error)}`);
36
+ return null;
37
+ });
38
+ const systemPrompt = buildAgentTurnSystemPrompt({
39
+ agentSystemPrompt: await this.options.getAgentSystemPrompt(),
40
+ adminUser: input.adminUser,
41
+ usernameField: adminforth.config.auth!.usernameField,
42
+ userLanguage,
43
+ chatSurface: input.chatSurface,
44
+ });
45
+
46
+ return [
47
+ new SystemMessage(systemPrompt),
48
+ new HumanMessage(input.prompt),
49
+ ];
50
+ }
51
+ }
@@ -0,0 +1,61 @@
1
+ import type { AgentEventEmitter } from "../../agentEvents.js";
2
+ import { VegaLiteStreamBuffer } from "./VegaLiteStreamBuffer.js";
3
+
4
+ export class TurnStreamConsumer {
5
+ async consume(input: {
6
+ stream: AsyncIterable<[any, any]>;
7
+ abortSignal?: AbortSignal;
8
+ emit?: AgentEventEmitter;
9
+ }) {
10
+ let fullResponse = "";
11
+ const textBuffer = new VegaLiteStreamBuffer();
12
+
13
+ for await (const rawChunk of input.stream) {
14
+ if (input.abortSignal?.aborted) {
15
+ throw new DOMException("This operation was aborted", "AbortError");
16
+ }
17
+
18
+ const [token, metadata] = rawChunk;
19
+ const nodeName =
20
+ typeof metadata?.langgraph_node === "string"
21
+ ? metadata.langgraph_node
22
+ : "";
23
+
24
+ if (nodeName && !["model", "model_request"].includes(nodeName)) {
25
+ continue;
26
+ }
27
+
28
+ const blocks = Array.isArray(token?.contentBlocks)
29
+ ? token.contentBlocks
30
+ : Array.isArray(token?.content)
31
+ ? token.content
32
+ : [];
33
+ const reasoningDelta = blocks
34
+ .filter((block: any) => block?.type === "reasoning")
35
+ .map((block: any) => String(block.reasoning ?? ""))
36
+ .join("");
37
+ const textDelta = blocks
38
+ .filter((block: any) => block?.type === "text")
39
+ .map((block: any) => String(block.text ?? ""))
40
+ .join("");
41
+
42
+ if (reasoningDelta) {
43
+ await input.emit?.({
44
+ type: "reasoning-delta",
45
+ delta: reasoningDelta,
46
+ });
47
+ }
48
+
49
+ if (textDelta) {
50
+ fullResponse += textDelta;
51
+ await textBuffer.push(textDelta, input.emit);
52
+ }
53
+ }
54
+
55
+ await textBuffer.flush(input.emit);
56
+
57
+ return {
58
+ text: fullResponse,
59
+ };
60
+ }
61
+ }
@@ -0,0 +1,90 @@
1
+ import type { AgentEventEmitter } from "../../agentEvents.js";
2
+
3
+ const VEGA_LITE_FENCE_START = "```vega-lite";
4
+ const FENCE_END = "```";
5
+
6
+ export class VegaLiteStreamBuffer {
7
+ private bufferedTextDelta = "";
8
+ private isRenderingVegaLite = false;
9
+
10
+ async push(textDelta: string, emit?: AgentEventEmitter) {
11
+ this.bufferedTextDelta += textDelta;
12
+
13
+ if (
14
+ hasUnclosedLatestVegaLiteBlock(this.bufferedTextDelta)
15
+ ) {
16
+ if (!this.isRenderingVegaLite) {
17
+ this.isRenderingVegaLite = true;
18
+ await emit?.({
19
+ type: "rendering",
20
+ phase: "start",
21
+ label: "Rendering...",
22
+ });
23
+ }
24
+ return;
25
+ }
26
+
27
+ if (this.isRenderingVegaLite) {
28
+ this.isRenderingVegaLite = false;
29
+ await emit?.({
30
+ type: "rendering",
31
+ phase: "end",
32
+ label: "Rendering...",
33
+ });
34
+ }
35
+
36
+ const streamableLength = this.bufferedTextDelta.includes(VEGA_LITE_FENCE_START)
37
+ ? this.bufferedTextDelta.length
38
+ : this.bufferedTextDelta.length - getPartialVegaLiteFenceStartLength(this.bufferedTextDelta);
39
+
40
+ if (!streamableLength) {
41
+ return;
42
+ }
43
+
44
+ await emit?.({
45
+ type: "text-delta",
46
+ delta: this.bufferedTextDelta.slice(0, streamableLength),
47
+ });
48
+ this.bufferedTextDelta = this.bufferedTextDelta.slice(streamableLength);
49
+ }
50
+
51
+ async flush(emit?: AgentEventEmitter) {
52
+ if (this.isRenderingVegaLite) {
53
+ await emit?.({
54
+ type: "rendering",
55
+ phase: "end",
56
+ label: "Rendering...",
57
+ });
58
+ this.isRenderingVegaLite = false;
59
+ }
60
+
61
+ if (this.bufferedTextDelta) {
62
+ await emit?.({
63
+ type: "text-delta",
64
+ delta: this.bufferedTextDelta,
65
+ });
66
+ this.bufferedTextDelta = "";
67
+ }
68
+ }
69
+ }
70
+
71
+ function hasUnclosedLatestVegaLiteBlock(text: string): boolean {
72
+ const latestBlockStart = text.lastIndexOf(VEGA_LITE_FENCE_START);
73
+
74
+ if (latestBlockStart === -1) {
75
+ return false;
76
+ }
77
+
78
+ const blockContentStart = latestBlockStart + VEGA_LITE_FENCE_START.length;
79
+ return text.indexOf(FENCE_END, blockContentStart) === -1;
80
+ }
81
+
82
+ function getPartialVegaLiteFenceStartLength(text: string): number {
83
+ for (let length = Math.min(text.length, VEGA_LITE_FENCE_START.length - 1); length > 0; length -= 1) {
84
+ if (VEGA_LITE_FENCE_START.startsWith(text.slice(-length))) {
85
+ return length;
86
+ }
87
+ }
88
+
89
+ return 0;
90
+ }
@@ -0,0 +1,92 @@
1
+ import type { AdminUser, AudioAdapter } from "adminforth";
2
+ import type { Messages } from "@langchain/langgraph";
3
+ import type { AgentChatModel, AgentMiddleware } from "../simpleAgent.js";
4
+ import type { SequenceDebugCollector } from "../middleware/sequenceDebug.js";
5
+ import type { PreviousUserMessage } from "../languageDetect.js";
6
+ import type { CurrentPageContext } from "../tools/getUserLocation.js";
7
+ import type { AgentEventEmitter } from "../../agentEvents.js";
8
+
9
+ export type BaseAgentTurnInput = {
10
+ prompt: string;
11
+ sessionId: string;
12
+ modeName?: string | null;
13
+ userTimeZone?: string;
14
+ currentPage?: CurrentPageContext;
15
+ chatSurface?: string;
16
+ adminPublicOrigin?: string;
17
+ abortSignal?: AbortSignal;
18
+ adminUser: AdminUser;
19
+ };
20
+
21
+ export type TextAgentTurnInput = BaseAgentTurnInput & {
22
+ emit: AgentEventEmitter;
23
+ failureLogMessage?: string;
24
+ abortLogMessage?: string;
25
+ };
26
+
27
+ export type SpeechAgentTurnInput = Omit<BaseAgentTurnInput, "prompt"> & {
28
+ emit: AgentEventEmitter;
29
+ audioAdapter: AudioAdapter;
30
+ audio: {
31
+ buffer: Buffer;
32
+ filename: string;
33
+ mimeType: string;
34
+ };
35
+ failureLogMessage?: string;
36
+ abortLogMessage?: string;
37
+ };
38
+
39
+ export type AgentTurnContext = {
40
+ adminUser: AdminUser;
41
+ userTimeZone: string;
42
+ sessionId: string;
43
+ turnId: string;
44
+ abortSignal?: AbortSignal;
45
+ currentPage?: CurrentPageContext;
46
+ chatSurface?: string;
47
+ adminPublicOrigin?: string;
48
+ };
49
+
50
+ export type AgentTurnObservability = {
51
+ emit?: AgentEventEmitter;
52
+ sequenceDebugSink: SequenceDebugCollector;
53
+ };
54
+
55
+ export type PreparedAgentTurn = {
56
+ prompt: string;
57
+ sessionId: string;
58
+ turnId: string;
59
+ previousUserMessages: PreviousUserMessage[];
60
+ modeName?: string | null;
61
+ context: AgentTurnContext;
62
+ observability: AgentTurnObservability;
63
+ };
64
+
65
+ export type AgentTurnModels = {
66
+ model: AgentChatModel;
67
+ summaryModel: AgentChatModel;
68
+ modelMiddleware?: AgentMiddleware[];
69
+ };
70
+
71
+ export type AgentRuntimeRunInput = {
72
+ models: AgentTurnModels;
73
+ messages: Messages;
74
+ context: AgentTurnContext;
75
+ observability: AgentTurnObservability;
76
+ };
77
+
78
+ export type RunAndPersistAgentResponseInput = BaseAgentTurnInput & {
79
+ emit?: AgentEventEmitter;
80
+ failureLogMessage: string;
81
+ abortLogMessage: string;
82
+ };
83
+
84
+ export type RunAndPersistAgentResponseResult = {
85
+ text: string;
86
+ turnId: string;
87
+ aborted: boolean;
88
+ failed: boolean;
89
+ };
90
+
91
+ export type HandleTurnInput = TextAgentTurnInput;
92
+ export type HandleSpeechTurnInput = SpeechAgentTurnInput;