@toolplex/ai-engine 0.1.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 (88) hide show
  1. package/LICENSE +98 -0
  2. package/README.md +292 -0
  3. package/dist/adapters/index.d.ts +9 -0
  4. package/dist/adapters/index.d.ts.map +1 -0
  5. package/dist/adapters/index.js +9 -0
  6. package/dist/adapters/index.js.map +1 -0
  7. package/dist/adapters/types.d.ts +137 -0
  8. package/dist/adapters/types.d.ts.map +1 -0
  9. package/dist/adapters/types.js +14 -0
  10. package/dist/adapters/types.js.map +1 -0
  11. package/dist/core/ChatEngine.d.ts +47 -0
  12. package/dist/core/ChatEngine.d.ts.map +1 -0
  13. package/dist/core/ChatEngine.js +355 -0
  14. package/dist/core/ChatEngine.js.map +1 -0
  15. package/dist/core/ToolBuilder.d.ts +25 -0
  16. package/dist/core/ToolBuilder.d.ts.map +1 -0
  17. package/dist/core/ToolBuilder.js +215 -0
  18. package/dist/core/ToolBuilder.js.map +1 -0
  19. package/dist/core/index.d.ts +6 -0
  20. package/dist/core/index.d.ts.map +1 -0
  21. package/dist/core/index.js +6 -0
  22. package/dist/core/index.js.map +1 -0
  23. package/dist/index.d.ts +41 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +49 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/mcp/DefaultStdioTransportFactory.d.ts +27 -0
  28. package/dist/mcp/DefaultStdioTransportFactory.d.ts.map +1 -0
  29. package/dist/mcp/DefaultStdioTransportFactory.js +60 -0
  30. package/dist/mcp/DefaultStdioTransportFactory.js.map +1 -0
  31. package/dist/mcp/MCPClient.d.ts +60 -0
  32. package/dist/mcp/MCPClient.d.ts.map +1 -0
  33. package/dist/mcp/MCPClient.js +164 -0
  34. package/dist/mcp/MCPClient.js.map +1 -0
  35. package/dist/mcp/index.d.ts +10 -0
  36. package/dist/mcp/index.d.ts.map +1 -0
  37. package/dist/mcp/index.js +11 -0
  38. package/dist/mcp/index.js.map +1 -0
  39. package/dist/mcp/paths.d.ts +16 -0
  40. package/dist/mcp/paths.d.ts.map +1 -0
  41. package/dist/mcp/paths.js +58 -0
  42. package/dist/mcp/paths.js.map +1 -0
  43. package/dist/mcp/types.d.ts +85 -0
  44. package/dist/mcp/types.d.ts.map +1 -0
  45. package/dist/mcp/types.js +7 -0
  46. package/dist/mcp/types.js.map +1 -0
  47. package/dist/providers/index.d.ts +40 -0
  48. package/dist/providers/index.d.ts.map +1 -0
  49. package/dist/providers/index.js +148 -0
  50. package/dist/providers/index.js.map +1 -0
  51. package/dist/providers/toolplex.d.ts +43 -0
  52. package/dist/providers/toolplex.d.ts.map +1 -0
  53. package/dist/providers/toolplex.js +168 -0
  54. package/dist/providers/toolplex.js.map +1 -0
  55. package/dist/types/index.d.ts +218 -0
  56. package/dist/types/index.d.ts.map +1 -0
  57. package/dist/types/index.js +8 -0
  58. package/dist/types/index.js.map +1 -0
  59. package/dist/utils/index.d.ts +8 -0
  60. package/dist/utils/index.d.ts.map +1 -0
  61. package/dist/utils/index.js +8 -0
  62. package/dist/utils/index.js.map +1 -0
  63. package/dist/utils/models.d.ts +30 -0
  64. package/dist/utils/models.d.ts.map +1 -0
  65. package/dist/utils/models.js +52 -0
  66. package/dist/utils/models.js.map +1 -0
  67. package/dist/utils/schema.d.ts +74 -0
  68. package/dist/utils/schema.d.ts.map +1 -0
  69. package/dist/utils/schema.js +253 -0
  70. package/dist/utils/schema.js.map +1 -0
  71. package/package.json +70 -0
  72. package/src/adapters/index.ts +9 -0
  73. package/src/adapters/types.ts +241 -0
  74. package/src/core/ChatEngine.ts +464 -0
  75. package/src/core/ToolBuilder.ts +323 -0
  76. package/src/core/index.ts +6 -0
  77. package/src/index.ts +86 -0
  78. package/src/mcp/DefaultStdioTransportFactory.ts +71 -0
  79. package/src/mcp/MCPClient.ts +209 -0
  80. package/src/mcp/index.ts +24 -0
  81. package/src/mcp/paths.ts +91 -0
  82. package/src/mcp/types.ts +93 -0
  83. package/src/providers/index.ts +177 -0
  84. package/src/providers/toolplex.ts +217 -0
  85. package/src/types/index.ts +290 -0
  86. package/src/utils/index.ts +8 -0
  87. package/src/utils/models.ts +59 -0
  88. package/src/utils/schema.ts +307 -0
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @toolplex/ai-engine - MCP Module
3
+ *
4
+ * MCP (Model Context Protocol) client for ToolPlex.
5
+ */
6
+
7
+ export { MCPClient } from "./MCPClient.js";
8
+ export type {
9
+ MCPSession,
10
+ MCPResult,
11
+ MCPTool,
12
+ MCPToolResult,
13
+ TransportFactory,
14
+ MCPClientConfig,
15
+ } from "./types.js";
16
+
17
+ // Path utilities
18
+ export { getToolplexClientPath } from "./paths.js";
19
+
20
+ // Default transport factory (uses system Node.js)
21
+ export {
22
+ DefaultStdioTransportFactory,
23
+ defaultStdioTransportFactory,
24
+ } from "./DefaultStdioTransportFactory.js";
@@ -0,0 +1,91 @@
1
+ /**
2
+ * @toolplex/ai-engine - MCP Paths
3
+ *
4
+ * Utilities for locating @toolplex/client MCP server.
5
+ */
6
+
7
+ import path from "path";
8
+ import fs from "fs";
9
+ import { fileURLToPath } from "url";
10
+ import { createRequire } from "module";
11
+
12
+ // Create __dirname equivalent for ESM
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+
16
+ // Create require function for ESM (for require.resolve)
17
+ const require = createRequire(import.meta.url);
18
+
19
+ /**
20
+ * Get the path to @toolplex/client's MCP server entry point.
21
+ *
22
+ * This resolves the path regardless of where npm installs the package
23
+ * (hoisted or nested in node_modules).
24
+ *
25
+ * @returns Path to the MCP server index.js
26
+ * @throws Error if @toolplex/client cannot be found
27
+ */
28
+ export function getToolplexClientPath(): string {
29
+ // Try to resolve using require.resolve
30
+ try {
31
+ const clientPackageJson = require.resolve("@toolplex/client/package.json");
32
+ const clientDir = path.dirname(clientPackageJson);
33
+ const mcpServerPath = path.join(
34
+ clientDir,
35
+ "dist",
36
+ "mcp-server",
37
+ "index.js",
38
+ );
39
+
40
+ if (fs.existsSync(mcpServerPath)) {
41
+ return mcpServerPath;
42
+ }
43
+ } catch {
44
+ // require.resolve failed, try fallback paths
45
+ }
46
+
47
+ // Fallback: try common paths
48
+ const fallbackPaths = [
49
+ // From process.cwd() (development)
50
+ path.resolve(
51
+ process.cwd(),
52
+ "node_modules/@toolplex/client/dist/mcp-server/index.js",
53
+ ),
54
+ // From this module's location (when installed as dependency)
55
+ path.resolve(
56
+ __dirname,
57
+ "../../node_modules/@toolplex/client/dist/mcp-server/index.js",
58
+ ),
59
+ path.resolve(
60
+ __dirname,
61
+ "../../../node_modules/@toolplex/client/dist/mcp-server/index.js",
62
+ ),
63
+ path.resolve(
64
+ __dirname,
65
+ "../../../../node_modules/@toolplex/client/dist/mcp-server/index.js",
66
+ ),
67
+ ];
68
+
69
+ // Add Electron production path if available (process.resourcesPath is Electron-specific)
70
+ const electronProcess = process as typeof process & {
71
+ resourcesPath?: string;
72
+ };
73
+ if (electronProcess.resourcesPath) {
74
+ fallbackPaths.push(
75
+ path.resolve(
76
+ electronProcess.resourcesPath,
77
+ "app/node_modules/@toolplex/client/dist/mcp-server/index.js",
78
+ ),
79
+ );
80
+ }
81
+
82
+ for (const fallbackPath of fallbackPaths) {
83
+ if (fs.existsSync(fallbackPath)) {
84
+ return fallbackPath;
85
+ }
86
+ }
87
+
88
+ throw new Error(
89
+ "@toolplex/client not found. Make sure @toolplex/ai-engine is properly installed.",
90
+ );
91
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * @toolplex/ai-engine - MCP Types
3
+ *
4
+ * Type definitions for MCP (Model Context Protocol) integration.
5
+ */
6
+
7
+ /**
8
+ * MCP Session - holds the client connection
9
+ * Client type is 'any' to avoid version mismatches between ai-engine and consumers
10
+ */
11
+ export interface MCPSession {
12
+ client: any; // MCP Client instance
13
+ transport: any; // Transport type varies by platform
14
+ }
15
+
16
+ /**
17
+ * Result from MCP operations
18
+ */
19
+ export interface MCPResult {
20
+ success: boolean;
21
+ data?: any;
22
+ error?: string;
23
+ }
24
+
25
+ /**
26
+ * MCP Tool definition from server
27
+ */
28
+ export interface MCPTool {
29
+ name: string;
30
+ description?: string;
31
+ inputSchema?: any;
32
+ }
33
+
34
+ /**
35
+ * MCP Tool call result
36
+ */
37
+ export interface MCPToolResult {
38
+ content: Array<{
39
+ type: string;
40
+ text?: string;
41
+ data?: any;
42
+ [key: string]: any;
43
+ }>;
44
+ isError?: boolean;
45
+ [key: string]: any;
46
+ }
47
+
48
+ /**
49
+ * Transport factory interface - platforms implement this
50
+ */
51
+ export interface TransportFactory {
52
+ /**
53
+ * Create a transport and connect to the MCP server
54
+ * @param apiKey - ToolPlex API key
55
+ * @param sessionResumeHistory - Optional session history for resuming
56
+ * @returns Connected MCP session
57
+ */
58
+ createTransport(
59
+ apiKey: string,
60
+ sessionResumeHistory?: string,
61
+ ): Promise<MCPSession>;
62
+
63
+ /**
64
+ * Close a transport
65
+ */
66
+ closeTransport(session: MCPSession): Promise<void>;
67
+ }
68
+
69
+ /**
70
+ * MCP Client configuration
71
+ */
72
+ export interface MCPClientConfig {
73
+ transportFactory: TransportFactory;
74
+ logger?: {
75
+ debug: (message: string, meta?: any) => void;
76
+ info: (message: string, meta?: any) => void;
77
+ warn: (message: string, meta?: any) => void;
78
+ error: (message: string, meta?: any) => void;
79
+ };
80
+ /**
81
+ * Optional image handler for processing image results
82
+ */
83
+ imageHandler?: {
84
+ initialize(userId: string): Promise<void>;
85
+ processToolResult(
86
+ result: any,
87
+ ): Promise<{ content: any[]; savedImages?: any[] }>;
88
+ };
89
+ /**
90
+ * Optional function to get current user ID (for image handling)
91
+ */
92
+ getCurrentUserId?: () => string | null;
93
+ }
@@ -0,0 +1,177 @@
1
+ /**
2
+ * @toolplex/ai-engine - Provider Factory
3
+ *
4
+ * Central provider management for the AI engine.
5
+ * Handles instantiation of both built-in AI SDK providers and custom providers.
6
+ */
7
+
8
+ import { createOpenAI } from "@ai-sdk/openai";
9
+ import { createAnthropic } from "@ai-sdk/anthropic";
10
+ import { createGoogleGenerativeAI } from "@ai-sdk/google";
11
+ import { createOpenRouter } from "@openrouter/ai-sdk-provider";
12
+ import { createToolPlex } from "./toolplex.js";
13
+ import type { AIProvider, ProviderCredentials } from "../types/index.js";
14
+ import type { LoggerAdapter } from "../adapters/types.js";
15
+
16
+ export { toolplexUsageMap } from "./toolplex.js";
17
+
18
+ export interface GetProviderOptions {
19
+ logger?: LoggerAdapter;
20
+ clientVersion?: string;
21
+ }
22
+
23
+ /**
24
+ * Get provider instance by ID
25
+ *
26
+ * @param providerId - Provider identifier (e.g., 'toolplex', 'openai', 'anthropic')
27
+ * @param credentials - API keys and credentials
28
+ * @param options - Optional logger and client version
29
+ * @returns Provider instance
30
+ */
31
+ export function getProvider(
32
+ providerId: string,
33
+ credentials: ProviderCredentials,
34
+ options?: GetProviderOptions,
35
+ ): AIProvider {
36
+ switch (providerId.toLowerCase()) {
37
+ case "toolplex": {
38
+ if (!credentials.toolplexApiKey) {
39
+ throw new Error("ToolPlex API key is required");
40
+ }
41
+ const toolplexProvider = createToolPlex({
42
+ apiKey: credentials.toolplexApiKey,
43
+ baseURL: "https://api.toolplex.ai",
44
+ clientVersion: options?.clientVersion,
45
+ logger: options?.logger,
46
+ });
47
+ return {
48
+ id: "toolplex",
49
+ chat: (modelId: string) => toolplexProvider(modelId),
50
+ };
51
+ }
52
+
53
+ case "openai": {
54
+ if (!credentials.openaiKey) {
55
+ throw new Error("OpenAI API key is required");
56
+ }
57
+ const openaiProvider = createOpenAI({ apiKey: credentials.openaiKey });
58
+ return {
59
+ id: "openai",
60
+ chat: (modelId: string) => openaiProvider(modelId),
61
+ };
62
+ }
63
+
64
+ case "anthropic": {
65
+ if (!credentials.anthropicKey) {
66
+ throw new Error("Anthropic API key is required");
67
+ }
68
+ const anthropicProvider = createAnthropic({
69
+ apiKey: credentials.anthropicKey,
70
+ });
71
+ return {
72
+ id: "anthropic",
73
+ chat: (modelId: string) => anthropicProvider(modelId),
74
+ };
75
+ }
76
+
77
+ case "google": {
78
+ if (!credentials.googleKey) {
79
+ throw new Error("Google API key is required");
80
+ }
81
+ const googleProvider = createGoogleGenerativeAI({
82
+ apiKey: credentials.googleKey,
83
+ });
84
+ return {
85
+ id: "google",
86
+ chat: (modelId: string) => googleProvider(modelId),
87
+ };
88
+ }
89
+
90
+ case "openrouter": {
91
+ if (!credentials.openrouterKey) {
92
+ throw new Error("OpenRouter API key is required");
93
+ }
94
+ const openrouterProvider = createOpenRouter({
95
+ apiKey: credentials.openrouterKey,
96
+ });
97
+ return {
98
+ id: "openrouter",
99
+ chat: (modelId: string) => openrouterProvider(modelId),
100
+ };
101
+ }
102
+
103
+ case "deepseek": {
104
+ if (!credentials.deepseekKey) {
105
+ throw new Error("DeepSeek API key is required");
106
+ }
107
+ // DeepSeek uses OpenAI-compatible API
108
+ const deepseekProvider = createOpenAI({
109
+ apiKey: credentials.deepseekKey,
110
+ baseURL: "https://api.deepseek.com/v1",
111
+ });
112
+ return {
113
+ id: "deepseek",
114
+ chat: (modelId: string) => deepseekProvider(modelId),
115
+ };
116
+ }
117
+
118
+ case "moonshot": {
119
+ if (!credentials.moonshotKey) {
120
+ throw new Error("Moonshot API key is required");
121
+ }
122
+ // Moonshot uses OpenAI-compatible API
123
+ const moonshotProvider = createOpenAI({
124
+ apiKey: credentials.moonshotKey,
125
+ baseURL: "https://api.moonshot.cn/v1",
126
+ });
127
+ return {
128
+ id: "moonshot",
129
+ chat: (modelId: string) => moonshotProvider(modelId),
130
+ };
131
+ }
132
+
133
+ default:
134
+ throw new Error(`Unknown provider: ${providerId}`);
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Get model instance for streaming
140
+ *
141
+ * @param modelId - Full model identifier (e.g., 'anthropic/claude-sonnet-4')
142
+ * @param credentials - API keys and credentials
143
+ * @param options - Optional logger and client version
144
+ * @returns Language model instance ready for streamText()
145
+ */
146
+ export function getModel(
147
+ modelId: string,
148
+ credentials: ProviderCredentials,
149
+ options?: GetProviderOptions,
150
+ ) {
151
+ // Parse model ID format: "provider/model-name"
152
+ const parts = modelId.split("/");
153
+ const providerId = parts[0];
154
+ const actualModelId = parts.slice(1).join("/");
155
+
156
+ const provider = getProvider(providerId, credentials, options);
157
+ return provider.chat(actualModelId);
158
+ }
159
+
160
+ /**
161
+ * Check if a provider is available (has required credentials)
162
+ *
163
+ * @param providerId - Provider identifier
164
+ * @param credentials - API keys and credentials
165
+ * @returns True if provider can be instantiated
166
+ */
167
+ export function isProviderAvailable(
168
+ providerId: string,
169
+ credentials: ProviderCredentials,
170
+ ): boolean {
171
+ try {
172
+ getProvider(providerId, credentials);
173
+ return true;
174
+ } catch {
175
+ return false;
176
+ }
177
+ }
@@ -0,0 +1,217 @@
1
+ /**
2
+ * @toolplex/ai-engine - ToolPlex Provider
3
+ *
4
+ * Custom ToolPlex Provider for Vercel AI SDK.
5
+ * Wraps the ToolPlex AI API backend (api.toolplex.ai) which proxies OpenRouter
6
+ * and returns OpenRouter-format SSE streaming responses.
7
+ *
8
+ * The backend handles:
9
+ * - Authentication via ToolPlex API keys (x-api-key header)
10
+ * - Usage tracking and enforcement
11
+ * - OpenRouter model access
12
+ * - Tool call handling
13
+ */
14
+
15
+ import { createOpenRouter } from "@openrouter/ai-sdk-provider";
16
+ import type { LoggerAdapter } from "../adapters/types.js";
17
+
18
+ export interface ToolPlexConfig {
19
+ apiKey: string;
20
+ baseURL?: string;
21
+ clientVersion?: string;
22
+ logger?: LoggerAdapter;
23
+ }
24
+
25
+ /**
26
+ * Global map to store usage data from DONE events
27
+ * Key: sessionId, Value: Usage data from backend
28
+ * This is cleared after being read by the engine
29
+ */
30
+ export const toolplexUsageMap = new Map<
31
+ string,
32
+ {
33
+ prompt_tokens: number;
34
+ completion_tokens: number;
35
+ total_tokens: number;
36
+ cost?: number;
37
+ }
38
+ >();
39
+
40
+ /**
41
+ * ToolPlex model factory function type
42
+ */
43
+ export type ToolPlexModelFactory = (modelId: string) => any;
44
+
45
+ /**
46
+ * Create a ToolPlex provider instance
47
+ *
48
+ * Uses the specialized OpenRouter SDK provider with custom configuration to work with
49
+ * the ToolPlex backend API which proxies OpenRouter and returns OpenRouter's SSE format.
50
+ */
51
+ export function createToolPlex(config: ToolPlexConfig): ToolPlexModelFactory {
52
+ const baseURL = config.baseURL || "https://api.toolplex.ai";
53
+ const logger = config.logger;
54
+
55
+ // Build headers
56
+ const headers: Record<string, string> = {
57
+ "x-api-key": config.apiKey,
58
+ };
59
+
60
+ if (config.clientVersion) {
61
+ headers["X-Client-Version"] = config.clientVersion;
62
+ }
63
+
64
+ // Create OpenRouter provider with custom configuration for ToolPlex backend
65
+ const provider = createOpenRouter({
66
+ apiKey: config.apiKey,
67
+ baseURL: `${baseURL}/ai`,
68
+ headers,
69
+ // Custom fetch to transform requests and intercept DONE events for usage data
70
+ fetch: async (url, init) => {
71
+ // Add provider field and session_id to request body
72
+ if (init?.body && typeof init.body === "string") {
73
+ try {
74
+ const body = JSON.parse(init.body);
75
+
76
+ // Extract session ID from headers if present
77
+ let sessionId: string | null = null;
78
+ if (init?.headers) {
79
+ if (init.headers instanceof Headers) {
80
+ sessionId = init.headers.get("x-session-id");
81
+ } else if (Array.isArray(init.headers)) {
82
+ const sessionHeader = init.headers.find(
83
+ ([key]) => key.toLowerCase() === "x-session-id",
84
+ );
85
+ sessionId = sessionHeader ? sessionHeader[1] : null;
86
+ } else {
87
+ sessionId =
88
+ (init.headers as Record<string, string>)["x-session-id"] ||
89
+ null;
90
+ }
91
+ }
92
+
93
+ logger?.debug("ToolPlex provider: Transforming request", {
94
+ hasSessionId: !!sessionId,
95
+ sessionId: sessionId ? sessionId.substring(0, 8) + "..." : null,
96
+ bodyKeys: Object.keys(body),
97
+ });
98
+
99
+ const toolplexBody = {
100
+ ...body,
101
+ provider: "openrouter",
102
+ ...(sessionId && { session_id: sessionId }),
103
+ };
104
+
105
+ init = {
106
+ ...init,
107
+ body: JSON.stringify(toolplexBody),
108
+ };
109
+ } catch (error) {
110
+ logger?.error("Failed to transform ToolPlex request", { error });
111
+ }
112
+ }
113
+
114
+ const response = await fetch(url, init);
115
+
116
+ // Intercept SSE stream to capture usage from DONE event
117
+ if (
118
+ response.body &&
119
+ response.headers.get("content-type")?.includes("text/event-stream")
120
+ ) {
121
+ const originalBody = response.body;
122
+ const reader = originalBody.getReader();
123
+ const decoder = new TextDecoder();
124
+
125
+ // Create a new readable stream that intercepts SSE events
126
+ const transformedStream = new ReadableStream({
127
+ async start(controller) {
128
+ let buffer = "";
129
+
130
+ try {
131
+ while (true) {
132
+ const { done, value } = await reader.read();
133
+
134
+ if (done) {
135
+ controller.close();
136
+ break;
137
+ }
138
+
139
+ // Decode chunk and add to buffer
140
+ buffer += decoder.decode(value, { stream: true });
141
+
142
+ // Process complete SSE events (separated by \n\n)
143
+ const events = buffer.split("\n\n");
144
+ buffer = events.pop() || ""; // Keep incomplete event in buffer
145
+
146
+ // Filter out DONE events and track usage
147
+ const filteredEvents: string[] = [];
148
+
149
+ for (const event of events) {
150
+ if (event.trim()) {
151
+ // Parse SSE event (format: "data: {...}")
152
+ const match = event.match(/^data: (.+)$/m);
153
+ if (match) {
154
+ try {
155
+ const data = JSON.parse(match[1]);
156
+
157
+ // Check for DONE event with usage data
158
+ if (data.done === true && data.usage) {
159
+ const sessionId = (init?.headers as any)?.[
160
+ "x-session-id"
161
+ ];
162
+
163
+ if (sessionId) {
164
+ toolplexUsageMap.set(sessionId, {
165
+ prompt_tokens: data.usage.prompt_tokens || 0,
166
+ completion_tokens:
167
+ data.usage.completion_tokens || 0,
168
+ total_tokens: data.usage.total_tokens || 0,
169
+ cost: data.usage.cost,
170
+ });
171
+ }
172
+
173
+ // Skip this event to prevent AI SDK from seeing invalid chunk format
174
+ continue;
175
+ }
176
+ } catch {
177
+ // Not JSON or different format - keep the event
178
+ }
179
+ }
180
+ }
181
+
182
+ // Keep this event
183
+ filteredEvents.push(event);
184
+ }
185
+
186
+ // Reconstruct the filtered stream
187
+ if (filteredEvents.length > 0) {
188
+ const filteredBuffer = filteredEvents.join("\n\n") + "\n\n";
189
+ controller.enqueue(Buffer.from(filteredBuffer, "utf8"));
190
+ }
191
+ }
192
+ } catch (error) {
193
+ logger?.error("Error intercepting ToolPlex SSE stream", {
194
+ error,
195
+ });
196
+ controller.error(error);
197
+ }
198
+ },
199
+ });
200
+
201
+ // Return a new response with the transformed stream
202
+ return new Response(transformedStream, {
203
+ status: response.status,
204
+ statusText: response.statusText,
205
+ headers: response.headers,
206
+ });
207
+ }
208
+
209
+ return response;
210
+ },
211
+ });
212
+
213
+ // Return a function that creates model instances
214
+ return function (modelId: string) {
215
+ return provider.chat(modelId);
216
+ };
217
+ }