@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 +23 -7
- package/dist/index.js +181 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
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
|
|
1089
|
-
|
|
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
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
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:
|
|
1106
|
-
actions: Object.freeze(
|
|
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
|
|
1115
|
-
|
|
1116
|
-
)
|
|
1117
|
-
|
|
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,
|
|
1120
|
-
json:
|
|
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 !==
|
|
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
|
|
1162
|
-
return [...value.matchAll(
|
|
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
|
}
|