@adminforth/agent 2.5.9 → 2.6.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.
@@ -28,8 +28,11 @@
28
28
  </span>
29
29
  <button
30
30
  type="button"
31
- class="shrink-0 rounded-md px-2 py-1 text-xs font-medium text-lightPrimary transition-colors hover:bg-lightPrimary/10 dark:text-darkPrimary dark:hover:bg-darkPrimary/10"
32
- :title="$t('Steer into the current response')"
31
+ class="shrink-0 rounded-md px-2 py-1 text-xs font-medium text-lightPrimary transition-colors hover:bg-lightPrimary/10 disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent dark:text-darkPrimary dark:hover:bg-darkPrimary/10 dark:disabled:hover:bg-transparent"
32
+ :disabled="agentStore.isFinalResponseStreaming"
33
+ :title="agentStore.isFinalResponseStreaming
34
+ ? $t('The final response is streaming; this message will be sent after it finishes')
35
+ : $t('Steer into the current response')"
33
36
  @click="agentStore.steerQueuedMessage(item.id)"
34
37
  >
35
38
  {{ $t('Steer') }}
@@ -134,6 +134,11 @@ export function createAgentChatManager({
134
134
  }
135
135
 
136
136
  function handleManualApprovalStreamPart(dataPart: any) {
137
+ if (dataPart?.type === 'error') {
138
+ const error = dataPart.errorText ?? dataPart.error;
139
+ throw new Error(typeof error === 'string' && error ? error : 'Agent approval failed');
140
+ }
141
+
137
142
  if (dataPart?.type === 'text-delta' && typeof dataPart.delta === 'string') {
138
143
  appendTextDelta(dataPart.delta);
139
144
  return;
@@ -344,6 +349,7 @@ export function createAgentChatManager({
344
349
  body: JSON.stringify({
345
350
  sessionId,
346
351
  decision,
352
+ mode: activeModeName.value,
347
353
  }),
348
354
  });
349
355
 
@@ -13,12 +13,14 @@ export type QueuedMessage = {
13
13
  type CreateAgentSteerQueueOptions = {
14
14
  activeSessionId: Ref<string | null>;
15
15
  currentChat: ShallowRef<Chat<any> | null | undefined>;
16
+ isFinalResponseStreaming: Ref<boolean>;
16
17
  sendMessage: (text: string) => void | Promise<void>;
17
18
  };
18
19
 
19
20
  export function createAgentSteerQueue({
20
21
  activeSessionId,
21
22
  currentChat,
23
+ isFinalResponseStreaming,
22
24
  sendMessage,
23
25
  }: CreateAgentSteerQueueOptions) {
24
26
  const queue = ref<QueuedMessage[]>([]);
@@ -146,7 +148,10 @@ export function createAgentSteerQueue({
146
148
  async function steerQueuedMessage(id: string) {
147
149
  const item = queue.value.find((candidate: QueuedMessage) => candidate.id === id);
148
150
  const sessionId = activeSessionId.value;
149
- if (!item || !sessionId || sessionId === PRE_SESSION_ID) {
151
+ // A steer is only consumed before a subsequent model call. Once the assistant is
152
+ // streaming its final text there is no such call left, so retain this item for the
153
+ // normal FIFO follow-up instead of silently losing the instruction server-side.
154
+ if (!item || !sessionId || sessionId === PRE_SESSION_ID || isFinalResponseStreaming.value) {
150
155
  return;
151
156
  }
152
157
  removeQueuedMessage(id);
@@ -161,6 +161,7 @@ export const useAgentStore = defineStore('agent', () => {
161
161
 
162
162
  if (approvalPart?.data) {
163
163
  approvalPart.data.status = 'processing';
164
+ approvalPart.data.error = undefined;
164
165
  }
165
166
 
166
167
  try {
@@ -172,6 +173,7 @@ export const useAgentStore = defineStore('agent', () => {
172
173
  } catch (error) {
173
174
  if (approvalPart?.data) {
174
175
  approvalPart.data.status = 'pending';
176
+ approvalPart.data.error = error instanceof Error ? error.message : String(error);
175
177
  }
176
178
  console.error('Error submitting tool approval', error);
177
179
  }
@@ -238,9 +240,22 @@ export const useAgentStore = defineStore('agent', () => {
238
240
  return status === 'submitted' || status === 'streaming' || hasPendingToolApproval.value;
239
241
  });
240
242
 
243
+ // During an assistant text stream, a newly submitted steer cannot be consumed: the
244
+ // steer middleware only runs before another model call. Keep it in the queue so it
245
+ // becomes a normal follow-up when the current turn ends.
246
+ const isFinalResponseStreaming = computed(() => {
247
+ const chat = currentChat.value as any;
248
+ const lastMessage = chat?.lastMessage;
249
+
250
+ return chat?.status === 'streaming'
251
+ && lastMessage?.role === 'assistant'
252
+ && lastMessage.parts?.some((part: any) => part.type === 'text' && part.state === 'streaming');
253
+ });
254
+
241
255
  const steerBuffer = createAgentSteerQueue({
242
256
  activeSessionId,
243
257
  currentChat,
258
+ isFinalResponseStreaming,
244
259
  sendMessage,
245
260
  });
246
261
 
@@ -646,6 +661,7 @@ export const useAgentStore = defineStore('agent', () => {
646
661
  //_________-Steer queue-_____________
647
662
  submitUserMessage,
648
663
  isTurnActive,
664
+ isFinalResponseStreaming,
649
665
  steerQueue: steerBuffer.queue,
650
666
  steerQueuedMessage: steerBuffer.steerQueuedMessage,
651
667
  removeQueuedMessage: steerBuffer.removeQueuedMessage,
@@ -70,6 +70,12 @@
70
70
  {{ data.status === 'processing' ? $t('Processing') : data.status === 'approved' ? $t('Approved') : $t('Rejected') }}
71
71
  </span>
72
72
  </div>
73
+ <p
74
+ v-if="data.error"
75
+ class="mt-3 text-sm leading-5 text-red-600 dark:text-red-400"
76
+ >
77
+ {{ data.error }}
78
+ </p>
73
79
  </div>
74
80
  </div>
75
81
  </div>
@@ -13,6 +13,7 @@ import { createSequenceDebugMiddleware } from "./middleware/sequenceDebug.js";
13
13
  import { createSteerMiddleware } from "./middleware/steerMiddleware.js";
14
14
  import { createAgentLlmMetricsLogger } from "./agentModels.js";
15
15
  import { contextSchema, toLangchainAgentContext } from "./agentContext.js";
16
+ import { createSystemMessagesMiddleware } from "./middleware/systemMessages.js";
16
17
  function createHumanInTheLoopInterrupts(apiBasedTools) {
17
18
  return Object.fromEntries(Object.entries(apiBasedTools)
18
19
  .filter(([, apiBasedTool]) => { var _a; return ((_a = apiBasedTool.agent) === null || _a === void 0 ? void 0 : _a.isDangerous) === true; })
@@ -43,12 +44,14 @@ export class AgentRuntime {
43
44
  descriptionPrefix: "Tool execution pending approval",
44
45
  });
45
46
  const steerMiddleware = createSteerMiddleware(this.options.steerBuffer);
47
+ const systemMessagesMiddleware = createSystemMessagesMiddleware();
46
48
  const middleware = [
47
49
  steerMiddleware,
48
50
  apiBasedToolsMiddleware,
49
51
  hitlMiddleware,
50
52
  ...((_a = input.models.modelMiddleware) !== null && _a !== void 0 ? _a : []),
51
53
  sequenceDebugMiddleware,
54
+ systemMessagesMiddleware,
52
55
  summarizationMiddleware({
53
56
  model: input.models.summaryModel,
54
57
  trigger: { tokens: 1024 * 64 },
@@ -0,0 +1 @@
1
+ export declare function createSystemMessagesMiddleware(): import("langchain").AgentMiddleware<undefined, undefined, unknown, readonly (import("@langchain/core/tools").ClientTool | import("@langchain/core/tools").ServerTool)[]>;
@@ -0,0 +1,55 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { createMiddleware } from "langchain";
11
+ function isSystemMessage(message) {
12
+ var _a, _b;
13
+ return (((_a = message._getType) === null || _a === void 0 ? void 0 : _a.call(message)) === "system" ||
14
+ ((_b = message.getType) === null || _b === void 0 ? void 0 : _b.call(message)) === "system" ||
15
+ message.type === "system");
16
+ }
17
+ function contentToText(content, text) {
18
+ if (text)
19
+ return text;
20
+ if (typeof content === "string")
21
+ return content;
22
+ if (!Array.isArray(content))
23
+ return "";
24
+ return content
25
+ .map((block) => {
26
+ var _a;
27
+ if (typeof block === "string")
28
+ return block;
29
+ if (block && typeof block === "object" && "text" in block) {
30
+ return String((_a = block.text) !== null && _a !== void 0 ? _a : "");
31
+ }
32
+ return "";
33
+ })
34
+ .join("");
35
+ }
36
+ function normalizeSystemMessages(request) {
37
+ const extraSystemText = request.messages
38
+ .filter(isSystemMessage)
39
+ .map((message) => contentToText(message.content, message.text))
40
+ .filter(Boolean)
41
+ .join("\n\n");
42
+ return Object.assign(Object.assign({}, request), { systemMessage: extraSystemText
43
+ ? request.systemMessage.concat(extraSystemText)
44
+ : request.systemMessage, messages: request.messages.filter((message) => !isSystemMessage(message)) });
45
+ }
46
+ export function createSystemMessagesMiddleware() {
47
+ return createMiddleware({
48
+ name: "SystemMessagesMiddleware",
49
+ wrapModelCall(request, handler) {
50
+ return __awaiter(this, void 0, void 0, function* () {
51
+ return handler(normalizeSystemMessages(request));
52
+ });
53
+ },
54
+ });
55
+ }
@@ -7,6 +7,11 @@ export declare class AdminForthCheckpointSaver extends BaseCheckpointSaver {
7
7
  constructor(adminforth: any, pluginOptions: PluginOptions);
8
8
  private get resourceConfig();
9
9
  private resource;
10
+ /**
11
+ * Checkpoints contain LangChain message instances. Plain JSON.stringify loses
12
+ * their constructors, so a resumed HITL middleware cannot recognize AIMessage
13
+ * tool calls. Use LangGraph's serializer, which preserves those constructors.
14
+ */
10
15
  private serialize;
11
16
  private deserialize;
12
17
  private now;
@@ -39,18 +39,28 @@ export class AdminForthCheckpointSaver extends BaseCheckpointSaver {
39
39
  resource() {
40
40
  return this.adminforth.resource(this.resourceConfig.resourceId);
41
41
  }
42
+ /**
43
+ * Checkpoints contain LangChain message instances. Plain JSON.stringify loses
44
+ * their constructors, so a resumed HITL middleware cannot recognize AIMessage
45
+ * tool calls. Use LangGraph's serializer, which preserves those constructors.
46
+ */
42
47
  serialize(value) {
43
- if (value === undefined || value === null)
44
- return null;
45
- return JSON.stringify(value);
48
+ return __awaiter(this, void 0, void 0, function* () {
49
+ if (value === undefined || value === null)
50
+ return null;
51
+ const [encoding, payload] = yield this.serde.dumpsTyped(value);
52
+ if (encoding !== "json") {
53
+ throw new Error(`Unsupported checkpoint payload encoding "${encoding}".`);
54
+ }
55
+ return new TextDecoder().decode(payload);
56
+ });
46
57
  }
47
58
  deserialize(value) {
48
- if (value === undefined || value === null)
49
- return null;
50
- if (typeof value === "string") {
51
- return JSON.parse(value);
52
- }
53
- return value;
59
+ return __awaiter(this, void 0, void 0, function* () {
60
+ if (value === undefined || value === null)
61
+ return null;
62
+ return this.serde.loadsTyped("json", typeof value === "string" ? value : JSON.stringify(value));
63
+ });
54
64
  }
55
65
  now() {
56
66
  return new Date().toISOString();
@@ -112,8 +122,8 @@ export class AdminForthCheckpointSaver extends BaseCheckpointSaver {
112
122
  [r.taskIdField]: null,
113
123
  [r.sequenceField]: 0,
114
124
  [r.createdAtField]: createdAt,
115
- [r.checkpointPayloadField]: this.serialize(checkpoint),
116
- [r.metadataPayloadField]: this.serialize(metadata),
125
+ [r.checkpointPayloadField]: yield this.serialize(checkpoint),
126
+ [r.metadataPayloadField]: yield this.serialize(metadata),
117
127
  [r.writesPayloadField]: null,
118
128
  [r.schemaVersionField]: 1,
119
129
  });
@@ -144,7 +154,7 @@ export class AdminForthCheckpointSaver extends BaseCheckpointSaver {
144
154
  [r.createdAtField]: createdAt,
145
155
  [r.checkpointPayloadField]: null,
146
156
  [r.metadataPayloadField]: null,
147
- [r.writesPayloadField]: this.serialize([channel, value]),
157
+ [r.writesPayloadField]: yield this.serialize([channel, value]),
148
158
  [r.schemaVersionField]: 1,
149
159
  });
150
160
  }
@@ -171,23 +181,23 @@ export class AdminForthCheckpointSaver extends BaseCheckpointSaver {
171
181
  }
172
182
  const resolvedCheckpointId = String(checkpointRow[r.checkpointIdField]);
173
183
  const writesRows = yield this.resource().list(Filters.AND(Filters.EQ(r.threadIdField, threadId), Filters.EQ(r.checkpointNamespaceField, storedCheckpointNs), Filters.EQ(r.checkpointIdField, resolvedCheckpointId), Filters.EQ(r.rowKindField, "writes")), undefined, undefined, [{ field: r.sequenceField, direction: "asc" }]);
174
- const pendingWrites = writesRows.flatMap((row) => {
184
+ const pendingWrites = (yield Promise.all(writesRows.map((row) => __awaiter(this, void 0, void 0, function* () {
175
185
  var _a;
176
186
  const taskId = String((_a = row[r.taskIdField]) !== null && _a !== void 0 ? _a : "");
177
- const write = this.deserialize(row[r.writesPayloadField]);
187
+ const write = yield this.deserialize(row[r.writesPayloadField]);
178
188
  if (!write) {
179
189
  return [];
180
190
  }
181
191
  const [channel, value] = write;
182
192
  return [[taskId, channel, value]];
183
- });
193
+ })))).flat();
184
194
  const parentCheckpointId = checkpointRow[r.parentCheckpointIdField]
185
195
  ? String(checkpointRow[r.parentCheckpointIdField])
186
196
  : null;
187
197
  const tuple = {
188
198
  config: this.buildConfig(threadId, checkpointNs, resolvedCheckpointId),
189
- checkpoint: this.deserialize(checkpointRow[r.checkpointPayloadField]),
190
- metadata: ((_a = this.deserialize(checkpointRow[r.metadataPayloadField])) !== null && _a !== void 0 ? _a : {}),
199
+ checkpoint: yield this.deserialize(checkpointRow[r.checkpointPayloadField]),
200
+ metadata: ((_a = yield this.deserialize(checkpointRow[r.metadataPayloadField])) !== null && _a !== void 0 ? _a : {}),
191
201
  parentConfig: parentCheckpointId
192
202
  ? this.buildConfig(threadId, checkpointNs, parentCheckpointId)
193
203
  : undefined,
@@ -25,6 +25,7 @@ const agentResponseBodySchema = z.object({
25
25
  const agentApprovalBodySchema = z.object({
26
26
  sessionId: z.string(),
27
27
  decision: z.enum(["approve", "reject"]),
28
+ mode: z.string().nullish(),
28
29
  }).strict();
29
30
  const agentSteerBodySchema = z.object({
30
31
  sessionId: z.string(),
@@ -151,6 +152,7 @@ export function setupCoreEndpoints(ctx, server) {
151
152
  yield ctx.handleTurn({
152
153
  prompt: "",
153
154
  sessionId: data.sessionId,
155
+ modeName: data.mode,
154
156
  approvalDecision: data.decision,
155
157
  abortSignal,
156
158
  adminUser: adminUser,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/agent",
3
- "version": "2.5.9",
3
+ "version": "2.6.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",