@loopstack/claude-module 0.21.1 → 0.22.0

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.
Files changed (46) hide show
  1. package/dist/claude.module.d.ts.map +1 -1
  2. package/dist/claude.module.js +1 -10
  3. package/dist/claude.module.js.map +1 -1
  4. package/dist/documents/claude-message-document.d.ts +3 -1
  5. package/dist/documents/claude-message-document.d.ts.map +1 -1
  6. package/dist/documents/claude-message-document.js +6 -18
  7. package/dist/documents/claude-message-document.js.map +1 -1
  8. package/dist/documents/claude-message-document.yaml +2 -2
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/tools/claude-generate-document.tool.d.ts +3 -6
  14. package/dist/tools/claude-generate-document.tool.d.ts.map +1 -1
  15. package/dist/tools/claude-generate-document.tool.js +9 -31
  16. package/dist/tools/claude-generate-document.tool.js.map +1 -1
  17. package/dist/tools/claude-generate-object.tool.d.ts +4 -5
  18. package/dist/tools/claude-generate-object.tool.d.ts.map +1 -1
  19. package/dist/tools/claude-generate-object.tool.js +7 -17
  20. package/dist/tools/claude-generate-object.tool.js.map +1 -1
  21. package/dist/tools/claude-generate-text.tool.d.ts +4 -6
  22. package/dist/tools/claude-generate-text.tool.d.ts.map +1 -1
  23. package/dist/tools/claude-generate-text.tool.js +6 -34
  24. package/dist/tools/claude-generate-text.tool.js.map +1 -1
  25. package/dist/tools/delegate-tool-calls.tool.d.ts +5 -10
  26. package/dist/tools/delegate-tool-calls.tool.d.ts.map +1 -1
  27. package/dist/tools/delegate-tool-calls.tool.js +37 -66
  28. package/dist/tools/delegate-tool-calls.tool.js.map +1 -1
  29. package/dist/tools/update-tool-result.tool.d.ts +4 -9
  30. package/dist/tools/update-tool-result.tool.d.ts.map +1 -1
  31. package/dist/tools/update-tool-result.tool.js +18 -53
  32. package/dist/tools/update-tool-result.tool.js.map +1 -1
  33. package/dist/types/claude.types.d.ts +19 -0
  34. package/dist/types/claude.types.d.ts.map +1 -1
  35. package/dist/types/claude.types.js.map +1 -1
  36. package/package.json +3 -3
  37. package/src/claude.module.ts +1 -12
  38. package/src/documents/claude-message-document.ts +6 -12
  39. package/src/documents/claude-message-document.yaml +2 -2
  40. package/src/index.ts +1 -0
  41. package/src/tools/claude-generate-document.tool.ts +11 -48
  42. package/src/tools/claude-generate-object.tool.ts +13 -29
  43. package/src/tools/claude-generate-text.tool.ts +8 -65
  44. package/src/tools/delegate-tool-calls.tool.ts +53 -112
  45. package/src/tools/update-tool-result.tool.ts +21 -61
  46. package/src/types/claude.types.ts +26 -0
@@ -2,16 +2,12 @@ import Anthropic from '@anthropic-ai/sdk';
2
2
  import { Inject } from '@nestjs/common';
3
3
  import { toJSONSchema, z } from 'zod';
4
4
  import {
5
- DocumentInterface,
6
- Input,
7
- RunContext,
5
+ BaseTool,
6
+ DocumentClass,
8
7
  Tool,
9
- ToolInterface,
10
8
  ToolResult,
11
- WorkflowInterface,
12
- WorkflowMetadataInterface,
13
- getBlockArgsSchema,
14
- getBlockDocument,
9
+ getBlockTypeFromMetadata,
10
+ getDocumentSchema,
15
11
  } from '@loopstack/common';
16
12
  import { ClaudeGenerateToolBaseSchema } from '../schemas/claude-generate-tool-base.schema';
17
13
  import { ClaudeClientService } from '../services';
@@ -21,7 +17,9 @@ import { applyCacheBreakpoints } from '../utils/cache.utils';
21
17
  export const ClaudeGenerateObjectSchema = ClaudeGenerateToolBaseSchema.extend({
22
18
  response: z.object({
23
19
  id: z.string().optional(),
24
- document: z.string(),
20
+ document: z.custom<DocumentClass>(
21
+ (val) => typeof val === 'function' && getBlockTypeFromMetadata(val as object) === 'document',
22
+ ),
25
23
  }),
26
24
  }).strict();
27
25
 
@@ -30,27 +28,18 @@ export type ClaudeGenerateObjectArgsType = z.infer<typeof ClaudeGenerateObjectSc
30
28
  const STRUCTURED_OUTPUT_TOOL_NAME = 'structured_output';
31
29
 
32
30
  @Tool({
33
- config: {
31
+ uiConfig: {
34
32
  description: 'Generates a structured object using the Anthropic Claude API',
35
33
  },
34
+ schema: ClaudeGenerateObjectSchema,
36
35
  })
37
- export class ClaudeGenerateObject implements ToolInterface<ClaudeGenerateObjectArgsType> {
36
+ export class ClaudeGenerateObject extends BaseTool {
38
37
  @Inject()
39
38
  private readonly claudeClientService: ClaudeClientService;
40
39
  @Inject()
41
40
  private readonly claudeMessagesHelperService: ClaudeMessagesHelperService;
42
41
 
43
- @Input({
44
- schema: ClaudeGenerateObjectSchema,
45
- })
46
- args: ClaudeGenerateObjectArgsType;
47
-
48
- async execute(
49
- args: ClaudeGenerateObjectArgsType,
50
- ctx: RunContext,
51
- parent: WorkflowInterface,
52
- metadata: WorkflowMetadataInterface,
53
- ): Promise<ToolResult> {
42
+ async call(args: ClaudeGenerateObjectArgsType): Promise<ToolResult> {
54
43
  const client = this.claudeClientService.getClient(args.claude);
55
44
  const model = this.claudeClientService.getModel(args.claude);
56
45
 
@@ -59,19 +48,14 @@ export class ClaudeGenerateObject implements ToolInterface<ClaudeGenerateObjectA
59
48
  if (args.prompt) {
60
49
  messages.push({ role: 'user', content: args.prompt });
61
50
  } else {
62
- const resolved = this.claudeMessagesHelperService.getMessages(metadata.documents, {
51
+ const resolved = this.claudeMessagesHelperService.getMessages(this.ctx.runtime.documents, {
63
52
  messages: args.messages as Anthropic.MessageParam[],
64
53
  messagesSearchTag: args.messagesSearchTag,
65
54
  });
66
55
  messages.push(...resolved);
67
56
  }
68
57
 
69
- const document = getBlockDocument<DocumentInterface>(parent, args.response.document);
70
- if (!document) {
71
- throw new Error(`Document with name "${args.response.document}" not found in tool execution context.`);
72
- }
73
-
74
- const responseSchema = getBlockArgsSchema(document);
58
+ const responseSchema = getDocumentSchema(args.response.document);
75
59
  if (!responseSchema) {
76
60
  throw new Error('Claude object generation source document must have a schema.');
77
61
  }
@@ -1,38 +1,27 @@
1
1
  import Anthropic from '@anthropic-ai/sdk';
2
2
  import { Inject } from '@nestjs/common';
3
3
  import { z } from 'zod';
4
- import {
5
- Input,
6
- RunContext,
7
- Tool,
8
- ToolCallEntry,
9
- ToolCallsMap,
10
- ToolInterface,
11
- ToolResult,
12
- ToolSideEffects,
13
- WorkflowInterface,
14
- WorkflowMetadataInterface,
15
- getBlockTool,
16
- } from '@loopstack/common';
4
+ import { BaseTool, Tool, ToolCallEntry, ToolCallsMap, ToolResult } from '@loopstack/common';
17
5
  import { ClaudeGenerateToolBaseSchema } from '../schemas/claude-generate-tool-base.schema';
18
6
  import { ClaudeClientService } from '../services';
19
7
  import { ClaudeMessagesHelperService } from '../services';
20
8
  import { ClaudeToolsHelperService } from '../services';
9
+ import { ClaudeGenerateTextResult } from '../types';
21
10
  import { applyCacheBreakpoints } from '../utils/cache.utils';
22
11
 
23
12
  export const ClaudeGenerateTextSchema = ClaudeGenerateToolBaseSchema.extend({
24
13
  tools: z.array(z.string()).optional(),
25
- document: z.string().optional(),
26
14
  }).strict();
27
15
 
28
16
  type ClaudeGenerateTextArgsType = z.infer<typeof ClaudeGenerateTextSchema>;
29
17
 
30
18
  @Tool({
31
- config: {
19
+ uiConfig: {
32
20
  description: 'Generates text using the Anthropic Claude API',
33
21
  },
22
+ schema: ClaudeGenerateTextSchema,
34
23
  })
35
- export class ClaudeGenerateText implements ToolInterface<ClaudeGenerateTextArgsType> {
24
+ export class ClaudeGenerateText extends BaseTool {
36
25
  @Inject()
37
26
  private readonly claudeClientService: ClaudeClientService;
38
27
  @Inject()
@@ -40,17 +29,7 @@ export class ClaudeGenerateText implements ToolInterface<ClaudeGenerateTextArgsT
40
29
  @Inject()
41
30
  private readonly claudeToolsHelperService: ClaudeToolsHelperService;
42
31
 
43
- @Input({
44
- schema: ClaudeGenerateTextSchema,
45
- })
46
- args: ClaudeGenerateTextArgsType;
47
-
48
- async execute(
49
- args: ClaudeGenerateTextArgsType,
50
- ctx: RunContext,
51
- parent: WorkflowInterface,
52
- runtime: WorkflowMetadataInterface,
53
- ): Promise<ToolResult> {
32
+ async call(args: ClaudeGenerateTextArgsType): Promise<ToolResult<ClaudeGenerateTextResult>> {
54
33
  const client = this.claudeClientService.getClient(args.claude);
55
34
  const model = this.claudeClientService.getModel(args.claude);
56
35
 
@@ -59,14 +38,14 @@ export class ClaudeGenerateText implements ToolInterface<ClaudeGenerateTextArgsT
59
38
  if (args.prompt) {
60
39
  messages.push({ role: 'user', content: args.prompt });
61
40
  } else {
62
- const resolved = this.claudeMessagesHelperService.getMessages(runtime.documents, {
41
+ const resolved = this.claudeMessagesHelperService.getMessages(this.ctx.runtime.documents, {
63
42
  messages: args.messages as Anthropic.MessageParam[],
64
43
  messagesSearchTag: args.messagesSearchTag,
65
44
  });
66
45
  messages.push(...resolved);
67
46
  }
68
47
 
69
- const tools = args.tools ? this.claudeToolsHelperService.getTools(args.tools, parent) : undefined;
48
+ const tools = args.tools ? this.claudeToolsHelperService.getTools(args.tools, this.ctx.parent) : undefined;
70
49
 
71
50
  const response = await this.handleGenerateText(client, {
72
51
  model,
@@ -79,21 +58,11 @@ export class ClaudeGenerateText implements ToolInterface<ClaudeGenerateTextArgsT
79
58
 
80
59
  const toolCalls = this.extractToolCalls(response);
81
60
 
82
- let effects: ToolSideEffects[] = [];
83
-
84
- if (args.document) {
85
- const docResult = await this.createResponseMessage(args.document, response, ctx, parent, runtime);
86
- if (docResult.effects) {
87
- effects = [...docResult.effects];
88
- }
89
- }
90
-
91
61
  return {
92
62
  data: {
93
63
  ...response,
94
64
  ...(toolCalls ? { toolCalls } : {}),
95
65
  },
96
- ...(effects.length > 0 ? { effects } : {}),
97
66
  metadata: {
98
67
  usage: {
99
68
  inputTokens: response.usage.input_tokens,
@@ -105,32 +74,6 @@ export class ClaudeGenerateText implements ToolInterface<ClaudeGenerateTextArgsT
105
74
  };
106
75
  }
107
76
 
108
- private async createResponseMessage(
109
- document: string,
110
- response: Anthropic.Message,
111
- ctx: RunContext,
112
- parent: WorkflowInterface,
113
- runtime: WorkflowMetadataInterface,
114
- ): Promise<ToolResult> {
115
- const createDocumentTool = getBlockTool<ToolInterface>(parent, 'createDocument');
116
- if (!createDocumentTool) {
117
- throw new Error('createDocument tool not found in parent context.');
118
- }
119
-
120
- return createDocumentTool.execute(
121
- {
122
- id: response.id,
123
- document,
124
- update: {
125
- content: response,
126
- },
127
- },
128
- ctx,
129
- parent,
130
- runtime,
131
- );
132
- }
133
-
134
77
  private extractToolCalls(response: Anthropic.Message): ToolCallsMap | null {
135
78
  if (response.stop_reason !== 'tool_use') {
136
79
  return null;
@@ -2,24 +2,24 @@ import Anthropic from '@anthropic-ai/sdk';
2
2
  import { Injectable, Logger } from '@nestjs/common';
3
3
  import { z } from 'zod';
4
4
  import {
5
- InjectTool,
6
- Input,
7
- RunContext,
5
+ BaseTool,
6
+ DocumentClass,
8
7
  Tool,
9
- ToolInterface,
8
+ ToolCallOptions,
10
9
  ToolResult,
11
- ToolSideEffects,
12
- WorkflowInterface,
13
- WorkflowMetadataInterface,
10
+ getBlockTool,
11
+ getBlockTypeFromMetadata,
14
12
  } from '@loopstack/common';
15
- import { CreateDocument, EventSubscriberService, StateMachineToolCallProcessorService } from '@loopstack/core';
13
+ import type { DelegateToolCallsResult, DelegateToolResultEntry } from '../types';
16
14
 
17
15
  const DelegateToolCallsSchema = z.object({
18
16
  message: z.object({
19
17
  id: z.string().optional(),
20
18
  content: z.array(z.any()),
21
19
  }),
22
- document: z.string().optional(),
20
+ document: z
21
+ .custom<DocumentClass>((val) => typeof val === 'function' && getBlockTypeFromMetadata(val as object) === 'document')
22
+ .optional(),
23
23
  skipResponseMessage: z.boolean().optional(),
24
24
  callback: z
25
25
  .object({
@@ -30,88 +30,53 @@ const DelegateToolCallsSchema = z.object({
30
30
 
31
31
  type DelegateToolCallsArgs = z.infer<typeof DelegateToolCallsSchema>;
32
32
 
33
- interface ToolResultEntry {
34
- type: 'tool_result';
35
- tool_use_id: string;
36
- content?: string;
37
- is_error?: boolean;
38
- }
39
-
40
33
  @Injectable()
41
34
  @Tool({
42
- config: {
35
+ uiConfig: {
43
36
  description: 'Delegate tool calls from an LLM response. Handles both sync tools and async task tools.',
44
37
  },
38
+ schema: DelegateToolCallsSchema,
45
39
  })
46
- export class DelegateToolCalls implements ToolInterface<DelegateToolCallsArgs> {
40
+ export class DelegateToolCalls extends BaseTool {
47
41
  private readonly logger = new Logger(DelegateToolCalls.name);
48
42
 
49
- @InjectTool() private createDocument: CreateDocument;
50
-
51
- constructor(
52
- private readonly toolCallProcessor: StateMachineToolCallProcessorService,
53
- private readonly eventSubscriberService: EventSubscriberService,
54
- ) {}
55
-
56
- @Input({
57
- schema: DelegateToolCallsSchema,
58
- })
59
- args: DelegateToolCallsArgs;
60
-
61
- async execute(
62
- args: DelegateToolCallsArgs,
63
- ctx: RunContext,
64
- parent: WorkflowInterface,
65
- metadata: WorkflowMetadataInterface,
66
- ): Promise<ToolResult> {
43
+ async call(args: DelegateToolCallsArgs): Promise<ToolResult<DelegateToolCallsResult>> {
67
44
  const contentBlocks = args.message.content as Anthropic.ContentBlock[];
68
45
  const toolUseBlocks = contentBlocks.filter((b): b is Anthropic.ToolUseBlock => b.type === 'tool_use');
69
46
 
70
47
  if (toolUseBlocks.length === 0) {
71
48
  return {
72
- data: { allCompleted: true, toolResults: [], message: args.message, pendingCount: 0 },
49
+ data: {
50
+ allCompleted: true,
51
+ toolResults: [],
52
+ message: args.message as DelegateToolCallsResult['message'],
53
+ pendingCount: 0,
54
+ },
73
55
  };
74
56
  }
75
57
 
76
- // 1. Execute ALL tools in parallel
77
- const results = await Promise.all(toolUseBlocks.map((block) => this.executeTool(block, ctx, parent, metadata)));
58
+ // 1. Execute ALL tools in parallel, passing callback via options (not args)
59
+ const results = await Promise.all(
60
+ toolUseBlocks.map((block) => {
61
+ const toolCallback = args.callback
62
+ ? {
63
+ transition: args.callback.transition,
64
+ metadata: { toolUseId: block.id, toolName: block.name },
65
+ }
66
+ : undefined;
67
+ return this.executeTool(block, toolCallback ? { callback: toolCallback } : undefined);
68
+ }),
69
+ );
78
70
 
79
- // 2. Register subscribers for async results + build toolResults array
71
+ // 2. Build toolResults array async tools signal via result.pending
80
72
  let pendingCount = 0;
81
- const toolResults: ToolResultEntry[] = [];
82
- const toolEffects: ToolSideEffects[] = [];
73
+ const toolResults: DelegateToolResultEntry[] = [];
83
74
 
84
75
  for (let i = 0; i < toolUseBlocks.length; i++) {
85
76
  const block = toolUseBlocks[i];
86
77
  const result = results[i];
87
78
 
88
- // Collect effects from tool execution (e.g. link documents)
89
- if (result.effects) {
90
- toolEffects.push(...result.effects);
91
- }
92
-
93
- const resultData = result.data as Record<string, unknown> | undefined;
94
- if (resultData?.mode === 'async') {
95
- // Validate callback is provided
96
- if (!args.callback?.transition) {
97
- throw new Error(
98
- `Tool "${block.name}" returned an async result but no callback.transition was provided. ` +
99
- `Add a callback configuration to the delegateToolCalls args.`,
100
- );
101
- }
102
-
103
- // Register event subscriber
104
- await this.eventSubscriberService.registerSubscriber(
105
- ctx.pipelineId,
106
- ctx.workflowId!,
107
- args.callback.transition,
108
- resultData.correlationId as string,
109
- resultData.eventName as string,
110
- ctx.userId,
111
- ctx.workspaceId,
112
- { toolUseId: block.id, toolName: block.name },
113
- );
114
-
79
+ if (result.pending) {
115
80
  pendingCount++;
116
81
  } else {
117
82
  toolResults.push({
@@ -123,43 +88,29 @@ export class DelegateToolCalls implements ToolInterface<DelegateToolCallsArgs> {
123
88
  }
124
89
  }
125
90
 
126
- // 3. Create response document first (tool call message), then append
127
- // tool execution effects (e.g. link documents) so they appear after.
128
- const effects: ToolSideEffects[] = [];
91
+ // 3. Create response document (tool call message)
129
92
  if (args.document && !args.skipResponseMessage) {
130
- const docResult = await this.createResponseDocument(args, toolResults, ctx, parent, metadata);
131
- if (docResult.effects) {
132
- effects.push(...docResult.effects);
133
- }
93
+ await this.createResponseDocument(args.document, args, toolResults);
134
94
  }
135
- effects.push(...toolEffects);
136
95
 
137
96
  return {
138
97
  data: {
139
98
  allCompleted: pendingCount === 0,
140
99
  toolResults,
141
- message: args.message,
100
+ message: args.message as DelegateToolCallsResult['message'],
142
101
  pendingCount,
143
102
  },
144
- effects,
145
103
  };
146
104
  }
147
105
 
148
- private async executeTool(
149
- block: Anthropic.ToolUseBlock,
150
- ctx: RunContext,
151
- parent: WorkflowInterface,
152
- metadata: WorkflowMetadataInterface,
153
- ): Promise<ToolResult> {
154
- const tool = this.toolCallProcessor.getTool(parent, block.name);
155
- const parsedArgs = this.toolCallProcessor.parseArgs(
156
- tool,
157
- block.input as Record<string, unknown> | undefined,
158
- metadata.transition!,
159
- );
160
-
106
+ private async executeTool(block: Anthropic.ToolUseBlock, options?: ToolCallOptions): Promise<ToolResult> {
161
107
  try {
162
- return await this.toolCallProcessor.executeToolCall(tool, parsedArgs, ctx, parent, metadata);
108
+ const tool = getBlockTool<BaseTool>(this.ctx.parent, block.name);
109
+ if (!tool) {
110
+ throw new Error(`Tool with name ${block.name} not found.`);
111
+ }
112
+
113
+ return await tool.call(block.input as Record<string, unknown>, options);
163
114
  } catch (error) {
164
115
  const errorMessage = error instanceof Error ? error.message : String(error);
165
116
  this.logger.error(`Tool "${block.name}" failed: ${errorMessage}`);
@@ -171,28 +122,18 @@ export class DelegateToolCalls implements ToolInterface<DelegateToolCallsArgs> {
171
122
  }
172
123
 
173
124
  private async createResponseDocument(
125
+ document: DocumentClass,
174
126
  args: DelegateToolCallsArgs,
175
- toolResults: ToolResultEntry[],
176
- ctx: RunContext,
177
- parent: WorkflowInterface,
178
- metadata: WorkflowMetadataInterface,
179
- ): Promise<ToolResult> {
180
- return this.createDocument.execute(
127
+ toolResults: DelegateToolResultEntry[],
128
+ ): Promise<void> {
129
+ await this.repository.save(
130
+ document,
181
131
  {
182
- id: args.message.id,
183
- document: args.document!,
184
- validate: 'skip' as const,
185
- update: {
186
- content: {
187
- role: 'assistant',
188
- content: args.message.content,
189
- toolResults,
190
- },
191
- },
132
+ role: 'assistant',
133
+ content: args.message.content,
134
+ toolResults,
192
135
  },
193
- ctx,
194
- parent,
195
- metadata,
136
+ { id: args.message.id, validate: 'skip' },
196
137
  );
197
138
  }
198
139
  }
@@ -1,17 +1,6 @@
1
1
  import { Injectable, Logger } from '@nestjs/common';
2
2
  import { z } from 'zod';
3
- import {
4
- InjectTool,
5
- Input,
6
- RunContext,
7
- Tool,
8
- ToolInterface,
9
- ToolResult,
10
- ToolSideEffects,
11
- WorkflowInterface,
12
- WorkflowMetadataInterface,
13
- } from '@loopstack/common';
14
- import { CreateDocument, StateMachineToolCallProcessorService } from '@loopstack/core';
3
+ import { BaseTool, DocumentClass, Tool, ToolResult, getBlockTool, getBlockTypeFromMetadata } from '@loopstack/common';
15
4
 
16
5
  const UpdateToolResultSchema = z.object({
17
6
  delegateResult: z.object({
@@ -21,35 +10,24 @@ const UpdateToolResultSchema = z.object({
21
10
  pendingCount: z.number(),
22
11
  }),
23
12
  completedTool: z.any(),
24
- document: z.string().optional(),
13
+ document: z
14
+ .custom<DocumentClass>((val) => typeof val === 'function' && getBlockTypeFromMetadata(val as object) === 'document')
15
+ .optional(),
25
16
  });
26
17
 
27
18
  type UpdateToolResultArgs = z.infer<typeof UpdateToolResultSchema>;
28
19
 
29
20
  @Injectable()
30
21
  @Tool({
31
- config: {
22
+ uiConfig: {
32
23
  description: 'Handle async tool completion callback. Updates the response document and tracks completion state.',
33
24
  },
25
+ schema: UpdateToolResultSchema,
34
26
  })
35
- export class UpdateToolResult implements ToolInterface<UpdateToolResultArgs> {
27
+ export class UpdateToolResult extends BaseTool {
36
28
  private readonly logger = new Logger(UpdateToolResult.name);
37
29
 
38
- @InjectTool() private createDocument: CreateDocument;
39
-
40
- constructor(private readonly toolCallProcessor: StateMachineToolCallProcessorService) {}
41
-
42
- @Input({
43
- schema: UpdateToolResultSchema,
44
- })
45
- args: UpdateToolResultArgs;
46
-
47
- async execute(
48
- args: UpdateToolResultArgs,
49
- ctx: RunContext,
50
- parent: WorkflowInterface,
51
- metadata: WorkflowMetadataInterface,
52
- ): Promise<ToolResult> {
30
+ async call(args: UpdateToolResultArgs): Promise<ToolResult> {
53
31
  const { delegateResult } = args;
54
32
  const completedToolRecord = args.completedTool as Record<string, unknown>;
55
33
 
@@ -66,15 +44,14 @@ export class UpdateToolResult implements ToolInterface<UpdateToolResultArgs> {
66
44
 
67
45
  const { toolUseId, toolName } = subscriberMetadata;
68
46
 
69
- // 2. Resolve tool and call complete() if it exists
70
- const tool = this.toolCallProcessor.getTool(parent, toolName);
47
+ // 2. Resolve tool and call complete()
48
+ const tool = getBlockTool<BaseTool>(this.ctx.parent, toolName);
49
+ if (!tool) {
50
+ throw new Error(`Tool with name ${toolName} not found.`);
51
+ }
71
52
  let toolResult: ToolResult;
72
53
  try {
73
- if (tool.complete) {
74
- toolResult = await tool.complete(completedToolRecord, ctx, parent, metadata);
75
- } else {
76
- toolResult = { data: completedToolRecord.result ?? completedToolRecord };
77
- }
54
+ toolResult = await tool.complete(completedToolRecord);
78
55
  } catch (error) {
79
56
  const errorMessage = error instanceof Error ? error.message : String(error);
80
57
  this.logger.error(`Tool "${toolName}" complete() failed: ${errorMessage}`);
@@ -97,33 +74,17 @@ export class UpdateToolResult implements ToolInterface<UpdateToolResultArgs> {
97
74
  const pendingCount = delegateResult.pendingCount - 1;
98
75
  const allCompleted = pendingCount === 0;
99
76
 
100
- // 5. Create updated response document (invalidates previous)
101
- const allEffects: ToolSideEffects[] = [];
102
- if (toolResult.effects) {
103
- allEffects.push(...toolResult.effects);
104
- }
105
-
77
+ // 5. Update response document (invalidates previous)
106
78
  if (args.document) {
107
- const docResult = await this.createDocument.execute(
79
+ await this.repository.save(
80
+ args.document,
108
81
  {
109
- id: (delegateResult.message as Record<string, unknown>).id as string,
110
- document: args.document,
111
- validate: 'skip' as const,
112
- update: {
113
- content: {
114
- role: 'assistant',
115
- content: (delegateResult.message as Record<string, unknown>).content,
116
- toolResults: updatedResults,
117
- },
118
- },
82
+ role: 'assistant',
83
+ content: (delegateResult.message as Record<string, unknown>).content,
84
+ toolResults: updatedResults,
119
85
  },
120
- ctx,
121
- parent,
122
- metadata,
86
+ { id: (delegateResult.message as Record<string, unknown>).id as string, validate: 'skip' },
123
87
  );
124
- if (docResult.effects) {
125
- allEffects.push(...docResult.effects);
126
- }
127
88
  }
128
89
 
129
90
  return {
@@ -133,7 +94,6 @@ export class UpdateToolResult implements ToolInterface<UpdateToolResultArgs> {
133
94
  allCompleted,
134
95
  pendingCount,
135
96
  },
136
- effects: allEffects.length > 0 ? allEffects : undefined,
137
97
  };
138
98
  }
139
99
  }
@@ -1,4 +1,5 @@
1
1
  import Anthropic from '@anthropic-ai/sdk';
2
+ import type { ToolCallsMap } from '@loopstack/common';
2
3
 
3
4
  /**
4
5
  * Configuration for creating a Claude client and selecting a model.
@@ -30,5 +31,30 @@ export interface ClaudeToolDefinition {
30
31
  input_schema: Anthropic.Tool['input_schema'];
31
32
  }
32
33
 
34
+ /**
35
+ * Result data from ClaudeGenerateText tool.
36
+ * Extends the Anthropic Message with optional tool call metadata.
37
+ */
38
+ export interface ClaudeGenerateTextResult extends Anthropic.Message {
39
+ toolCalls?: ToolCallsMap;
40
+ }
41
+
42
+ /**
43
+ * Result data from DelegateToolCalls tool.
44
+ */
45
+ export interface DelegateToolCallsResult {
46
+ allCompleted: boolean;
47
+ toolResults: DelegateToolResultEntry[];
48
+ message: { id?: string; content: Anthropic.ContentBlock[] };
49
+ pendingCount: number;
50
+ }
51
+
52
+ export interface DelegateToolResultEntry {
53
+ type: 'tool_result';
54
+ tool_use_id: string;
55
+ content?: string;
56
+ is_error?: boolean;
57
+ }
58
+
33
59
  // Re-export Anthropic namespace for consumer convenience
34
60
  export { default as Anthropic } from '@anthropic-ai/sdk';