@nexface/agent 0.1.13-alpha.4 → 0.1.13-alpha.5

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/request.js CHANGED
@@ -2,17 +2,25 @@ import { assertCurrentHistory } from "./history-version.js";
2
2
  import { MessageSchema } from "@ag-ui/core";
3
3
  import { getToolResultError } from "./tool-result.js";
4
4
  export const approvalResponseSchema = {
5
- type: "object", properties: { approved: { type: "boolean" } }, required: ["approved"], additionalProperties: false,
5
+ type: "object",
6
+ properties: { approved: { type: "boolean" } },
7
+ required: ["approved"],
8
+ additionalProperties: false,
6
9
  };
7
10
  export const toolResultResponseSchema = {
8
- type: "object", properties: { toolResultMessageId: { type: "string" }, confirmed: { type: "boolean" } }, required: ["toolResultMessageId"], additionalProperties: false,
11
+ type: "object",
12
+ properties: { toolResultMessageId: { type: "string" }, confirmed: { type: "boolean" } },
13
+ required: ["toolResultMessageId"],
14
+ additionalProperties: false,
9
15
  };
10
16
  export function parseResume(value, allowMetadata = false) {
11
17
  if (!value || typeof value !== "object" || Array.isArray(value))
12
18
  throw new Error("Invalid resume entry.");
13
19
  const entry = value;
14
20
  const allowed = ["interruptId", "status", "payload", ...(allowMetadata ? ["metadata"] : [])];
15
- if (Object.keys(entry).some((key) => !allowed.includes(key)) || typeof entry.interruptId !== "string" || !entry.interruptId)
21
+ if (Object.keys(entry).some((key) => !allowed.includes(key)) ||
22
+ typeof entry.interruptId !== "string" ||
23
+ !entry.interruptId)
16
24
  throw new Error("Invalid resume entry.");
17
25
  if (entry.status === "cancelled") {
18
26
  if (entry.payload !== undefined)
@@ -20,13 +28,31 @@ export function parseResume(value, allowMetadata = false) {
20
28
  return { interruptId: entry.interruptId, status: "cancelled" };
21
29
  }
22
30
  const payload = entry.payload;
23
- if (entry.status !== "resolved" || !payload || typeof payload !== "object" || Array.isArray(payload))
31
+ if (entry.status !== "resolved" ||
32
+ !payload ||
33
+ typeof payload !== "object" ||
34
+ Array.isArray(payload))
24
35
  throw new Error("A resolved response requires a payload.");
25
- if (typeof payload.approved === "boolean" && Object.keys(payload).every((key) => key === "approved")) {
26
- return { interruptId: entry.interruptId, status: "resolved", payload: { approved: payload.approved } };
36
+ if (typeof payload.approved === "boolean" &&
37
+ Object.keys(payload).every((key) => key === "approved")) {
38
+ return {
39
+ interruptId: entry.interruptId,
40
+ status: "resolved",
41
+ payload: { approved: payload.approved },
42
+ };
27
43
  }
28
- if (typeof payload.toolResultMessageId === "string" && payload.toolResultMessageId && (payload.confirmed === undefined || typeof payload.confirmed === "boolean") && Object.keys(payload).every((key) => key === "toolResultMessageId" || key === "confirmed")) {
29
- return { interruptId: entry.interruptId, status: "resolved", payload: { toolResultMessageId: payload.toolResultMessageId, ...(payload.confirmed === undefined ? {} : { confirmed: payload.confirmed }) } };
44
+ if (typeof payload.toolResultMessageId === "string" &&
45
+ payload.toolResultMessageId &&
46
+ (payload.confirmed === undefined || typeof payload.confirmed === "boolean") &&
47
+ Object.keys(payload).every((key) => key === "toolResultMessageId" || key === "confirmed")) {
48
+ return {
49
+ interruptId: entry.interruptId,
50
+ status: "resolved",
51
+ payload: {
52
+ toolResultMessageId: payload.toolResultMessageId,
53
+ ...(payload.confirmed === undefined ? {} : { confirmed: payload.confirmed }),
54
+ },
55
+ };
30
56
  }
31
57
  throw new Error("Invalid resume payload.");
32
58
  }
@@ -43,23 +69,41 @@ export function convertMessages(values) {
43
69
  if (message.role === "assistant") {
44
70
  for (const call of message.toolCalls ?? [])
45
71
  calls.set(call.id, call.function.name);
46
- return [{ id: message.id, role: "assistant", content: message.content ?? null, toolCalls: (message.toolCalls ?? []).map((call) => ({ id: call.id, ...call.function })) }];
72
+ return [
73
+ {
74
+ id: message.id,
75
+ role: "assistant",
76
+ content: message.content ?? null,
77
+ toolCalls: (message.toolCalls ?? []).map((call) => ({ id: call.id, ...call.function })),
78
+ },
79
+ ];
47
80
  }
48
81
  if (message.role === "tool") {
49
82
  const name = calls.get(message.toolCallId);
50
83
  if (!name)
51
84
  throw new Error("A tool result has no preceding tool call.");
52
85
  const content = message.error !== undefined && getToolResultError(message.content) === undefined
53
- ? JSON.stringify({ error: { code: "TOOL_ERROR", message: message.error }, content: message.content }) : message.content;
86
+ ? JSON.stringify({
87
+ error: { code: "TOOL_ERROR", message: message.error },
88
+ content: message.content,
89
+ })
90
+ : message.content;
54
91
  return [{ id: message.id, role: "tool", name, toolCallId: message.toolCallId, content }];
55
92
  }
56
93
  if (message.role === "user") {
57
- if (Array.isArray(message.content) && message.content.some((part) => part.type !== "text" && part.type !== "image"))
94
+ if (Array.isArray(message.content) &&
95
+ message.content.some((part) => part.type !== "text" && part.type !== "image"))
58
96
  throw new Error("This Agent supports text and image input only.");
59
97
  const content = typeof message.content === "string" ? message.content.trim() : message.content;
60
98
  if (!content.length)
61
99
  throw new Error("Agent message content is required.");
62
- return [{ id: message.id, role: "user", content: content }];
100
+ return [
101
+ {
102
+ id: message.id,
103
+ role: "user",
104
+ content: content,
105
+ },
106
+ ];
63
107
  }
64
108
  throw new Error(`Unsupported model history role: ${message.role}.`);
65
109
  });
@@ -68,22 +112,29 @@ export function convertMessages(values) {
68
112
  }
69
113
  export function decodeRequest(value) {
70
114
  const extension = value.forwardedProps?.nexface;
71
- if (extension && (extension.version !== 3 || "checkpoint" in extension || "confirmedToolCallId" in extension))
115
+ if (extension &&
116
+ (extension.version !== 3 || "checkpoint" in extension || "confirmedToolCallId" in extension))
72
117
  throw new Error("Unsupported Nexface request version or recovery channel. Upgrade client and server together and start a new conversation.");
73
118
  const state = value.state;
74
119
  if (state != null && (typeof state !== "object" || Array.isArray(state)))
75
120
  throw new Error("Invalid Agent state.");
76
- if (state && "nexface" in state && (!state.nexface || typeof state.nexface !== "object" || Array.isArray(state.nexface)))
121
+ if (state &&
122
+ "nexface" in state &&
123
+ (!state.nexface || typeof state.nexface !== "object" || Array.isArray(state.nexface)))
77
124
  throw new Error("Invalid Nexface state.");
78
125
  const restored = state?.nexface;
79
- if (restored && (restored.version !== 3 || restored.threadId !== value.threadId || !Array.isArray(restored.timeline)))
126
+ if (restored &&
127
+ (restored.version !== 3 ||
128
+ restored.threadId !== value.threadId ||
129
+ !Array.isArray(restored.timeline)))
80
130
  throw new Error("Agent state does not match this thread or version. Start a new conversation.");
81
131
  const converted = new Map(convertMessages(value.messages).map((message) => [message.id, message]));
82
132
  const source = new Map(value.messages.map((message) => [message.id, message]));
83
133
  const used = new Set();
84
134
  const messages = (restored?.timeline ?? []).flatMap((entry) => {
85
135
  if ("internal" in entry) {
86
- if (!entry.internal || (entry.internal.role !== "system" && entry.internal.role !== "application_context"))
136
+ if (!entry.internal ||
137
+ (entry.internal.role !== "system" && entry.internal.role !== "application_context"))
87
138
  throw new Error("Invalid internal history.");
88
139
  return [structuredClone(entry.internal)];
89
140
  }
@@ -100,7 +151,10 @@ export function decodeRequest(value) {
100
151
  const receipts = [];
101
152
  let toolResult;
102
153
  if (pending) {
103
- if (!resume || resume.interruptId !== pending.interruptId || !Array.isArray(pending.calls) || !pending.calls.length)
154
+ if (!resume ||
155
+ resume.interruptId !== pending.interruptId ||
156
+ !Array.isArray(pending.calls) ||
157
+ !pending.calls.length)
104
158
  throw new Error("Resume does not match the pending interrupt.");
105
159
  if (pending.kind !== "client-tool-execution")
106
160
  throw new Error("Invalid pending kind.");
@@ -108,12 +162,19 @@ export function decodeRequest(value) {
108
162
  if (!("toolResultMessageId" in resume.payload))
109
163
  throw new Error("A client tool requires a result reference.");
110
164
  const message = source.get(resume.payload.toolResultMessageId);
111
- if (!message || used.has(message.id) || message.role !== "tool" || message.toolCallId !== pending.calls[0].id)
165
+ if (!message ||
166
+ used.has(message.id) ||
167
+ message.role !== "tool" ||
168
+ message.toolCallId !== pending.calls[0].id)
112
169
  throw new Error("Resume result does not match the pending call.");
113
170
  const owner = [...messages].reverse().find((item) => item.role === "assistant");
114
171
  const ownerIndex = value.messages.findIndex((item) => item.id === owner?.id);
115
172
  const receiptIndex = value.messages.indexOf(message);
116
- if (ownerIndex < 0 || receiptIndex <= ownerIndex || value.messages.slice(ownerIndex + 1, receiptIndex).some((item) => item.role === "user" || (item.role === "assistant" && item.toolCalls?.length)))
173
+ if (ownerIndex < 0 ||
174
+ receiptIndex <= ownerIndex ||
175
+ value.messages
176
+ .slice(ownerIndex + 1, receiptIndex)
177
+ .some((item) => item.role === "user" || (item.role === "assistant" && item.toolCalls?.length)))
117
178
  throw new Error("The receipt does not belong to the pending assistant batch.");
118
179
  let payload;
119
180
  try {
@@ -125,11 +186,18 @@ export function decodeRequest(value) {
125
186
  }
126
187
  if (message.error !== undefined)
127
188
  payload = { error: { code: payload?.error?.code ?? "TOOL_ERROR", message: message.error } };
128
- if (!payload || typeof payload !== "object" || (!("result" in payload) && !("error" in payload)))
189
+ if (!payload ||
190
+ typeof payload !== "object" ||
191
+ (!("result" in payload) && !("error" in payload)))
129
192
  throw new Error("Invalid tool result.");
130
- if (payload.error && (typeof payload.error.code !== "string" || typeof payload.error.message !== "string"))
193
+ if (payload.error &&
194
+ (typeof payload.error.code !== "string" || typeof payload.error.message !== "string"))
131
195
  throw new Error("Invalid tool error.");
132
- toolResult = { toolCallId: message.toolCallId, ...(payload.error ? { error: payload.error } : { result: payload.result }), ...(resume.payload.confirmed === undefined ? {} : { confirmed: resume.payload.confirmed }) };
196
+ toolResult = {
197
+ toolCallId: message.toolCallId,
198
+ ...(payload.error ? { error: payload.error } : { result: payload.result }),
199
+ ...(resume.payload.confirmed === undefined ? {} : { confirmed: resume.payload.confirmed }),
200
+ };
133
201
  receipts.push(message);
134
202
  }
135
203
  else {
@@ -138,7 +206,9 @@ export function decodeRequest(value) {
138
206
  for (const message of value.messages.slice(ownerIndex + 1)) {
139
207
  if (message.role === "user")
140
208
  break;
141
- if (used.has(message.id) || message.role !== "tool" || !pending.calls.some((call) => call.id === message.toolCallId))
209
+ if (used.has(message.id) ||
210
+ message.role !== "tool" ||
211
+ !pending.calls.some((call) => call.id === message.toolCallId))
142
212
  continue;
143
213
  let payload;
144
214
  try {
@@ -148,7 +218,9 @@ export function decodeRequest(value) {
148
218
  throw new Error("Invalid cancellation receipt.");
149
219
  }
150
220
  const first = message.toolCallId === pending.calls[0].id;
151
- if (!(first ? ["TOOL_CANCELLED", "TOOL_CONFIRMATION_INVALIDATED"] : ["TOOL_SKIPPED", "TOOL_CANCELLED"]).includes(payload?.error?.code))
221
+ if (!(first
222
+ ? ["TOOL_CANCELLED", "TOOL_CONFIRMATION_INVALIDATED"]
223
+ : ["TOOL_SKIPPED", "TOOL_CANCELLED"]).includes(payload?.error?.code))
152
224
  throw new Error("Cancellation cannot replace an execution result.");
153
225
  if (receipts.some((receipt) => receipt.role === "tool" && receipt.toolCallId === message.toolCallId))
154
226
  throw new Error("Repeated cancellation receipt.");
@@ -162,7 +234,9 @@ export function decodeRequest(value) {
162
234
  const newMessages = value.messages.flatMap((message) => {
163
235
  if (used.has(message.id) || receiptIds.has(message.id))
164
236
  return [];
165
- if (pending && message.role === "tool" && pending.calls.some((call) => call.id === message.toolCallId))
237
+ if (pending &&
238
+ message.role === "tool" &&
239
+ pending.calls.some((call) => call.id === message.toolCallId))
166
240
  throw new Error("Repeated or unexpected pending result.");
167
241
  const convertedMessage = converted.get(message.id);
168
242
  if (pending && resume?.status === "resolved" && convertedMessage)
@@ -170,17 +244,33 @@ export function decodeRequest(value) {
170
244
  return convertedMessage ? [convertedMessage] : [];
171
245
  });
172
246
  return {
173
- messages, newMessages, receipts, ...(restored ? { continuation: structuredClone(restored) } : {}), ...(resume ? { resume } : {}), ...(toolResult ? { toolResult } : {}),
174
- tools: value.tools.map((tool) => ({ name: tool.name, description: tool.description, inputSchema: tool.parameters, effect: extension?.toolMetadata?.[tool.name]?.effect ?? "action" })),
247
+ messages,
248
+ newMessages,
249
+ receipts,
250
+ ...(restored ? { continuation: structuredClone(restored) } : {}),
251
+ ...(resume ? { resume } : {}),
252
+ ...(toolResult ? { toolResult } : {}),
253
+ tools: value.tools.map((tool) => ({
254
+ name: tool.name,
255
+ description: tool.description,
256
+ inputSchema: tool.parameters,
257
+ effect: extension?.toolMetadata?.[tool.name]?.effect ?? "action",
258
+ })),
175
259
  contexts: value.context.map((context, index) => {
176
260
  let data = context.value;
177
261
  try {
178
262
  data = JSON.parse(context.value);
179
263
  }
180
- catch { /* Context values may be plain text. */ }
264
+ catch {
265
+ /* Context values may be plain text. */
266
+ }
181
267
  if (data && typeof data === "object" && "name" in data && "value" in data)
182
268
  return data;
183
- return { name: `context${index}`, description: context.description, value: data };
269
+ return {
270
+ name: `context${index}`,
271
+ description: context.description,
272
+ value: data,
273
+ };
184
274
  }),
185
275
  };
186
276
  }
@@ -190,10 +280,29 @@ export function toProtocolMessages(messages) {
190
280
  if (message.role === "system" || message.role === "application_context")
191
281
  return [];
192
282
  if (message.role === "assistant")
193
- return [{ id: message.id, role: "assistant", ...(message.content === null ? {} : { content: message.content }), toolCalls: message.toolCalls.map(({ id, name, arguments: args }) => ({ id, type: "function", function: { name, arguments: args } })) }];
283
+ return [
284
+ {
285
+ id: message.id,
286
+ role: "assistant",
287
+ ...(message.content === null ? {} : { content: message.content }),
288
+ toolCalls: message.toolCalls.map(({ id, name, arguments: args }) => ({
289
+ id,
290
+ type: "function",
291
+ function: { name, arguments: args },
292
+ })),
293
+ },
294
+ ];
194
295
  if (message.role === "tool") {
195
296
  const error = getToolResultError(message.content);
196
- return [{ id: message.id, role: "tool", toolCallId: message.toolCallId, content: message.content, ...(error === undefined ? {} : { error }) }];
297
+ return [
298
+ {
299
+ id: message.id,
300
+ role: "tool",
301
+ toolCallId: message.toolCallId,
302
+ content: message.content,
303
+ ...(error === undefined ? {} : { error }),
304
+ },
305
+ ];
197
306
  }
198
307
  return [{ id: message.id, role: "user", content: message.content }];
199
308
  });
@@ -215,15 +324,22 @@ export function runResponse(run) {
215
324
  controller.error(error);
216
325
  }
217
326
  },
218
- async cancel() { run.cancel(); await iterator.return?.(); },
219
- }), { headers: {
327
+ async cancel() {
328
+ run.cancel();
329
+ await iterator.return?.();
330
+ },
331
+ }), {
332
+ headers: {
220
333
  "Content-Type": "text/event-stream",
221
334
  "Cache-Control": "no-cache, no-transform",
222
335
  "X-Accel-Buffering": "no",
223
- } });
336
+ },
337
+ });
224
338
  }
225
339
  export function mergeProtocolMessages(messages, previous) {
226
- const reasoningOwners = new Set(previous.filter((message) => message.role === "reasoning").map((message) => message.metadata?.nexface?.reasoningOwnerId));
340
+ const reasoningOwners = new Set(previous
341
+ .filter((message) => message.role === "reasoning")
342
+ .map((message) => message.metadata?.nexface?.reasoningOwnerId));
227
343
  const remaining = new Map(messages.map((message) => [message.id, message]));
228
344
  const mergedHistory = previous.flatMap((old) => {
229
345
  const message = remaining.get(old.id);
@@ -231,17 +347,30 @@ export function mergeProtocolMessages(messages, previous) {
231
347
  return old.role === "activity" || old.role === "reasoning" ? [old] : [];
232
348
  remaining.delete(old.id);
233
349
  const merged = { ...old, ...message };
234
- if (old.role === "assistant" && message.role === "assistant" && old.content === "" && message.content === undefined)
350
+ if (old.role === "assistant" &&
351
+ message.role === "assistant" &&
352
+ old.content === "" &&
353
+ message.content === undefined)
235
354
  delete merged.content;
236
355
  // Preserve streamed display offsets when a finish tool replaces model text.
237
- if (old.role === "assistant" && message.role === "assistant" && reasoningOwners.has(old.id)
238
- && old.content && old.content !== message.content && old.metadata?.nexface?.reasoningText === undefined) {
239
- merged.metadata = { ...old.metadata, nexface: { ...old.metadata?.nexface, reasoningText: old.content } };
356
+ if (old.role === "assistant" &&
357
+ message.role === "assistant" &&
358
+ reasoningOwners.has(old.id) &&
359
+ old.content &&
360
+ old.content !== message.content &&
361
+ old.metadata?.nexface?.reasoningText === undefined) {
362
+ merged.metadata = {
363
+ ...old.metadata,
364
+ nexface: { ...old.metadata?.nexface, reasoningText: old.content },
365
+ };
240
366
  }
241
367
  if (message.role === "tool" && old.role === "tool" && old.error !== undefined)
242
368
  merged.content = old.content;
243
369
  if (message.role === "assistant" && old.role === "assistant")
244
- merged.toolCalls = message.toolCalls?.map((call) => ({ ...old.toolCalls?.find((item) => item.id === call.id), ...call }));
370
+ merged.toolCalls = message.toolCalls?.map((call) => ({
371
+ ...old.toolCalls?.find((item) => item.id === call.id),
372
+ ...call,
373
+ }));
245
374
  return [MessageSchema.parse(merged)];
246
375
  });
247
376
  return [...mergedHistory, ...remaining.values()];
@@ -1,5 +1,5 @@
1
1
  export declare const TOOL_ERROR_CODES: readonly ["EXECUTION_OUTCOME_UNKNOWN", "ACTION_IN_FLIGHT", "ACTION_NOT_HANDLED", "ACTION_TARGET_UNAVAILABLE", "BROWSER_EXECUTION_FAILED", "BROWSER_SNAPSHOT_REQUIRED", "BROWSER_TOOLS_UNAVAILABLE", "CONTRACT_MISMATCH", "DUPLICATE_BINDING", "INVALID_INPUT", "INVALID_TOOL_RUNTIME", "OPERATION_BUSY", "TOOL_CANCELLED", "TOOL_CONFIRMATION_INVALIDATED", "TOOL_CONFIRMATION_REJECTED", "TOOL_ERROR", "TOOL_REGISTRATION_TIMEOUT", "TOOL_SKIPPED", "TOOL_UNAVAILABLE", "UNKNOWN_TOOL"];
2
- type AgentToolErrorCode = typeof TOOL_ERROR_CODES[number];
2
+ type AgentToolErrorCode = (typeof TOOL_ERROR_CODES)[number];
3
3
  export declare class AgentToolError extends Error {
4
4
  readonly code: AgentToolErrorCode;
5
5
  readonly details?: unknown | undefined;
@@ -3,8 +3,17 @@ const types = new Set(["object", "array", "string", "number", "integer", "boolea
3
3
  const counts = ["minLength", "maxLength", "minItems", "maxItems"];
4
4
  const bounds = ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum"];
5
5
  const keywords = new Set([
6
- "type", "description", "properties", "required", "items", "enum", "additionalProperties",
7
- ...counts, ...bounds, "pattern", "uniqueItems",
6
+ "type",
7
+ "description",
8
+ "properties",
9
+ "required",
10
+ "items",
11
+ "enum",
12
+ "additionalProperties",
13
+ ...counts,
14
+ ...bounds,
15
+ "pattern",
16
+ "uniqueItems",
8
17
  ]);
9
18
  const checkedSchemas = new WeakSet();
10
19
  const patterns = new WeakMap();
@@ -24,7 +33,9 @@ function assertJson(value, path, ancestors = new Set()) {
24
33
  invalid(path, "must be JSON-compatible.");
25
34
  if (ancestors.has(value))
26
35
  invalid(path, "must not contain circular references.");
27
- if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
36
+ if (!Array.isArray(value) &&
37
+ Object.getPrototypeOf(value) !== Object.prototype &&
38
+ Object.getPrototypeOf(value) !== null) {
28
39
  invalid(path, "must be a plain JSON object.");
29
40
  }
30
41
  if (Object.getOwnPropertySymbols(value).length)
@@ -85,7 +96,8 @@ function assertSchema(value, path) {
85
96
  invalid(`${path}.${key}`, "must be a non-negative integer.");
86
97
  }
87
98
  for (const key of bounds) {
88
- if (value[key] !== undefined && (typeof value[key] !== "number" || !Number.isFinite(value[key])))
99
+ if (value[key] !== undefined &&
100
+ (typeof value[key] !== "number" || !Number.isFinite(value[key])))
89
101
  invalid(`${path}.${key}`, "must be a finite number.");
90
102
  }
91
103
  if (value.pattern !== undefined) {
@@ -95,7 +107,8 @@ function assertSchema(value, path) {
95
107
  }
96
108
  if (value.enum !== undefined && (!Array.isArray(value.enum) || value.enum.length === 0))
97
109
  invalid(`${path}.enum`, "must be a non-empty array.");
98
- if (Array.isArray(value.enum) && value.enum.some((item, index, entries) => entries.slice(0, index).some((other) => jsonEqual(item, other))))
110
+ if (Array.isArray(value.enum) &&
111
+ value.enum.some((item, index, entries) => entries.slice(0, index).some((other) => jsonEqual(item, other))))
99
112
  invalid(`${path}.enum`, "must contain unique values.");
100
113
  if (value.properties !== undefined) {
101
114
  if (value.type !== "object" || !isRecord(value.properties))
@@ -104,7 +117,10 @@ function assertSchema(value, path) {
104
117
  assertSchema(property, `${path}.properties.${name}`);
105
118
  }
106
119
  if (value.required !== undefined) {
107
- if (value.type !== "object" || !Array.isArray(value.required) || value.required.some((name) => typeof name !== "string") || new Set(value.required).size !== value.required.length)
120
+ if (value.type !== "object" ||
121
+ !Array.isArray(value.required) ||
122
+ value.required.some((name) => typeof name !== "string") ||
123
+ new Set(value.required).size !== value.required.length)
108
124
  invalid(`${path}.required`, "must contain unique property names.");
109
125
  const properties = value.properties;
110
126
  if (value.required.some((name) => !properties || !Object.hasOwn(properties, name)))
@@ -118,7 +134,7 @@ function assertSchema(value, path) {
118
134
  }
119
135
  export function assertAgentToolInputSchema(value, path = "inputSchema") {
120
136
  if (!isRecord(value) || value.type !== "object")
121
- invalid(path, 'root type must be object.');
137
+ invalid(path, "root type must be object.");
122
138
  if (checkedSchemas.has(value))
123
139
  return;
124
140
  const frozen = assertJson(value, path);
@@ -130,21 +146,29 @@ function jsonEqual(left, right) {
130
146
  if (left === right)
131
147
  return true;
132
148
  if (Array.isArray(left) && Array.isArray(right))
133
- return left.length === right.length && left.every((item, index) => jsonEqual(item, right[index]));
149
+ return (left.length === right.length && left.every((item, index) => jsonEqual(item, right[index])));
134
150
  if (!isRecord(left) || !isRecord(right))
135
151
  return false;
136
152
  const keys = Object.keys(left);
137
- return keys.length === Object.keys(right).length && keys.every((key) => Object.hasOwn(right, key) && jsonEqual(left[key], right[key]));
153
+ return (keys.length === Object.keys(right).length &&
154
+ keys.every((key) => Object.hasOwn(right, key) && jsonEqual(left[key], right[key])));
138
155
  }
139
156
  function typeMatches(schema, value) {
140
157
  switch (schema.type) {
141
- case undefined: return true;
142
- case "null": return value === null;
143
- case "array": return Array.isArray(value);
144
- case "object": return isRecord(value);
145
- case "integer": return typeof value === "number" && Number.isInteger(value);
146
- case "number": return typeof value === "number" && Number.isFinite(value);
147
- default: return typeof value === schema.type;
158
+ case undefined:
159
+ return true;
160
+ case "null":
161
+ return value === null;
162
+ case "array":
163
+ return Array.isArray(value);
164
+ case "object":
165
+ return isRecord(value);
166
+ case "integer":
167
+ return typeof value === "number" && Number.isInteger(value);
168
+ case "number":
169
+ return typeof value === "number" && Number.isFinite(value);
170
+ default:
171
+ return typeof value === schema.type;
148
172
  }
149
173
  }
150
174
  export function validateAgentToolInput(schema, value, path = "input") {
@@ -177,7 +201,8 @@ export function validateAgentToolInput(schema, value, path = "input") {
177
201
  errors.push(`${path} must have at least ${schema.minItems} items.`);
178
202
  if (schema.maxItems !== undefined && value.length > schema.maxItems)
179
203
  errors.push(`${path} must have at most ${schema.maxItems} items.`);
180
- if (schema.uniqueItems && value.some((item, index) => value.slice(0, index).some((other) => jsonEqual(item, other))))
204
+ if (schema.uniqueItems &&
205
+ value.some((item, index) => value.slice(0, index).some((other) => jsonEqual(item, other))))
181
206
  errors.push(`${path} must contain unique items.`);
182
207
  if (schema.items)
183
208
  value.forEach((item, index) => errors.push(...validateAgentToolInput(schema.items, item, `${path}[${index}]`)));
@@ -188,7 +213,9 @@ export function validateAgentToolInput(schema, value, path = "input") {
188
213
  errors.push(`${path}.${key} is required.`);
189
214
  }
190
215
  for (const [key, item] of Object.entries(value)) {
191
- const property = schema.properties && Object.hasOwn(schema.properties, key) ? schema.properties[key] : undefined;
216
+ const property = schema.properties && Object.hasOwn(schema.properties, key)
217
+ ? schema.properties[key]
218
+ : undefined;
192
219
  if (property)
193
220
  errors.push(...validateAgentToolInput(property, item, `${path}.${key}`));
194
221
  else if (schema.additionalProperties === false)
@@ -15,9 +15,7 @@ function invalidResponse(message, details) {
15
15
  throw new CatalogValidationError("INVALID_TOOL_CATALOG", message, details);
16
16
  }
17
17
  function assertJsonCompatible(value, path, seen = new WeakSet()) {
18
- if (value === null
19
- || typeof value === "string"
20
- || typeof value === "boolean")
18
+ if (value === null || typeof value === "string" || typeof value === "boolean")
21
19
  return;
22
20
  if (typeof value === "number") {
23
21
  if (!Number.isFinite(value))
@@ -54,10 +52,7 @@ function canonicalize(value) {
54
52
  return value;
55
53
  return Object.fromEntries(Object.keys(value)
56
54
  .sort()
57
- .map((key) => [
58
- key,
59
- canonicalize(value[key]),
60
- ]));
55
+ .map((key) => [key, canonicalize(value[key])]));
61
56
  }
62
57
  function deepFreeze(value, seen = new WeakSet()) {
63
58
  if (!value || typeof value !== "object" || seen.has(value))
@@ -72,8 +67,10 @@ function sameDefinition(left, right) {
72
67
  }
73
68
  const BUSINESS_TOOL_NAME_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
74
69
  const RESERVED_BUSINESS_TOOL_NAMES = new Set([
75
- "nxf_beginOperation", "nxf_endOperation",
76
- "nxf_applyAction", "nxf_delegatePageTask",
70
+ "nxf_beginOperation",
71
+ "nxf_endOperation",
72
+ "nxf_applyAction",
73
+ "nxf_delegatePageTask",
77
74
  "nxf_callAppTool",
78
75
  "nxf_finish",
79
76
  "nxf_getSnapshot",
@@ -81,8 +78,8 @@ const RESERVED_BUSINESS_TOOL_NAMES = new Set([
81
78
  "nxf_loadSkill",
82
79
  ]);
83
80
  function assertBusinessToolDefinition(definition) {
84
- if (!BUSINESS_TOOL_NAME_PATTERN.test(definition.name)
85
- || RESERVED_BUSINESS_TOOL_NAMES.has(definition.name)) {
81
+ if (!BUSINESS_TOOL_NAME_PATTERN.test(definition.name) ||
82
+ RESERVED_BUSINESS_TOOL_NAMES.has(definition.name)) {
86
83
  invalidResponse(`Tool catalog returned an invalid tool name "${definition.name}".`);
87
84
  }
88
85
  if (typeof definition.description !== "string" || !definition.description.trim()) {
@@ -101,8 +98,7 @@ export function validateToolCatalog(value) {
101
98
  invalidResponse(`Tool definition at index ${index} must be an object.`);
102
99
  }
103
100
  const candidate = definition;
104
- const unexpectedKeys = Object.keys(candidate)
105
- .filter((key) => !["name", "description", "inputSchema", "effect"].includes(key));
101
+ const unexpectedKeys = Object.keys(candidate).filter((key) => !["name", "description", "inputSchema", "effect"].includes(key));
106
102
  if (unexpectedKeys.length > 0) {
107
103
  invalidResponse(`Tool definition at index ${index} has unexpected fields: ${unexpectedKeys.join(", ")}.`);
108
104
  }
@@ -143,8 +139,7 @@ export function validateToolCatalog(value) {
143
139
  }
144
140
  for (const definition of value)
145
141
  deepFreeze(definition);
146
- return [...value]
147
- .sort((left, right) => left.name.localeCompare(right.name));
142
+ return [...value].sort((left, right) => left.name.localeCompare(right.name));
148
143
  }
149
144
  export function validateContextCatalog(value) {
150
145
  if (!Array.isArray(value)) {
@@ -156,26 +151,30 @@ export function validateContextCatalog(value) {
156
151
  invalidResponse(`Context at index ${index} must be an object.`);
157
152
  }
158
153
  const candidate = entry;
159
- const unexpectedKeys = Object.keys(candidate)
160
- .filter((key) => !["name", "description", "value"].includes(key));
154
+ const unexpectedKeys = Object.keys(candidate).filter((key) => !["name", "description", "value"].includes(key));
161
155
  if (unexpectedKeys.length > 0) {
162
156
  invalidResponse(`Context at index ${index} has unexpected fields: ${unexpectedKeys.join(", ")}.`);
163
157
  }
164
- if (!/^[A-Za-z0-9_-]{1,64}$/.test(candidate.name ?? "")
165
- || ["nxf_callAppTool", "nxf_finish", "nxf_getSnapshot", "nxf_executeAction", "nxf_applyAction", "nxf_delegatePageTask"]
166
- .includes(candidate.name)) {
158
+ if (!/^[A-Za-z0-9_-]{1,64}$/.test(candidate.name ?? "") ||
159
+ [
160
+ "nxf_callAppTool",
161
+ "nxf_finish",
162
+ "nxf_getSnapshot",
163
+ "nxf_executeAction",
164
+ "nxf_applyAction",
165
+ "nxf_delegatePageTask",
166
+ ].includes(candidate.name)) {
167
167
  invalidResponse(`Context at index ${index} has an invalid name.`);
168
168
  }
169
169
  if (names.has(candidate.name)) {
170
170
  invalidResponse(`Tool catalog returned duplicate context name "${candidate.name}".`);
171
171
  }
172
172
  names.add(candidate.name);
173
- if (candidate.description !== undefined
174
- && (typeof candidate.description !== "string" || !candidate.description.trim())) {
173
+ if (candidate.description !== undefined &&
174
+ (typeof candidate.description !== "string" || !candidate.description.trim())) {
175
175
  invalidResponse(`Context "${candidate.name}" must have a non-empty description when provided.`);
176
176
  }
177
177
  assertJsonCompatible(candidate.value, `contexts[${index}].value`);
178
178
  }
179
- return [...value]
180
- .sort((left, right) => left.name.localeCompare(right.name));
179
+ return [...value].sort((left, right) => left.name.localeCompare(right.name));
181
180
  }
@@ -3,9 +3,14 @@ export function getToolResultError(content) {
3
3
  const error = JSON.parse(content)?.error;
4
4
  if (typeof error === "string")
5
5
  return error;
6
- if (error && typeof error === "object" && "message" in error && typeof error.message === "string")
6
+ if (error &&
7
+ typeof error === "object" &&
8
+ "message" in error &&
9
+ typeof error.message === "string")
7
10
  return error.message;
8
11
  }
9
- catch { /* Tool results may contain plain text. */ }
12
+ catch {
13
+ /* Tool results may contain plain text. */
14
+ }
10
15
  return undefined;
11
16
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexface/agent",
3
- "version": "0.1.13-alpha.4",
3
+ "version": "0.1.13-alpha.5",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22.19.0"
@@ -43,7 +43,7 @@
43
43
  "@ag-ui/core": "0.0.59"
44
44
  },
45
45
  "peerDependencies": {
46
- "@nexface/tools": "0.1.13-alpha.4"
46
+ "@nexface/tools": "0.1.13-alpha.5"
47
47
  },
48
48
  "repository": {
49
49
  "type": "git",