@adminforth/agent 1.48.0 → 1.49.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.
@@ -47,6 +47,70 @@ function getEnabledApiToolNames(messages: unknown[]) {
47
47
  return enabledToolNames;
48
48
  }
49
49
 
50
+ const loadedApiToolNamesBySession = new Map<string, Set<string>>();
51
+
52
+ function getSessionLoadedApiToolNames(sessionId: string) {
53
+ let toolNames = loadedApiToolNamesBySession.get(sessionId);
54
+
55
+ if (!toolNames) {
56
+ toolNames = new Set<string>();
57
+ loadedApiToolNamesBySession.set(sessionId, toolNames);
58
+ }
59
+
60
+ return toolNames;
61
+ }
62
+
63
+ function getEnabledApiToolNamesForSession(messages: unknown[], sessionId?: string) {
64
+ const enabledToolNames = getEnabledApiToolNames(messages);
65
+
66
+ if (!sessionId) {
67
+ return enabledToolNames;
68
+ }
69
+
70
+ for (const toolName of getSessionLoadedApiToolNames(sessionId)) {
71
+ enabledToolNames.add(toolName);
72
+ }
73
+
74
+ return enabledToolNames;
75
+ }
76
+
77
+ function getToolMessageContent(message: unknown) {
78
+ if (!ToolMessage.isInstance(message)) {
79
+ return "";
80
+ }
81
+
82
+ return typeof message.content === "string"
83
+ ? message.content
84
+ : Array.isArray(message.content)
85
+ ? message.content
86
+ .map((block) =>
87
+ typeof block === "string"
88
+ ? block
89
+ : "text" in block
90
+ ? block.text
91
+ : "",
92
+ )
93
+ .join("")
94
+ : "";
95
+ }
96
+
97
+ function rememberLoadedToolFromFetchResult(sessionId: string, result: unknown) {
98
+ if (!ToolMessage.isInstance(result) || result.name !== "fetch_tool_schema") {
99
+ return;
100
+ }
101
+
102
+ try {
103
+ const parsed = JSON.parse(getToolMessageContent(result)) as {
104
+ status?: number;
105
+ name?: string;
106
+ };
107
+
108
+ if (parsed.status === 200 && parsed.name) {
109
+ getSessionLoadedApiToolNames(sessionId).add(parsed.name);
110
+ }
111
+ } catch {}
112
+ }
113
+
50
114
  export function createApiBasedToolsMiddleware(
51
115
  apiBasedTools: Record<string, ApiBasedTool>,
52
116
  adminforth: IAdminForth,
@@ -62,7 +126,11 @@ export function createApiBasedToolsMiddleware(
62
126
  return createMiddleware({
63
127
  name: "ApiBasedToolsMiddleware",
64
128
  async wrapModelCall(request, handler) {
65
- const enabledApiToolNames = getEnabledApiToolNames(request.state.messages);
129
+ const { sessionId } = request.runtime.context as { sessionId?: string };
130
+ const enabledApiToolNames = getEnabledApiToolNamesForSession(
131
+ request.state.messages,
132
+ sessionId,
133
+ );
66
134
  const tools = [...enabledApiToolNames]
67
135
  .filter((toolName) => !alwaysAvailableApiToolNames.has(toolName))
68
136
  .map((toolName) => dynamicTools[toolName]);
@@ -80,9 +148,10 @@ export function createApiBasedToolsMiddleware(
80
148
  async wrapToolCall(request, handler) {
81
149
  const startedAt = Date.now();
82
150
  const toolInput = JSON.stringify(request.toolCall.args ?? {});
83
- const { adminUser, emitToolCallEvent, userTimeZone } = request.runtime.context as {
151
+ const { adminUser, emitToolCallEvent, sessionId, userTimeZone } = request.runtime.context as {
84
152
  adminUser: AdminUser;
85
153
  emitToolCallEvent: ToolCallEventSink;
154
+ sessionId: string;
86
155
  userTimeZone: string;
87
156
  };
88
157
  const toolArgs = (request.toolCall.args ?? {}) as Record<string, unknown>;
@@ -120,7 +189,10 @@ export function createApiBasedToolsMiddleware(
120
189
  if (request.tool) {
121
190
  result = await handler(request);
122
191
  } else {
123
- const enabledApiToolNames = getEnabledApiToolNames(request.state.messages);
192
+ const enabledApiToolNames = getEnabledApiToolNamesForSession(
193
+ request.state.messages,
194
+ sessionId,
195
+ );
124
196
 
125
197
  if (enabledApiToolNames.has(request.toolCall.name)) {
126
198
  result = await handler({
@@ -137,6 +209,10 @@ export function createApiBasedToolsMiddleware(
137
209
  }
138
210
  }
139
211
 
212
+ if (sessionId) {
213
+ rememberLoadedToolFromFetchResult(sessionId, result);
214
+ }
215
+
140
216
  toolCallTracker.finishSuccess(result);
141
217
  return result;
142
218
  } catch (error) {
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,667,891 bytes received 921 bytes 3,337,624.00 bytes/sec
66
- total size is 1,663,796 speedup is 1.00
65
+ sent 1,668,700 bytes received 921 bytes 3,339,242.00 bytes/sec
66
+ total size is 1,664,547 speedup is 1.00
@@ -118,10 +118,13 @@ const THRESHOLD_TO_SHOW_BUTTON = 10;
118
118
  let messageResizeObserver: ResizeObserver | null = null;
119
119
  let observedLastUserMessageElement: HTMLElement | null = null;
120
120
  let observedLastAgentMessageElement: HTMLElement | null = null;
121
+ let updateSpacerFrameId: number | null = null;
122
+ let pendingSpacerUpdate: Promise<void> | null = null;
123
+ let spacerUpdateQueued = false;
121
124
 
122
125
  onMounted(async () => {
123
126
  messageResizeObserver = new ResizeObserver(() => {
124
- updateSpacerHeight();
127
+ scheduleSpacerHeightUpdate();
125
128
  });
126
129
 
127
130
  await import('@incremark/theme/styles.css')
@@ -136,6 +139,10 @@ onUnmounted(() => {
136
139
 
137
140
  stopObservingLastMessages();
138
141
  messageResizeObserver?.disconnect();
142
+ if (updateSpacerFrameId !== null) {
143
+ cancelAnimationFrame(updateSpacerFrameId);
144
+ updateSpacerFrameId = null;
145
+ }
139
146
  agentStore.stopPlaceholderAnimation();
140
147
  });
141
148
 
@@ -221,6 +228,30 @@ async function updateSpacerHeight() {
221
228
  spacerHeight.value = Math.max(0, clientHeight - (lastUserMessageHeight + MASK_HEIGHT + lastAgentMessageHeight));
222
229
  }
223
230
 
231
+ function scheduleSpacerHeightUpdate() {
232
+ spacerUpdateQueued = true;
233
+
234
+ if (updateSpacerFrameId !== null) {
235
+ return;
236
+ }
237
+
238
+ updateSpacerFrameId = requestAnimationFrame(() => {
239
+ updateSpacerFrameId = null;
240
+
241
+ if (pendingSpacerUpdate) {
242
+ return;
243
+ }
244
+
245
+ spacerUpdateQueued = false;
246
+ pendingSpacerUpdate = updateSpacerHeight().finally(() => {
247
+ pendingSpacerUpdate = null;
248
+ if (spacerUpdateQueued) {
249
+ scheduleSpacerHeightUpdate();
250
+ }
251
+ });
252
+ });
253
+ }
254
+
224
255
  function stopObservingLastMessages() {
225
256
  if (!messageResizeObserver) {
226
257
  return;
@@ -285,4 +316,4 @@ function recalculateScroll() {
285
316
  }
286
317
  }
287
318
 
288
- </script>
319
+ </script>
@@ -41,6 +41,53 @@ function getEnabledApiToolNames(messages) {
41
41
  }
42
42
  return enabledToolNames;
43
43
  }
44
+ const loadedApiToolNamesBySession = new Map();
45
+ function getSessionLoadedApiToolNames(sessionId) {
46
+ let toolNames = loadedApiToolNamesBySession.get(sessionId);
47
+ if (!toolNames) {
48
+ toolNames = new Set();
49
+ loadedApiToolNamesBySession.set(sessionId, toolNames);
50
+ }
51
+ return toolNames;
52
+ }
53
+ function getEnabledApiToolNamesForSession(messages, sessionId) {
54
+ const enabledToolNames = getEnabledApiToolNames(messages);
55
+ if (!sessionId) {
56
+ return enabledToolNames;
57
+ }
58
+ for (const toolName of getSessionLoadedApiToolNames(sessionId)) {
59
+ enabledToolNames.add(toolName);
60
+ }
61
+ return enabledToolNames;
62
+ }
63
+ function getToolMessageContent(message) {
64
+ if (!ToolMessage.isInstance(message)) {
65
+ return "";
66
+ }
67
+ return typeof message.content === "string"
68
+ ? message.content
69
+ : Array.isArray(message.content)
70
+ ? message.content
71
+ .map((block) => typeof block === "string"
72
+ ? block
73
+ : "text" in block
74
+ ? block.text
75
+ : "")
76
+ .join("")
77
+ : "";
78
+ }
79
+ function rememberLoadedToolFromFetchResult(sessionId, result) {
80
+ if (!ToolMessage.isInstance(result) || result.name !== "fetch_tool_schema") {
81
+ return;
82
+ }
83
+ try {
84
+ const parsed = JSON.parse(getToolMessageContent(result));
85
+ if (parsed.status === 200 && parsed.name) {
86
+ getSessionLoadedApiToolNames(sessionId).add(parsed.name);
87
+ }
88
+ }
89
+ catch (_a) { }
90
+ }
44
91
  export function createApiBasedToolsMiddleware(apiBasedTools, adminforth) {
45
92
  const alwaysAvailableApiToolNames = new Set(ALWAYS_AVAILABLE_API_TOOL_NAMES);
46
93
  const dynamicTools = Object.fromEntries(Object.entries(apiBasedTools).map(([toolName, apiBasedTool]) => [
@@ -51,7 +98,8 @@ export function createApiBasedToolsMiddleware(apiBasedTools, adminforth) {
51
98
  name: "ApiBasedToolsMiddleware",
52
99
  wrapModelCall(request, handler) {
53
100
  return __awaiter(this, void 0, void 0, function* () {
54
- const enabledApiToolNames = getEnabledApiToolNames(request.state.messages);
101
+ const { sessionId } = request.runtime.context;
102
+ const enabledApiToolNames = getEnabledApiToolNamesForSession(request.state.messages, sessionId);
55
103
  const tools = [...enabledApiToolNames]
56
104
  .filter((toolName) => !alwaysAvailableApiToolNames.has(toolName))
57
105
  .map((toolName) => dynamicTools[toolName]);
@@ -65,7 +113,7 @@ export function createApiBasedToolsMiddleware(apiBasedTools, adminforth) {
65
113
  var _a, _b, _c, _d;
66
114
  const startedAt = Date.now();
67
115
  const toolInput = JSON.stringify((_a = request.toolCall.args) !== null && _a !== void 0 ? _a : {});
68
- const { adminUser, emitToolCallEvent, userTimeZone } = request.runtime.context;
116
+ const { adminUser, emitToolCallEvent, sessionId, userTimeZone } = request.runtime.context;
69
117
  const toolArgs = ((_b = request.toolCall.args) !== null && _b !== void 0 ? _b : {});
70
118
  let toolInfo;
71
119
  if (request.toolCall.name === "fetch_skill") {
@@ -99,7 +147,7 @@ export function createApiBasedToolsMiddleware(apiBasedTools, adminforth) {
99
147
  result = yield handler(request);
100
148
  }
101
149
  else {
102
- const enabledApiToolNames = getEnabledApiToolNames(request.state.messages);
150
+ const enabledApiToolNames = getEnabledApiToolNamesForSession(request.state.messages, sessionId);
103
151
  if (enabledApiToolNames.has(request.toolCall.name)) {
104
152
  result = yield handler(Object.assign(Object.assign({}, request), { tool: dynamicTools[request.toolCall.name] }));
105
153
  }
@@ -112,6 +160,9 @@ export function createApiBasedToolsMiddleware(apiBasedTools, adminforth) {
112
160
  });
113
161
  }
114
162
  }
163
+ if (sessionId) {
164
+ rememberLoadedToolFromFetchResult(sessionId, result);
165
+ }
115
166
  toolCallTracker.finishSuccess(result);
116
167
  return result;
117
168
  }
@@ -118,10 +118,13 @@ const THRESHOLD_TO_SHOW_BUTTON = 10;
118
118
  let messageResizeObserver: ResizeObserver | null = null;
119
119
  let observedLastUserMessageElement: HTMLElement | null = null;
120
120
  let observedLastAgentMessageElement: HTMLElement | null = null;
121
+ let updateSpacerFrameId: number | null = null;
122
+ let pendingSpacerUpdate: Promise<void> | null = null;
123
+ let spacerUpdateQueued = false;
121
124
 
122
125
  onMounted(async () => {
123
126
  messageResizeObserver = new ResizeObserver(() => {
124
- updateSpacerHeight();
127
+ scheduleSpacerHeightUpdate();
125
128
  });
126
129
 
127
130
  await import('@incremark/theme/styles.css')
@@ -136,6 +139,10 @@ onUnmounted(() => {
136
139
 
137
140
  stopObservingLastMessages();
138
141
  messageResizeObserver?.disconnect();
142
+ if (updateSpacerFrameId !== null) {
143
+ cancelAnimationFrame(updateSpacerFrameId);
144
+ updateSpacerFrameId = null;
145
+ }
139
146
  agentStore.stopPlaceholderAnimation();
140
147
  });
141
148
 
@@ -221,6 +228,30 @@ async function updateSpacerHeight() {
221
228
  spacerHeight.value = Math.max(0, clientHeight - (lastUserMessageHeight + MASK_HEIGHT + lastAgentMessageHeight));
222
229
  }
223
230
 
231
+ function scheduleSpacerHeightUpdate() {
232
+ spacerUpdateQueued = true;
233
+
234
+ if (updateSpacerFrameId !== null) {
235
+ return;
236
+ }
237
+
238
+ updateSpacerFrameId = requestAnimationFrame(() => {
239
+ updateSpacerFrameId = null;
240
+
241
+ if (pendingSpacerUpdate) {
242
+ return;
243
+ }
244
+
245
+ spacerUpdateQueued = false;
246
+ pendingSpacerUpdate = updateSpacerHeight().finally(() => {
247
+ pendingSpacerUpdate = null;
248
+ if (spacerUpdateQueued) {
249
+ scheduleSpacerHeightUpdate();
250
+ }
251
+ });
252
+ });
253
+ }
254
+
224
255
  function stopObservingLastMessages() {
225
256
  if (!messageResizeObserver) {
226
257
  return;
@@ -285,4 +316,4 @@ function recalculateScroll() {
285
316
  }
286
317
  }
287
318
 
288
- </script>
319
+ </script>
@@ -11,6 +11,7 @@ import { randomUUID } from "crypto";
11
11
  function createAgentEventStream(res, options = {}) {
12
12
  let isStreamClosed = false;
13
13
  let activeBlock = null;
14
+ const isAiUiMessageStream = options.vercelAiUiMessageStream === true;
14
15
  res.writeHead(200, Object.assign({ "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive" }, (options.vercelAiUiMessageStream
15
16
  ? { "x-vercel-ai-ui-message-stream": "v1" }
16
17
  : {})));
@@ -89,7 +90,7 @@ function createAgentEventStream(res, options = {}) {
89
90
  },
90
91
  transcript(text, language) {
91
92
  stream.send({
92
- type: "transcript",
93
+ type: isAiUiMessageStream ? "data-transcript" : "transcript",
93
94
  data: {
94
95
  text,
95
96
  language,
@@ -98,7 +99,7 @@ function createAgentEventStream(res, options = {}) {
98
99
  },
99
100
  response(text, sessionId, turnId) {
100
101
  stream.send({
101
- type: "response",
102
+ type: isAiUiMessageStream ? "data-response" : "response",
102
103
  data: {
103
104
  text,
104
105
  sessionId,
@@ -108,7 +109,7 @@ function createAgentEventStream(res, options = {}) {
108
109
  },
109
110
  speechResponse(transcript, response, sessionId, turnId) {
110
111
  stream.send({
111
- type: "speech-response",
112
+ type: isAiUiMessageStream ? "data-speech-response" : "speech-response",
112
113
  data: {
113
114
  transcript,
114
115
  response,
@@ -119,7 +120,7 @@ function createAgentEventStream(res, options = {}) {
119
120
  },
120
121
  audioStart(mimeType, format, sampleRate, channelCount, bitsPerSample) {
121
122
  stream.send({
122
- type: "audio-start",
123
+ type: isAiUiMessageStream ? "data-audio-start" : "audio-start",
123
124
  data: {
124
125
  mimeType,
125
126
  format,
@@ -131,22 +132,25 @@ function createAgentEventStream(res, options = {}) {
131
132
  },
132
133
  audioDelta(value) {
133
134
  stream.send({
134
- type: "audio-delta",
135
+ type: isAiUiMessageStream ? "data-audio-delta" : "audio-delta",
135
136
  data: {
136
137
  base64: Buffer.from(value).toString("base64"),
137
138
  },
138
139
  });
139
140
  },
140
141
  audioDone() {
141
- stream.send({
142
- type: "audio-done",
143
- });
142
+ stream.send(Object.assign({ type: isAiUiMessageStream ? "data-audio-done" : "audio-done" }, (isAiUiMessageStream ? { data: {} } : {})));
144
143
  },
145
144
  error(error) {
146
- stream.send({
147
- type: "error",
148
- error,
149
- });
145
+ stream.send(isAiUiMessageStream
146
+ ? {
147
+ type: "error",
148
+ errorText: error,
149
+ }
150
+ : {
151
+ type: "error",
152
+ error,
153
+ });
150
154
  },
151
155
  end() {
152
156
  if (isStreamClosed || res.writableEnded || res.destroyed) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/agent",
3
- "version": "1.48.0",
3
+ "version": "1.49.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -22,6 +22,7 @@ function createAgentEventStream(
22
22
  ) {
23
23
  let isStreamClosed = false;
24
24
  let activeBlock: { type: "text" | "reasoning"; id: string } | null = null;
25
+ const isAiUiMessageStream = options.vercelAiUiMessageStream === true;
25
26
 
26
27
  res.writeHead(200, {
27
28
  "Content-Type": "text/event-stream",
@@ -124,7 +125,7 @@ function createAgentEventStream(
124
125
 
125
126
  transcript(text: string, language?: string) {
126
127
  stream.send({
127
- type: "transcript",
128
+ type: isAiUiMessageStream ? "data-transcript" : "transcript",
128
129
  data: {
129
130
  text,
130
131
  language,
@@ -134,7 +135,7 @@ function createAgentEventStream(
134
135
 
135
136
  response(text: string, sessionId: string, turnId: string) {
136
137
  stream.send({
137
- type: "response",
138
+ type: isAiUiMessageStream ? "data-response" : "response",
138
139
  data: {
139
140
  text,
140
141
  sessionId,
@@ -150,7 +151,7 @@ function createAgentEventStream(
150
151
  turnId: string,
151
152
  ) {
152
153
  stream.send({
153
- type: "speech-response",
154
+ type: isAiUiMessageStream ? "data-speech-response" : "speech-response",
154
155
  data: {
155
156
  transcript,
156
157
  response,
@@ -168,7 +169,7 @@ function createAgentEventStream(
168
169
  bitsPerSample: number,
169
170
  ) {
170
171
  stream.send({
171
- type: "audio-start",
172
+ type: isAiUiMessageStream ? "data-audio-start" : "audio-start",
172
173
  data: {
173
174
  mimeType,
174
175
  format,
@@ -181,7 +182,7 @@ function createAgentEventStream(
181
182
 
182
183
  audioDelta(value: Uint8Array) {
183
184
  stream.send({
184
- type: "audio-delta",
185
+ type: isAiUiMessageStream ? "data-audio-delta" : "audio-delta",
185
186
  data: {
186
187
  base64: Buffer.from(value).toString("base64"),
187
188
  },
@@ -190,15 +191,21 @@ function createAgentEventStream(
190
191
 
191
192
  audioDone() {
192
193
  stream.send({
193
- type: "audio-done",
194
+ type: isAiUiMessageStream ? "data-audio-done" : "audio-done",
195
+ ...(isAiUiMessageStream ? { data: {} } : {}),
194
196
  });
195
197
  },
196
198
 
197
199
  error(error: string) {
198
- stream.send({
199
- type: "error",
200
- error,
201
- });
200
+ stream.send(isAiUiMessageStream
201
+ ? {
202
+ type: "error",
203
+ errorText: error,
204
+ }
205
+ : {
206
+ type: "error",
207
+ error,
208
+ });
202
209
  },
203
210
 
204
211
  end() {