@adminforth/agent 2.0.10 → 2.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.
Files changed (201) hide show
  1. package/README.md +333 -10
  2. package/dist/application/ports.d.ts +66 -0
  3. package/dist/application/runTurnUseCase.d.ts +71 -0
  4. package/dist/application/runTurnUseCase.js +364 -0
  5. package/dist/custom/ChatFooter.vue +58 -30
  6. package/dist/custom/SteerDebugPanel.vue +45 -0
  7. package/dist/custom/SteerQueue.vue +54 -0
  8. package/dist/custom/composables/agentStore/constants.ts +1 -0
  9. package/dist/custom/composables/agentStore/useAgentSessions.ts +35 -16
  10. package/dist/custom/composables/agentStore/useAgentSteerQueue.ts +114 -0
  11. package/dist/custom/composables/useAgentStore.ts +46 -1
  12. package/dist/custom/conversation_area/MessageRenderer.vue +7 -1
  13. package/dist/custom/speech_recognition_frontend/AudioLines.vue +1 -1
  14. package/dist/custom/speech_recognition_frontend/MicrophoneButon.vue +3 -3
  15. package/dist/{agentEvents.d.ts → domain/agentEvents.d.ts} +5 -1
  16. package/dist/domain/steerBuffer.d.ts +33 -0
  17. package/dist/domain/steerBuffer.js +51 -0
  18. package/dist/{agent → domain}/systemPrompt.js +1 -1
  19. package/dist/{agent/turn → domain}/turnTypes.d.ts +41 -37
  20. package/dist/{agent/turn/VegaLiteStreamBuffer.d.ts → domain/vegaLiteStreamBuffer.d.ts} +1 -1
  21. package/dist/index.d.ts +4 -2
  22. package/dist/index.js +49 -35
  23. package/dist/{agent/runtime/AgentContext.d.ts → llm/agentContext.d.ts} +3 -3
  24. package/dist/{agent/simpleAgent.d.ts → llm/agentModels.d.ts} +18 -16
  25. package/dist/llm/agentModels.js +65 -0
  26. package/dist/{agent/runtime/AgentRuntime.d.ts → llm/agentRuntime.d.ts} +14 -2
  27. package/dist/{agent/runtime/AgentRuntime.js → llm/agentRuntime.js} +41 -6
  28. package/dist/llm/langGraphLlm.d.ts +25 -0
  29. package/dist/llm/langGraphLlm.js +54 -0
  30. package/dist/{agent/middleware/apiBasedTools.d.ts → llm/middleware/apiToolsMiddleware.d.ts} +1 -1
  31. package/dist/{agent/middleware/apiBasedTools.js → llm/middleware/apiToolsMiddleware.js} +8 -14
  32. package/dist/{agent → llm}/middleware/sequenceDebug.d.ts +1 -1
  33. package/dist/llm/middleware/steerMiddleware.d.ts +21 -0
  34. package/dist/llm/middleware/steerMiddleware.js +50 -0
  35. package/dist/{agent/models/AgentModelFactory.d.ts → llm/modelFactory.d.ts} +1 -1
  36. package/dist/{agent/models/AgentModelFactory.js → llm/modelFactory.js} +1 -1
  37. package/dist/llm/streamAdapter.d.ts +18 -0
  38. package/dist/llm/streamAdapter.js +117 -0
  39. package/dist/{sessionStore.d.ts → persistence/sessionStore.d.ts} +19 -2
  40. package/dist/{sessionStore.js → persistence/sessionStore.js} +53 -0
  41. package/dist/{agent/tools/AgentToolProvider.d.ts → tools/agentToolProvider.d.ts} +1 -1
  42. package/dist/{agent/tools/AgentToolProvider.js → tools/agentToolProvider.js} +1 -1
  43. package/dist/{apiBasedTools.js → tools/apiBasedTools.js} +0 -3
  44. package/dist/{agent/tools → tools}/apiTool.d.ts +1 -1
  45. package/dist/{agent/tools → tools}/fetchSkill.js +1 -1
  46. package/dist/{agent/tools → tools}/fetchToolSchema.d.ts +1 -1
  47. package/dist/{agent/tools → tools}/index.d.ts +1 -1
  48. package/dist/{agent/tools → tools}/navigateUser.d.ts +3 -3
  49. package/dist/{endpoints → transport/http}/context.d.ts +12 -4
  50. package/dist/transport/http/context.js +1 -0
  51. package/dist/{endpoints/core.js → transport/http/coreEndpoints.js} +23 -3
  52. package/dist/{endpoints/sessions.js → transport/http/sessionEndpoints.js} +36 -1
  53. package/dist/transport/sse/sseWriter.d.ts +29 -0
  54. package/dist/{surfaces/web-sse/createSseEventEmitter.js → transport/sse/sseWriter.js} +27 -0
  55. package/dist/{chatSurfaceService.d.ts → transport/surfaces/chatSurfaceService.d.ts} +3 -3
  56. package/dist/{chatSurfaceService.js → transport/surfaces/chatSurfaceService.js} +7 -5
  57. package/dist/{agent/speech/SpeechTurnService.d.ts → transport/surfaces/speechTurnService.d.ts} +1 -1
  58. package/dist/{agent/speech/SpeechTurnService.js → transport/surfaces/speechTurnService.js} +2 -2
  59. package/dist/types.d.ts +10 -4
  60. package/package.json +11 -2
  61. package/.woodpecker/buildRelease.sh +0 -13
  62. package/.woodpecker/buildSlackNotify.sh +0 -46
  63. package/.woodpecker/release.yml +0 -57
  64. package/agent/checkpointer.ts +0 -329
  65. package/agent/languageDetect.ts +0 -92
  66. package/agent/middleware/apiBasedTools.ts +0 -174
  67. package/agent/middleware/openAiResponsesContinuation.ts +0 -92
  68. package/agent/middleware/sequenceDebug.ts +0 -429
  69. package/agent/models/AgentModeResolver.ts +0 -9
  70. package/agent/models/AgentModelFactory.ts +0 -28
  71. package/agent/runtime/AgentContext.ts +0 -30
  72. package/agent/runtime/AgentRuntime.ts +0 -89
  73. package/agent/simpleAgent.ts +0 -199
  74. package/agent/skills/registry.ts +0 -162
  75. package/agent/speech/SpeechTurnService.ts +0 -179
  76. package/agent/systemPrompt.ts +0 -133
  77. package/agent/toolCallEvents.ts +0 -135
  78. package/agent/tools/AgentToolProvider.ts +0 -28
  79. package/agent/tools/apiTool.ts +0 -66
  80. package/agent/tools/fetchSkill.ts +0 -68
  81. package/agent/tools/fetchToolSchema.ts +0 -47
  82. package/agent/tools/getUserLocation.ts +0 -45
  83. package/agent/tools/index.ts +0 -31
  84. package/agent/tools/navigateUser.ts +0 -210
  85. package/agent/turn/TurnContextBuilder.ts +0 -36
  86. package/agent/turn/TurnLifecycleService.ts +0 -47
  87. package/agent/turn/TurnPersistenceService.ts +0 -33
  88. package/agent/turn/TurnPromptBuilder.ts +0 -51
  89. package/agent/turn/TurnStreamConsumer.ts +0 -69
  90. package/agent/turn/VegaLiteStreamBuffer.ts +0 -90
  91. package/agent/turn/turnTypes.ts +0 -100
  92. package/agentEvents.ts +0 -75
  93. package/agentTurnService.ts +0 -314
  94. package/apiBasedTools.ts +0 -712
  95. package/build.log +0 -67
  96. package/chatSurfaceService.ts +0 -319
  97. package/custom/ChatFooter.vue +0 -173
  98. package/custom/ChatHeader.vue +0 -78
  99. package/custom/ChatSurface.vue +0 -160
  100. package/custom/CustomAutoScrollContainer.vue +0 -164
  101. package/custom/SessionsHistory.vue +0 -88
  102. package/custom/chat.ts +0 -82
  103. package/custom/composables/agentAudio/utils.ts +0 -220
  104. package/custom/composables/agentStore/constants.ts +0 -19
  105. package/custom/composables/agentStore/pageContext.ts +0 -8
  106. package/custom/composables/agentStore/useAgentChat.ts +0 -241
  107. package/custom/composables/agentStore/useAgentPlaceholder.ts +0 -142
  108. package/custom/composables/agentStore/useAgentSessions.ts +0 -350
  109. package/custom/composables/useAgentAudio.ts +0 -272
  110. package/custom/composables/useAgentStore.ts +0 -510
  111. package/custom/composables/useAgentTransitions.ts +0 -58
  112. package/custom/conversation_area/ConversationArea.vue +0 -301
  113. package/custom/conversation_area/MessageRenderer.vue +0 -49
  114. package/custom/conversation_area/ProcessingTimeline.vue +0 -286
  115. package/custom/conversation_area/ReasoningRenderer.vue +0 -79
  116. package/custom/conversation_area/SystemMessageRenderer.vue +0 -28
  117. package/custom/conversation_area/TextRenderer.vue +0 -182
  118. package/custom/conversation_area/ThreeDotsAnimation.vue +0 -35
  119. package/custom/conversation_area/ToolApprovalRenderer.vue +0 -98
  120. package/custom/conversation_area/ToolRenderer.vue +0 -178
  121. package/custom/conversation_area/ToolsGroup.vue +0 -59
  122. package/custom/env.d.ts +0 -7
  123. package/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +0 -393
  124. package/custom/incremark_code_renderers/incremarkCodeHighlight.ts +0 -285
  125. package/custom/incremark_code_renderers/incremarkRenderer.ts +0 -653
  126. package/custom/incremark_code_renderers/renderIncremarkMarkdown.ts +0 -118
  127. package/custom/package.json +0 -30
  128. package/custom/pnpm-lock.yaml +0 -2394
  129. package/custom/public/agentAudio/agent-processing.mp3 +0 -0
  130. package/custom/skills/analyze_data/SKILL.md +0 -25
  131. package/custom/skills/charts/SKILL.md +0 -193
  132. package/custom/skills/fetch_data/SKILL.md +0 -31
  133. package/custom/skills/mutate_data/SKILL.md +0 -121
  134. package/custom/speech_recognition_frontend/AudioLines.vue +0 -43
  135. package/custom/speech_recognition_frontend/MicrophoneButon.vue +0 -185
  136. package/custom/speech_recognition_frontend/types/voice-activity-detection.d.ts +0 -22
  137. package/custom/speech_recognition_frontend/voiceActivityDetection.ts +0 -62
  138. package/custom/tsconfig.json +0 -15
  139. package/custom/types.ts +0 -108
  140. package/custom/utils.ts +0 -38
  141. package/dist/agent/middleware/openAiResponsesContinuation.d.ts +0 -1
  142. package/dist/agent/middleware/openAiResponsesContinuation.js +0 -66
  143. package/dist/agent/models/AgentModeResolver.d.ts +0 -9
  144. package/dist/agent/models/AgentModeResolver.js +0 -9
  145. package/dist/agent/simpleAgent.js +0 -118
  146. package/dist/agent/turn/TurnContextBuilder.d.ts +0 -14
  147. package/dist/agent/turn/TurnContextBuilder.js +0 -31
  148. package/dist/agent/turn/TurnLifecycleService.d.ts +0 -24
  149. package/dist/agent/turn/TurnLifecycleService.js +0 -47
  150. package/dist/agent/turn/TurnPersistenceService.d.ts +0 -13
  151. package/dist/agent/turn/TurnPersistenceService.js +0 -35
  152. package/dist/agent/turn/TurnPromptBuilder.d.ts +0 -19
  153. package/dist/agent/turn/TurnPromptBuilder.js +0 -43
  154. package/dist/agent/turn/TurnStreamConsumer.d.ts +0 -11
  155. package/dist/agent/turn/TurnStreamConsumer.js +0 -84
  156. package/dist/agentTurnService.d.ts +0 -24
  157. package/dist/agentTurnService.js +0 -269
  158. package/dist/surfaces/web-sse/createSseEventEmitter.d.ts +0 -14
  159. package/endpoints/chatSurfaces.ts +0 -66
  160. package/endpoints/context.ts +0 -52
  161. package/endpoints/core.ts +0 -181
  162. package/endpoints/sessions.ts +0 -183
  163. package/errors.ts +0 -10
  164. package/index.ts +0 -192
  165. package/pnpm-workspace.yaml +0 -4
  166. package/sanitizeSpeechText.ts +0 -32
  167. package/sessionStore.ts +0 -105
  168. package/surfaces/web-sse/createSseEventEmitter.ts +0 -311
  169. package/tsconfig.json +0 -14
  170. package/types.ts +0 -127
  171. /package/dist/{agent/turn/turnTypes.js → application/ports.js} +0 -0
  172. /package/dist/{agentEvents.js → domain/agentEvents.js} +0 -0
  173. /package/dist/{agent → domain}/languageDetect.d.ts +0 -0
  174. /package/dist/{agent → domain}/languageDetect.js +0 -0
  175. /package/dist/{agent → domain}/systemPrompt.d.ts +0 -0
  176. /package/dist/{agent → domain}/toolCallEvents.d.ts +0 -0
  177. /package/dist/{agent → domain}/toolCallEvents.js +0 -0
  178. /package/dist/{endpoints/context.js → domain/turnTypes.js} +0 -0
  179. /package/dist/{agent/turn/VegaLiteStreamBuffer.js → domain/vegaLiteStreamBuffer.js} +0 -0
  180. /package/dist/{agent/runtime/AgentContext.js → llm/agentContext.js} +0 -0
  181. /package/dist/{agent → llm}/middleware/sequenceDebug.js +0 -0
  182. /package/dist/{agent/checkpointer.d.ts → persistence/checkpointStore.d.ts} +0 -0
  183. /package/dist/{agent/checkpointer.js → persistence/checkpointStore.js} +0 -0
  184. /package/dist/{errors.d.ts → shared/errors.d.ts} +0 -0
  185. /package/dist/{errors.js → shared/errors.js} +0 -0
  186. /package/dist/{sanitizeSpeechText.d.ts → shared/sanitizeSpeechText.d.ts} +0 -0
  187. /package/dist/{sanitizeSpeechText.js → shared/sanitizeSpeechText.js} +0 -0
  188. /package/dist/{apiBasedTools.d.ts → tools/apiBasedTools.d.ts} +0 -0
  189. /package/dist/{agent/tools → tools}/apiTool.js +0 -0
  190. /package/dist/{agent/tools → tools}/fetchSkill.d.ts +0 -0
  191. /package/dist/{agent/tools → tools}/fetchToolSchema.js +0 -0
  192. /package/dist/{agent/tools → tools}/getUserLocation.d.ts +0 -0
  193. /package/dist/{agent/tools → tools}/getUserLocation.js +0 -0
  194. /package/dist/{agent/tools → tools}/index.js +0 -0
  195. /package/dist/{agent/tools → tools}/navigateUser.js +0 -0
  196. /package/dist/{agent → tools}/skills/registry.d.ts +0 -0
  197. /package/dist/{agent → tools}/skills/registry.js +0 -0
  198. /package/dist/{endpoints/chatSurfaces.d.ts → transport/http/chatSurfaceEndpoints.d.ts} +0 -0
  199. /package/dist/{endpoints/chatSurfaces.js → transport/http/chatSurfaceEndpoints.js} +0 -0
  200. /package/dist/{endpoints/core.d.ts → transport/http/coreEndpoints.d.ts} +0 -0
  201. /package/dist/{endpoints/sessions.d.ts → transport/http/sessionEndpoints.d.ts} +0 -0
@@ -1,28 +0,0 @@
1
- import type { CompletionAdapter } from "adminforth";
2
- import { createAgentChatModel } from "../simpleAgent.js";
3
- import type { AgentTurnModels } from "../turn/turnTypes.js";
4
-
5
- export class AgentModelFactory {
6
- constructor(private readonly maxTokens: number) {}
7
-
8
- async create(completionAdapter: CompletionAdapter): Promise<AgentTurnModels> {
9
- const [primaryModelSpec, summaryModelSpec] = await Promise.all([
10
- createAgentChatModel({
11
- adapter: completionAdapter,
12
- maxTokens: this.maxTokens,
13
- purpose: "primary",
14
- }),
15
- createAgentChatModel({
16
- adapter: completionAdapter,
17
- maxTokens: this.maxTokens,
18
- purpose: "summary",
19
- }),
20
- ]);
21
-
22
- return {
23
- model: primaryModelSpec.model,
24
- summaryModel: summaryModelSpec.model,
25
- modelMiddleware: primaryModelSpec.middleware,
26
- };
27
- }
28
- }
@@ -1,30 +0,0 @@
1
- import type { AdminUser } from "adminforth";
2
- import { z } from "zod";
3
- import type { AgentEventEmitter } from "../../agentEvents.js";
4
- import type { SequenceDebugCollector } from "../middleware/sequenceDebug.js";
5
- import type { CurrentPageContext } from "../tools/getUserLocation.js";
6
- import type { AgentTurnContext } from "../turn/turnTypes.js";
7
-
8
- export const contextSchema = z.object({
9
- adminUser: z.custom<AdminUser>(),
10
- userTimeZone: z.string(),
11
- sessionId: z.string(),
12
- turnId: z.string(),
13
- abortSignal: z.custom<AbortSignal>().optional(),
14
- currentPage: z.custom<CurrentPageContext>().optional(),
15
- chatSurface: z.string().optional(),
16
- adminBaseUrl: z.string().optional(),
17
- adminPublicOrigin: z.string().optional(),
18
- emit: z.custom<AgentEventEmitter>().optional(),
19
- sequenceDebugSink: z.custom<SequenceDebugCollector>(),
20
- });
21
-
22
- export function toLangchainAgentContext(
23
- context: AgentTurnContext & {
24
- adminBaseUrl: string;
25
- emit?: AgentEventEmitter;
26
- sequenceDebugSink: SequenceDebugCollector;
27
- },
28
- ) {
29
- return context;
30
- }
@@ -1,89 +0,0 @@
1
- import type { IAdminForth } from "adminforth";
2
- import { createAgent, summarizationMiddleware, humanInTheLoopMiddleware } from "langchain";
3
- import type { BaseCheckpointSaver } from "@langchain/langgraph";
4
- import { createApiBasedToolsMiddleware } from "../middleware/apiBasedTools.js";
5
- import { createSequenceDebugMiddleware } from "../middleware/sequenceDebug.js";
6
- import { createAgentLlmMetricsLogger } from "../simpleAgent.js";
7
- import type { AgentToolProvider } from "../tools/AgentToolProvider.js";
8
- import type { AgentRuntimeRunInput } from "../turn/turnTypes.js";
9
- import { contextSchema, toLangchainAgentContext } from "./AgentContext.js";
10
- import type { ApiBasedTool } from "../../apiBasedTools.js";
11
-
12
- function createHumanInTheLoopInterrupts(
13
- apiBasedTools: Record<string, ApiBasedTool>,
14
- ): Record<string, { allowedDecisions: ("approve" | "reject" | "edit")[] }> {
15
- return Object.fromEntries(
16
- Object.entries(apiBasedTools)
17
- .filter(([, apiBasedTool]) => apiBasedTool.agent?.isDangerous === true)
18
- .map(([toolName]) => [
19
- toolName,
20
- {
21
- allowedDecisions: ["approve", "reject"],
22
- },
23
- ]),
24
- );
25
- }
26
-
27
- export type AgentRuntimeOptions = {
28
- name: string;
29
- getAdminforth: () => IAdminForth;
30
- getCheckpointer: () => BaseCheckpointSaver;
31
- toolProvider: AgentToolProvider;
32
- };
33
-
34
- export class AgentRuntime {
35
- constructor(private readonly options: AgentRuntimeOptions) {}
36
-
37
- async stream(input: AgentRuntimeRunInput) {
38
- const apiBasedTools = this.options.toolProvider.getApiBasedTools();
39
- const tools = await this.options.toolProvider.getTools(apiBasedTools);
40
- const adminforth = this.options.getAdminforth();
41
- const apiBasedToolsMiddleware = createApiBasedToolsMiddleware(
42
- apiBasedTools,
43
- adminforth,
44
- );
45
- const sequenceDebugMiddleware = createSequenceDebugMiddleware(
46
- input.observability.sequenceDebugSink,
47
- );
48
- const hitlMiddleware = humanInTheLoopMiddleware({
49
- interruptOn: createHumanInTheLoopInterrupts(apiBasedTools),
50
- descriptionPrefix: "Tool execution pending approval",
51
- });
52
- const middleware = [
53
- apiBasedToolsMiddleware,
54
- hitlMiddleware,
55
- ...(input.models.modelMiddleware ?? []),
56
- sequenceDebugMiddleware,
57
- summarizationMiddleware({
58
- model: input.models.summaryModel,
59
- trigger: { tokens: 1024 * 64 },
60
- keep: { messages: 10 },
61
- }),
62
- ] as const;
63
-
64
- const agent = createAgent({
65
- name: this.options.name,
66
- model: input.models.model,
67
- checkpointer: this.options.getCheckpointer(),
68
- tools,
69
- contextSchema,
70
- middleware,
71
- });
72
-
73
- return agent.stream(input.input as any, {
74
- streamMode: ["messages", "updates"],
75
- recursionLimit: 100,
76
- callbacks: [createAgentLlmMetricsLogger()],
77
- signal: input.context.abortSignal,
78
- configurable: {
79
- thread_id: input.context.sessionId,
80
- },
81
- context: toLangchainAgentContext({
82
- ...input.context,
83
- adminBaseUrl: adminforth.config.baseUrlSlashed,
84
- emit: input.observability.emit,
85
- sequenceDebugSink: input.observability.sequenceDebugSink,
86
- }),
87
- });
88
- }
89
- }
@@ -1,199 +0,0 @@
1
- import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
2
- import {
3
- logger,
4
- type CompletionAdapter,
5
- } from "adminforth";
6
- import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
7
- import type { LLMResult } from "@langchain/core/outputs";
8
- import {
9
- createSequenceDebugMiddleware,
10
- } from "./middleware/sequenceDebug.js";
11
-
12
- export type AgentChatModel = BaseChatModel<any, any>;
13
- export type AgentModelPurpose = "primary" | "summary";
14
- export type AgentModeCompletionAdapter = CompletionAdapter & {
15
- getLangChainAgentSpec(params: {
16
- maxTokens: number;
17
- purpose: AgentModelPurpose;
18
- }): Promise<{
19
- model: unknown;
20
- middleware?: unknown[];
21
- }> | {
22
- model: unknown;
23
- middleware?: unknown[];
24
- };
25
- };
26
-
27
- export type AgentMiddleware = ReturnType<typeof createSequenceDebugMiddleware>;
28
-
29
- type AgentChatModelSpec = {
30
- model: AgentChatModel;
31
- middleware: AgentMiddleware[];
32
- };
33
-
34
- type LlmOutputTokenUsage = {
35
- promptTokens?: unknown;
36
- completionTokens?: unknown;
37
- };
38
-
39
- type MessageUsageMetadata = {
40
- input_tokens?: unknown;
41
- output_tokens?: unknown;
42
- };
43
-
44
- type PendingLlmRun = {
45
- startedAt: number;
46
- firstTokenAt?: number;
47
- };
48
-
49
- function isLangChainAgentCompletionAdapter(
50
- adapter: CompletionAdapter,
51
- ): adapter is AgentModeCompletionAdapter {
52
- return typeof (adapter as AgentModeCompletionAdapter)
53
- .getLangChainAgentSpec === "function";
54
- }
55
-
56
- async function getAgentChatModelSpec(params: {
57
- adapter: AgentModeCompletionAdapter;
58
- maxTokens: number;
59
- purpose: AgentModelPurpose;
60
- }): Promise<AgentChatModelSpec> {
61
- const spec = await params.adapter.getLangChainAgentSpec({
62
- maxTokens: params.maxTokens,
63
- purpose: params.purpose,
64
- });
65
-
66
- return {
67
- model: spec.model as AgentChatModel,
68
- middleware: (spec.middleware ?? []) as AgentMiddleware[],
69
- };
70
- }
71
-
72
- function getFiniteNumber(value: unknown) {
73
- return typeof value === "number" && Number.isFinite(value)
74
- ? value
75
- : undefined;
76
- }
77
-
78
- function extractTokenUsage(output: LLMResult) {
79
- const llmOutputTokenUsage = (
80
- output.llmOutput as { tokenUsage?: LlmOutputTokenUsage } | undefined
81
- )?.tokenUsage;
82
-
83
- const promptTokens = getFiniteNumber(llmOutputTokenUsage?.promptTokens);
84
- const completionTokens = getFiniteNumber(
85
- llmOutputTokenUsage?.completionTokens,
86
- );
87
-
88
- if (promptTokens !== undefined || completionTokens !== undefined) {
89
- return {
90
- InputTokens: promptTokens ?? 0,
91
- outputTokens: completionTokens ?? 0,
92
- };
93
- }
94
-
95
- let InputTokens = 0;
96
- let outputTokens = 0;
97
-
98
- for (const generationBatch of output.generations) {
99
- for (const generation of generationBatch) {
100
- if (!("message" in generation) || !generation.message) {
101
- continue;
102
- }
103
-
104
- const message = generation.message as {
105
- usage_metadata?: MessageUsageMetadata;
106
- response_metadata?: {
107
- tokenUsage?: LlmOutputTokenUsage;
108
- };
109
- };
110
-
111
- InputTokens +=
112
- getFiniteNumber(message.usage_metadata?.input_tokens) ??
113
- getFiniteNumber(message.response_metadata?.tokenUsage?.promptTokens) ??
114
- 0;
115
- outputTokens +=
116
- getFiniteNumber(message.usage_metadata?.output_tokens) ??
117
- getFiniteNumber(
118
- message.response_metadata?.tokenUsage?.completionTokens,
119
- ) ??
120
- 0;
121
- }
122
- }
123
-
124
- return { InputTokens, outputTokens };
125
- }
126
-
127
- class AgentLlmMetricsLogger extends BaseCallbackHandler {
128
- name = "AgentLlmMetricsLogger";
129
- lc_prefer_streaming = true;
130
-
131
- private readonly pendingRuns = new Map<string, PendingLlmRun>();
132
-
133
- async handleLLMStart(_llm: unknown, _prompts: string[], runId: string) {
134
- this.pendingRuns.set(runId, { startedAt: Date.now() });
135
- }
136
-
137
- async handleLLMNewToken(
138
- _token: string,
139
- _chunk: unknown,
140
- runId: string,
141
- ) {
142
- const pendingRun = this.pendingRuns.get(runId);
143
-
144
- if (!pendingRun || pendingRun.firstTokenAt !== undefined) {
145
- return;
146
- }
147
-
148
- pendingRun.firstTokenAt = Date.now();
149
- }
150
-
151
- async handleLLMEnd(output: LLMResult, runId: string) {
152
- const pendingRun = this.pendingRuns.get(runId);
153
-
154
- if (!pendingRun) {
155
- return;
156
- }
157
-
158
- this.pendingRuns.delete(runId);
159
-
160
- const finishedAt = Date.now();
161
- const rtt = finishedAt - pendingRun.startedAt;
162
- const ttft =
163
- pendingRun.firstTokenAt === undefined
164
- ? rtt
165
- : pendingRun.firstTokenAt - pendingRun.startedAt;
166
- const { InputTokens, outputTokens } = extractTokenUsage(output);
167
-
168
- logger.info(
169
- { InputTokens, outputTokens, ttft, rtt },
170
- "LLM call finished",
171
- );
172
- }
173
-
174
- async handleLLMError(_error: unknown, runId: string) {
175
- this.pendingRuns.delete(runId);
176
- }
177
- }
178
-
179
- export function createAgentLlmMetricsLogger() {
180
- return new AgentLlmMetricsLogger();
181
- }
182
-
183
- export async function createAgentChatModel(params: {
184
- adapter: CompletionAdapter;
185
- maxTokens: number;
186
- purpose: AgentModelPurpose;
187
- }) {
188
- if (!isLangChainAgentCompletionAdapter(params.adapter)) {
189
- throw new Error(
190
- "AdminForth Agent requires completionAdapter to implement getLangChainAgentSpec({ maxTokens, purpose }).",
191
- );
192
- }
193
-
194
- return await getAgentChatModelSpec({
195
- adapter: params.adapter,
196
- maxTokens: params.maxTokens,
197
- purpose: params.purpose,
198
- });
199
- }
@@ -1,162 +0,0 @@
1
- import { readdir, readFile } from "fs/promises";
2
- import path from "path";
3
- import { parse as parseYaml } from "yaml";
4
-
5
- const SKILL_MARKDOWN_FILENAME = "SKILL.md";
6
- const SKILL_FRONTMATTER_SEPARATOR = "\n---\n";
7
-
8
- export interface AgentSkillManifest {
9
- directoryName: string;
10
- name: string;
11
- description: string;
12
- instructions: string;
13
- }
14
-
15
- function normalizePluginSkillDirectoryPaths(pluginCustomFolderPaths: string[] = []) {
16
- return Array.from(new Set(
17
- pluginCustomFolderPaths
18
- .map((pluginCustomFolderPath) => path.resolve(pluginCustomFolderPath, "skills")),
19
- ));
20
- }
21
-
22
- function parseSkillManifest(directoryName: string, markdown: string): AgentSkillManifest {
23
- const [frontmatterBlock, instructions = ""] = markdown.split("\r\n").join("\n").split(
24
- SKILL_FRONTMATTER_SEPARATOR,
25
- 2,
26
- );
27
- const metadata = parseYaml(frontmatterBlock) as {
28
- name?: string;
29
- description?: string;
30
- };
31
-
32
- return {
33
- directoryName,
34
- name: metadata.name ?? directoryName,
35
- description: metadata.description ?? "",
36
- instructions: instructions.trim(),
37
- };
38
- }
39
-
40
- async function readSkillManifest(skillsDirectoryPath: string, directoryName: string) {
41
- const markdown = await readFile(
42
- path.join(skillsDirectoryPath, directoryName, SKILL_MARKDOWN_FILENAME),
43
- "utf8",
44
- );
45
-
46
- return parseSkillManifest(directoryName, markdown);
47
- }
48
-
49
- async function listDirectorySkillManifests(skillsDirectoryPath: string) {
50
- try {
51
- const entries = await readdir(skillsDirectoryPath, { withFileTypes: true });
52
-
53
- return await Promise.all(
54
- entries
55
- .filter((entry) => entry.isDirectory())
56
- .map((entry) => entry.name)
57
- .sort()
58
- .map((directoryName) => readSkillManifest(skillsDirectoryPath, directoryName)),
59
- );
60
- } catch (error) {
61
- if ((error as NodeJS.ErrnoException).code === "ENOENT") {
62
- return [];
63
- }
64
-
65
- throw error;
66
- }
67
- }
68
-
69
- function mergeSkillManifests(skillGroups: AgentSkillManifest[][]) {
70
- return Array.from(
71
- new Map(
72
- skillGroups.flat().map((skill) => [
73
- `${skill.name}:${skill.directoryName}`,
74
- skill,
75
- ]),
76
- ).values(),
77
- );
78
- }
79
-
80
- export function getProjectSkillsDirectoryPath(customComponentsDir: string) {
81
- return path.resolve(customComponentsDir, "skills");
82
- }
83
-
84
- function getProjectSkillDirectoryPaths(
85
- customComponentsDir: string,
86
- pluginCustomFolderPaths: string[] = [],
87
- ) {
88
- return Array.from(new Set([
89
- getProjectSkillsDirectoryPath(customComponentsDir),
90
- ...normalizePluginSkillDirectoryPaths(pluginCustomFolderPaths),
91
- ]));
92
- }
93
-
94
- export async function listProjectSkillManifests(
95
- customComponentsDir: string,
96
- pluginCustomFolderPaths: string[] = [],
97
- ) {
98
- return mergeSkillManifests(
99
- await Promise.all(
100
- getProjectSkillDirectoryPaths(customComponentsDir, pluginCustomFolderPaths).map(
101
- listDirectorySkillManifests,
102
- ),
103
- ),
104
- );
105
- }
106
-
107
- export async function listSkillManifests(
108
- customComponentsDir: string,
109
- pluginCustomFolderPaths: string[] = [],
110
- ) {
111
- return await listProjectSkillManifests(customComponentsDir, pluginCustomFolderPaths);
112
- }
113
-
114
- export async function loadSkillManifest(
115
- skillName: string,
116
- customComponentsDir: string,
117
- pluginCustomFolderPaths: string[] = [],
118
- ) {
119
- const manifests = await listSkillManifests(customComponentsDir, pluginCustomFolderPaths);
120
-
121
- return (
122
- manifests.find(
123
- (manifest) =>
124
- manifest.name === skillName || manifest.directoryName === skillName,
125
- ) ?? null
126
- );
127
- }
128
-
129
- export async function loadSkillMarkdown(
130
- skillName: string,
131
- customComponentsDir: string,
132
- pluginCustomFolderPaths: string[] = [],
133
- ) {
134
- const manifest = await loadSkillManifest(
135
- skillName,
136
- customComponentsDir,
137
- pluginCustomFolderPaths,
138
- );
139
-
140
- if (!manifest) {
141
- return null;
142
- }
143
-
144
- const directories = [
145
- ...getProjectSkillDirectoryPaths(customComponentsDir, pluginCustomFolderPaths),
146
- ];
147
-
148
- for (const skillsDirectoryPath of directories) {
149
- try {
150
- return await readFile(
151
- path.join(skillsDirectoryPath, manifest.directoryName, SKILL_MARKDOWN_FILENAME),
152
- "utf8",
153
- );
154
- } catch (error) {
155
- if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
156
- throw error;
157
- }
158
- }
159
- }
160
-
161
- return null;
162
- }
@@ -1,179 +0,0 @@
1
- import { logger } from "adminforth";
2
- import { getErrorMessage, isAbortError } from "../../errors.js";
3
- import { sanitizeSpeechText } from "../../sanitizeSpeechText.js";
4
- import type {
5
- RunAndPersistAgentResponseInput,
6
- RunAndPersistAgentResponseResult,
7
- SpeechAgentTurnInput,
8
- } from "../turn/turnTypes.js";
9
-
10
- export class SpeechTurnService {
11
- constructor(
12
- private readonly runAndPersistAgentResponse: (
13
- input: RunAndPersistAgentResponseInput,
14
- ) => Promise<RunAndPersistAgentResponseResult>,
15
- ) {}
16
-
17
- async handle(input: SpeechAgentTurnInput) {
18
- let transcription;
19
-
20
- try {
21
- transcription = await input.audioAdapter.transcribe({
22
- buffer: input.audio.buffer,
23
- filename: input.audio.filename,
24
- mimeType: input.audio.mimeType,
25
- language: "auto",
26
- abortSignal: input.abortSignal,
27
- });
28
- } catch (error) {
29
- if (input.abortSignal?.aborted || isAbortError(error)) {
30
- logger.info("Agent speech transcription aborted by the client");
31
- await input.emit({ type: "finish" });
32
- return null;
33
- }
34
-
35
- logger.error(`Agent speech transcription failed:\n${getErrorMessage(error)}`);
36
- await input.emit({
37
- type: "error",
38
- error: "Speech transcription failed. Check server logs for details.",
39
- });
40
- await input.emit({ type: "finish" });
41
- return null;
42
- }
43
-
44
- if (input.abortSignal?.aborted) {
45
- await input.emit({ type: "finish" });
46
- return null;
47
- }
48
-
49
- const prompt = transcription.text;
50
- if (!prompt) {
51
- await input.emit({
52
- type: "error",
53
- error: "Speech transcription is empty",
54
- });
55
- await input.emit({ type: "finish" });
56
- return null;
57
- }
58
-
59
- await input.emit({
60
- type: "transcript",
61
- text: transcription.text,
62
- language: transcription.language,
63
- });
64
-
65
- const agentResponse = await this.runAndPersistAgentResponse({
66
- prompt,
67
- sessionId: input.sessionId,
68
- modeName: input.modeName,
69
- userTimeZone: input.userTimeZone,
70
- currentPage: input.currentPage,
71
- chatSurface: input.chatSurface,
72
- adminPublicOrigin: input.adminPublicOrigin,
73
- abortSignal: input.abortSignal,
74
- adminUser: input.adminUser,
75
- emit: async (event) => {
76
- if (event.type === "tool-call") {
77
- await input.emit(event);
78
- }
79
- },
80
- failureLogMessage: input.failureLogMessage ?? "Agent speech response failed",
81
- abortLogMessage: input.abortLogMessage ?? "Agent speech response aborted by the client",
82
- });
83
-
84
- if (agentResponse.aborted) {
85
- await input.emit({ type: "finish" });
86
- return agentResponse;
87
- }
88
-
89
- if (agentResponse.failed) {
90
- await input.emit({
91
- type: "error",
92
- error: agentResponse.text,
93
- });
94
- await input.emit({ type: "finish" });
95
- return agentResponse;
96
- }
97
-
98
- try {
99
- await input.emit({
100
- type: "speech-response",
101
- transcript: {
102
- text: transcription.text,
103
- language: transcription.language,
104
- },
105
- response: {
106
- text: agentResponse.text,
107
- },
108
- sessionId: input.sessionId,
109
- turnId: agentResponse.turnId,
110
- });
111
- const speech = await input.audioAdapter.synthesize({
112
- text: sanitizeSpeechText(agentResponse.text),
113
- stream: true,
114
- streamFormat: "audio",
115
- format: "pcm",
116
- abortSignal: input.abortSignal,
117
- });
118
-
119
- await input.emit({
120
- type: "audio-start",
121
- mimeType: speech.mimeType,
122
- format: speech.format,
123
- sampleRate: 24000,
124
- channelCount: 1,
125
- bitsPerSample: 16,
126
- });
127
-
128
- const reader = speech.audioStream.getReader();
129
- const cancelAudioStream = () => {
130
- void reader.cancel().catch(() => undefined);
131
- };
132
-
133
- try {
134
- input.abortSignal?.addEventListener("abort", cancelAudioStream, { once: true });
135
-
136
- while (true) {
137
- if (input.abortSignal?.aborted) {
138
- await reader.cancel().catch(() => undefined);
139
- break;
140
- }
141
-
142
- const { value, done } = await reader.read();
143
-
144
- if (done) {
145
- break;
146
- }
147
-
148
- if (input.abortSignal?.aborted) {
149
- break;
150
- }
151
-
152
- await input.emit({
153
- type: "audio-delta",
154
- value,
155
- });
156
- }
157
- } finally {
158
- input.abortSignal?.removeEventListener("abort", cancelAudioStream);
159
- reader.releaseLock();
160
- }
161
-
162
- await input.emit({ type: "audio-done" });
163
- await input.emit({ type: "finish" });
164
- return agentResponse;
165
- } catch (error) {
166
- if (input.abortSignal?.aborted || isAbortError(error)) {
167
- logger.info("Agent speech audio streaming aborted by the client");
168
- } else {
169
- logger.error(`Agent speech audio streaming failed:\n${getErrorMessage(error)}`);
170
- await input.emit({
171
- type: "error",
172
- error: getErrorMessage(error),
173
- });
174
- }
175
- await input.emit({ type: "finish" });
176
- return agentResponse;
177
- }
178
- }
179
- }