@adminforth/agent 1.50.0 → 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 (58) 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 +2 -2
  19. package/custom/skills/fetch_data/SKILL.md +2 -0
  20. package/custom/skills/mutate_data/SKILL.md +4 -0
  21. package/dist/agent/middleware/apiBasedTools.js +9 -2
  22. package/dist/agent/models/AgentModeResolver.d.ts +9 -0
  23. package/dist/agent/models/AgentModeResolver.js +9 -0
  24. package/dist/agent/models/AgentModelFactory.d.ts +7 -0
  25. package/dist/agent/models/AgentModelFactory.js +36 -0
  26. package/dist/agent/runtime/AgentContext.d.ts +28 -0
  27. package/dist/agent/runtime/AgentContext.js +17 -0
  28. package/dist/agent/runtime/AgentRuntime.d.ts +15 -0
  29. package/dist/agent/runtime/AgentRuntime.js +57 -0
  30. package/dist/agent/simpleAgent.d.ts +15 -45
  31. package/dist/agent/simpleAgent.js +1 -67
  32. package/dist/agent/speech/SpeechTurnService.d.ts +6 -0
  33. package/dist/agent/speech/SpeechTurnService.js +168 -0
  34. package/dist/agent/tools/AgentToolProvider.d.ts +9 -0
  35. package/dist/agent/tools/AgentToolProvider.js +27 -0
  36. package/dist/agent/tools/navigateUser.js +1 -1
  37. package/dist/agent/turn/TurnContextBuilder.d.ts +14 -0
  38. package/dist/agent/turn/TurnContextBuilder.js +31 -0
  39. package/dist/agent/turn/TurnLifecycleService.d.ts +17 -0
  40. package/dist/agent/turn/TurnLifecycleService.js +31 -0
  41. package/dist/agent/turn/TurnPersistenceService.d.ts +13 -0
  42. package/dist/agent/turn/TurnPersistenceService.js +35 -0
  43. package/dist/agent/turn/TurnPromptBuilder.d.ts +19 -0
  44. package/dist/agent/turn/TurnPromptBuilder.js +43 -0
  45. package/dist/agent/turn/TurnStreamConsumer.d.ts +10 -0
  46. package/dist/agent/turn/TurnStreamConsumer.js +78 -0
  47. package/dist/agent/turn/VegaLiteStreamBuffer.d.ts +7 -0
  48. package/dist/agent/turn/VegaLiteStreamBuffer.js +87 -0
  49. package/dist/agent/turn/turnTypes.d.ts +81 -0
  50. package/dist/agent/turn/turnTypes.js +1 -0
  51. package/dist/agentTurnService.d.ts +20 -69
  52. package/dist/agentTurnService.js +60 -373
  53. package/dist/custom/skills/fetch_data/SKILL.md +2 -0
  54. package/dist/custom/skills/mutate_data/SKILL.md +4 -0
  55. package/dist/index.d.ts +1 -0
  56. package/dist/index.js +22 -7
  57. package/index.ts +35 -7
  58. package/package.json +1 -1
@@ -1,298 +1,102 @@
1
- import type { AdminUser, AudioAdapter, IAdminForth } from "adminforth";
2
1
  import { logger } from "adminforth";
3
2
  import { randomUUID } from "crypto";
4
- import { HumanMessage, SystemMessage } from "langchain";
5
- import type { BaseCheckpointSaver } from "@langchain/langgraph";
6
- import { createAgentChatModel, callAgent } from "./agent/simpleAgent.js";
3
+ import { AgentModelFactory } from "./agent/models/AgentModelFactory.js";
4
+ import { AgentModeResolver } from "./agent/models/AgentModeResolver.js";
7
5
  import { createSequenceDebugCollector } from "./agent/middleware/sequenceDebug.js";
8
- import { detectUserLanguage, type PreviousUserMessage } from "./agent/languageDetect.js";
9
- import { prepareApiBasedTools as buildApiBasedTools } from "./apiBasedTools.js";
10
- import type { AgentEventEmitter } from "./agentEvents.js";
11
- import { buildAgentTurnSystemPrompt } from "./agent/systemPrompt.js";
12
- import type { CurrentPageContext } from "./agent/tools/getUserLocation.js";
13
- import { isAbortError, getErrorMessage } from "./errors.js";
14
- import { sanitizeSpeechText } from "./sanitizeSpeechText.js";
15
- import type { AgentSessionStore } from "./sessionStore.js";
16
- import type { PluginOptions } from "./types.js";
17
-
18
- type AgentTurnRunInput = {
19
- prompt: string;
20
- sessionId: string;
21
- turnId: string;
22
- previousUserMessages: PreviousUserMessage[];
23
- modeName?: string | null;
24
- userTimeZone: string;
25
- currentPage?: CurrentPageContext;
26
- chatSurface?: string;
27
- adminPublicOrigin?: string;
28
- abortSignal?: AbortSignal;
29
- adminUser: AdminUser;
30
- sequenceDebugCollector: ReturnType<typeof createSequenceDebugCollector>;
31
- emit?: AgentEventEmitter;
32
- };
33
-
34
- export type RunAndPersistAgentResponseInput = {
35
- prompt: string;
36
- sessionId: string;
37
- modeName?: string | null;
38
- userTimeZone: string;
39
- currentPage?: CurrentPageContext;
40
- chatSurface?: string;
41
- adminPublicOrigin?: string;
42
- abortSignal?: AbortSignal;
43
- adminUser: AdminUser;
44
- emit?: AgentEventEmitter;
45
- failureLogMessage: string;
46
- abortLogMessage: string;
47
- };
48
-
49
- export type RunAndPersistAgentResponseResult = {
50
- text: string;
51
- turnId: string;
52
- aborted: boolean;
53
- failed: boolean;
54
- };
55
-
56
- export type HandleTurnInput = Omit<RunAndPersistAgentResponseInput, "failureLogMessage" | "abortLogMessage"> & {
57
- emit: AgentEventEmitter;
58
- failureLogMessage?: string;
59
- abortLogMessage?: string;
60
- };
61
-
62
- export type HandleSpeechTurnInput = Omit<HandleTurnInput, "prompt"> & {
63
- audioAdapter: AudioAdapter;
64
- audio: {
65
- buffer: Buffer;
66
- filename: string;
67
- mimeType: string;
68
- };
69
- };
70
-
71
- type AgentTurnServiceOptions = {
72
- getAdminforth: () => IAdminForth;
73
- getPluginInstanceId: () => string;
74
- options: PluginOptions;
75
- sessionStore: AgentSessionStore;
76
- getCheckpointer: () => BaseCheckpointSaver;
77
- getInternalAgentResourceIds: () => string[];
78
- getAgentSystemPrompt: () => Promise<string>;
79
- };
80
-
81
- const VEGA_LITE_FENCE_START = "```vega-lite";
82
- const COMPLETE_VEGA_LITE_BLOCK_RE = /```vega-lite[\s\S]*?```/;
6
+ import { AgentRuntime } from "./agent/runtime/AgentRuntime.js";
7
+ import { TurnContextBuilder } from "./agent/turn/TurnContextBuilder.js";
8
+ import { TurnLifecycleService } from "./agent/turn/TurnLifecycleService.js";
9
+ import { TurnPromptBuilder } from "./agent/turn/TurnPromptBuilder.js";
10
+ import { TurnStreamConsumer } from "./agent/turn/TurnStreamConsumer.js";
11
+ import type {
12
+ BaseAgentTurnInput,
13
+ HandleTurnInput,
14
+ PreparedAgentTurn,
15
+ RunAndPersistAgentResponseInput,
16
+ RunAndPersistAgentResponseResult,
17
+ } from "./agent/turn/turnTypes.js";
18
+ import { getErrorMessage, isAbortError } from "./errors.js";
19
+
20
+ export type {
21
+ BaseAgentTurnInput,
22
+ HandleSpeechTurnInput,
23
+ HandleTurnInput,
24
+ RunAndPersistAgentResponseInput,
25
+ RunAndPersistAgentResponseResult,
26
+ } from "./agent/turn/turnTypes.js";
83
27
 
84
28
  export class AgentTurnService {
85
- constructor(private serviceOptions: AgentTurnServiceOptions) {}
86
-
87
- private async runAgentTurn(input: AgentTurnRunInput) {
88
- const adminforth = this.serviceOptions.getAdminforth();
89
- const options = this.serviceOptions.options;
90
- let fullResponse = "";
91
- let bufferedTextDelta = "";
92
- let isRenderingVegaLite = false;
93
- const maxTokens = options.maxTokens ?? 1000;
94
- const selectedMode = options.modes.find((mode) => mode.name === input.modeName) ?? options.modes[0];
95
- const [primaryModelSpec, summaryModelSpec] = await Promise.all([
96
- createAgentChatModel({
97
- adapter: selectedMode.completionAdapter,
98
- maxTokens,
99
- purpose: "primary",
100
- }),
101
- createAgentChatModel({
102
- adapter: selectedMode.completionAdapter,
103
- maxTokens,
104
- purpose: "summary",
105
- }),
106
- ]);
107
- const model = primaryModelSpec.model;
108
- const summaryModel = summaryModelSpec.model;
109
- const modelMiddleware = primaryModelSpec.middleware;
110
-
111
- const userLanguage = await detectUserLanguage(selectedMode.completionAdapter, input.prompt, input.previousUserMessages)
112
- .catch((error) => {
113
- if (input.abortSignal?.aborted || isAbortError(error)) {
114
- throw error;
115
- }
116
-
117
- logger.warn(`Failed to detect user language: ${getErrorMessage(error)}`);
118
- return null;
119
- });
120
- const systemPrompt = buildAgentTurnSystemPrompt({
121
- agentSystemPrompt: await this.serviceOptions.getAgentSystemPrompt(),
122
- adminUser: input.adminUser,
123
- usernameField: adminforth.config.auth!.usernameField,
124
- userLanguage,
125
- chatSurface: input.chatSurface,
29
+ constructor(
30
+ private readonly lifecycle: TurnLifecycleService,
31
+ private readonly contextBuilder: TurnContextBuilder,
32
+ private readonly modeResolver: AgentModeResolver,
33
+ private readonly modelFactory: AgentModelFactory,
34
+ private readonly promptBuilder: TurnPromptBuilder,
35
+ private readonly runtime: AgentRuntime,
36
+ private readonly streamConsumer: TurnStreamConsumer,
37
+ ) {}
38
+
39
+ private async prepareTurn(input: BaseAgentTurnInput): Promise<PreparedAgentTurn> {
40
+ const sequenceDebugCollector = createSequenceDebugCollector();
41
+ const { turnId, previousUserMessages } = await this.lifecycle.start(input);
42
+ const context = await this.contextBuilder.build({
43
+ base: input,
44
+ turnId,
126
45
  });
127
- const apiBasedTools = buildApiBasedTools(
128
- adminforth,
129
- this.serviceOptions.getInternalAgentResourceIds(),
130
- );
131
46
 
132
- const stream = await callAgent({
133
- name: `adminforth-agent-${this.serviceOptions.getPluginInstanceId()}`,
134
- model,
135
- summaryModel,
136
- modelMiddleware,
137
- checkpointer: this.serviceOptions.getCheckpointer(),
138
- messages: [
139
- new SystemMessage(systemPrompt),
140
- new HumanMessage(input.prompt),
141
- ],
142
- adminUser: input.adminUser,
143
- adminforth,
144
- apiBasedTools,
145
- customComponentsDir: adminforth.config.customization.customComponentsDir ?? "custom",
146
- pluginCustomFolderPaths: adminforth.activatedPlugins.map((plugin) => plugin.customFolderPath),
47
+ return {
48
+ prompt: input.prompt,
147
49
  sessionId: input.sessionId,
148
- turnId: input.turnId,
149
- currentPage: input.currentPage,
150
- chatSurface: input.chatSurface,
151
- adminPublicOrigin: input.adminPublicOrigin,
152
- userTimeZone: input.userTimeZone,
153
- abortSignal: input.abortSignal,
154
- emitToolCallEvent: (event) => {
155
- input.sequenceDebugCollector.handleToolCallEvent(event);
156
- void input.emit?.({
157
- type: "tool-call",
158
- data: event,
159
- });
50
+ turnId,
51
+ previousUserMessages,
52
+ modeName: input.modeName,
53
+ context,
54
+ observability: {
55
+ emit: undefined,
56
+ sequenceDebugSink: sequenceDebugCollector,
160
57
  },
161
- emitAgentEvent: input.emit,
162
- sequenceDebugSink: input.sequenceDebugCollector,
163
- });
164
-
165
- for await (const rawChunk of stream as AsyncIterable<[any, any]>) {
166
- if (input.abortSignal?.aborted) {
167
- throw new DOMException("This operation was aborted", "AbortError");
168
- }
169
-
170
- const [token, metadata] = rawChunk;
171
-
172
- const nodeName =
173
- typeof metadata?.langgraph_node === "string"
174
- ? metadata.langgraph_node
175
- : "";
176
-
177
- if (nodeName && !["model", "model_request"].includes(nodeName)) {
178
- continue;
179
- }
180
-
181
- const blocks = Array.isArray(token?.contentBlocks)
182
- ? token.contentBlocks
183
- : Array.isArray(token?.content)
184
- ? token.content
185
- : [];
186
- const reasoningDelta = blocks
187
- .filter((b: any) => b?.type === "reasoning")
188
- .map((b: any) => String(b.reasoning ?? ""))
189
- .join("");
190
-
191
- const textDelta = blocks
192
- .filter((b: any) => b?.type === "text")
193
- .map((b: any) => String(b.text ?? ""))
194
- .join("");
195
-
196
- if (reasoningDelta) {
197
- await input.emit?.({
198
- type: "reasoning-delta",
199
- delta: reasoningDelta,
200
- });
201
- }
202
-
203
- if (textDelta) {
204
- fullResponse += textDelta;
205
- bufferedTextDelta += textDelta;
206
-
207
- if (
208
- bufferedTextDelta.includes(VEGA_LITE_FENCE_START) &&
209
- !COMPLETE_VEGA_LITE_BLOCK_RE.test(bufferedTextDelta)
210
- ) {
211
- if (!isRenderingVegaLite) {
212
- isRenderingVegaLite = true;
213
- await input.emit?.({
214
- type: "rendering",
215
- phase: "start",
216
- label: "Rendering...",
217
- });
218
- }
219
- continue;
220
- }
221
-
222
- if (isRenderingVegaLite) {
223
- isRenderingVegaLite = false;
224
- await input.emit?.({
225
- type: "rendering",
226
- phase: "end",
227
- label: "Rendering...",
228
- });
229
- }
230
-
231
- const streamableLength = bufferedTextDelta.includes(VEGA_LITE_FENCE_START)
232
- ? bufferedTextDelta.length
233
- : bufferedTextDelta.length - getPartialVegaLiteFenceStartLength(bufferedTextDelta);
234
-
235
- if (!streamableLength) {
236
- continue;
237
- }
238
-
239
- await input.emit?.({
240
- type: "text-delta",
241
- delta: bufferedTextDelta.slice(0, streamableLength),
242
- });
243
- bufferedTextDelta = bufferedTextDelta.slice(streamableLength);
244
- }
245
- }
246
-
247
- if (isRenderingVegaLite) {
248
- await input.emit?.({
249
- type: "rendering",
250
- phase: "end",
251
- label: "Rendering...",
252
- });
253
- }
254
-
255
- if (bufferedTextDelta) {
256
- await input.emit?.({
257
- type: "text-delta",
258
- delta: bufferedTextDelta,
259
- });
260
- }
261
-
262
- return {
263
- text: fullResponse,
264
58
  };
265
59
  }
266
60
 
267
- async runAndPersistAgentResponse(input: RunAndPersistAgentResponseInput) {
268
- const adminforth = this.serviceOptions.getAdminforth();
269
- const options = this.serviceOptions.options;
270
- const previousUserMessages = await this.serviceOptions.sessionStore.getPreviousUserMessages(input.sessionId);
271
- const turnId = await this.serviceOptions.sessionStore.createNewTurn(input.sessionId, input.prompt);
272
- await adminforth.resource(options.sessionResource.resourceId).update(input.sessionId, {
273
- [options.sessionResource.createdAtField]: new Date().toISOString(),
61
+ private async runAgentTurn(input: PreparedAgentTurn) {
62
+ const selectedMode = this.modeResolver.resolve(input.modeName);
63
+ const [models, messages] = await Promise.all([
64
+ this.modelFactory.create(selectedMode.completionAdapter),
65
+ this.promptBuilder.build({
66
+ prompt: input.prompt,
67
+ previousUserMessages: input.previousUserMessages,
68
+ adminUser: input.context.adminUser,
69
+ completionAdapter: selectedMode.completionAdapter,
70
+ chatSurface: input.context.chatSurface,
71
+ abortSignal: input.context.abortSignal,
72
+ }),
73
+ ]);
74
+ const stream = await this.runtime.stream({
75
+ models,
76
+ messages,
77
+ context: input.context,
78
+ observability: input.observability,
274
79
  });
275
- const sequenceDebugCollector = createSequenceDebugCollector();
80
+
81
+ return this.streamConsumer.consume({
82
+ stream: stream as AsyncIterable<[any, any]>,
83
+ abortSignal: input.context.abortSignal,
84
+ emit: input.observability.emit,
85
+ });
86
+ }
87
+
88
+ async runAndPersistAgentResponse(
89
+ input: RunAndPersistAgentResponseInput,
90
+ ): Promise<RunAndPersistAgentResponseResult> {
91
+ const preparedTurn = await this.prepareTurn(input);
92
+ preparedTurn.observability.emit = input.emit;
93
+
276
94
  let fullResponse = "";
277
95
  let aborted = false;
278
96
  let failed = false;
279
97
 
280
98
  try {
281
- const agentResponse = await this.runAgentTurn({
282
- prompt: input.prompt,
283
- sessionId: input.sessionId,
284
- turnId,
285
- previousUserMessages,
286
- modeName: input.modeName,
287
- userTimeZone: input.userTimeZone,
288
- currentPage: input.currentPage,
289
- chatSurface: input.chatSurface,
290
- adminPublicOrigin: input.adminPublicOrigin,
291
- abortSignal: input.abortSignal,
292
- adminUser: input.adminUser,
293
- sequenceDebugCollector,
294
- emit: input.emit,
295
- });
99
+ const agentResponse = await this.runAgentTurn(preparedTurn);
296
100
  fullResponse = agentResponse.text;
297
101
  } catch (error) {
298
102
  if (input.abortSignal?.aborted || isAbortError(error)) {
@@ -305,20 +109,16 @@ export class AgentTurnService {
305
109
  }
306
110
  }
307
111
 
308
- sequenceDebugCollector.flush();
309
- const turnUpdates: Record<string, unknown> = {
310
- [options.turnResource.responseField]: fullResponse,
311
- };
312
-
313
- if (options.turnResource.debugField) {
314
- turnUpdates[options.turnResource.debugField] = sequenceDebugCollector.getHistory();
315
- }
316
-
317
- await adminforth.resource(options.turnResource.resourceId).update(turnId, turnUpdates);
112
+ preparedTurn.observability.sequenceDebugSink.flush();
113
+ await this.lifecycle.finish({
114
+ turnId: preparedTurn.turnId,
115
+ responseText: fullResponse,
116
+ debugHistory: preparedTurn.observability.sequenceDebugSink.getHistory(),
117
+ });
318
118
 
319
119
  return {
320
120
  text: fullResponse,
321
- turnId,
121
+ turnId: preparedTurn.turnId,
322
122
  aborted,
323
123
  failed,
324
124
  };
@@ -365,177 +165,4 @@ export class AgentTurnService {
365
165
 
366
166
  return agentResponse;
367
167
  }
368
-
369
- async handleSpeechTurn(input: HandleSpeechTurnInput) {
370
- let transcription;
371
-
372
- try {
373
- transcription = await input.audioAdapter.transcribe({
374
- buffer: input.audio.buffer,
375
- filename: input.audio.filename,
376
- mimeType: input.audio.mimeType,
377
- language: "auto",
378
- abortSignal: input.abortSignal,
379
- });
380
- } catch (error) {
381
- if (input.abortSignal?.aborted || isAbortError(error)) {
382
- logger.info("Agent speech transcription aborted by the client");
383
- await input.emit({ type: "finish" });
384
- return null;
385
- }
386
-
387
- logger.error(`Agent speech transcription failed:\n${getErrorMessage(error)}`);
388
- await input.emit({
389
- type: "error",
390
- error: "Speech transcription failed. Check server logs for details.",
391
- });
392
- await input.emit({ type: "finish" });
393
- return null;
394
- }
395
-
396
- if (input.abortSignal?.aborted) {
397
- await input.emit({ type: "finish" });
398
- return null;
399
- }
400
-
401
- const prompt = transcription.text;
402
- if (!prompt) {
403
- await input.emit({
404
- type: "error",
405
- error: "Speech transcription is empty",
406
- });
407
- await input.emit({ type: "finish" });
408
- return null;
409
- }
410
-
411
- await input.emit({
412
- type: "transcript",
413
- text: transcription.text,
414
- language: transcription.language,
415
- });
416
-
417
- const agentResponse = await this.runAndPersistAgentResponse({
418
- prompt,
419
- sessionId: input.sessionId,
420
- modeName: input.modeName,
421
- userTimeZone: input.userTimeZone,
422
- currentPage: input.currentPage,
423
- chatSurface: input.chatSurface,
424
- adminPublicOrigin: input.adminPublicOrigin,
425
- abortSignal: input.abortSignal,
426
- adminUser: input.adminUser,
427
- emit: async (event) => {
428
- if (event.type === "tool-call") {
429
- await input.emit(event);
430
- }
431
- },
432
- failureLogMessage: input.failureLogMessage ?? "Agent speech response failed",
433
- abortLogMessage: input.abortLogMessage ?? "Agent speech response aborted by the client",
434
- });
435
-
436
- if (agentResponse.aborted) {
437
- await input.emit({ type: "finish" });
438
- return agentResponse;
439
- }
440
-
441
- if (agentResponse.failed) {
442
- await input.emit({
443
- type: "error",
444
- error: agentResponse.text,
445
- });
446
- await input.emit({ type: "finish" });
447
- return agentResponse;
448
- }
449
-
450
- try {
451
- await input.emit({
452
- type: "speech-response",
453
- transcript: {
454
- text: transcription.text,
455
- language: transcription.language,
456
- },
457
- response: {
458
- text: agentResponse.text,
459
- },
460
- sessionId: input.sessionId,
461
- turnId: agentResponse.turnId,
462
- });
463
- const speech = await input.audioAdapter.synthesize({
464
- text: sanitizeSpeechText(agentResponse.text),
465
- stream: true,
466
- streamFormat: "audio",
467
- format: "pcm",
468
- abortSignal: input.abortSignal,
469
- });
470
-
471
- await input.emit({
472
- type: "audio-start",
473
- mimeType: speech.mimeType,
474
- format: speech.format,
475
- sampleRate: 24000,
476
- channelCount: 1,
477
- bitsPerSample: 16,
478
- });
479
-
480
- const reader = speech.audioStream.getReader();
481
- const cancelAudioStream = () => {
482
- void reader.cancel().catch(() => undefined);
483
- };
484
-
485
- try {
486
- input.abortSignal?.addEventListener("abort", cancelAudioStream, { once: true });
487
-
488
- while (true) {
489
- if (input.abortSignal?.aborted) {
490
- await reader.cancel().catch(() => undefined);
491
- break;
492
- }
493
-
494
- const { value, done } = await reader.read();
495
-
496
- if (done) {
497
- break;
498
- }
499
-
500
- if (input.abortSignal?.aborted) {
501
- break;
502
- }
503
-
504
- await input.emit({
505
- type: "audio-delta",
506
- value,
507
- });
508
- }
509
- } finally {
510
- input.abortSignal?.removeEventListener("abort", cancelAudioStream);
511
- reader.releaseLock();
512
- }
513
-
514
- await input.emit({ type: "audio-done" });
515
- await input.emit({ type: "finish" });
516
- return agentResponse;
517
- } catch (error) {
518
- if (input.abortSignal?.aborted || isAbortError(error)) {
519
- logger.info("Agent speech audio streaming aborted by the client");
520
- } else {
521
- logger.error(`Agent speech audio streaming failed:\n${getErrorMessage(error)}`);
522
- await input.emit({
523
- type: "error",
524
- error: getErrorMessage(error),
525
- });
526
- }
527
- await input.emit({ type: "finish" });
528
- return agentResponse;
529
- }
530
- }
531
- }
532
-
533
- function getPartialVegaLiteFenceStartLength(text: string): number {
534
- for (let length = Math.min(text.length, VEGA_LITE_FENCE_START.length - 1); length > 0; length -= 1) {
535
- if (VEGA_LITE_FENCE_START.startsWith(text.slice(-length))) {
536
- return length;
537
- }
538
- }
539
-
540
- return 0;
541
168
  }
package/build.log CHANGED
@@ -62,5 +62,5 @@ custom/speech_recognition_frontend/voiceActivityDetection.ts
62
62
  custom/speech_recognition_frontend/types/
63
63
  custom/speech_recognition_frontend/types/voice-activity-detection.d.ts
64
64
 
65
- sent 1,670,250 bytes received 921 bytes 3,342,342.00 bytes/sec
66
- total size is 1,666,155 speedup is 1.00
65
+ sent 1,670,939 bytes received 917 bytes 3,343,712.00 bytes/sec
66
+ total size is 1,666,790 speedup is 1.00
@@ -24,6 +24,8 @@ If several rows look similar, do not guess which one is "the same" record. Show
24
24
 
25
25
  For long texts show only several first words and add "..." at the end (only if user did not request this field specifically).
26
26
 
27
+ Never show information about more than 10 records in one message. If a tool returns more than 10 relevant records, summarize the total count if known, show only the 10 most relevant records, and offer to narrow filters, sort differently, or continue with the next 10.
28
+
27
29
  Also when you communicate with user about record, add related link to this record. Build it as `{ADMIN_BASE_PATH}resource/{resourceId}/show/{primary key}`. Use _label from `get_resource_data` as anchor text for link (use markdown link). Links should always be relative paths and must start with `ADMIN_BASE_PATH`. Do not add an extra slash after `ADMIN_BASE_PATH`.
28
30
 
29
31
  Before sending the link, verify that the `resourceId`, `{primary key}`, `_label`, and shown fields come from the same exact returned row.
@@ -27,6 +27,8 @@ Also please add related link to record with will be changed. Build it as `{ADMIN
27
27
 
28
28
  Before sending the confirmation, verify that the `resourceId`, `{primary key}`, `_label`, and all shown fields come from the same exact fetched row.
29
29
 
30
+ Never show information about more than 10 records in one message. If a mutation plan affects more than 10 records, show only the 10 most important examples plus the total count if known, and ask the user to confirm the clearly described full batch.
31
+
30
32
  And in the same message ask user for final confirmation.
31
33
 
32
34
  When creating new record, show user all data which you gona create and in same message ask for confirmation.
@@ -123,6 +125,8 @@ For decimal fields please use string values with dot as decimal separator.
123
125
 
124
126
  After creation of new record also show user a link to this record. If several records record were created, show links to all of them in list.
125
127
 
128
+ If more than 10 records were created, show links or details for only 10 of them and summarize the remaining count.
129
+
126
130
  Omit any pictures or file paths, you are not capable of doing it. If they are not required all is good, if they are required, explain to user that you are not able to create record because of this reason.
127
131
 
128
132
  ### Working with dates
@@ -65,7 +65,14 @@ export function createApiBasedToolsMiddleware(apiBasedTools, adminforth) {
65
65
  var _a, _b, _c, _d;
66
66
  const startedAt = Date.now();
67
67
  const toolInput = JSON.stringify((_a = request.toolCall.args) !== null && _a !== void 0 ? _a : {});
68
- const { adminUser, emitToolCallEvent, userTimeZone } = request.runtime.context;
68
+ const { adminUser, emit, sequenceDebugSink, userTimeZone } = request.runtime.context;
69
+ const emitToolCall = (event) => {
70
+ sequenceDebugSink.handleToolCallEvent(event);
71
+ void (emit === null || emit === void 0 ? void 0 : emit({
72
+ type: "tool-call",
73
+ data: event,
74
+ }));
75
+ };
69
76
  const toolArgs = ((_b = request.toolCall.args) !== null && _b !== void 0 ? _b : {});
70
77
  let toolInfo;
71
78
  if (request.toolCall.name === "fetch_skill") {
@@ -84,7 +91,7 @@ export function createApiBasedToolsMiddleware(apiBasedTools, adminforth) {
84
91
  });
85
92
  }
86
93
  const toolCallTracker = createToolCallTracker({
87
- emit: emitToolCallEvent,
94
+ emit: emitToolCall,
88
95
  toolCallId: request.toolCall.id,
89
96
  toolName: request.toolCall.name,
90
97
  toolInfo,
@@ -0,0 +1,9 @@
1
+ import type { PluginOptions } from "../../types.js";
2
+ export declare class AgentModeResolver {
3
+ private readonly options;
4
+ constructor(options: PluginOptions);
5
+ resolve(modeName?: string | null): {
6
+ name: string;
7
+ completionAdapter: import("../simpleAgent.js").AgentModeCompletionAdapter;
8
+ };
9
+ }
@@ -0,0 +1,9 @@
1
+ export class AgentModeResolver {
2
+ constructor(options) {
3
+ this.options = options;
4
+ }
5
+ resolve(modeName) {
6
+ var _a;
7
+ return (_a = this.options.modes.find((mode) => mode.name === modeName)) !== null && _a !== void 0 ? _a : this.options.modes[0];
8
+ }
9
+ }
@@ -0,0 +1,7 @@
1
+ import type { CompletionAdapter } from "adminforth";
2
+ import type { AgentTurnModels } from "../turn/turnTypes.js";
3
+ export declare class AgentModelFactory {
4
+ private readonly maxTokens;
5
+ constructor(maxTokens: number);
6
+ create(completionAdapter: CompletionAdapter): Promise<AgentTurnModels>;
7
+ }