@nextclaw/ncp-agent-runtime 0.3.8 → 0.3.9

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
@@ -166,6 +166,9 @@ declare class DefaultNcpAgentRuntime implements NcpAgentRuntime {
166
166
  * The stream encoder does not emit RunFinished; it only converts chunks to NCP events.
167
167
  */
168
168
  private runLoop;
169
+ private executeToolCall;
170
+ private resolveValidationIssues;
171
+ private executeValidatedToolCall;
169
172
  private tapStream;
170
173
  }
171
174
  //#endregion
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import { createHash, randomUUID } from "node:crypto";
3
3
  import { copyFile, mkdir, readFile, stat, writeFile } from "node:fs/promises";
4
4
  import { basename, dirname, join, resolve } from "node:path";
5
5
  import { NcpAssistantTextStreamNormalizer, NcpEventType, isHiddenNcpMessage, normalizeAssistantText } from "@nextclaw/ncp";
6
+ import AjvPkg from "ajv";
6
7
  //#region src/user-content.ts
7
8
  function readOptionalString$1(value) {
8
9
  if (typeof value !== "string") return null;
@@ -859,6 +860,12 @@ var EchoNcpLLMApi = class {
859
860
  };
860
861
  //#endregion
861
862
  //#region src/utils.ts
863
+ const toolSchemaValidator = new AjvPkg({
864
+ allErrors: true,
865
+ strict: false,
866
+ removeAdditional: false
867
+ });
868
+ const validatorCache = /* @__PURE__ */ new WeakMap();
862
869
  function genId() {
863
870
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
864
871
  }
@@ -911,47 +918,25 @@ function parseToolArgs(args) {
911
918
  }
912
919
  function validateToolArgs(args, schema) {
913
920
  if (!schema) return [];
914
- return validateToolValue(args, schema, "");
921
+ const validate = getOrCreateValidator(schema);
922
+ if (validate(args)) return [];
923
+ return formatSchemaIssues(validate.errors);
915
924
  }
916
- function validateToolValue(value, schema, path) {
917
- const label = path || "parameter";
918
- const type = typeof schema.type === "string" ? schema.type : void 0;
919
- if (type && !matchesSchemaType(value, type)) return [`${label} should be ${type}`];
920
- const errors = [];
921
- if (schema.enum && !schema.enum.includes(value)) errors.push(`${label} must be one of ${JSON.stringify(schema.enum)}`);
922
- if (typeof value === "number") {
923
- if (schema.minimum !== void 0 && value < schema.minimum) errors.push(`${label} must be >= ${schema.minimum}`);
924
- if (schema.maximum !== void 0 && value > schema.maximum) errors.push(`${label} must be <= ${schema.maximum}`);
925
- }
926
- if (typeof value === "string") {
927
- if (schema.minLength !== void 0 && value.length < schema.minLength) errors.push(`${label} must be at least ${schema.minLength} chars`);
928
- if (schema.maxLength !== void 0 && value.length > schema.maxLength) errors.push(`${label} must be at most ${schema.maxLength} chars`);
929
- }
930
- if (type === "object") {
931
- const objectValue = value;
932
- for (const key of schema.required ?? []) if (!(key in objectValue)) errors.push(`missing required ${path ? `${path}.${key}` : key}`);
933
- const properties = schema.properties ?? {};
934
- for (const [key, childValue] of Object.entries(objectValue)) {
935
- const childSchema = properties[key];
936
- if (!childSchema) continue;
937
- errors.push(...validateToolValue(childValue, childSchema, path ? `${path}.${key}` : key));
938
- }
939
- }
940
- if (type === "array" && schema.items && Array.isArray(value)) value.forEach((item, index) => {
941
- errors.push(...validateToolValue(item, schema.items, `${label}[${index}]`));
942
- });
943
- return errors;
925
+ function getOrCreateValidator(schema) {
926
+ const cached = validatorCache.get(schema);
927
+ if (cached) return cached;
928
+ const validate = toolSchemaValidator.compile(schema);
929
+ validatorCache.set(schema, validate);
930
+ return validate;
944
931
  }
945
- function matchesSchemaType(value, type) {
946
- switch (type) {
947
- case "string": return typeof value === "string";
948
- case "integer": return typeof value === "number" && Number.isInteger(value);
949
- case "number": return typeof value === "number";
950
- case "boolean": return typeof value === "boolean";
951
- case "array": return Array.isArray(value);
952
- case "object": return typeof value === "object" && value !== null && !Array.isArray(value);
953
- default: return true;
954
- }
932
+ function formatSchemaIssues(errors) {
933
+ if (!errors || errors.length === 0) return ["Tool arguments do not match the declared schema."];
934
+ return errors.map((error) => {
935
+ const instancePath = error.instancePath.replace(/^\//, "").replace(/\//g, ".");
936
+ if (error.keyword === "required" && "missingProperty" in error.params && typeof error.params.missingProperty === "string") return `${instancePath ? `${instancePath}.${error.params.missingProperty}` : error.params.missingProperty} is required`;
937
+ if (error.keyword === "additionalProperties" && "additionalProperty" in error.params && typeof error.params.additionalProperty === "string") return `${instancePath ? `${instancePath}.${error.params.additionalProperty}` : error.params.additionalProperty} is not allowed`;
938
+ return `${instancePath || "parameter"}: ${error.message ?? "invalid"}`;
939
+ });
955
940
  }
956
941
  function createInvalidToolArgumentsResult(params) {
957
942
  return {
@@ -966,6 +951,18 @@ function createInvalidToolArgumentsResult(params) {
966
951
  }
967
952
  };
968
953
  }
954
+ function createToolExecutionFailedResult(params) {
955
+ const { toolCallId, toolName, error } = params;
956
+ return {
957
+ ok: false,
958
+ error: {
959
+ code: "tool_execution_failed",
960
+ message: error instanceof Error ? error.message : String(error),
961
+ toolCallId,
962
+ toolName
963
+ }
964
+ };
965
+ }
969
966
  function appendToolRoundToInput(input, reasoning, text, toolResults) {
970
967
  const assistantMsg = {
971
968
  role: "assistant",
@@ -1115,43 +1112,14 @@ var DefaultNcpAgentRuntime = class {
1115
1112
  for await (const event of this.streamEncoder.encode(tappedStream, ctx)) yield event;
1116
1113
  const toolResults = [];
1117
1114
  for (const toolCall of roundCollector.getToolCalls()) {
1118
- const tool = this.toolRegistry.getTool(toolCall.toolName);
1119
- const parsedArgs = parseToolArgs(toolCall.args);
1120
- let result;
1121
- let args = null;
1122
- if (!parsedArgs.ok) result = createInvalidToolArgumentsResult({
1123
- toolCallId: toolCall.toolCallId,
1124
- toolName: toolCall.toolName,
1125
- rawArgumentsText: parsedArgs.rawText,
1126
- issues: parsedArgs.issues
1127
- });
1128
- else {
1129
- const schemaIssues = validateToolArgs(parsedArgs.value, tool?.parameters);
1130
- const validationIssues = schemaIssues.length > 0 ? schemaIssues : typeof tool?.validateArgs === "function" ? tool.validateArgs(parsedArgs.value) : [];
1131
- if (validationIssues.length > 0) result = createInvalidToolArgumentsResult({
1132
- toolCallId: toolCall.toolCallId,
1133
- toolName: toolCall.toolName,
1134
- rawArgumentsText: parsedArgs.rawText,
1135
- issues: validationIssues
1136
- });
1137
- else {
1138
- args = parsedArgs.value;
1139
- result = await this.toolRegistry.execute(toolCall.toolCallId, toolCall.toolName, parsedArgs.value);
1140
- }
1141
- }
1142
- toolResults.push({
1143
- toolCallId: toolCall.toolCallId,
1144
- toolName: toolCall.toolName,
1145
- args,
1146
- rawArgsText: parsedArgs.rawText,
1147
- result
1148
- });
1115
+ const toolResult = await this.executeToolCall(toolCall);
1116
+ toolResults.push(toolResult);
1149
1117
  yield {
1150
1118
  type: NcpEventType.MessageToolCallResult,
1151
1119
  payload: {
1152
1120
  sessionId: ctx.sessionId,
1153
1121
  toolCallId: toolCall.toolCallId,
1154
- content: result
1122
+ content: toolResult.result
1155
1123
  }
1156
1124
  };
1157
1125
  }
@@ -1170,6 +1138,58 @@ var DefaultNcpAgentRuntime = class {
1170
1138
  currentInput = appendToolRoundToInput(currentInput, roundCollector.getReasoning(), roundCollector.getText(), toolResults);
1171
1139
  }
1172
1140
  };
1141
+ executeToolCall = async function(toolCall) {
1142
+ const tool = this.toolRegistry.getTool(toolCall.toolName);
1143
+ const parsedArgs = parseToolArgs(toolCall.args);
1144
+ if (!parsedArgs.ok) return {
1145
+ toolCallId: toolCall.toolCallId,
1146
+ toolName: toolCall.toolName,
1147
+ args: null,
1148
+ rawArgsText: parsedArgs.rawText,
1149
+ result: createInvalidToolArgumentsResult({
1150
+ toolCallId: toolCall.toolCallId,
1151
+ toolName: toolCall.toolName,
1152
+ rawArgumentsText: parsedArgs.rawText,
1153
+ issues: parsedArgs.issues
1154
+ })
1155
+ };
1156
+ const validationIssues = this.resolveValidationIssues(parsedArgs.value, tool);
1157
+ if (validationIssues.length > 0) return {
1158
+ toolCallId: toolCall.toolCallId,
1159
+ toolName: toolCall.toolName,
1160
+ args: null,
1161
+ rawArgsText: parsedArgs.rawText,
1162
+ result: createInvalidToolArgumentsResult({
1163
+ toolCallId: toolCall.toolCallId,
1164
+ toolName: toolCall.toolName,
1165
+ rawArgumentsText: parsedArgs.rawText,
1166
+ issues: validationIssues
1167
+ })
1168
+ };
1169
+ return {
1170
+ toolCallId: toolCall.toolCallId,
1171
+ toolName: toolCall.toolName,
1172
+ args: parsedArgs.value,
1173
+ rawArgsText: parsedArgs.rawText,
1174
+ result: await this.executeValidatedToolCall(toolCall, parsedArgs.value)
1175
+ };
1176
+ };
1177
+ resolveValidationIssues = function(args, tool) {
1178
+ const schemaIssues = validateToolArgs(args, tool?.parameters);
1179
+ if (schemaIssues.length > 0) return schemaIssues;
1180
+ return typeof tool?.validateArgs === "function" ? tool.validateArgs(args) : [];
1181
+ };
1182
+ executeValidatedToolCall = async function(toolCall, args) {
1183
+ try {
1184
+ return await this.toolRegistry.execute(toolCall.toolCallId, toolCall.toolName, args);
1185
+ } catch (error) {
1186
+ return createToolExecutionFailedResult({
1187
+ toolCallId: toolCall.toolCallId,
1188
+ toolName: toolCall.toolName,
1189
+ error
1190
+ });
1191
+ }
1192
+ };
1173
1193
  tapStream = async function* (stream, onChunk) {
1174
1194
  for await (const chunk of stream) {
1175
1195
  onChunk(chunk);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ncp-agent-runtime",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "private": false,
5
5
  "description": "Default agent runtime implementation built on NCP interfaces.",
6
6
  "type": "module",
@@ -15,6 +15,7 @@
15
15
  "dist"
16
16
  ],
17
17
  "dependencies": {
18
+ "ajv": "^8.17.1",
18
19
  "@nextclaw/ncp": "0.5.0"
19
20
  },
20
21
  "devDependencies": {