@lobu/worker 3.0.8 → 3.0.12

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 (52) hide show
  1. package/dist/index.d.ts.map +1 -1
  2. package/dist/index.js +4 -6
  3. package/dist/index.js.map +1 -1
  4. package/dist/openclaw/session-context.d.ts.map +1 -1
  5. package/dist/openclaw/session-context.js +1 -1
  6. package/dist/openclaw/session-context.js.map +1 -1
  7. package/package.json +2 -2
  8. package/USAGE.md +0 -120
  9. package/docs/custom-base-image.md +0 -88
  10. package/scripts/worker-entrypoint.sh +0 -184
  11. package/src/__tests__/audio-provider-suggestions.test.ts +0 -198
  12. package/src/__tests__/embedded-just-bash-bootstrap.test.ts +0 -39
  13. package/src/__tests__/embedded-tools.test.ts +0 -558
  14. package/src/__tests__/instructions.test.ts +0 -59
  15. package/src/__tests__/memory-flush-runtime.test.ts +0 -138
  16. package/src/__tests__/memory-flush.test.ts +0 -64
  17. package/src/__tests__/model-resolver.test.ts +0 -156
  18. package/src/__tests__/processor.test.ts +0 -225
  19. package/src/__tests__/setup.ts +0 -109
  20. package/src/__tests__/sse-client.test.ts +0 -48
  21. package/src/__tests__/tool-policy.test.ts +0 -269
  22. package/src/__tests__/worker.test.ts +0 -89
  23. package/src/core/error-handler.ts +0 -70
  24. package/src/core/project-scanner.ts +0 -65
  25. package/src/core/types.ts +0 -125
  26. package/src/core/url-utils.ts +0 -9
  27. package/src/core/workspace.ts +0 -138
  28. package/src/embedded/just-bash-bootstrap.ts +0 -228
  29. package/src/gateway/gateway-integration.ts +0 -287
  30. package/src/gateway/message-batcher.ts +0 -128
  31. package/src/gateway/sse-client.ts +0 -955
  32. package/src/gateway/types.ts +0 -68
  33. package/src/index.ts +0 -146
  34. package/src/instructions/builder.ts +0 -80
  35. package/src/instructions/providers.ts +0 -27
  36. package/src/modules/lifecycle.ts +0 -92
  37. package/src/openclaw/custom-tools.ts +0 -290
  38. package/src/openclaw/instructions.ts +0 -38
  39. package/src/openclaw/model-resolver.ts +0 -150
  40. package/src/openclaw/plugin-loader.ts +0 -427
  41. package/src/openclaw/processor.ts +0 -216
  42. package/src/openclaw/session-context.ts +0 -277
  43. package/src/openclaw/tool-policy.ts +0 -212
  44. package/src/openclaw/tools.ts +0 -208
  45. package/src/openclaw/worker.ts +0 -1792
  46. package/src/server.ts +0 -329
  47. package/src/shared/audio-provider-suggestions.ts +0 -132
  48. package/src/shared/processor-utils.ts +0 -33
  49. package/src/shared/provider-auth-hints.ts +0 -64
  50. package/src/shared/tool-display-config.ts +0 -75
  51. package/src/shared/tool-implementations.ts +0 -768
  52. package/tsconfig.json +0 -21
@@ -1,68 +0,0 @@
1
- /**
2
- * Shared types for gateway communication
3
- */
4
-
5
- import type { AgentOptions, ThreadResponsePayload } from "@lobu/core";
6
-
7
- /**
8
- * Platform-specific metadata (e.g., Slack team_id, channel, thread_ts)
9
- */
10
- interface PlatformMetadata {
11
- team_id?: string;
12
- channel?: string;
13
- ts?: string;
14
- thread_ts?: string;
15
- files?: unknown[];
16
- traceId?: string; // Trace ID for end-to-end observability
17
- [key: string]: string | number | boolean | unknown[] | undefined;
18
- }
19
-
20
- /**
21
- * Job type for queue messages
22
- * - message: Standard agent message execution
23
- * - exec: Direct command execution in sandbox
24
- */
25
- export type JobType = "message" | "exec";
26
-
27
- /**
28
- * Message payload for agent execution
29
- */
30
- export interface MessagePayload {
31
- botId: string;
32
- userId: string;
33
- agentId: string;
34
- conversationId: string;
35
- platform: string;
36
- channelId: string;
37
- messageId: string;
38
- messageText: string;
39
- platformMetadata: PlatformMetadata;
40
- agentOptions: AgentOptions;
41
- jobId?: string; // Optional job ID from gateway
42
- teamId?: string; // Optional team ID (WhatsApp uses top-level, Slack uses platformMetadata)
43
-
44
- // Job type (default: "message")
45
- jobType?: JobType;
46
-
47
- // Exec-specific fields (only used when jobType === "exec")
48
- execId?: string; // Unique ID for exec job (for response routing)
49
- execCommand?: string; // Command to execute
50
- execCwd?: string; // Working directory for command
51
- execEnv?: Record<string, string>; // Additional environment variables
52
- execTimeout?: number; // Timeout in milliseconds
53
- }
54
-
55
- /**
56
- * Queued message with timestamp
57
- */
58
- export interface QueuedMessage {
59
- payload: MessagePayload;
60
- timestamp: number;
61
- }
62
-
63
- /**
64
- * Response data sent back to gateway
65
- */
66
- export type ResponseData = ThreadResponsePayload & {
67
- originalMessageId: string;
68
- };
package/src/index.ts DELETED
@@ -1,146 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- import {
4
- createLogger,
5
- initSentry,
6
- initTracing,
7
- moduleRegistry,
8
- } from "@lobu/core";
9
-
10
- const logger = createLogger("worker");
11
-
12
- import { setupWorkspaceEnv } from "./core/workspace";
13
- import { GatewayClient } from "./gateway/sse-client";
14
- import { startWorkerHttpServer, stopWorkerHttpServer } from "./server";
15
-
16
- /**
17
- * Main entry point for gateway-based persistent worker
18
- */
19
- async function main() {
20
- // Register global rejection/exception handlers early
21
- process.on("unhandledRejection", (reason) => {
22
- logger.error("Unhandled rejection:", reason);
23
- process.exit(1);
24
- });
25
-
26
- process.on("uncaughtException", (error) => {
27
- logger.error("Uncaught exception:", error);
28
- process.exit(1);
29
- });
30
-
31
- logger.info("Starting worker...");
32
-
33
- // Initialize Sentry for error tracking
34
- await initSentry();
35
-
36
- // Initialize OpenTelemetry tracing for distributed tracing
37
- // Worker traces are sent to Tempo via gateway proxy
38
- const tempoEndpoint = process.env.TEMPO_ENDPOINT;
39
- logger.debug(`TEMPO_ENDPOINT: ${tempoEndpoint}`);
40
- if (tempoEndpoint) {
41
- initTracing({
42
- serviceName: "lobu-worker",
43
- tempoEndpoint,
44
- });
45
- logger.info(`Tracing initialized: lobu-worker -> ${tempoEndpoint}`);
46
- }
47
-
48
- // Discover and register available modules
49
- await moduleRegistry.registerAvailableModules();
50
-
51
- // Initialize all registered modules
52
- await moduleRegistry.initAll();
53
- logger.info("✅ Modules initialized");
54
-
55
- logger.info("🔄 Starting in gateway mode (SSE/HTTP-based persistent worker)");
56
-
57
- // Get user ID from environment
58
- const userId = process.env.USER_ID;
59
-
60
- if (!userId) {
61
- logger.error(
62
- "❌ USER_ID environment variable is required for gateway mode"
63
- );
64
- process.exit(1);
65
- }
66
-
67
- try {
68
- // Get required environment variables
69
- const deploymentName = process.env.DEPLOYMENT_NAME;
70
- const dispatcherUrl = process.env.DISPATCHER_URL;
71
- const workerToken = process.env.WORKER_TOKEN;
72
-
73
- if (!deploymentName) {
74
- logger.error("❌ DEPLOYMENT_NAME environment variable is required");
75
- process.exit(1);
76
- }
77
- if (!dispatcherUrl) {
78
- logger.error("❌ DISPATCHER_URL environment variable is required");
79
- process.exit(1);
80
- }
81
- if (!workerToken) {
82
- logger.error("❌ WORKER_TOKEN environment variable is required");
83
- process.exit(1);
84
- }
85
-
86
- setupWorkspaceEnv(deploymentName);
87
-
88
- // Start HTTP server before connecting to gateway
89
- const httpPort = await startWorkerHttpServer();
90
- logger.info(`Worker HTTP server started on port ${httpPort}`);
91
-
92
- // Initialize gateway client directly
93
- logger.info(`🚀 Starting Gateway-based Persistent Worker`);
94
- logger.info(`- User ID: ${userId}`);
95
- logger.info(`- Deployment: ${deploymentName}`);
96
- logger.info(`- Dispatcher URL: ${dispatcherUrl}`);
97
-
98
- const gatewayClient = new GatewayClient(
99
- dispatcherUrl,
100
- workerToken,
101
- userId,
102
- deploymentName,
103
- httpPort
104
- );
105
-
106
- // Register signal handlers before async operations
107
- let isShuttingDown = false;
108
-
109
- process.on("SIGTERM", async () => {
110
- if (isShuttingDown) return;
111
- isShuttingDown = true;
112
- logger.info("Received SIGTERM, shutting down gateway worker...");
113
- await gatewayClient.stop();
114
- await stopWorkerHttpServer();
115
- process.exit(0);
116
- });
117
-
118
- process.on("SIGINT", async () => {
119
- if (isShuttingDown) return;
120
- isShuttingDown = true;
121
- logger.info("Received SIGINT, shutting down gateway worker...");
122
- await gatewayClient.stop();
123
- await stopWorkerHttpServer();
124
- process.exit(0);
125
- });
126
-
127
- logger.info("🔌 Connecting to dispatcher...");
128
- await gatewayClient.start();
129
- logger.info("✅ Gateway worker started successfully");
130
-
131
- // Keep process alive
132
- await new Promise(() => {
133
- // Keep process running indefinitely so we can listen messages from the queue
134
- }); // Wait forever
135
- } catch (error) {
136
- logger.error("❌ Gateway worker failed:", error);
137
- process.exit(1);
138
- }
139
- }
140
-
141
- export type { WorkerConfig } from "./core/types";
142
-
143
- main().catch((error) => {
144
- logger.error("Fatal error in main:", error);
145
- process.exit(1);
146
- });
@@ -1,80 +0,0 @@
1
- import {
2
- createLogger,
3
- type InstructionContext,
4
- type InstructionProvider,
5
- } from "@lobu/core";
6
-
7
- const logger = createLogger("instruction-generator");
8
-
9
- /**
10
- * Build custom instructions by collecting from multiple providers
11
- * @param providers - Array of instruction providers
12
- * @param context - Context information for instruction generation
13
- * @returns Complete instruction text
14
- */
15
- async function buildInstructions(
16
- providers: InstructionProvider[],
17
- context: InstructionContext
18
- ): Promise<string> {
19
- // Sort by priority (lower priority = earlier in output)
20
- const sortedProviders = [...providers].sort(
21
- (a, b) => a.priority - b.priority
22
- );
23
-
24
- logger.debug(
25
- `Building instructions with ${sortedProviders.length} providers`
26
- );
27
-
28
- const sections: string[] = [];
29
-
30
- // Collect instructions from all providers
31
- for (const provider of sortedProviders) {
32
- try {
33
- const instructions = await provider.getInstructions(context);
34
- if (instructions?.trim()) {
35
- sections.push(instructions.trim());
36
- logger.debug(
37
- `Provider ${provider.name} contributed ${instructions.length} characters`
38
- );
39
- }
40
- } catch (error) {
41
- logger.error(
42
- `Failed to get instructions from provider ${provider.name}:`,
43
- error
44
- );
45
- }
46
- }
47
-
48
- const finalInstructions = sections.join("\n\n");
49
- logger.info(
50
- `Built custom instructions: ${finalInstructions.length} characters from ${sections.length} providers`
51
- );
52
-
53
- return finalInstructions;
54
- }
55
-
56
- /**
57
- * Generate custom instructions using modular providers
58
- * Only generates worker-local instructions (core, projects)
59
- * Platform and MCP instructions are provided by the gateway
60
- */
61
- export async function generateCustomInstructions(
62
- providers: InstructionProvider[],
63
- context: InstructionContext
64
- ): Promise<string> {
65
- try {
66
- const instructions = await buildInstructions(providers, context);
67
-
68
- logger.info(
69
- `[WORKER-INSTRUCTIONS] Generated ${instructions.length} characters from ${providers.length} local providers`
70
- );
71
- logger.debug(`[WORKER-INSTRUCTIONS] \n${instructions}`);
72
-
73
- return instructions;
74
- } catch (error) {
75
- logger.error("Failed to generate worker instructions:", error);
76
- const fallback = `You are a helpful AI agent for user ${context.userId}.`;
77
- logger.warn(`[WORKER-INSTRUCTIONS] Using fallback: ${fallback}`);
78
- return fallback;
79
- }
80
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * Instruction providers for worker
3
- */
4
-
5
- import type { InstructionContext, InstructionProvider } from "@lobu/core";
6
-
7
- /**
8
- * Provides information about available projects in the workspace
9
- */
10
- export class ProjectsInstructionProvider implements InstructionProvider {
11
- name = "projects";
12
- priority = 30;
13
-
14
- getInstructions(context: InstructionContext): string {
15
- if (!context.availableProjects || context.availableProjects.length === 0) {
16
- return `**Available projects:**
17
- - none`;
18
- }
19
-
20
- const projectList = context.availableProjects
21
- .map((project: string) => ` - ${project}`)
22
- .join("\n");
23
-
24
- return `**Available projects:**
25
- ${projectList}`;
26
- }
27
- }
@@ -1,92 +0,0 @@
1
- import {
2
- createLogger,
3
- type ModuleSessionContext,
4
- moduleRegistry,
5
- type SessionContext,
6
- type WorkerModule,
7
- } from "@lobu/core";
8
-
9
- const logger = createLogger("worker");
10
-
11
- /**
12
- * Execute an operation on all worker modules with consistent error handling.
13
- * Errors in individual modules are logged but do not halt iteration.
14
- */
15
- async function executeForAllModules<T>(
16
- operation: (module: WorkerModule) => Promise<T>,
17
- operationName: string
18
- ): Promise<T[]> {
19
- const workerModules = moduleRegistry.getWorkerModules();
20
- const results: T[] = [];
21
- for (const module of workerModules) {
22
- try {
23
- results.push(await operation(module));
24
- } catch (error) {
25
- logger.error(
26
- `Failed to execute ${operationName} for module ${module.name}:`,
27
- error
28
- );
29
- }
30
- }
31
- return results;
32
- }
33
-
34
- export async function onSessionStart(
35
- context: SessionContext
36
- ): Promise<SessionContext> {
37
- // Convert to module session context
38
- const moduleContext: ModuleSessionContext = {
39
- userId: context.userId,
40
- conversationId: context.conversationId || "",
41
- systemPrompt: context.customInstructions || "",
42
- workspace: undefined,
43
- };
44
-
45
- let updatedContext = moduleContext;
46
-
47
- await executeForAllModules(async (module) => {
48
- updatedContext = await module.onSessionStart(updatedContext);
49
- }, "onSessionStart");
50
-
51
- // Merge back into original context, mapping systemPrompt back to customInstructions
52
- return {
53
- ...context,
54
- customInstructions:
55
- updatedContext.systemPrompt || context.customInstructions,
56
- };
57
- }
58
-
59
- /**
60
- * Configuration for module workspace initialization
61
- */
62
- interface ModuleWorkspaceConfig {
63
- workspaceDir: string;
64
- username: string;
65
- sessionKey: string;
66
- }
67
-
68
- export async function initModuleWorkspace(
69
- config: ModuleWorkspaceConfig
70
- ): Promise<void> {
71
- await executeForAllModules(
72
- (module) => module.initWorkspace(config),
73
- "initWorkspace"
74
- );
75
- }
76
-
77
- export async function collectModuleData(context: {
78
- workspaceDir: string;
79
- userId: string;
80
- conversationId: string;
81
- }): Promise<Record<string, unknown>> {
82
- const moduleData: Record<string, unknown> = {};
83
-
84
- await executeForAllModules(async (module) => {
85
- const data = await module.onBeforeResponse(context);
86
- if (data !== null) {
87
- moduleData[module.name] = data;
88
- }
89
- }, "onBeforeResponse");
90
-
91
- return moduleData;
92
- }
@@ -1,290 +0,0 @@
1
- import { getCustomToolDescription, type McpToolDef } from "@lobu/core";
2
- import type { AgentToolResult } from "@mariozechner/pi-agent-core";
3
- import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
4
- import type { Static } from "@sinclair/typebox";
5
- import { type TSchema, Type } from "@sinclair/typebox";
6
- import type { GatewayParams, TextResult } from "../shared/tool-implementations";
7
- import {
8
- askUserQuestion,
9
- callMcpTool,
10
- cancelReminder,
11
- generateAudio,
12
- generateImage,
13
- getChannelHistory,
14
- listReminders,
15
- scheduleReminder,
16
- uploadUserFile,
17
- } from "../shared/tool-implementations";
18
-
19
- type ToolResult = AgentToolResult<Record<string, unknown>>;
20
-
21
- /** Adapt shared TextResult to OpenClaw's ToolResult (adds details field) */
22
- function toToolResult(result: TextResult): ToolResult {
23
- return { content: result.content, details: {} };
24
- }
25
-
26
- /**
27
- * Create a ToolDefinition with proper type bridging between TypeBox schemas
28
- * and the shared tool implementation functions. Eliminates per-tool `as` casts
29
- * by casting once at the boundary.
30
- */
31
- function defineTool<T extends TSchema>(config: {
32
- name: string;
33
- description: string;
34
- parameters: T;
35
- run: (args: Static<T>) => Promise<TextResult>;
36
- }): ToolDefinition {
37
- return {
38
- name: config.name,
39
- label: config.name,
40
- description: config.description,
41
- parameters: config.parameters,
42
- execute: async (_toolCallId, args) =>
43
- toToolResult(await config.run(args as Static<T>)),
44
- };
45
- }
46
-
47
- export function createOpenClawCustomTools(params: {
48
- gatewayUrl: string;
49
- workerToken: string;
50
- channelId: string;
51
- conversationId: string;
52
- platform?: string;
53
- }): ToolDefinition[] {
54
- const gw: GatewayParams = {
55
- gatewayUrl: params.gatewayUrl,
56
- workerToken: params.workerToken,
57
- channelId: params.channelId,
58
- conversationId: params.conversationId,
59
- platform: params.platform || "slack",
60
- };
61
-
62
- const tools: ToolDefinition[] = [
63
- defineTool({
64
- name: "UploadUserFile",
65
- description: getCustomToolDescription("UploadUserFile"),
66
- parameters: Type.Object({
67
- file_path: Type.String({
68
- description:
69
- "Path to the file to show (absolute or relative to workspace)",
70
- }),
71
- description: Type.Optional(
72
- Type.String({
73
- description:
74
- "Optional description of what the file contains or shows",
75
- })
76
- ),
77
- }),
78
- run: (args) => uploadUserFile(gw, args),
79
- }),
80
-
81
- defineTool({
82
- name: "ScheduleReminder",
83
- description: getCustomToolDescription("ScheduleReminder"),
84
- parameters: Type.Object({
85
- task: Type.String({
86
- description: "Description of what you need to do when reminded",
87
- }),
88
- delayMinutes: Type.Optional(
89
- Type.Number({
90
- description:
91
- "Minutes from now to trigger (1-1440, max 24 hours). Use this OR cron, not both.",
92
- })
93
- ),
94
- cron: Type.Optional(
95
- Type.String({
96
- description:
97
- "Cron expression for recurring schedule (e.g., '*/30 * * * *' for every 30 min). Use this OR delayMinutes, not both.",
98
- })
99
- ),
100
- maxIterations: Type.Optional(
101
- Type.Number({
102
- description:
103
- "Maximum iterations for recurring schedules (default: 10, max: 100). Only used with cron.",
104
- })
105
- ),
106
- }),
107
- run: (args) => scheduleReminder(gw, args),
108
- }),
109
-
110
- defineTool({
111
- name: "CancelReminder",
112
- description: getCustomToolDescription("CancelReminder"),
113
- parameters: Type.Object({
114
- scheduleId: Type.String({
115
- description: "The schedule ID returned from ScheduleReminder",
116
- }),
117
- }),
118
- run: (args) => cancelReminder(gw, args),
119
- }),
120
-
121
- defineTool({
122
- name: "ListReminders",
123
- description: getCustomToolDescription("ListReminders"),
124
- parameters: Type.Object({}),
125
- run: () => listReminders(gw),
126
- }),
127
-
128
- defineTool({
129
- name: "GenerateImage",
130
- description: getCustomToolDescription("GenerateImage"),
131
- parameters: Type.Object({
132
- prompt: Type.String({
133
- description: "The image prompt to generate",
134
- }),
135
- size: Type.Optional(
136
- Type.Union(
137
- [
138
- Type.Literal("1024x1024"),
139
- Type.Literal("1024x1536"),
140
- Type.Literal("1536x1024"),
141
- Type.Literal("auto"),
142
- ],
143
- {
144
- description: "Output image size (default: 1024x1024)",
145
- }
146
- )
147
- ),
148
- quality: Type.Optional(
149
- Type.Union(
150
- [
151
- Type.Literal("low"),
152
- Type.Literal("medium"),
153
- Type.Literal("high"),
154
- Type.Literal("auto"),
155
- ],
156
- {
157
- description: "Image quality (default: auto)",
158
- }
159
- )
160
- ),
161
- background: Type.Optional(
162
- Type.Union(
163
- [
164
- Type.Literal("transparent"),
165
- Type.Literal("opaque"),
166
- Type.Literal("auto"),
167
- ],
168
- {
169
- description: "Background style (default: auto)",
170
- }
171
- )
172
- ),
173
- format: Type.Optional(
174
- Type.Union(
175
- [Type.Literal("png"), Type.Literal("jpeg"), Type.Literal("webp")],
176
- {
177
- description: "Output image format (default: png)",
178
- }
179
- )
180
- ),
181
- }),
182
- run: (args) => generateImage(gw, args),
183
- }),
184
-
185
- defineTool({
186
- name: "GenerateAudio",
187
- description: getCustomToolDescription("GenerateAudio"),
188
- parameters: Type.Object({
189
- text: Type.String({
190
- description: "The text to convert to speech (max 4096 characters)",
191
- }),
192
- voice: Type.Optional(
193
- Type.String({
194
- description:
195
- "Voice ID (provider-specific). OpenAI: alloy, echo, fable, onyx, nova, shimmer. Leave empty for default.",
196
- })
197
- ),
198
- speed: Type.Optional(
199
- Type.Number({
200
- description: "Speech speed (0.5-2.0, default 1.0).",
201
- })
202
- ),
203
- }),
204
- run: (args) => generateAudio(gw, args),
205
- }),
206
-
207
- defineTool({
208
- name: "GetChannelHistory",
209
- description: getCustomToolDescription("GetChannelHistory"),
210
- parameters: Type.Object({
211
- limit: Type.Optional(
212
- Type.Number({
213
- description: "Number of messages to fetch (default 50, max 100)",
214
- })
215
- ),
216
- before: Type.Optional(
217
- Type.String({
218
- description:
219
- "ISO timestamp cursor - fetch messages before this time (for pagination)",
220
- })
221
- ),
222
- }),
223
- run: (args) => getChannelHistory(gw, args),
224
- }),
225
-
226
- defineTool({
227
- name: "AskUserQuestion",
228
- description: getCustomToolDescription("AskUserQuestion"),
229
- parameters: Type.Object({
230
- question: Type.String({
231
- description: "The question to ask the user",
232
- }),
233
- options: Type.Array(Type.String(), {
234
- description: "Array of button labels for the user to choose from",
235
- }),
236
- }),
237
- run: (args) => askUserQuestion(gw, args),
238
- }),
239
- ];
240
-
241
- return tools;
242
- }
243
-
244
- /**
245
- * Convert MCP tool definitions from session context into first-class
246
- * OpenClaw ToolDefinition objects that call the MCP proxy directly.
247
- * Tools are dynamically discovered from each MCP server (e.g. owletto).
248
- */
249
- export function createMcpToolDefinitions(
250
- mcpTools: Record<string, McpToolDef[]>,
251
- gw: GatewayParams,
252
- mcpContext?: Record<string, string>
253
- ): ToolDefinition[] {
254
- const tools: ToolDefinition[] = [];
255
-
256
- for (const [mcpId, defs] of Object.entries(mcpTools)) {
257
- const contextPrefix = mcpContext?.[mcpId];
258
- for (const def of defs) {
259
- if (!def.name || typeof def.name !== "string" || !def.name.trim()) {
260
- continue;
261
- }
262
- const schema = def.inputSchema
263
- ? Type.Unsafe(def.inputSchema)
264
- : Type.Object({});
265
-
266
- const baseDescription = def.description || `MCP tool from ${mcpId}`;
267
- const description = contextPrefix
268
- ? `[${contextPrefix}] ${baseDescription}`
269
- : baseDescription;
270
-
271
- tools.push({
272
- name: def.name,
273
- label: `${mcpId}/${def.name}`,
274
- description,
275
- parameters: schema,
276
- execute: async (_toolCallId, args) =>
277
- toToolResult(
278
- await callMcpTool(
279
- gw,
280
- mcpId,
281
- def.name,
282
- (args || {}) as Record<string, unknown>
283
- )
284
- ),
285
- });
286
- }
287
- }
288
-
289
- return tools;
290
- }