@adminforth/agent 2.2.5 → 2.3.1

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.
@@ -63,13 +63,65 @@ export class AgentSessionStore {
63
63
  }
64
64
  getSessionTurns(sessionId) {
65
65
  return __awaiter(this, void 0, void 0, function* () {
66
- const turns = yield this.getAdminforth().resource(this.options.turnResource.resourceId).list([Filters.EQ(this.options.turnResource.sessionIdField, sessionId)], undefined, undefined, [Sorts.ASC(this.options.turnResource.createdAtField)]);
66
+ const turns = yield this.getAdminforth().resource(this.options.turnResource.resourceId).list([Filters.EQ(this.options.turnResource.sessionIdField, sessionId)], undefined, undefined, [Sorts.ASC(this.options.turnResource.createdAtField), Sorts.ASC(this.options.turnResource.idField)]);
67
67
  return turns.map(turn => ({
68
+ id: turn[this.options.turnResource.idField],
68
69
  prompt: turn[this.options.turnResource.promptField],
69
70
  response: turn[this.options.turnResource.responseField],
70
71
  }));
71
72
  });
72
73
  }
74
+ /**
75
+ * Ordered non-system (real conversation) turns with their stored checkpoint id.
76
+ * Used by message editing to locate the target turn and its previous turn's
77
+ * fork point. Stable order (`createdAt ASC, id ASC`) so positions never drift.
78
+ */
79
+ getAgentTurns(sessionId) {
80
+ return __awaiter(this, void 0, void 0, function* () {
81
+ const checkpointIdField = this.options.turnResource.checkpointIdField;
82
+ const turns = yield this.getAdminforth().resource(this.options.turnResource.resourceId).list([Filters.EQ(this.options.turnResource.sessionIdField, sessionId)], undefined, undefined, [Sorts.ASC(this.options.turnResource.createdAtField), Sorts.ASC(this.options.turnResource.idField)]);
83
+ return turns
84
+ .filter((turn) => turn[this.options.turnResource.promptField] !== AGENT_SYSTEM_TURN_PROMPT)
85
+ .map((turn) => ({
86
+ id: turn[this.options.turnResource.idField],
87
+ prompt: turn[this.options.turnResource.promptField],
88
+ response: turn[this.options.turnResource.responseField],
89
+ checkpointId: checkpointIdField
90
+ ? (turn[checkpointIdField] ? String(turn[checkpointIdField]) : null)
91
+ : null,
92
+ }));
93
+ });
94
+ }
95
+ /**
96
+ * Apply a message edit and branch: replace the edited turn's prompt and truncate
97
+ * everything after it. Deletes later turns FIRST, then updates the edited turn, so
98
+ * an interruption can only leave a consistent "truncated, edit-not-yet-applied"
99
+ * state (recoverable by retry) — never "edited but stale later turns remain".
100
+ * (AdminForth's resource API has no transactions, hence the deliberate ordering.)
101
+ */
102
+ editTurnAndTruncateAfter(input) {
103
+ return __awaiter(this, void 0, void 0, function* () {
104
+ const r = this.options.turnResource;
105
+ const resource = this.getAdminforth().resource(r.resourceId);
106
+ const turns = yield resource.list([Filters.EQ(r.sessionIdField, input.sessionId)], undefined, undefined, [Sorts.ASC(r.createdAtField), Sorts.ASC(r.idField)]);
107
+ const targetIndex = turns.findIndex((turn) => turn[r.idField] === input.turnId);
108
+ if (targetIndex === -1) {
109
+ throw new Error(`Turn "${input.turnId}" not found in session "${input.sessionId}".`);
110
+ }
111
+ const laterTurns = turns.slice(targetIndex + 1);
112
+ for (const turn of laterTurns) {
113
+ yield resource.delete(turn[r.idField]);
114
+ }
115
+ const updates = {
116
+ [r.promptField]: input.newPrompt,
117
+ [r.responseField]: "not_finished",
118
+ };
119
+ if (r.checkpointIdField) {
120
+ updates[r.checkpointIdField] = null;
121
+ }
122
+ yield resource.update(input.turnId, updates);
123
+ });
124
+ }
73
125
  getPreviousUserMessages(sessionId) {
74
126
  return __awaiter(this, void 0, void 0, function* () {
75
127
  const turns = yield this.getAdminforth().resource(this.options.turnResource.resourceId).list([Filters.EQ(this.options.turnResource.sessionIdField, sessionId)], 2, undefined, [Sorts.DESC(this.options.turnResource.createdAtField)]);
@@ -121,6 +173,9 @@ export class AgentSessionStore {
121
173
  if (this.options.turnResource.debugField) {
122
174
  turnUpdates[this.options.turnResource.debugField] = input.debugHistory;
123
175
  }
176
+ if (this.options.turnResource.checkpointIdField && input.checkpointId !== undefined) {
177
+ turnUpdates[this.options.turnResource.checkpointIdField] = input.checkpointId;
178
+ }
124
179
  yield this.getAdminforth()
125
180
  .resource(this.options.turnResource.resourceId)
126
181
  .update(input.turnId, turnUpdates);
@@ -1,7 +1,8 @@
1
1
  import type { ChatSurfaceAdapter, ChatSurfaceEventSink, ChatSurfaceIncomingMessage, IAdminForth } from "adminforth";
2
- import type { HandleSpeechTurnInput, HandleTurnInput, RunAndPersistAgentResponseInput, RunAndPersistAgentResponseResult } from "../../domain/turnTypes.js";
2
+ import type { HandleEditTurnInput, HandleSpeechTurnInput, HandleTurnInput, RunAndPersistAgentResponseInput, RunAndPersistAgentResponseResult } from "../../domain/turnTypes.js";
3
3
  import type { PluginOptions } from "../../types.js";
4
4
  export type SessionTurn = {
5
+ id: string;
5
6
  prompt: string;
6
7
  response: string;
7
8
  };
@@ -9,6 +10,7 @@ export type AgentEndpointsContext = {
9
10
  adminforth: IAdminForth;
10
11
  options: PluginOptions;
11
12
  handleTurn(input: HandleTurnInput): Promise<RunAndPersistAgentResponseResult>;
13
+ handleEditTurn(input: HandleEditTurnInput): Promise<RunAndPersistAgentResponseResult>;
12
14
  handleSpeechTurn(input: HandleSpeechTurnInput): Promise<RunAndPersistAgentResponseResult | null>;
13
15
  steer(input: {
14
16
  sessionId: string;
@@ -24,6 +26,6 @@ export type AgentEndpointsContext = {
24
26
  appendSteerToCurrentTurn(sessionId: string, steerText: string): Promise<void>;
25
27
  handleChatSurfaceMessage(adapter: ChatSurfaceAdapter, incoming: ChatSurfaceIncomingMessage, sink: ChatSurfaceEventSink): Promise<void>;
26
28
  };
27
- export type CoreEndpointsContext = Pick<AgentEndpointsContext, "options" | "handleTurn" | "handleSpeechTurn" | "steer">;
29
+ export type CoreEndpointsContext = Pick<AgentEndpointsContext, "options" | "handleTurn" | "handleEditTurn" | "handleSpeechTurn" | "steer">;
28
30
  export type SessionEndpointsContext = Pick<AgentEndpointsContext, "adminforth" | "options" | "getSessionTurns" | "createNewTurn" | "createSystemTurn" | "appendSteerToCurrentTurn">;
29
31
  export type ChatSurfaceEndpointsContext = Pick<AgentEndpointsContext, "adminforth" | "options" | "handleChatSurfaceMessage">;
@@ -30,6 +30,14 @@ const agentSteerBodySchema = z.object({
30
30
  sessionId: z.string(),
31
31
  message: z.string().min(1),
32
32
  }).strict();
33
+ const agentEditBodySchema = z.object({
34
+ sessionId: z.string(),
35
+ turnId: z.string(),
36
+ message: z.string().min(1),
37
+ mode: z.string().nullish(),
38
+ timeZone: z.string().optional(),
39
+ currentPage: currentPageContextSchema.optional(),
40
+ }).strict();
33
41
  // Sent as multipart/form-data (via multer), so every field arrives as a plain string on the wire.
34
42
  // `currentPage` is JSON.stringify'd by the client into a form field and must be parsed back into
35
43
  // an object. Zod -> JSON-Schema conversion cannot represent `.transform()` at all (it throws), so
@@ -103,6 +111,33 @@ export function setupCoreEndpoints(ctx, server) {
103
111
  return null;
104
112
  })
105
113
  });
114
+ server.endpoint({
115
+ method: 'POST',
116
+ path: `/agent/edit`,
117
+ request_schema: agentEditBodySchema,
118
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, _raw_express_res, abortSignal }) {
119
+ var _b;
120
+ const data = body;
121
+ const emit = createSseEventEmitter(_raw_express_res, {
122
+ vercelAiUiMessageStream: true,
123
+ closeActiveBlockOnToolStart: true,
124
+ });
125
+ yield ctx.handleEditTurn({
126
+ prompt: data.message,
127
+ turnId: data.turnId,
128
+ sessionId: data.sessionId,
129
+ modeName: data.mode,
130
+ userTimeZone: (_b = data.timeZone) !== null && _b !== void 0 ? _b : 'UTC',
131
+ currentPage: data.currentPage,
132
+ abortSignal,
133
+ adminUser: adminUser,
134
+ emit,
135
+ failureLogMessage: "Agent message edit streaming failed",
136
+ abortLogMessage: "Agent message edit streaming aborted by the client",
137
+ });
138
+ return null;
139
+ })
140
+ });
106
141
  server.endpoint({
107
142
  method: 'POST',
108
143
  path: `/agent/approval`,
@@ -81,6 +81,7 @@ export function setupSessionEndpoints(ctx, server) {
81
81
  messages.push({
82
82
  text: turn.response,
83
83
  role: 'system',
84
+ turnId: turn.id,
84
85
  });
85
86
  return messages;
86
87
  }
@@ -88,12 +89,14 @@ export function setupSessionEndpoints(ctx, server) {
88
89
  messages.push({
89
90
  text: turn.prompt,
90
91
  role: 'user',
92
+ turnId: turn.id,
91
93
  });
92
94
  }
93
95
  if (turn.response && turn.response !== "not_finished") {
94
96
  messages.push({
95
97
  text: turn.response,
96
98
  role: 'assistant',
99
+ turnId: turn.id,
97
100
  });
98
101
  }
99
102
  return messages;
@@ -124,6 +124,15 @@ function createAgentEventStream(res, options = {}) {
124
124
  },
125
125
  });
126
126
  },
127
+ turnPersisted(sessionId, turnId) {
128
+ stream.send({
129
+ type: isAiUiMessageStream ? "data-turn-persisted" : "turn-persisted",
130
+ data: {
131
+ sessionId,
132
+ turnId,
133
+ },
134
+ });
135
+ },
127
136
  response(text, sessionId, turnId) {
128
137
  stream.send({
129
138
  type: isAiUiMessageStream ? "data-response" : "response",
@@ -240,6 +249,9 @@ export function createSseEventEmitter(res, options = {}) {
240
249
  case "transcript":
241
250
  stream.transcript(event.text, event.language);
242
251
  break;
252
+ case "turn-persisted":
253
+ stream.turnPersisted(event.sessionId, event.turnId);
254
+ break;
243
255
  case "response":
244
256
  stream.response(event.text, event.sessionId, event.turnId);
245
257
  break;
package/dist/types.d.ts CHANGED
@@ -21,6 +21,7 @@ interface ITurnResource {
21
21
  promptField: string;
22
22
  responseField: string;
23
23
  debugField?: string;
24
+ checkpointIdField?: string;
24
25
  }
25
26
  interface ICheckpointResource {
26
27
  resourceId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/agent",
3
- "version": "2.2.5",
3
+ "version": "2.3.1",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",