@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.
- package/dist/claude.module.d.ts.map +1 -1
- package/dist/claude.module.js +1 -10
- package/dist/claude.module.js.map +1 -1
- package/dist/documents/claude-message-document.d.ts +3 -1
- package/dist/documents/claude-message-document.d.ts.map +1 -1
- package/dist/documents/claude-message-document.js +6 -18
- package/dist/documents/claude-message-document.js.map +1 -1
- package/dist/documents/claude-message-document.yaml +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/tools/claude-generate-document.tool.d.ts +3 -6
- package/dist/tools/claude-generate-document.tool.d.ts.map +1 -1
- package/dist/tools/claude-generate-document.tool.js +9 -31
- package/dist/tools/claude-generate-document.tool.js.map +1 -1
- package/dist/tools/claude-generate-object.tool.d.ts +4 -5
- package/dist/tools/claude-generate-object.tool.d.ts.map +1 -1
- package/dist/tools/claude-generate-object.tool.js +7 -17
- package/dist/tools/claude-generate-object.tool.js.map +1 -1
- package/dist/tools/claude-generate-text.tool.d.ts +4 -6
- package/dist/tools/claude-generate-text.tool.d.ts.map +1 -1
- package/dist/tools/claude-generate-text.tool.js +6 -34
- package/dist/tools/claude-generate-text.tool.js.map +1 -1
- package/dist/tools/delegate-tool-calls.tool.d.ts +5 -10
- package/dist/tools/delegate-tool-calls.tool.d.ts.map +1 -1
- package/dist/tools/delegate-tool-calls.tool.js +37 -66
- package/dist/tools/delegate-tool-calls.tool.js.map +1 -1
- package/dist/tools/update-tool-result.tool.d.ts +4 -9
- package/dist/tools/update-tool-result.tool.d.ts.map +1 -1
- package/dist/tools/update-tool-result.tool.js +18 -53
- package/dist/tools/update-tool-result.tool.js.map +1 -1
- package/dist/types/claude.types.d.ts +19 -0
- package/dist/types/claude.types.d.ts.map +1 -1
- package/dist/types/claude.types.js.map +1 -1
- package/package.json +3 -3
- package/src/claude.module.ts +1 -12
- package/src/documents/claude-message-document.ts +6 -12
- package/src/documents/claude-message-document.yaml +2 -2
- package/src/index.ts +1 -0
- package/src/tools/claude-generate-document.tool.ts +11 -48
- package/src/tools/claude-generate-object.tool.ts +13 -29
- package/src/tools/claude-generate-text.tool.ts +8 -65
- package/src/tools/delegate-tool-calls.tool.ts +53 -112
- package/src/tools/update-tool-result.tool.ts +21 -61
- 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
|
-
|
|
6
|
-
|
|
7
|
-
RunContext,
|
|
5
|
+
BaseTool,
|
|
6
|
+
DocumentClass,
|
|
8
7
|
Tool,
|
|
9
|
-
ToolInterface,
|
|
10
8
|
ToolResult,
|
|
11
|
-
|
|
12
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
19
|
+
uiConfig: {
|
|
32
20
|
description: 'Generates text using the Anthropic Claude API',
|
|
33
21
|
},
|
|
22
|
+
schema: ClaudeGenerateTextSchema,
|
|
34
23
|
})
|
|
35
|
-
export class ClaudeGenerateText
|
|
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
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
RunContext,
|
|
5
|
+
BaseTool,
|
|
6
|
+
DocumentClass,
|
|
8
7
|
Tool,
|
|
9
|
-
|
|
8
|
+
ToolCallOptions,
|
|
10
9
|
ToolResult,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
WorkflowMetadataInterface,
|
|
10
|
+
getBlockTool,
|
|
11
|
+
getBlockTypeFromMetadata,
|
|
14
12
|
} from '@loopstack/common';
|
|
15
|
-
import {
|
|
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
|
|
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
|
-
|
|
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
|
|
40
|
+
export class DelegateToolCalls extends BaseTool {
|
|
47
41
|
private readonly logger = new Logger(DelegateToolCalls.name);
|
|
48
42
|
|
|
49
|
-
|
|
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: {
|
|
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(
|
|
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.
|
|
71
|
+
// 2. Build toolResults array — async tools signal via result.pending
|
|
80
72
|
let pendingCount = 0;
|
|
81
|
-
const toolResults:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
): Promise<ToolResult> {
|
|
180
|
-
return this.createDocument.execute(
|
|
127
|
+
toolResults: DelegateToolResultEntry[],
|
|
128
|
+
): Promise<void> {
|
|
129
|
+
await this.repository.save(
|
|
130
|
+
document,
|
|
181
131
|
{
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
27
|
+
export class UpdateToolResult extends BaseTool {
|
|
36
28
|
private readonly logger = new Logger(UpdateToolResult.name);
|
|
37
29
|
|
|
38
|
-
|
|
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()
|
|
70
|
-
const tool = this.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
79
|
+
await this.repository.save(
|
|
80
|
+
args.document,
|
|
108
81
|
{
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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';
|