@clinebot/agents 0.0.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/README.md +145 -0
- package/dist/agent-input.d.ts +2 -0
- package/dist/agent.d.ts +56 -0
- package/dist/extensions.d.ts +21 -0
- package/dist/hooks/engine.d.ts +42 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/lifecycle.d.ts +5 -0
- package/dist/hooks/node.d.ts +2 -0
- package/dist/hooks/subprocess-runner.d.ts +16 -0
- package/dist/hooks/subprocess.d.ts +268 -0
- package/dist/index.browser.d.ts +1 -0
- package/dist/index.browser.js +49 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +49 -0
- package/dist/index.node.d.ts +5 -0
- package/dist/index.node.js +49 -0
- package/dist/mcp/index.d.ts +4 -0
- package/dist/mcp/policies.d.ts +14 -0
- package/dist/mcp/tools.d.ts +9 -0
- package/dist/mcp/types.d.ts +35 -0
- package/dist/message-builder.d.ts +31 -0
- package/dist/prompts/cline.d.ts +1 -0
- package/dist/prompts/index.d.ts +1 -0
- package/dist/runtime/agent-runtime-bus.d.ts +13 -0
- package/dist/runtime/conversation-store.d.ts +16 -0
- package/dist/runtime/lifecycle-orchestrator.d.ts +28 -0
- package/dist/runtime/tool-orchestrator.d.ts +39 -0
- package/dist/runtime/turn-processor.d.ts +21 -0
- package/dist/teams/index.d.ts +3 -0
- package/dist/teams/multi-agent.d.ts +566 -0
- package/dist/teams/spawn-agent-tool.d.ts +85 -0
- package/dist/teams/team-tools.d.ts +51 -0
- package/dist/tools/ask-question.d.ts +12 -0
- package/dist/tools/create.d.ts +59 -0
- package/dist/tools/execution.d.ts +61 -0
- package/dist/tools/formatting.d.ts +20 -0
- package/dist/tools/index.d.ts +11 -0
- package/dist/tools/registry.d.ts +26 -0
- package/dist/tools/validation.d.ts +27 -0
- package/dist/types.d.ts +826 -0
- package/package.json +54 -0
- package/src/agent-input.ts +116 -0
- package/src/agent.test.ts +931 -0
- package/src/agent.ts +1050 -0
- package/src/example.test.ts +564 -0
- package/src/extensions.ts +337 -0
- package/src/hooks/engine.test.ts +163 -0
- package/src/hooks/engine.ts +537 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/lifecycle.ts +239 -0
- package/src/hooks/node.ts +18 -0
- package/src/hooks/subprocess-runner.ts +140 -0
- package/src/hooks/subprocess.test.ts +180 -0
- package/src/hooks/subprocess.ts +620 -0
- package/src/index.browser.ts +1 -0
- package/src/index.node.ts +21 -0
- package/src/index.ts +133 -0
- package/src/mcp/index.ts +17 -0
- package/src/mcp/policies.test.ts +51 -0
- package/src/mcp/policies.ts +53 -0
- package/src/mcp/tools.test.ts +76 -0
- package/src/mcp/tools.ts +60 -0
- package/src/mcp/types.ts +41 -0
- package/src/message-builder.test.ts +175 -0
- package/src/message-builder.ts +429 -0
- package/src/prompts/cline.ts +49 -0
- package/src/prompts/index.ts +1 -0
- package/src/runtime/agent-runtime-bus.ts +53 -0
- package/src/runtime/conversation-store.ts +61 -0
- package/src/runtime/lifecycle-orchestrator.ts +90 -0
- package/src/runtime/tool-orchestrator.ts +177 -0
- package/src/runtime/turn-processor.ts +250 -0
- package/src/streaming.test.ts +197 -0
- package/src/streaming.ts +307 -0
- package/src/teams/index.ts +63 -0
- package/src/teams/multi-agent.lifecycle.test.ts +48 -0
- package/src/teams/multi-agent.ts +1866 -0
- package/src/teams/spawn-agent-tool.test.ts +172 -0
- package/src/teams/spawn-agent-tool.ts +223 -0
- package/src/teams/team-tools.test.ts +448 -0
- package/src/teams/team-tools.ts +929 -0
- package/src/tools/ask-question.ts +78 -0
- package/src/tools/create.ts +104 -0
- package/src/tools/execution.ts +311 -0
- package/src/tools/formatting.ts +73 -0
- package/src/tools/index.ts +45 -0
- package/src/tools/registry.ts +52 -0
- package/src/tools/tools.test.ts +292 -0
- package/src/tools/validation.ts +73 -0
- package/src/types.ts +966 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @clinebot/agents
|
|
3
|
+
*
|
|
4
|
+
* Public API for building agentic loops.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// =============================================================================
|
|
8
|
+
// Core Agent
|
|
9
|
+
// =============================================================================
|
|
10
|
+
|
|
11
|
+
export { Agent, createAgent } from "./agent.js";
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Tooling (consumer-facing)
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
type AskQuestionExecutor,
|
|
19
|
+
type AskQuestionInput,
|
|
20
|
+
AskQuestionInputSchema,
|
|
21
|
+
type AskQuestionToolConfig,
|
|
22
|
+
createAskQuestionTool,
|
|
23
|
+
createTool,
|
|
24
|
+
toToolDefinition,
|
|
25
|
+
toToolDefinitions,
|
|
26
|
+
} from "./tools/index.js";
|
|
27
|
+
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// Hooks
|
|
30
|
+
// =============================================================================
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
type HookDispatchInput,
|
|
34
|
+
HookEngine,
|
|
35
|
+
type HookHandler,
|
|
36
|
+
} from "./hooks/index.js";
|
|
37
|
+
export type {
|
|
38
|
+
HookEventName,
|
|
39
|
+
HookEventPayload,
|
|
40
|
+
RunHookOptions,
|
|
41
|
+
RunHookResult,
|
|
42
|
+
RunSubprocessEventOptions,
|
|
43
|
+
RunSubprocessEventResult,
|
|
44
|
+
SubprocessHookControl,
|
|
45
|
+
SubprocessHooksOptions,
|
|
46
|
+
} from "./hooks/node.js";
|
|
47
|
+
export {
|
|
48
|
+
createSubprocessHooks,
|
|
49
|
+
HookEventNameSchema,
|
|
50
|
+
HookEventPayloadSchema,
|
|
51
|
+
parseHookEventPayload,
|
|
52
|
+
runHook,
|
|
53
|
+
runSubprocessEvent,
|
|
54
|
+
} from "./hooks/node.js";
|
|
55
|
+
|
|
56
|
+
// =============================================================================
|
|
57
|
+
// Prompts and formatting
|
|
58
|
+
// =============================================================================
|
|
59
|
+
|
|
60
|
+
export { formatFileContentBlock } from "@clinebot/shared";
|
|
61
|
+
export { getClineDefaultSystemPrompt } from "./prompts/index.js";
|
|
62
|
+
|
|
63
|
+
// =============================================================================
|
|
64
|
+
// Teams and spawn support
|
|
65
|
+
// =============================================================================
|
|
66
|
+
|
|
67
|
+
export {
|
|
68
|
+
AgentTeamsRuntime,
|
|
69
|
+
type AgentTeamsRuntimeOptions,
|
|
70
|
+
type BootstrapAgentTeamsOptions,
|
|
71
|
+
type BootstrapAgentTeamsResult,
|
|
72
|
+
bootstrapAgentTeams,
|
|
73
|
+
type CreateAgentTeamsToolsOptions,
|
|
74
|
+
createAgentTeamsTools,
|
|
75
|
+
createSpawnAgentTool,
|
|
76
|
+
type SubAgentEndContext,
|
|
77
|
+
type SubAgentStartContext,
|
|
78
|
+
type TeamEvent,
|
|
79
|
+
type TeammateLifecycleSpec,
|
|
80
|
+
type TeamOutcome,
|
|
81
|
+
type TeamOutcomeFragment,
|
|
82
|
+
type TeamRunRecord,
|
|
83
|
+
type TeamRunStatus,
|
|
84
|
+
type TeamRuntimeState,
|
|
85
|
+
type TeamTeammateRuntimeConfig,
|
|
86
|
+
type TeamTeammateSpec,
|
|
87
|
+
} from "./teams/index.js";
|
|
88
|
+
|
|
89
|
+
// =============================================================================
|
|
90
|
+
// MCP bridge
|
|
91
|
+
// =============================================================================
|
|
92
|
+
|
|
93
|
+
export {
|
|
94
|
+
type CreateDisabledMcpToolPoliciesOptions,
|
|
95
|
+
type CreateDisabledMcpToolPolicyOptions,
|
|
96
|
+
type CreateMcpToolsOptions,
|
|
97
|
+
createDisabledMcpToolPolicies,
|
|
98
|
+
createDisabledMcpToolPolicy,
|
|
99
|
+
createMcpTools,
|
|
100
|
+
type McpToolCallRequest,
|
|
101
|
+
type McpToolCallResult,
|
|
102
|
+
type McpToolDescriptor,
|
|
103
|
+
type McpToolNameTransform,
|
|
104
|
+
type McpToolProvider,
|
|
105
|
+
} from "./mcp/index.js";
|
|
106
|
+
|
|
107
|
+
// =============================================================================
|
|
108
|
+
// Public types
|
|
109
|
+
// =============================================================================
|
|
110
|
+
|
|
111
|
+
export {
|
|
112
|
+
type AgentConfig,
|
|
113
|
+
AgentConfigSchema,
|
|
114
|
+
type AgentEvent,
|
|
115
|
+
type AgentHooks,
|
|
116
|
+
type AgentResult,
|
|
117
|
+
AgentResultSchema,
|
|
118
|
+
type AgentUsage,
|
|
119
|
+
AgentUsageSchema,
|
|
120
|
+
type BasicLogger,
|
|
121
|
+
type ContentBlock,
|
|
122
|
+
type HookErrorMode,
|
|
123
|
+
type Message,
|
|
124
|
+
type ModelInfo,
|
|
125
|
+
type Tool,
|
|
126
|
+
type ToolApprovalRequest,
|
|
127
|
+
type ToolApprovalResult,
|
|
128
|
+
type ToolCallRecord,
|
|
129
|
+
ToolCallRecordSchema,
|
|
130
|
+
type ToolContext,
|
|
131
|
+
ToolContextSchema,
|
|
132
|
+
type ToolPolicy,
|
|
133
|
+
} from "./types.js";
|
package/src/mcp/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
CreateDisabledMcpToolPoliciesOptions,
|
|
3
|
+
CreateDisabledMcpToolPolicyOptions,
|
|
4
|
+
} from "./policies.js";
|
|
5
|
+
export {
|
|
6
|
+
createDisabledMcpToolPolicies,
|
|
7
|
+
createDisabledMcpToolPolicy,
|
|
8
|
+
} from "./policies.js";
|
|
9
|
+
export { createMcpTools } from "./tools.js";
|
|
10
|
+
export type {
|
|
11
|
+
CreateMcpToolsOptions,
|
|
12
|
+
McpToolCallRequest,
|
|
13
|
+
McpToolCallResult,
|
|
14
|
+
McpToolDescriptor,
|
|
15
|
+
McpToolNameTransform,
|
|
16
|
+
McpToolProvider,
|
|
17
|
+
} from "./types.js";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
createDisabledMcpToolPolicies,
|
|
4
|
+
createDisabledMcpToolPolicy,
|
|
5
|
+
} from "./policies.js";
|
|
6
|
+
|
|
7
|
+
describe("mcp policy helpers", () => {
|
|
8
|
+
it("creates a disabled policy for a single MCP tool", () => {
|
|
9
|
+
expect(
|
|
10
|
+
createDisabledMcpToolPolicy({
|
|
11
|
+
serverName: "docs",
|
|
12
|
+
toolName: "search",
|
|
13
|
+
}),
|
|
14
|
+
).toEqual({
|
|
15
|
+
docs__search: {
|
|
16
|
+
enabled: false,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("creates disabled policies for multiple MCP tools", () => {
|
|
22
|
+
expect(
|
|
23
|
+
createDisabledMcpToolPolicies({
|
|
24
|
+
serverName: "docs",
|
|
25
|
+
toolNames: ["search", "read"],
|
|
26
|
+
}),
|
|
27
|
+
).toEqual({
|
|
28
|
+
docs__search: {
|
|
29
|
+
enabled: false,
|
|
30
|
+
},
|
|
31
|
+
docs__read: {
|
|
32
|
+
enabled: false,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("respects custom MCP tool name transforms", () => {
|
|
38
|
+
expect(
|
|
39
|
+
createDisabledMcpToolPolicy({
|
|
40
|
+
serverName: "docs",
|
|
41
|
+
toolName: "search",
|
|
42
|
+
nameTransform: ({ serverName, toolName }) =>
|
|
43
|
+
`mcp:${serverName}:${toolName}`,
|
|
44
|
+
}),
|
|
45
|
+
).toEqual({
|
|
46
|
+
"mcp:docs:search": {
|
|
47
|
+
enabled: false,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ToolPolicy } from "../types.js";
|
|
2
|
+
import type { McpToolNameTransform } from "./types.js";
|
|
3
|
+
|
|
4
|
+
function defaultMcpToolNameTransform(input: {
|
|
5
|
+
serverName: string;
|
|
6
|
+
toolName: string;
|
|
7
|
+
}): string {
|
|
8
|
+
return `${input.serverName}__${input.toolName}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface CreateDisabledMcpToolPolicyOptions {
|
|
12
|
+
serverName: string;
|
|
13
|
+
toolName: string;
|
|
14
|
+
nameTransform?: McpToolNameTransform;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface CreateDisabledMcpToolPoliciesOptions {
|
|
18
|
+
serverName: string;
|
|
19
|
+
toolNames: readonly string[];
|
|
20
|
+
nameTransform?: McpToolNameTransform;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function createDisabledMcpToolPolicy(
|
|
24
|
+
options: CreateDisabledMcpToolPolicyOptions,
|
|
25
|
+
): Record<string, ToolPolicy> {
|
|
26
|
+
const nameTransform = options.nameTransform ?? defaultMcpToolNameTransform;
|
|
27
|
+
const name = nameTransform({
|
|
28
|
+
serverName: options.serverName,
|
|
29
|
+
toolName: options.toolName,
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
[name]: {
|
|
33
|
+
enabled: false,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function createDisabledMcpToolPolicies(
|
|
39
|
+
options: CreateDisabledMcpToolPoliciesOptions,
|
|
40
|
+
): Record<string, ToolPolicy> {
|
|
41
|
+
const policies: Record<string, ToolPolicy> = {};
|
|
42
|
+
for (const toolName of options.toolNames) {
|
|
43
|
+
Object.assign(
|
|
44
|
+
policies,
|
|
45
|
+
createDisabledMcpToolPolicy({
|
|
46
|
+
serverName: options.serverName,
|
|
47
|
+
toolName,
|
|
48
|
+
nameTransform: options.nameTransform,
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return policies;
|
|
53
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { createMcpTools } from "./tools.js";
|
|
3
|
+
import type { McpToolDescriptor, McpToolProvider } from "./types.js";
|
|
4
|
+
|
|
5
|
+
describe("createMcpTools", () => {
|
|
6
|
+
it("converts MCP tools into agent tools and delegates execution", async () => {
|
|
7
|
+
const descriptors: readonly McpToolDescriptor[] = [
|
|
8
|
+
{
|
|
9
|
+
name: "search_docs",
|
|
10
|
+
description: "Search documentation",
|
|
11
|
+
inputSchema: {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: { query: { type: "string" } },
|
|
14
|
+
required: ["query"],
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
const provider: McpToolProvider = {
|
|
19
|
+
listTools: vi.fn(async () => descriptors),
|
|
20
|
+
callTool: vi.fn(async (request) => ({ ok: true, request })),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const tools = await createMcpTools({
|
|
24
|
+
serverName: "docs",
|
|
25
|
+
provider,
|
|
26
|
+
timeoutMs: 12000,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(tools).toHaveLength(1);
|
|
30
|
+
expect(tools[0].name).toBe("docs__search_docs");
|
|
31
|
+
expect(tools[0].description).toBe("Search documentation");
|
|
32
|
+
expect(tools[0].inputSchema.required).toEqual(["query"]);
|
|
33
|
+
expect(tools[0].timeoutMs).toBe(12000);
|
|
34
|
+
|
|
35
|
+
const result = await tools[0].execute(
|
|
36
|
+
{ query: "mcp auth" },
|
|
37
|
+
{
|
|
38
|
+
agentId: "agent-1",
|
|
39
|
+
conversationId: "conv-1",
|
|
40
|
+
iteration: 1,
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
expect(result).toMatchObject({ ok: true });
|
|
45
|
+
expect(provider.callTool).toHaveBeenCalledWith(
|
|
46
|
+
expect.objectContaining({
|
|
47
|
+
serverName: "docs",
|
|
48
|
+
toolName: "search_docs",
|
|
49
|
+
arguments: { query: "mcp auth" },
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("supports custom MCP tool name transforms", async () => {
|
|
55
|
+
const provider: McpToolProvider = {
|
|
56
|
+
listTools: async () => [
|
|
57
|
+
{
|
|
58
|
+
name: "list_files",
|
|
59
|
+
inputSchema: {
|
|
60
|
+
type: "object",
|
|
61
|
+
properties: {},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
callTool: async () => ({ ok: true }),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const tools = await createMcpTools({
|
|
69
|
+
serverName: "workspace",
|
|
70
|
+
provider,
|
|
71
|
+
nameTransform: ({ serverName, toolName }) => `${toolName}@${serverName}`,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(tools[0].name).toBe("list_files@workspace");
|
|
75
|
+
});
|
|
76
|
+
});
|
package/src/mcp/tools.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createTool } from "../tools/create.js";
|
|
2
|
+
import type { Tool } from "../types.js";
|
|
3
|
+
import type { CreateMcpToolsOptions, McpToolDescriptor } from "./types.js";
|
|
4
|
+
|
|
5
|
+
function defaultMcpToolNameTransform(input: {
|
|
6
|
+
serverName: string;
|
|
7
|
+
toolName: string;
|
|
8
|
+
}): string {
|
|
9
|
+
return `${input.serverName}__${input.toolName}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function defaultMcpDescription(
|
|
13
|
+
serverName: string,
|
|
14
|
+
tool: McpToolDescriptor,
|
|
15
|
+
): string {
|
|
16
|
+
const base = tool.description?.trim();
|
|
17
|
+
if (base) {
|
|
18
|
+
return base;
|
|
19
|
+
}
|
|
20
|
+
return `Execute MCP tool "${tool.name}" from server "${serverName}".`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Convert tools exposed by an MCP server into regular Agent tools.
|
|
25
|
+
*
|
|
26
|
+
* The adapter is intentionally thin: the provider remains the source of truth
|
|
27
|
+
* for connectivity, authorization, caching, and execution behavior.
|
|
28
|
+
*/
|
|
29
|
+
export async function createMcpTools(
|
|
30
|
+
options: CreateMcpToolsOptions,
|
|
31
|
+
): Promise<Tool[]> {
|
|
32
|
+
const descriptors = await options.provider.listTools(options.serverName);
|
|
33
|
+
const nameTransform = options.nameTransform ?? defaultMcpToolNameTransform;
|
|
34
|
+
|
|
35
|
+
return descriptors.map((descriptor) => {
|
|
36
|
+
const agentToolName = nameTransform({
|
|
37
|
+
serverName: options.serverName,
|
|
38
|
+
toolName: descriptor.name,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return createTool({
|
|
42
|
+
name: agentToolName,
|
|
43
|
+
description: defaultMcpDescription(options.serverName, descriptor),
|
|
44
|
+
inputSchema: descriptor.inputSchema,
|
|
45
|
+
timeoutMs: options.timeoutMs,
|
|
46
|
+
retryable: options.retryable,
|
|
47
|
+
maxRetries: options.maxRetries,
|
|
48
|
+
execute: async (input: unknown, context) =>
|
|
49
|
+
options.provider.callTool({
|
|
50
|
+
serverName: options.serverName,
|
|
51
|
+
toolName: descriptor.name,
|
|
52
|
+
arguments:
|
|
53
|
+
input && typeof input === "object" && !Array.isArray(input)
|
|
54
|
+
? (input as Record<string, unknown>)
|
|
55
|
+
: undefined,
|
|
56
|
+
context,
|
|
57
|
+
}),
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
package/src/mcp/types.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ToolContext } from "../types.js";
|
|
2
|
+
|
|
3
|
+
export interface McpToolDescriptor {
|
|
4
|
+
name: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
inputSchema: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface McpToolCallRequest {
|
|
10
|
+
serverName: string;
|
|
11
|
+
toolName: string;
|
|
12
|
+
arguments?: Record<string, unknown>;
|
|
13
|
+
context?: ToolContext;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type McpToolCallResult = unknown;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Minimal MCP capability contract required by the agent package.
|
|
20
|
+
*
|
|
21
|
+
* Implementations can be local, remote, cached, persistent, or fully managed by
|
|
22
|
+
* another package (for example, @clinebot/core).
|
|
23
|
+
*/
|
|
24
|
+
export interface McpToolProvider {
|
|
25
|
+
listTools(serverName: string): Promise<readonly McpToolDescriptor[]>;
|
|
26
|
+
callTool(request: McpToolCallRequest): Promise<McpToolCallResult>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type McpToolNameTransform = (input: {
|
|
30
|
+
serverName: string;
|
|
31
|
+
toolName: string;
|
|
32
|
+
}) => string;
|
|
33
|
+
|
|
34
|
+
export interface CreateMcpToolsOptions {
|
|
35
|
+
serverName: string;
|
|
36
|
+
provider: McpToolProvider;
|
|
37
|
+
nameTransform?: McpToolNameTransform;
|
|
38
|
+
timeoutMs?: number;
|
|
39
|
+
retryable?: boolean;
|
|
40
|
+
maxRetries?: number;
|
|
41
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { MessageBuilder } from "./message-builder.js";
|
|
3
|
+
|
|
4
|
+
describe("MessageBuilder", () => {
|
|
5
|
+
it("keeps cached indexes consistent across append and reset flows", () => {
|
|
6
|
+
const builder = new MessageBuilder();
|
|
7
|
+
const firstReadUse = {
|
|
8
|
+
role: "assistant" as const,
|
|
9
|
+
content: [
|
|
10
|
+
{
|
|
11
|
+
type: "tool_use" as const,
|
|
12
|
+
id: "call_1",
|
|
13
|
+
name: "read",
|
|
14
|
+
input: { path: "src/app.ts" },
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
const firstReadResult = {
|
|
19
|
+
role: "user" as const,
|
|
20
|
+
content: [
|
|
21
|
+
{
|
|
22
|
+
type: "tool_result" as const,
|
|
23
|
+
tool_use_id: "call_1",
|
|
24
|
+
content: '[{"path":"src/app.ts","content":"export const v = 1;"}]',
|
|
25
|
+
is_error: false,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
const secondReadUse = {
|
|
30
|
+
role: "assistant" as const,
|
|
31
|
+
content: [
|
|
32
|
+
{
|
|
33
|
+
type: "tool_use" as const,
|
|
34
|
+
id: "call_2",
|
|
35
|
+
name: "read",
|
|
36
|
+
input: { path: "src/app.ts" },
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
const secondReadResult = {
|
|
41
|
+
role: "user" as const,
|
|
42
|
+
content: [
|
|
43
|
+
{
|
|
44
|
+
type: "tool_result" as const,
|
|
45
|
+
tool_use_id: "call_2",
|
|
46
|
+
content: '[{"path":"src/app.ts","content":"export const v = 2;"}]',
|
|
47
|
+
is_error: false,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const initial = builder.buildForApi([firstReadUse, firstReadResult]);
|
|
53
|
+
expect(initial[1]?.content).toEqual(firstReadResult.content);
|
|
54
|
+
|
|
55
|
+
const appended = builder.buildForApi([
|
|
56
|
+
firstReadUse,
|
|
57
|
+
firstReadResult,
|
|
58
|
+
secondReadUse,
|
|
59
|
+
secondReadResult,
|
|
60
|
+
]);
|
|
61
|
+
const firstContent = (
|
|
62
|
+
appended[1] as { content: Array<{ content: string }> }
|
|
63
|
+
).content[0]?.content;
|
|
64
|
+
expect(firstContent).toContain("[outdated - see the latest file content]");
|
|
65
|
+
|
|
66
|
+
const reset = builder.buildForApi([secondReadUse, secondReadResult]);
|
|
67
|
+
expect(reset[1]?.content).toEqual(secondReadResult.content);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("truncates long file blocks in user messages", () => {
|
|
71
|
+
const builder = new MessageBuilder();
|
|
72
|
+
const longFileContent = "a".repeat(120_500);
|
|
73
|
+
const messages = [
|
|
74
|
+
{
|
|
75
|
+
role: "user" as const,
|
|
76
|
+
content: [
|
|
77
|
+
{
|
|
78
|
+
type: "text" as const,
|
|
79
|
+
text: "Please review this file.",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
type: "file" as const,
|
|
83
|
+
path: "src/big.ts",
|
|
84
|
+
content: longFileContent,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const built = builder.buildForApi(messages);
|
|
91
|
+
const fileBlock = (
|
|
92
|
+
built[0] as { content: Array<{ type: string; content?: string }> }
|
|
93
|
+
).content.find((block) => block.type === "file");
|
|
94
|
+
|
|
95
|
+
expect(fileBlock?.content).toContain("...[truncated 20500 chars]...");
|
|
96
|
+
expect(fileBlock?.content).not.toBe(longFileContent);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("replaces outdated file blocks in read tool results", () => {
|
|
100
|
+
const builder = new MessageBuilder();
|
|
101
|
+
const firstReadUse = {
|
|
102
|
+
role: "assistant" as const,
|
|
103
|
+
content: [
|
|
104
|
+
{
|
|
105
|
+
type: "tool_use" as const,
|
|
106
|
+
id: "call_1",
|
|
107
|
+
name: "read",
|
|
108
|
+
input: { path: "src/app.ts" },
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
};
|
|
112
|
+
const firstReadResult = {
|
|
113
|
+
role: "user" as const,
|
|
114
|
+
content: [
|
|
115
|
+
{
|
|
116
|
+
type: "tool_result" as const,
|
|
117
|
+
tool_use_id: "call_1",
|
|
118
|
+
content: [
|
|
119
|
+
{
|
|
120
|
+
type: "file" as const,
|
|
121
|
+
path: "src/app.ts",
|
|
122
|
+
content: "export const v = 1;",
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
is_error: false,
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
};
|
|
129
|
+
const secondReadUse = {
|
|
130
|
+
role: "assistant" as const,
|
|
131
|
+
content: [
|
|
132
|
+
{
|
|
133
|
+
type: "tool_use" as const,
|
|
134
|
+
id: "call_2",
|
|
135
|
+
name: "read",
|
|
136
|
+
input: { path: "src/app.ts" },
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
};
|
|
140
|
+
const secondReadResult = {
|
|
141
|
+
role: "user" as const,
|
|
142
|
+
content: [
|
|
143
|
+
{
|
|
144
|
+
type: "tool_result" as const,
|
|
145
|
+
tool_use_id: "call_2",
|
|
146
|
+
content: [
|
|
147
|
+
{
|
|
148
|
+
type: "file" as const,
|
|
149
|
+
path: "src/app.ts",
|
|
150
|
+
content: "export const v = 2;",
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
is_error: false,
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const built = builder.buildForApi([
|
|
159
|
+
firstReadUse,
|
|
160
|
+
firstReadResult,
|
|
161
|
+
secondReadUse,
|
|
162
|
+
secondReadResult,
|
|
163
|
+
]);
|
|
164
|
+
const firstToolResult = (
|
|
165
|
+
built[1] as { content: Array<{ content: unknown }> }
|
|
166
|
+
).content[0] as {
|
|
167
|
+
content: Array<{ type: string; content?: string }>;
|
|
168
|
+
};
|
|
169
|
+
const firstFile = firstToolResult.content.find(
|
|
170
|
+
(entry) => entry.type === "file",
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
expect(firstFile?.content).toBe("[outdated - see the latest file content]");
|
|
174
|
+
});
|
|
175
|
+
});
|