@csntgao/ai-employee-client 0.15.0 → 0.15.2

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.
package/dist/index.d.ts CHANGED
@@ -61,7 +61,7 @@ interface AiEmployeeTransport {
61
61
  }
62
62
  type AiEmployeeTransportErrorCode = 'UNAUTHORIZED' | 'ACCESS_DENIED' | 'AI_EMPLOYEE_NOT_AVAILABLE' | 'APPLICATION_NOT_SUPPORTED' | 'ORGANIZATION_CONTEXT_REQUIRED' | 'INVALID_ARGUMENT' | 'IDEMPOTENCY_CONFLICT' | 'REALTIME_UNAVAILABLE' | 'TOO_MANY_REQUESTS';
63
63
  type AiEmployeeConnectionState = 'idle' | 'connecting' | 'authenticating' | 'subscribing' | 'connected' | 'reconnecting' | 'disconnected' | 'destroyed';
64
- type AiEmployeeErrorCode = 'INVALID_CONFIGURATION' | 'SESSION_UNAVAILABLE' | 'HISTORY_LOAD_FAILED' | 'TOKEN_UNAVAILABLE' | 'WEBSOCKET_UNAVAILABLE' | 'WEBSOCKET_ERROR' | 'AUTHENTICATION_FAILED' | 'ORGANIZATION_CONTEXT_REQUIRED' | 'REALTIME_UNAVAILABLE' | 'CHANNEL_UNAVAILABLE' | 'SESSION_NOT_FOUND' | 'PROTOCOL_ERROR' | 'SEND_FAILED' | 'FIRST_MESSAGE_CONFLICT' | 'RATE_LIMITED' | 'SEND_IN_PROGRESS' | 'EMPTY_MESSAGE' | 'SOURCE_SESSION_NOT_FOUND' | 'SOURCE_SESSION_ACCESS_DENIED' | 'HANDOFF_ACTION_NOT_FOUND' | 'HANDOFF_ACTION_MISMATCH' | 'CUSTOMER_SUPPORT_UNAVAILABLE' | 'CUSTOMER_SUPPORT_HANDOFF_FAILED' | 'CLIENT_DESTROYED';
64
+ type AiEmployeeErrorCode = 'INVALID_CONFIGURATION' | 'SESSION_UNAVAILABLE' | 'HISTORY_LOAD_FAILED' | 'TOKEN_UNAVAILABLE' | 'WEBSOCKET_UNAVAILABLE' | 'WEBSOCKET_ERROR' | 'AUTHENTICATION_FAILED' | 'ORGANIZATION_CONTEXT_REQUIRED' | 'REALTIME_UNAVAILABLE' | 'CHANNEL_UNAVAILABLE' | 'SESSION_NOT_FOUND' | 'PROTOCOL_ERROR' | 'SEND_FAILED' | 'FIRST_MESSAGE_CONFLICT' | 'RATE_LIMITED' | 'SEND_IN_PROGRESS' | 'EMPTY_MESSAGE' | 'SOURCE_SESSION_NOT_FOUND' | 'SOURCE_SESSION_ACCESS_DENIED' | 'HANDOFF_ACTION_NOT_FOUND' | 'HANDOFF_ACTION_MISMATCH' | 'CUSTOMER_SUPPORT_UNAVAILABLE' | 'CUSTOMER_SUPPORT_HANDOFF_FAILED' | 'FILE_ACCESS_DENIED' | 'FILE_NOT_FOUND' | 'FILE_PATH_AMBIGUOUS' | 'FILE_DOWNLOAD_FAILED' | 'CLIENT_DESTROYED';
65
65
  interface AiEmployeeClientErrorDetail {
66
66
  code: AiEmployeeErrorCode;
67
67
  message: string;
@@ -91,6 +91,10 @@ interface AiEmployeeClientEventMap {
91
91
  'processing-state-change': {
92
92
  processing: boolean;
93
93
  };
94
+ 'work-activity': {
95
+ sessionId: string;
96
+ text: string;
97
+ };
94
98
  error: AiEmployeeClientErrorDetail;
95
99
  }
96
100
  interface AiEmployeeReconnectOptions {
@@ -226,16 +230,27 @@ interface OpenCustomerSupportAssistantAction {
226
230
  trigger: 'out_of_scope';
227
231
  entry: 'help_and_support';
228
232
  }
229
- type AiEmployeeAssistantAction = OpenCustomerSupportAssistantAction;
233
+ interface OpenFileAssistantAction {
234
+ type: 'assistant_action';
235
+ action: 'open_file';
236
+ transactionId: string;
237
+ sessionId: string;
238
+ name: string;
239
+ size: string;
240
+ path: string;
241
+ }
242
+ type AiEmployeeAssistantAction = OpenCustomerSupportAssistantAction | OpenFileAssistantAction;
230
243
  interface ParsedAiEmployeeAssistantContent {
231
244
  content: string;
232
245
  actions: readonly AiEmployeeAssistantAction[];
246
+ rejectedActionTypes?: readonly AiEmployeeAssistantAction['action'][];
233
247
  }
234
248
  /**
235
- * Parses a structurally valid platform action from the final standalone JSON
236
- * block of an assistant response. Invalid or ambiguous input is returned
237
- * unchanged and is never made executable. Renderers must additionally compare
238
- * `action.sessionId` with the session that owns the assistant message.
249
+ * Parses structurally valid platform actions from consecutive standalone JSON
250
+ * blocks at the end of an assistant response. Invalid or ambiguous trailing
251
+ * action blocks are removed from display content, reported only by stable action
252
+ * type, and never made executable. Renderers must additionally compare every
253
+ * `action.sessionId` with the session that owns the message.
239
254
  */
240
255
  declare function parseAiEmployeeAssistantContent(content: string): ParsedAiEmployeeAssistantContent;
241
256
  declare function isUuidV4(value: unknown): value is string;
@@ -658,6 +673,7 @@ interface PingFrame {
658
673
  }
659
674
  interface ParsedSessionMessage {
660
675
  messages: AiEmployeeMessage[];
676
+ activities: string[];
661
677
  fallbackResult?: AiEmployeeMessage;
662
678
  processing?: boolean;
663
679
  }
@@ -679,4 +695,4 @@ declare function parseJsonRecord(raw: string): JsonRecord | null;
679
695
  declare function parseAiEmployeeEvent(raw: unknown): ParsedAiEmployeeEvent;
680
696
  declare function hashText(value: string): string;
681
697
 
682
- export { type AiEmployeeAssistantAction, type AiEmployeeAttachmentReference, AiEmployeeClient, AiEmployeeClientError, type AiEmployeeClientErrorDetail, type AiEmployeeClientEventMap, type AiEmployeeClientListener, type AiEmployeeClientOptions, type AiEmployeeClientStartResult, type AiEmployeeConnectionState, type AiEmployeeErrorCode, type AiEmployeeFirstMessageOptions, type AiEmployeeFirstMessageRequest, type AiEmployeeFirstMessageRequestState, type AiEmployeeFirstMessageRequestStore, type AiEmployeeFirstMessageResult, type AiEmployeeMessage, type AiEmployeeMessageRole, type AiEmployeeReconnectOptions, type AiEmployeeSessionSummary, type AiEmployeeTransport, AiEmployeeTransportError, type AiEmployeeTransportErrorCode, type AiEmployeeWebSocketFactory, type AuthenticateFrame, type CustomerSupportAgent, CustomerSupportClient, CustomerSupportClientError, type CustomerSupportClientEventMap, type CustomerSupportClientListener, type CustomerSupportClientOptions, type CustomerSupportCreateSessionRequest, type CustomerSupportCreateSessionResult, type CustomerSupportErrorCode, type CustomerSupportErrorDetail, type CustomerSupportHandoffRequest, type CustomerSupportHandoffResult, type CustomerSupportMessage, type CustomerSupportMessagePage, type CustomerSupportSenderType, type CustomerSupportSession, type CustomerSupportSessionStatus, type CustomerSupportTransport, type NotificationAttachment, NotificationCenterClient, NotificationCenterClientError, type NotificationCenterClientEventMap, type NotificationCenterClientListener, type NotificationCenterClientOptions, type NotificationCenterErrorCode, type NotificationCenterErrorDetail, type NotificationCenterTransport, type NotificationContentType, type NotificationDto, type NotificationInboxQuery, type NotificationInboxResult, type NotificationMarkAllReadResult, type NotificationMarkReadResult, type NotificationReadStatus, type NotificationRealtimeChange, type NotificationResource, type NotificationSource, type NotificationTypeSummary, type OpenCustomerSupportAssistantAction, type ParsedAiEmployeeAssistantContent, type ParsedAiEmployeeEvent, type ParsedSessionMessage, type PingFrame, type SessionSubscriptionFrame, createAuthenticateFrame, createPingFrame, createSubscriptionFrame, hashText, isUuidV4, parseAiEmployeeAssistantContent, parseAiEmployeeEvent, parseJsonRecord };
698
+ export { type AiEmployeeAssistantAction, type AiEmployeeAttachmentReference, AiEmployeeClient, AiEmployeeClientError, type AiEmployeeClientErrorDetail, type AiEmployeeClientEventMap, type AiEmployeeClientListener, type AiEmployeeClientOptions, type AiEmployeeClientStartResult, type AiEmployeeConnectionState, type AiEmployeeErrorCode, type AiEmployeeFirstMessageOptions, type AiEmployeeFirstMessageRequest, type AiEmployeeFirstMessageRequestState, type AiEmployeeFirstMessageRequestStore, type AiEmployeeFirstMessageResult, type AiEmployeeMessage, type AiEmployeeMessageRole, type AiEmployeeReconnectOptions, type AiEmployeeSessionSummary, type AiEmployeeTransport, AiEmployeeTransportError, type AiEmployeeTransportErrorCode, type AiEmployeeWebSocketFactory, type AuthenticateFrame, type CustomerSupportAgent, CustomerSupportClient, CustomerSupportClientError, type CustomerSupportClientEventMap, type CustomerSupportClientListener, type CustomerSupportClientOptions, type CustomerSupportCreateSessionRequest, type CustomerSupportCreateSessionResult, type CustomerSupportErrorCode, type CustomerSupportErrorDetail, type CustomerSupportHandoffRequest, type CustomerSupportHandoffResult, type CustomerSupportMessage, type CustomerSupportMessagePage, type CustomerSupportSenderType, type CustomerSupportSession, type CustomerSupportSessionStatus, type CustomerSupportTransport, type NotificationAttachment, NotificationCenterClient, NotificationCenterClientError, type NotificationCenterClientEventMap, type NotificationCenterClientListener, type NotificationCenterClientOptions, type NotificationCenterErrorCode, type NotificationCenterErrorDetail, type NotificationCenterTransport, type NotificationContentType, type NotificationDto, type NotificationInboxQuery, type NotificationInboxResult, type NotificationMarkAllReadResult, type NotificationMarkReadResult, type NotificationReadStatus, type NotificationRealtimeChange, type NotificationResource, type NotificationSource, type NotificationTypeSummary, type OpenCustomerSupportAssistantAction, type OpenFileAssistantAction, type ParsedAiEmployeeAssistantContent, type ParsedAiEmployeeEvent, type ParsedSessionMessage, type PingFrame, type SessionSubscriptionFrame, createAuthenticateFrame, createPingFrame, createSubscriptionFrame, hashText, isUuidV4, parseAiEmployeeAssistantContent, parseAiEmployeeEvent, parseJsonRecord };
package/dist/index.js CHANGED
@@ -45,10 +45,13 @@ function parseAiEmployeeEvent(raw) {
45
45
  }
46
46
  function parseSessionPayload(payload, sessionId) {
47
47
  const messages = [];
48
+ const activities = [];
48
49
  const patches = Array.isArray(payload.JsonPatch) ? payload.JsonPatch : [];
49
50
  for (const rawPatch of patches) {
50
51
  const patch = asRecord(rawPatch);
51
52
  const value = asRecord(patch.value);
53
+ const activity = workActivityFromEntry(value);
54
+ if (activity) activities.push(activity);
52
55
  const parsed = messageFromEntry(value, sessionId);
53
56
  if (parsed) messages.push(parsed);
54
57
  }
@@ -74,7 +77,58 @@ function parseSessionPayload(payload, sessionId) {
74
77
  let processing;
75
78
  if (payload.Ready === true) processing = true;
76
79
  if (payload.finished === true || payload.stopped === true) processing = false;
77
- return { messages, fallbackResult, processing };
80
+ return { messages, activities, fallbackResult, processing };
81
+ }
82
+ function workActivityFromEntry(entry) {
83
+ const entryTypeValue = entry.entry_type;
84
+ const entryType = typeof entryTypeValue === "string" ? entryTypeValue : stringValue(asRecord(entryTypeValue).type);
85
+ const entryTypeRecord = asRecord(entryTypeValue);
86
+ if (entryType === "thinking" || entryType === "status_update" || entryType === "output") {
87
+ return safeActivityText(extractActivityText(entry.content));
88
+ }
89
+ if (entryType === "loading") {
90
+ return safeActivityText(extractActivityText(entry.content)) || "\u6B63\u5728\u52A0\u8F7D\u6240\u9700\u4FE1\u606F\u2026";
91
+ }
92
+ if (entryType === "tool_result") return "\u5DE5\u5177\u5904\u7406\u5B8C\u6210\uFF0C\u6B63\u5728\u7EE7\u7EED\u2026";
93
+ if (entryType !== "tool_use") return "";
94
+ const actionType = asRecord(entryTypeRecord.action_type);
95
+ const action = stringValue(actionType.action);
96
+ if (action === "search") {
97
+ const query = safeActivityText(stringValue(actionType.query), 60);
98
+ return query ? `\u6B63\u5728\u641C\u7D22\uFF1A${query}` : "\u6B63\u5728\u641C\u7D22\u76F8\u5173\u4FE1\u606F\u2026";
99
+ }
100
+ if (action === "web_fetch") return "\u6B63\u5728\u8BFB\u53D6\u7F51\u9875\u5185\u5BB9\u2026";
101
+ if (action === "file_read") return "\u6B63\u5728\u8BFB\u53D6\u8D44\u6599\u2026";
102
+ if (action === "file_edit") return "\u6B63\u5728\u6574\u7406\u6587\u4EF6\u2026";
103
+ if (action === "command_run") return "\u6B63\u5728\u6267\u884C\u4EFB\u52A1\u2026";
104
+ const toolName = safeActivityText(stringValue(entryTypeRecord.tool_name), 40);
105
+ return toolName ? `\u6B63\u5728\u8C03\u7528 ${toolName}\u2026` : "\u6B63\u5728\u8C03\u7528\u5DE5\u5177\u2026";
106
+ }
107
+ function extractActivityText(raw) {
108
+ if (typeof raw !== "string") return "";
109
+ const value = raw.trim();
110
+ if (!value) return "";
111
+ try {
112
+ const parsed = JSON.parse(value);
113
+ if (typeof parsed === "string") return parsed.trim();
114
+ const activity = asRecord(parsed);
115
+ for (const key of ["thinking", "status", "text", "message", "content", "result"]) {
116
+ const candidate = stringValue(activity[key]).trim();
117
+ if (candidate) return candidate;
118
+ }
119
+ return "";
120
+ } catch {
121
+ return value;
122
+ }
123
+ }
124
+ function safeActivityText(value, maximumCharacters = 120) {
125
+ const withoutCredentials = value.replace(/\bBearer\s+[^\s]+/gi, "Bearer [\u5DF2\u9690\u85CF]").replace(/\b[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, "[\u5DF2\u9690\u85CF]");
126
+ const normalized = [...withoutCredentials].map((character) => {
127
+ const codePoint = character.codePointAt(0) ?? 0;
128
+ return codePoint > 31 && codePoint !== 127 ? character : " ";
129
+ }).join("").replace(/\s+/g, " ").trim();
130
+ const characters = [...normalized];
131
+ return characters.length <= maximumCharacters ? normalized : `${characters.slice(0, maximumCharacters).join("")}\u2026`;
78
132
  }
79
133
  function messageFromEntry(entry, sessionId) {
80
134
  const entryTypeValue = entry.entry_type;
@@ -618,6 +672,11 @@ var AiEmployeeClient = class {
618
672
  }
619
673
  if (event.result.processing === true) this.assistantReceivedInCurrentTurn = false;
620
674
  if (event.result.processing !== void 0) this.setProcessing(event.result.processing);
675
+ for (const text2 of event.result.activities) {
676
+ if (!this.assistantReceivedInCurrentTurn) {
677
+ this.emit("work-activity", { sessionId: event.sessionId, text: text2 });
678
+ }
679
+ }
621
680
  for (const message of event.result.messages) {
622
681
  if (message.role === "user") this.assistantReceivedInCurrentTurn = false;
623
682
  if (message.role === "user" && this.matchesPendingLocalMessage(message)) continue;
@@ -1085,25 +1144,65 @@ function numberValue(value) {
1085
1144
  }
1086
1145
 
1087
1146
  // src/assistant-actions.ts
1088
- var ACTION_KEYS = ["action", "entry", "session-id", "transaction-id", "trigger", "type"];
1089
- var OPEN_CUSTOMER_SUPPORT_MARKER = /"action"\s*:\s*"open_customer_support"/g;
1147
+ var CUSTOMER_SUPPORT_ACTION_KEYS = [
1148
+ "action",
1149
+ "entry",
1150
+ "session-id",
1151
+ "transaction-id",
1152
+ "trigger",
1153
+ "type"
1154
+ ];
1155
+ var FILE_ACTION_KEYS = [
1156
+ "action",
1157
+ "name",
1158
+ "path",
1159
+ "session-id",
1160
+ "size",
1161
+ "transaction-id",
1162
+ "type"
1163
+ ];
1164
+ var SUPPORTED_ACTION_MARKER = /"action"\s*:\s*"(open_customer_support|open_file)"/g;
1090
1165
  var UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1091
1166
  var SESSION_ID_PATTERN = /^[A-Za-z0-9._:-]{1,64}$/;
1167
+ var FILE_SIZE_PATTERN = /^\d+(?:\.\d+)?(?:B|KB|MB|GB|TB)$/;
1092
1168
  function parseAiEmployeeAssistantContent(content) {
1093
1169
  const source = typeof content === "string" ? content : "";
1094
- const candidate = trailingStructuredJson(source);
1095
- if (!candidate || markerCount(source) !== 1) return unchanged(source);
1096
- let value;
1097
- try {
1098
- value = JSON.parse(candidate.json);
1099
- } catch {
1100
- return unchanged(source);
1170
+ let remaining = source;
1171
+ const actions = [];
1172
+ const rejectedActionTypes = [];
1173
+ let strippedMarkerCount = 0;
1174
+ while (true) {
1175
+ const candidate = trailingStructuredJson(remaining);
1176
+ if (!candidate) break;
1177
+ const candidateActionTypes = actionMarkerTypes(candidate.json);
1178
+ if (candidateActionTypes.length === 0) break;
1179
+ strippedMarkerCount += candidateActionTypes.length;
1180
+ let value;
1181
+ try {
1182
+ value = JSON.parse(candidate.json);
1183
+ } catch {
1184
+ rejectedActionTypes.unshift(...candidateActionTypes);
1185
+ remaining = candidate.content;
1186
+ continue;
1187
+ }
1188
+ const action = parseAssistantAction(value);
1189
+ if (!action || candidateActionTypes.length !== 1) {
1190
+ rejectedActionTypes.unshift(...candidateActionTypes);
1191
+ } else {
1192
+ actions.unshift(action);
1193
+ }
1194
+ remaining = candidate.content;
1195
+ }
1196
+ const sourceActionTypes = actionMarkerTypes(source);
1197
+ if (sourceActionTypes.length === 0) return unchanged(source);
1198
+ if (strippedMarkerCount !== sourceActionTypes.length) return unchanged(source);
1199
+ const transactionIds = new Set(actions.map((action) => action.transactionId));
1200
+ if (rejectedActionTypes.length > 0 || actions.length === 0 || transactionIds.size !== actions.length || actions.filter((action) => action.action === "open_customer_support").length > 1) {
1201
+ return rejected(remaining, sourceActionTypes);
1101
1202
  }
1102
- const action = parseOpenCustomerSupportAction(value);
1103
- if (!action) return unchanged(source);
1104
1203
  return Object.freeze({
1105
- content: candidate.content,
1106
- actions: Object.freeze([action])
1204
+ content: remaining,
1205
+ actions: Object.freeze(actions)
1107
1206
  });
1108
1207
  }
1109
1208
  function isUuidV4(value) {
@@ -1111,13 +1210,17 @@ function isUuidV4(value) {
1111
1210
  }
1112
1211
  function trailingStructuredJson(content) {
1113
1212
  const withoutTrailingWhitespace = content.trimEnd();
1114
- const fenced = withoutTrailingWhitespace.match(
1115
- /(?:^|\r?\n)[\t ]*```json[\t ]*\r?\n([\s\S]*?)\r?\n[\t ]*```$/i
1116
- );
1117
- if (fenced?.index !== void 0) {
1213
+ const fencedPattern = /(?:^|\r?\n)[\t ]*```json[\t ]*\r?\n([\s\S]*?)\r?\n[\t ]*```[\t ]*/gi;
1214
+ let trailingFence = null;
1215
+ for (const match of withoutTrailingWhitespace.matchAll(fencedPattern)) {
1216
+ if (match.index + match[0].length === withoutTrailingWhitespace.length) {
1217
+ trailingFence = match;
1218
+ }
1219
+ }
1220
+ if (trailingFence) {
1118
1221
  return {
1119
- content: withoutTrailingWhitespace.slice(0, fenced.index).trimEnd(),
1120
- json: fenced[1]?.trim() ?? ""
1222
+ content: withoutTrailingWhitespace.slice(0, trailingFence.index).trimEnd(),
1223
+ json: trailingFence[1]?.trim() ?? ""
1121
1224
  };
1122
1225
  }
1123
1226
  const starts = [...withoutTrailingWhitespace.matchAll(/(?:^|\r?\n)[\t ]*(\{)/g)];
@@ -1137,10 +1240,16 @@ function trailingStructuredJson(content) {
1137
1240
  }
1138
1241
  return null;
1139
1242
  }
1243
+ function parseAssistantAction(value) {
1244
+ if (!isRecord(value)) return null;
1245
+ if (value.action === "open_customer_support") return parseOpenCustomerSupportAction(value);
1246
+ if (value.action === "open_file") return parseOpenFileAction(value);
1247
+ return null;
1248
+ }
1140
1249
  function parseOpenCustomerSupportAction(value) {
1141
1250
  if (!isRecord(value)) return null;
1142
1251
  const keys = Object.keys(value).sort();
1143
- if (keys.length !== ACTION_KEYS.length || keys.some((key, index) => key !== ACTION_KEYS[index])) {
1252
+ if (keys.length !== CUSTOMER_SUPPORT_ACTION_KEYS.length || keys.some((key, index) => key !== CUSTOMER_SUPPORT_ACTION_KEYS[index])) {
1144
1253
  return null;
1145
1254
  }
1146
1255
  if (value.type !== "assistant_action" || value.action !== "open_customer_support" || !isUuidV4(value["transaction-id"]) || !isAssistantActionSessionId(value["session-id"]) || value.trigger !== "out_of_scope" || value.entry !== "help_and_support") {
@@ -1155,15 +1264,64 @@ function parseOpenCustomerSupportAction(value) {
1155
1264
  entry: "help_and_support"
1156
1265
  });
1157
1266
  }
1267
+ function parseOpenFileAction(value) {
1268
+ if (!isRecord(value)) return null;
1269
+ const keys = Object.keys(value).sort();
1270
+ if (keys.length !== FILE_ACTION_KEYS.length || keys.some((key, index) => key !== FILE_ACTION_KEYS[index])) {
1271
+ return null;
1272
+ }
1273
+ const transactionId = value["transaction-id"];
1274
+ const sessionId = value["session-id"];
1275
+ const name = value.name;
1276
+ const size = value.size;
1277
+ const path = value.path;
1278
+ if (value.type !== "assistant_action" || value.action !== "open_file" || !isCanonicalUuidV4(transactionId) || !isAssistantActionSessionId(sessionId) || !isSafeRelativeFilePath(path) || typeof name !== "string" || !name || name !== name.trim() || name !== path.slice(path.lastIndexOf("/") + 1) || typeof size !== "string" || size !== size.trim() || !FILE_SIZE_PATTERN.test(size)) {
1279
+ return null;
1280
+ }
1281
+ return Object.freeze({
1282
+ type: "assistant_action",
1283
+ action: "open_file",
1284
+ transactionId,
1285
+ sessionId,
1286
+ name,
1287
+ size,
1288
+ path
1289
+ });
1290
+ }
1158
1291
  function isAssistantActionSessionId(value) {
1159
1292
  return typeof value === "string" && SESSION_ID_PATTERN.test(value);
1160
1293
  }
1161
- function markerCount(value) {
1162
- return [...value.matchAll(OPEN_CUSTOMER_SUPPORT_MARKER)].length;
1294
+ function actionMarkerTypes(value) {
1295
+ return [...value.matchAll(SUPPORTED_ACTION_MARKER)].flatMap((match) => {
1296
+ const action = match[1];
1297
+ return action === "open_customer_support" || action === "open_file" ? [action] : [];
1298
+ });
1299
+ }
1300
+ function isCanonicalUuidV4(value) {
1301
+ return isUuidV4(value) && value === value.toLowerCase();
1302
+ }
1303
+ function isSafeRelativeFilePath(value) {
1304
+ if (typeof value !== "string" || !value || value !== value.trim() || value.startsWith("/") || value.startsWith("\\") || value.includes("\\") || /^[A-Za-z]:/.test(value) || hasControlCharacter(value)) {
1305
+ return false;
1306
+ }
1307
+ return value.split("/").every((part) => part && part !== "." && part !== ".." && !part.startsWith("."));
1308
+ }
1309
+ function hasControlCharacter(value) {
1310
+ return [...value].some((character) => {
1311
+ const codePoint = character.codePointAt(0) ?? 0;
1312
+ return codePoint <= 31 || codePoint === 127;
1313
+ });
1163
1314
  }
1164
1315
  function unchanged(content) {
1165
1316
  return Object.freeze({ content, actions: Object.freeze([]) });
1166
1317
  }
1318
+ function rejected(content, actionTypes) {
1319
+ return Object.freeze({
1320
+ content,
1321
+ actions: Object.freeze([]),
1322
+ rejectedActionTypes: Object.freeze([...new Set(actionTypes)])
1323
+ });
1324
+ }
1167
1325
  function isRecord(value) {
1168
1326
  return value !== null && typeof value === "object" && !Array.isArray(value);
1169
1327
  }