@nextclaw/ncp-agent-runtime 0.3.9 → 0.3.11

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
@@ -1,4 +1,4 @@
1
- import { NcpAgentConversationStateManager, NcpAgentRunInput, NcpAgentRunOptions, NcpAgentRuntime, NcpAssistantReasoningNormalizationMode, NcpContextBuilder, NcpContextPrepareOptions, NcpEncodeContext, NcpEndpointEvent, NcpLLMApi, NcpLLMApiInput, NcpLLMApiOptions, NcpMessagePart, NcpRoundBuffer, NcpStreamEncoder, NcpTool, NcpToolCallResult, NcpToolDefinition, NcpToolRegistry, OpenAIChatChunk, OpenAIContentPart } from "@nextclaw/ncp";
1
+ import { NcpAgentConversationStateManager, NcpAgentRunInput, NcpAgentRunOptions, NcpAgentRuntime, NcpAssistantReasoningNormalizationMode, NcpContextBuilder, NcpContextPrepareOptions, NcpEncodeContext, NcpEndpointEvent, NcpInvalidToolArgumentsResult, NcpLLMApi, NcpLLMApiInput, NcpLLMApiOptions, NcpMessagePart, NcpRoundBuffer, NcpStreamEncoder, NcpTool, NcpToolCallResult, NcpToolDefinition, NcpToolRegistry, OpenAIChatChunk, OpenAIContentPart, OpenAITool } from "@nextclaw/ncp";
2
2
 
3
3
  //#region src/asset-store.d.ts
4
4
  type StoredAssetRecord = {
@@ -172,4 +172,44 @@ declare class DefaultNcpAgentRuntime implements NcpAgentRuntime {
172
172
  private tapStream;
173
173
  }
174
174
  //#endregion
175
- export { type AssetMeta, type AssetPutInput, type AssetRef, DefaultNcpAgentRuntime, type DefaultNcpAgentRuntimeConfig, DefaultNcpContextBuilder, DefaultNcpRoundBuffer, DefaultNcpStreamEncoder, DefaultNcpToolRegistry, EchoNcpLLMApi, LocalAssetStore, type StoredAssetRecord, buildAssetContentPath, buildNcpUserContent, isTextLikeAsset };
175
+ //#region src/utils.d.ts
176
+ type ParsedToolArgs = {
177
+ ok: true;
178
+ rawText: string;
179
+ value: Record<string, unknown>;
180
+ } | {
181
+ ok: false;
182
+ rawText: string;
183
+ issues: string[];
184
+ };
185
+ declare function genId(): string;
186
+ declare function getOpenAiFunctionParametersSchemaIssues(schema: Record<string, unknown> | undefined): string[];
187
+ declare function assertOpenAiFunctionParametersSchema(params: {
188
+ toolName: string;
189
+ schema: Record<string, unknown> | undefined;
190
+ }): void;
191
+ declare function buildOpenAiFunctionTool(definition: NcpToolDefinition): OpenAITool;
192
+ declare function parseToolArgs(args: unknown): ParsedToolArgs;
193
+ declare function validateToolArgs(args: Record<string, unknown>, schema: Record<string, unknown> | undefined): string[];
194
+ declare function createInvalidToolArgumentsResult(params: {
195
+ toolCallId: string;
196
+ toolName: string;
197
+ rawArgumentsText: string;
198
+ issues: string[];
199
+ }): NcpInvalidToolArgumentsResult;
200
+ declare function createToolExecutionFailedResult(params: {
201
+ toolCallId: string;
202
+ toolName: string;
203
+ error: unknown;
204
+ }): {
205
+ ok: false;
206
+ error: {
207
+ code: "tool_execution_failed";
208
+ message: string;
209
+ toolCallId: string;
210
+ toolName: string;
211
+ };
212
+ };
213
+ declare function appendToolRoundToInput(input: NcpLLMApiInput, reasoning: string, text: string, toolResults: ReadonlyArray<NcpToolCallResult>): NcpLLMApiInput;
214
+ //#endregion
215
+ export { type AssetMeta, type AssetPutInput, type AssetRef, DefaultNcpAgentRuntime, type DefaultNcpAgentRuntimeConfig, DefaultNcpContextBuilder, DefaultNcpRoundBuffer, DefaultNcpStreamEncoder, DefaultNcpToolRegistry, EchoNcpLLMApi, LocalAssetStore, type StoredAssetRecord, appendToolRoundToInput, assertOpenAiFunctionParametersSchema, buildAssetContentPath, buildNcpUserContent, buildOpenAiFunctionTool, createInvalidToolArgumentsResult, createToolExecutionFailedResult, genId, getOpenAiFunctionParametersSchemaIssues, isTextLikeAsset, parseToolArgs, validateToolArgs };
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { copyFileSync, existsSync, readFileSync } from "node:fs";
2
+ import AjvPkg from "ajv";
2
3
  import { createHash, randomUUID } from "node:crypto";
3
4
  import { copyFile, mkdir, readFile, stat, writeFile } from "node:fs/promises";
4
5
  import { basename, dirname, join, resolve } from "node:path";
5
6
  import { NcpAssistantTextStreamNormalizer, NcpEventType, isHiddenNcpMessage, normalizeAssistantText } from "@nextclaw/ncp";
6
- import AjvPkg from "ajv";
7
7
  //#region src/user-content.ts
8
8
  function readOptionalString$1(value) {
9
9
  if (typeof value !== "string") return null;
@@ -147,6 +147,177 @@ function buildNcpUserContent(parts, options = {}) {
147
147
  return content;
148
148
  }
149
149
  //#endregion
150
+ //#region src/utils.ts
151
+ const toolSchemaValidator = new AjvPkg({
152
+ allErrors: true,
153
+ strict: false,
154
+ removeAdditional: false
155
+ });
156
+ const validatorCache = /* @__PURE__ */ new WeakMap();
157
+ const DISALLOWED_OPENAI_TOOL_SCHEMA_TOP_LEVEL_KEYWORDS = [
158
+ "oneOf",
159
+ "anyOf",
160
+ "allOf",
161
+ "enum",
162
+ "not"
163
+ ];
164
+ function genId() {
165
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
166
+ }
167
+ function isRecord$1(value) {
168
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
169
+ }
170
+ function stringifyRawArgs(args) {
171
+ if (typeof args === "string") return args;
172
+ if (args && typeof args === "object" && !Array.isArray(args)) try {
173
+ return JSON.stringify(args);
174
+ } catch {
175
+ return "[unserializable-object]";
176
+ }
177
+ return String(args ?? "");
178
+ }
179
+ function getOpenAiFunctionParametersSchemaIssues(schema) {
180
+ if (!schema) return ["parameters must be declared as a JSON Schema object"];
181
+ if (!isRecord$1(schema)) return ["parameters must be a JSON Schema object"];
182
+ const issues = [];
183
+ if (schema.type !== "object") issues.push("root schema must set type to \"object\"");
184
+ for (const keyword of DISALLOWED_OPENAI_TOOL_SCHEMA_TOP_LEVEL_KEYWORDS) if (keyword in schema) issues.push(`root schema must not declare top-level "${keyword}"`);
185
+ return issues;
186
+ }
187
+ function assertOpenAiFunctionParametersSchema(params) {
188
+ const issues = getOpenAiFunctionParametersSchemaIssues(params.schema);
189
+ if (issues.length === 0) return;
190
+ throw new Error(`Tool "${params.toolName}" declares an unsupported OpenAI-compatible parameters schema: ${issues.join("; ")}. See docs/internal/openai-tool-schema.md.`);
191
+ }
192
+ function buildOpenAiFunctionTool(definition) {
193
+ assertOpenAiFunctionParametersSchema({
194
+ toolName: definition.name,
195
+ schema: definition.parameters
196
+ });
197
+ return {
198
+ type: "function",
199
+ function: {
200
+ name: definition.name,
201
+ description: definition.description,
202
+ parameters: definition.parameters
203
+ }
204
+ };
205
+ }
206
+ function parseToolArgs(args) {
207
+ if (args && typeof args === "object" && !Array.isArray(args)) return {
208
+ ok: true,
209
+ rawText: stringifyRawArgs(args),
210
+ value: args
211
+ };
212
+ const rawText = stringifyRawArgs(args);
213
+ if (typeof args !== "string") return {
214
+ ok: false,
215
+ rawText,
216
+ issues: ["Tool arguments must be a JSON object string."]
217
+ };
218
+ const trimmed = args.trim();
219
+ if (!trimmed) return {
220
+ ok: false,
221
+ rawText,
222
+ issues: ["Tool arguments are empty."]
223
+ };
224
+ try {
225
+ const parsed = JSON.parse(trimmed);
226
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {
227
+ ok: false,
228
+ rawText,
229
+ issues: ["Tool arguments JSON must decode to an object."]
230
+ };
231
+ return {
232
+ ok: true,
233
+ rawText,
234
+ value: parsed
235
+ };
236
+ } catch (error) {
237
+ return {
238
+ ok: false,
239
+ rawText,
240
+ issues: [error instanceof Error ? error.message : "Failed to parse tool arguments JSON."]
241
+ };
242
+ }
243
+ }
244
+ function validateToolArgs(args, schema) {
245
+ if (!schema) return [];
246
+ const validate = getOrCreateValidator(schema);
247
+ if (validate(args)) return [];
248
+ return formatSchemaIssues(validate.errors);
249
+ }
250
+ function getOrCreateValidator(schema) {
251
+ const cached = validatorCache.get(schema);
252
+ if (cached) return cached;
253
+ const validate = toolSchemaValidator.compile(schema);
254
+ validatorCache.set(schema, validate);
255
+ return validate;
256
+ }
257
+ function formatSchemaIssues(errors) {
258
+ if (!errors || errors.length === 0) return ["Tool arguments do not match the declared schema."];
259
+ return errors.map((error) => {
260
+ const instancePath = error.instancePath.replace(/^\//, "").replace(/\//g, ".");
261
+ if (error.keyword === "required" && "missingProperty" in error.params && typeof error.params.missingProperty === "string") return `${instancePath ? `${instancePath}.${error.params.missingProperty}` : error.params.missingProperty} is required`;
262
+ 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`;
263
+ return `${instancePath || "parameter"}: ${error.message ?? "invalid"}`;
264
+ });
265
+ }
266
+ function createInvalidToolArgumentsResult(params) {
267
+ const { toolCallId, toolName, rawArgumentsText, issues } = params;
268
+ return {
269
+ ok: false,
270
+ error: {
271
+ code: "invalid_tool_arguments",
272
+ message: "Tool arguments are invalid.",
273
+ toolCallId,
274
+ toolName,
275
+ rawArgumentsText,
276
+ issues
277
+ }
278
+ };
279
+ }
280
+ function createToolExecutionFailedResult(params) {
281
+ const { toolCallId, toolName, error } = params;
282
+ return {
283
+ ok: false,
284
+ error: {
285
+ code: "tool_execution_failed",
286
+ message: error instanceof Error ? error.message : String(error),
287
+ toolCallId,
288
+ toolName
289
+ }
290
+ };
291
+ }
292
+ function appendToolRoundToInput(input, reasoning, text, toolResults) {
293
+ const assistantMsg = {
294
+ role: "assistant",
295
+ content: text || null,
296
+ ...reasoning ? { reasoning_content: reasoning } : {},
297
+ tool_calls: toolResults.map((tr) => ({
298
+ id: tr.toolCallId,
299
+ type: "function",
300
+ function: {
301
+ name: tr.toolName,
302
+ arguments: tr.rawArgsText
303
+ }
304
+ }))
305
+ };
306
+ const toolMsgs = toolResults.map((tr) => ({
307
+ role: "tool",
308
+ content: typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result ?? {}),
309
+ tool_call_id: tr.toolCallId
310
+ }));
311
+ return {
312
+ ...input,
313
+ messages: [
314
+ ...input.messages,
315
+ assistantMsg,
316
+ ...toolMsgs
317
+ ]
318
+ };
319
+ }
320
+ //#endregion
150
321
  //#region src/context-builder.ts
151
322
  function isRecord(value) {
152
323
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -262,14 +433,7 @@ var DefaultNcpContextBuilder = class {
262
433
  for (const msg of sessionMessages.slice(-maxMessages)) messages.push(...messageToOpenAI(msg, { assetStore: this.assetStore }));
263
434
  for (const msg of input.messages) messages.push(...messageToOpenAI(msg, { assetStore: this.assetStore }));
264
435
  const toolDefinitions = this.toolRegistry?.getToolDefinitions() ?? [];
265
- const tools = (requestedToolNames.length > 0 ? toolDefinitions.filter((definition) => requestedToolNames.includes(definition.name)) : toolDefinitions).map((definition) => ({
266
- type: "function",
267
- function: {
268
- name: definition.name,
269
- description: definition.description,
270
- parameters: definition.parameters
271
- }
272
- }));
436
+ const tools = (requestedToolNames.length > 0 ? toolDefinitions.filter((definition) => requestedToolNames.includes(definition.name)) : toolDefinitions).map(buildOpenAiFunctionTool);
273
437
  return {
274
438
  messages,
275
439
  tools: tools && tools.length > 0 ? tools : void 0,
@@ -859,139 +1023,6 @@ var EchoNcpLLMApi = class {
859
1023
  };
860
1024
  };
861
1025
  //#endregion
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();
869
- function genId() {
870
- return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
871
- }
872
- function stringifyRawArgs(args) {
873
- if (typeof args === "string") return args;
874
- if (args && typeof args === "object" && !Array.isArray(args)) try {
875
- return JSON.stringify(args);
876
- } catch {
877
- return "[unserializable-object]";
878
- }
879
- return String(args ?? "");
880
- }
881
- function parseToolArgs(args) {
882
- if (args && typeof args === "object" && !Array.isArray(args)) return {
883
- ok: true,
884
- rawText: stringifyRawArgs(args),
885
- value: args
886
- };
887
- const rawText = stringifyRawArgs(args);
888
- if (typeof args !== "string") return {
889
- ok: false,
890
- rawText,
891
- issues: ["Tool arguments must be a JSON object string."]
892
- };
893
- const trimmed = args.trim();
894
- if (!trimmed) return {
895
- ok: false,
896
- rawText,
897
- issues: ["Tool arguments are empty."]
898
- };
899
- try {
900
- const parsed = JSON.parse(trimmed);
901
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {
902
- ok: false,
903
- rawText,
904
- issues: ["Tool arguments JSON must decode to an object."]
905
- };
906
- return {
907
- ok: true,
908
- rawText,
909
- value: parsed
910
- };
911
- } catch (error) {
912
- return {
913
- ok: false,
914
- rawText,
915
- issues: [error instanceof Error ? error.message : "Failed to parse tool arguments JSON."]
916
- };
917
- }
918
- }
919
- function validateToolArgs(args, schema) {
920
- if (!schema) return [];
921
- const validate = getOrCreateValidator(schema);
922
- if (validate(args)) return [];
923
- return formatSchemaIssues(validate.errors);
924
- }
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;
931
- }
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
- });
940
- }
941
- function createInvalidToolArgumentsResult(params) {
942
- return {
943
- ok: false,
944
- error: {
945
- code: "invalid_tool_arguments",
946
- message: "Tool arguments are invalid.",
947
- toolCallId: params.toolCallId,
948
- toolName: params.toolName,
949
- rawArgumentsText: params.rawArgumentsText,
950
- issues: params.issues
951
- }
952
- };
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
- }
966
- function appendToolRoundToInput(input, reasoning, text, toolResults) {
967
- const assistantMsg = {
968
- role: "assistant",
969
- content: text || null,
970
- ...reasoning ? { reasoning_content: reasoning } : {},
971
- tool_calls: toolResults.map((tr) => ({
972
- id: tr.toolCallId,
973
- type: "function",
974
- function: {
975
- name: tr.toolName,
976
- arguments: tr.rawArgsText
977
- }
978
- }))
979
- };
980
- const toolMsgs = toolResults.map((tr) => ({
981
- role: "tool",
982
- content: typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result ?? {}),
983
- tool_call_id: tr.toolCallId
984
- }));
985
- return {
986
- ...input,
987
- messages: [
988
- ...input.messages,
989
- assistantMsg,
990
- ...toolMsgs
991
- ]
992
- };
993
- }
994
- //#endregion
995
1026
  //#region src/round-collector.ts
996
1027
  var DefaultNcpRoundCollector = class {
997
1028
  rawText = "";
@@ -1198,4 +1229,4 @@ var DefaultNcpAgentRuntime = class {
1198
1229
  };
1199
1230
  };
1200
1231
  //#endregion
1201
- export { DefaultNcpAgentRuntime, DefaultNcpContextBuilder, DefaultNcpRoundBuffer, DefaultNcpStreamEncoder, DefaultNcpToolRegistry, EchoNcpLLMApi, LocalAssetStore, buildAssetContentPath, buildNcpUserContent, isTextLikeAsset };
1232
+ export { DefaultNcpAgentRuntime, DefaultNcpContextBuilder, DefaultNcpRoundBuffer, DefaultNcpStreamEncoder, DefaultNcpToolRegistry, EchoNcpLLMApi, LocalAssetStore, appendToolRoundToInput, assertOpenAiFunctionParametersSchema, buildAssetContentPath, buildNcpUserContent, buildOpenAiFunctionTool, createInvalidToolArgumentsResult, createToolExecutionFailedResult, genId, getOpenAiFunctionParametersSchemaIssues, isTextLikeAsset, parseToolArgs, validateToolArgs };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ncp-agent-runtime",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "private": false,
5
5
  "description": "Default agent runtime implementation built on NCP interfaces.",
6
6
  "type": "module",
@@ -16,7 +16,7 @@
16
16
  ],
17
17
  "dependencies": {
18
18
  "ajv": "^8.17.1",
19
- "@nextclaw/ncp": "0.5.0"
19
+ "@nextclaw/ncp": "0.5.1"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^20.17.6",