@looopy-ai/core 2.1.15 → 2.1.17

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.
@@ -1,5 +1,7 @@
1
1
  import { concat, defer, filter, map, mergeMap, shareReplay } from 'rxjs';
2
2
  import { startLLMCallSpan, startLoopIterationSpan } from '../observability/spans';
3
+ import { toolErrorEvent } from '../tools/tool-result-events';
4
+ import { safeValidateToolCall } from '../types/tools';
3
5
  import { getSystemPrompts } from '../utils/prompt';
4
6
  import { runToolCall } from './tools';
5
7
  export const runIteration = (context, config, history) => {
@@ -35,7 +37,35 @@ export const runIteration = (context, config, history) => {
35
37
  stream: true,
36
38
  sessionId: context.taskId,
37
39
  })
38
- .pipe(finishLLMCallSpan, map((event) => ({
40
+ .pipe(finishLLMCallSpan, map((event) => {
41
+ if (event.kind === 'tool-call') {
42
+ const validation = safeValidateToolCall({
43
+ id: event.toolCallId,
44
+ type: 'function',
45
+ function: {
46
+ name: event.toolName,
47
+ arguments: event.arguments,
48
+ },
49
+ });
50
+ if (!validation.success) {
51
+ const errorMessage = `Invalid tool call format: ${(validation.errors || []).map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`;
52
+ context.logger.error({
53
+ toolCallId: event.toolCallId,
54
+ toolName: event.toolName,
55
+ errors: validation.errors,
56
+ }, 'Invalid tool call from LLM - tool name must match ^[a-zA-Z0-9_-]+$');
57
+ return toolErrorEvent({
58
+ id: event.toolCallId,
59
+ type: 'function',
60
+ function: {
61
+ name: event.toolName,
62
+ arguments: event.arguments,
63
+ },
64
+ }, errorMessage);
65
+ }
66
+ }
67
+ return event;
68
+ }), map((event) => ({
39
69
  contextId: context.contextId,
40
70
  taskId: context.taskId,
41
71
  path: undefined,
@@ -3,6 +3,7 @@ import { isChildTaskEvent } from '../events/utils';
3
3
  import { startToolExecuteSpan } from '../observability/spans';
4
4
  import { toolErrorEvent } from '../tools/tool-result-events';
5
5
  import { isToolPlugin } from '../types/core';
6
+ import { safeValidateToolCall } from '../types/tools';
6
7
  export const runToolCall = (context, toolCall) => {
7
8
  const logger = context.logger.child({
8
9
  component: 'tool-call',
@@ -10,6 +11,24 @@ export const runToolCall = (context, toolCall) => {
10
11
  toolName: toolCall.toolName,
11
12
  });
12
13
  return defer(async () => {
14
+ const toolCallInput = {
15
+ id: toolCall.toolCallId,
16
+ type: 'function',
17
+ function: {
18
+ name: toolCall.toolName,
19
+ arguments: toolCall.arguments,
20
+ },
21
+ };
22
+ const validation = safeValidateToolCall(toolCallInput);
23
+ if (!validation.success) {
24
+ const errorMessage = `Invalid tool call format: ${(validation.errors || []).map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`;
25
+ logger.error({
26
+ toolCallId: toolCall.toolCallId,
27
+ toolName: toolCall.toolName,
28
+ errors: validation.errors,
29
+ }, 'Invalid tool call from LLM - tool name must match ^[a-zA-Z0-9_-]+$');
30
+ return of(toolErrorEvent(toolCallInput, errorMessage));
31
+ }
13
32
  const matchingPlugins = await Promise.all(context.plugins.filter(isToolPlugin).map(async (p) => ({
14
33
  plugin: p,
15
34
  tool: await p.getTool?.(toolCall.toolName),
@@ -29,14 +48,6 @@ export const runToolCall = (context, toolCall) => {
29
48
  arguments: toolCall.arguments,
30
49
  timestamp: new Date().toISOString(),
31
50
  };
32
- const toolCallInput = {
33
- id: toolCall.toolCallId,
34
- type: 'function',
35
- function: {
36
- name: toolCall.toolName,
37
- arguments: toolCall.arguments,
38
- },
39
- };
40
51
  const { tapFinish } = startToolExecuteSpan(context, toolCall);
41
52
  const execution$ = defer(() => {
42
53
  try {
@@ -41,8 +41,9 @@ export declare function validateToolDefinitions(tools: unknown): ToolDefinition[
41
41
  export declare function safeValidateToolDefinitions(tools: unknown): {
42
42
  success: boolean;
43
43
  data?: ToolDefinition[];
44
- errors?: z.ZodIssue[];
44
+ errors?: z.core.$ZodIssue[];
45
45
  };
46
+ export declare const toolFunctionNameRegex: RegExp;
46
47
  export declare const ToolCallSchema: z.ZodObject<{
47
48
  id: z.ZodString;
48
49
  type: z.ZodLiteral<"function">;
@@ -51,3 +52,9 @@ export declare const ToolCallSchema: z.ZodObject<{
51
52
  arguments: z.ZodRecord<z.ZodString, z.ZodUnknown>;
52
53
  }, z.core.$strip>;
53
54
  }, z.core.$strip>;
55
+ export declare function validateToolCall(toolCall: unknown): ToolCall;
56
+ export declare function safeValidateToolCall(toolCall: unknown): {
57
+ success: boolean;
58
+ data?: ToolCall;
59
+ errors?: z.core.$ZodIssue[];
60
+ };
@@ -44,11 +44,24 @@ export function safeValidateToolDefinitions(tools) {
44
44
  }
45
45
  return { success: false, errors: result.error.issues };
46
46
  }
47
+ export const toolFunctionNameRegex = /^[a-zA-Z0-9_-]+$/;
47
48
  export const ToolCallSchema = z.object({
48
49
  id: z.string(),
49
50
  type: z.literal('function'),
50
51
  function: z.object({
51
- name: z.string(),
52
+ name: z
53
+ .string()
54
+ .regex(toolFunctionNameRegex, 'Tool name must contain only alphanumeric characters, underscores, and hyphens'),
52
55
  arguments: z.record(z.string(), z.unknown()),
53
56
  }),
54
57
  });
58
+ export function validateToolCall(toolCall) {
59
+ return ToolCallSchema.parse(toolCall);
60
+ }
61
+ export function safeValidateToolCall(toolCall) {
62
+ const result = ToolCallSchema.safeParse(toolCall);
63
+ if (result.success) {
64
+ return { success: true, data: result.data };
65
+ }
66
+ return { success: false, errors: result.error.issues };
67
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@looopy-ai/core",
3
- "version": "2.1.15",
3
+ "version": "2.1.17",
4
4
  "description": "RxJS-based AI agent framework",
5
5
  "repository": {
6
6
  "url": "https://github.com/looopy-ai/lib"