@tambo-ai/client 0.0.1

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 (235) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +100 -0
  3. package/dist/index.d.ts +43 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +78 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/mcp/elicitation.d.ts +59 -0
  8. package/dist/mcp/elicitation.d.ts.map +1 -0
  9. package/dist/mcp/elicitation.js +27 -0
  10. package/dist/mcp/elicitation.js.map +1 -0
  11. package/dist/mcp/index.d.ts +6 -0
  12. package/dist/mcp/index.d.ts.map +1 -0
  13. package/dist/mcp/index.js +14 -0
  14. package/dist/mcp/index.js.map +1 -0
  15. package/dist/mcp/mcp-client.d.ts +185 -0
  16. package/dist/mcp/mcp-client.d.ts.map +1 -0
  17. package/dist/mcp/mcp-client.js +219 -0
  18. package/dist/mcp/mcp-client.js.map +1 -0
  19. package/dist/mcp/mcp-constants.d.ts +19 -0
  20. package/dist/mcp/mcp-constants.d.ts.map +1 -0
  21. package/dist/mcp/mcp-constants.js +21 -0
  22. package/dist/mcp/mcp-constants.js.map +1 -0
  23. package/dist/model/component-metadata.d.ts +390 -0
  24. package/dist/model/component-metadata.d.ts.map +1 -0
  25. package/dist/model/component-metadata.js +3 -0
  26. package/dist/model/component-metadata.js.map +1 -0
  27. package/dist/model/mcp-server-info.d.ts +72 -0
  28. package/dist/model/mcp-server-info.d.ts.map +1 -0
  29. package/dist/model/mcp-server-info.js +29 -0
  30. package/dist/model/mcp-server-info.js.map +1 -0
  31. package/dist/schema/index.d.ts +5 -0
  32. package/dist/schema/index.d.ts.map +1 -0
  33. package/dist/schema/index.js +15 -0
  34. package/dist/schema/index.js.map +1 -0
  35. package/dist/schema/json-schema.d.ts +42 -0
  36. package/dist/schema/json-schema.d.ts.map +1 -0
  37. package/dist/schema/json-schema.js +114 -0
  38. package/dist/schema/json-schema.js.map +1 -0
  39. package/dist/schema/schema.d.ts +49 -0
  40. package/dist/schema/schema.d.ts.map +1 -0
  41. package/dist/schema/schema.js +129 -0
  42. package/dist/schema/schema.js.map +1 -0
  43. package/dist/schema/standard-schema.d.ts +22 -0
  44. package/dist/schema/standard-schema.d.ts.map +1 -0
  45. package/dist/schema/standard-schema.js +42 -0
  46. package/dist/schema/standard-schema.js.map +1 -0
  47. package/dist/schema/validate.d.ts +14 -0
  48. package/dist/schema/validate.d.ts.map +1 -0
  49. package/dist/schema/validate.js +148 -0
  50. package/dist/schema/validate.js.map +1 -0
  51. package/dist/tambo-client.d.ts +292 -0
  52. package/dist/tambo-client.d.ts.map +1 -0
  53. package/dist/tambo-client.js +508 -0
  54. package/dist/tambo-client.js.map +1 -0
  55. package/dist/tambo-stream.d.ts +112 -0
  56. package/dist/tambo-stream.d.ts.map +1 -0
  57. package/dist/tambo-stream.js +345 -0
  58. package/dist/tambo-stream.js.map +1 -0
  59. package/dist/types/auth.d.ts +24 -0
  60. package/dist/types/auth.d.ts.map +1 -0
  61. package/dist/types/auth.js +3 -0
  62. package/dist/types/auth.js.map +1 -0
  63. package/dist/types/event.d.ts +89 -0
  64. package/dist/types/event.d.ts.map +1 -0
  65. package/dist/types/event.js +57 -0
  66. package/dist/types/event.js.map +1 -0
  67. package/dist/types/message.d.ts +122 -0
  68. package/dist/types/message.d.ts.map +1 -0
  69. package/dist/types/message.js +10 -0
  70. package/dist/types/message.js.map +1 -0
  71. package/dist/types/thread.d.ts +58 -0
  72. package/dist/types/thread.d.ts.map +1 -0
  73. package/dist/types/thread.js +9 -0
  74. package/dist/types/thread.js.map +1 -0
  75. package/dist/types/tool-choice.d.ts +8 -0
  76. package/dist/types/tool-choice.d.ts.map +1 -0
  77. package/dist/types/tool-choice.js +3 -0
  78. package/dist/types/tool-choice.js.map +1 -0
  79. package/dist/utils/event-accumulator.d.ts +165 -0
  80. package/dist/utils/event-accumulator.d.ts.map +1 -0
  81. package/dist/utils/event-accumulator.js +1278 -0
  82. package/dist/utils/event-accumulator.js.map +1 -0
  83. package/dist/utils/json-patch.d.ts +18 -0
  84. package/dist/utils/json-patch.d.ts.map +1 -0
  85. package/dist/utils/json-patch.js +35 -0
  86. package/dist/utils/json-patch.js.map +1 -0
  87. package/dist/utils/keyed-throttle.d.ts +42 -0
  88. package/dist/utils/keyed-throttle.d.ts.map +1 -0
  89. package/dist/utils/keyed-throttle.js +86 -0
  90. package/dist/utils/keyed-throttle.js.map +1 -0
  91. package/dist/utils/registry-conversion.d.ts +53 -0
  92. package/dist/utils/registry-conversion.d.ts.map +1 -0
  93. package/dist/utils/registry-conversion.js +115 -0
  94. package/dist/utils/registry-conversion.js.map +1 -0
  95. package/dist/utils/send-message.d.ts +140 -0
  96. package/dist/utils/send-message.d.ts.map +1 -0
  97. package/dist/utils/send-message.js +183 -0
  98. package/dist/utils/send-message.js.map +1 -0
  99. package/dist/utils/stream-handler.d.ts +45 -0
  100. package/dist/utils/stream-handler.d.ts.map +1 -0
  101. package/dist/utils/stream-handler.js +47 -0
  102. package/dist/utils/stream-handler.js.map +1 -0
  103. package/dist/utils/thread-utils.d.ts +16 -0
  104. package/dist/utils/thread-utils.d.ts.map +1 -0
  105. package/dist/utils/thread-utils.js +34 -0
  106. package/dist/utils/thread-utils.js.map +1 -0
  107. package/dist/utils/tool-call-tracker.d.ts +74 -0
  108. package/dist/utils/tool-call-tracker.d.ts.map +1 -0
  109. package/dist/utils/tool-call-tracker.js +181 -0
  110. package/dist/utils/tool-call-tracker.js.map +1 -0
  111. package/dist/utils/tool-executor.d.ts +67 -0
  112. package/dist/utils/tool-executor.d.ts.map +1 -0
  113. package/dist/utils/tool-executor.js +160 -0
  114. package/dist/utils/tool-executor.js.map +1 -0
  115. package/dist/utils/unstrictify.d.ts +32 -0
  116. package/dist/utils/unstrictify.d.ts.map +1 -0
  117. package/dist/utils/unstrictify.js +160 -0
  118. package/dist/utils/unstrictify.js.map +1 -0
  119. package/esm/index.d.ts +43 -0
  120. package/esm/index.d.ts.map +1 -0
  121. package/esm/index.js +78 -0
  122. package/esm/index.js.map +1 -0
  123. package/esm/mcp/elicitation.d.ts +59 -0
  124. package/esm/mcp/elicitation.d.ts.map +1 -0
  125. package/esm/mcp/elicitation.js +27 -0
  126. package/esm/mcp/elicitation.js.map +1 -0
  127. package/esm/mcp/index.d.ts +6 -0
  128. package/esm/mcp/index.d.ts.map +1 -0
  129. package/esm/mcp/index.js +14 -0
  130. package/esm/mcp/index.js.map +1 -0
  131. package/esm/mcp/mcp-client.d.ts +185 -0
  132. package/esm/mcp/mcp-client.d.ts.map +1 -0
  133. package/esm/mcp/mcp-client.js +219 -0
  134. package/esm/mcp/mcp-client.js.map +1 -0
  135. package/esm/mcp/mcp-constants.d.ts +19 -0
  136. package/esm/mcp/mcp-constants.d.ts.map +1 -0
  137. package/esm/mcp/mcp-constants.js +21 -0
  138. package/esm/mcp/mcp-constants.js.map +1 -0
  139. package/esm/model/component-metadata.d.ts +390 -0
  140. package/esm/model/component-metadata.d.ts.map +1 -0
  141. package/esm/model/component-metadata.js +3 -0
  142. package/esm/model/component-metadata.js.map +1 -0
  143. package/esm/model/mcp-server-info.d.ts +72 -0
  144. package/esm/model/mcp-server-info.d.ts.map +1 -0
  145. package/esm/model/mcp-server-info.js +29 -0
  146. package/esm/model/mcp-server-info.js.map +1 -0
  147. package/esm/schema/index.d.ts +5 -0
  148. package/esm/schema/index.d.ts.map +1 -0
  149. package/esm/schema/index.js +15 -0
  150. package/esm/schema/index.js.map +1 -0
  151. package/esm/schema/json-schema.d.ts +42 -0
  152. package/esm/schema/json-schema.d.ts.map +1 -0
  153. package/esm/schema/json-schema.js +114 -0
  154. package/esm/schema/json-schema.js.map +1 -0
  155. package/esm/schema/schema.d.ts +49 -0
  156. package/esm/schema/schema.d.ts.map +1 -0
  157. package/esm/schema/schema.js +129 -0
  158. package/esm/schema/schema.js.map +1 -0
  159. package/esm/schema/standard-schema.d.ts +22 -0
  160. package/esm/schema/standard-schema.d.ts.map +1 -0
  161. package/esm/schema/standard-schema.js +42 -0
  162. package/esm/schema/standard-schema.js.map +1 -0
  163. package/esm/schema/validate.d.ts +14 -0
  164. package/esm/schema/validate.d.ts.map +1 -0
  165. package/esm/schema/validate.js +148 -0
  166. package/esm/schema/validate.js.map +1 -0
  167. package/esm/tambo-client.d.ts +292 -0
  168. package/esm/tambo-client.d.ts.map +1 -0
  169. package/esm/tambo-client.js +508 -0
  170. package/esm/tambo-client.js.map +1 -0
  171. package/esm/tambo-stream.d.ts +112 -0
  172. package/esm/tambo-stream.d.ts.map +1 -0
  173. package/esm/tambo-stream.js +345 -0
  174. package/esm/tambo-stream.js.map +1 -0
  175. package/esm/types/auth.d.ts +24 -0
  176. package/esm/types/auth.d.ts.map +1 -0
  177. package/esm/types/auth.js +3 -0
  178. package/esm/types/auth.js.map +1 -0
  179. package/esm/types/event.d.ts +89 -0
  180. package/esm/types/event.d.ts.map +1 -0
  181. package/esm/types/event.js +57 -0
  182. package/esm/types/event.js.map +1 -0
  183. package/esm/types/message.d.ts +122 -0
  184. package/esm/types/message.d.ts.map +1 -0
  185. package/esm/types/message.js +10 -0
  186. package/esm/types/message.js.map +1 -0
  187. package/esm/types/thread.d.ts +58 -0
  188. package/esm/types/thread.d.ts.map +1 -0
  189. package/esm/types/thread.js +9 -0
  190. package/esm/types/thread.js.map +1 -0
  191. package/esm/types/tool-choice.d.ts +8 -0
  192. package/esm/types/tool-choice.d.ts.map +1 -0
  193. package/esm/types/tool-choice.js +3 -0
  194. package/esm/types/tool-choice.js.map +1 -0
  195. package/esm/utils/event-accumulator.d.ts +165 -0
  196. package/esm/utils/event-accumulator.d.ts.map +1 -0
  197. package/esm/utils/event-accumulator.js +1278 -0
  198. package/esm/utils/event-accumulator.js.map +1 -0
  199. package/esm/utils/json-patch.d.ts +18 -0
  200. package/esm/utils/json-patch.d.ts.map +1 -0
  201. package/esm/utils/json-patch.js +35 -0
  202. package/esm/utils/json-patch.js.map +1 -0
  203. package/esm/utils/keyed-throttle.d.ts +42 -0
  204. package/esm/utils/keyed-throttle.d.ts.map +1 -0
  205. package/esm/utils/keyed-throttle.js +86 -0
  206. package/esm/utils/keyed-throttle.js.map +1 -0
  207. package/esm/utils/registry-conversion.d.ts +53 -0
  208. package/esm/utils/registry-conversion.d.ts.map +1 -0
  209. package/esm/utils/registry-conversion.js +115 -0
  210. package/esm/utils/registry-conversion.js.map +1 -0
  211. package/esm/utils/send-message.d.ts +140 -0
  212. package/esm/utils/send-message.d.ts.map +1 -0
  213. package/esm/utils/send-message.js +183 -0
  214. package/esm/utils/send-message.js.map +1 -0
  215. package/esm/utils/stream-handler.d.ts +45 -0
  216. package/esm/utils/stream-handler.d.ts.map +1 -0
  217. package/esm/utils/stream-handler.js +47 -0
  218. package/esm/utils/stream-handler.js.map +1 -0
  219. package/esm/utils/thread-utils.d.ts +16 -0
  220. package/esm/utils/thread-utils.d.ts.map +1 -0
  221. package/esm/utils/thread-utils.js +34 -0
  222. package/esm/utils/thread-utils.js.map +1 -0
  223. package/esm/utils/tool-call-tracker.d.ts +74 -0
  224. package/esm/utils/tool-call-tracker.d.ts.map +1 -0
  225. package/esm/utils/tool-call-tracker.js +181 -0
  226. package/esm/utils/tool-call-tracker.js.map +1 -0
  227. package/esm/utils/tool-executor.d.ts +67 -0
  228. package/esm/utils/tool-executor.d.ts.map +1 -0
  229. package/esm/utils/tool-executor.js +160 -0
  230. package/esm/utils/tool-executor.js.map +1 -0
  231. package/esm/utils/unstrictify.d.ts +32 -0
  232. package/esm/utils/unstrictify.d.ts.map +1 -0
  233. package/esm/utils/unstrictify.js +160 -0
  234. package/esm/utils/unstrictify.js.map +1 -0
  235. package/package.json +90 -0
@@ -0,0 +1,508 @@
1
+ "use strict";
2
+ /**
3
+ * TamboClient - Framework-agnostic client for Tambo AI
4
+ *
5
+ * Main entry point for the `@tambo-ai/client` package. Manages threads,
6
+ * tool registration, streaming, and state. Compatible with
7
+ * useSyncExternalStore for React integration.
8
+ */
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.TamboClient = void 0;
14
+ const typescript_sdk_1 = __importDefault(require("@tambo-ai/typescript-sdk"));
15
+ const mcp_server_info_1 = require("./model/mcp-server-info");
16
+ const mcp_client_1 = require("./mcp/mcp-client");
17
+ const event_accumulator_1 = require("./utils/event-accumulator");
18
+ const tambo_stream_1 = require("./tambo-stream");
19
+ /**
20
+ * TamboClient manages threads, tool execution, streaming, and state.
21
+ *
22
+ * Compatible with `useSyncExternalStore(client.subscribe, client.getState)`
23
+ * for React integration.
24
+ * @example
25
+ * ```typescript
26
+ * const client = new TamboClient({ apiKey: "your-api-key" });
27
+ *
28
+ * // Stream mode
29
+ * const stream = client.run("Hello, AI!");
30
+ * for await (const { event, snapshot } of stream) {
31
+ * console.log(snapshot.messages);
32
+ * }
33
+ *
34
+ * // Promise mode
35
+ * const thread = await client.run("Hello, AI!").thread;
36
+ * console.log(thread.messages);
37
+ * ```
38
+ */
39
+ class TamboClient {
40
+ sdkClient;
41
+ state;
42
+ listeners = new Set();
43
+ pendingNotification = false;
44
+ toolRegistry = {};
45
+ componentList = {};
46
+ mcpClients = new Map();
47
+ activeRuns = {};
48
+ contextHelpers = new Map();
49
+ options;
50
+ /**
51
+ * Create a new TamboClient.
52
+ * @param options - Client configuration.
53
+ */
54
+ constructor(options) {
55
+ if (options.tamboUrl && options.environment) {
56
+ throw new Error("Cannot specify both 'tamboUrl' and 'environment'. Choose one.");
57
+ }
58
+ if (options.userKey && options.userToken) {
59
+ throw new Error("Cannot specify both 'userKey' and 'userToken'. Choose one.");
60
+ }
61
+ this.options = options;
62
+ this.state = (0, event_accumulator_1.createInitialState)();
63
+ // Resolve base URL
64
+ const baseURL = options.tamboUrl ?? this.resolveBaseUrl(options.environment);
65
+ this.sdkClient = new typescript_sdk_1.default({
66
+ apiKey: options.apiKey,
67
+ baseURL,
68
+ });
69
+ // Register initial tools
70
+ if (options.tools) {
71
+ for (const tool of options.tools) {
72
+ this.toolRegistry[tool.name] = tool;
73
+ }
74
+ }
75
+ // Connect MCP servers (fire-and-forget)
76
+ if (options.mcpServers) {
77
+ for (const server of options.mcpServers) {
78
+ void this.connectMcpServer(server).catch((err) => {
79
+ console.error(`[TamboClient] Failed to connect MCP server:`, err);
80
+ });
81
+ }
82
+ }
83
+ }
84
+ // -- Core operations --
85
+ /**
86
+ * Start a new run with a message. Returns a TamboStream immediately.
87
+ *
88
+ * The stream's processing loop runs in the background. Use async iteration
89
+ * to observe events, or `await stream.thread` to get the final result.
90
+ * @param message - The message string or InputMessage object.
91
+ * @param options - Run configuration.
92
+ * @returns A TamboStream for observing the response.
93
+ */
94
+ run(message, options = {}) {
95
+ const { threadId, autoExecuteTools = true, maxSteps = 10, toolChoice, debug = false, additionalContext, signal, userMessageText, } = options;
96
+ // Resolve the actual thread ID
97
+ const resolvedThreadId = threadId && !(0, event_accumulator_1.isPlaceholderThreadId)(threadId) ? threadId : undefined;
98
+ // Check for concurrent runs
99
+ if (resolvedThreadId && this.activeRuns[resolvedThreadId]) {
100
+ throw new Error(`A run is already active on thread "${resolvedThreadId}". Wait for it to complete or abort it first.`);
101
+ }
102
+ // Normalize message to InputMessage
103
+ const inputMessage = typeof message === "string"
104
+ ? { role: "user", content: [{ type: "text", text: message }] }
105
+ : message;
106
+ // If userMessageText not provided, extract from string messages
107
+ const resolvedUserMessageText = userMessageText ?? (typeof message === "string" ? message : undefined);
108
+ // Get previousRunId from thread state
109
+ const threadState = resolvedThreadId
110
+ ? this.state.threadMap[resolvedThreadId]
111
+ : undefined;
112
+ const previousRunId = threadState?.streaming.runId ?? threadState?.lastCompletedRunId;
113
+ // Collect context helpers and beforeRun in the stream's processing loop
114
+ const streamOptions = {
115
+ client: this.sdkClient,
116
+ message: inputMessage,
117
+ threadId: resolvedThreadId,
118
+ userMessageText: resolvedUserMessageText,
119
+ componentList: this.componentList,
120
+ toolRegistry: this.toolRegistry,
121
+ userKey: this.options.userKey,
122
+ previousRunId,
123
+ additionalContext: this.mergeContextForRun(additionalContext),
124
+ toolChoice,
125
+ autoExecuteTools,
126
+ maxSteps,
127
+ debug,
128
+ signal,
129
+ dispatch: (action) => this.dispatch(action),
130
+ getThreadSnapshot: (tid) => this.getThread(tid),
131
+ };
132
+ const stream = new tambo_stream_1.TamboStream(streamOptions);
133
+ // Track active run
134
+ if (resolvedThreadId) {
135
+ this.activeRuns[resolvedThreadId] = stream;
136
+ }
137
+ // Clean up active run tracking when stream completes
138
+ void stream.thread
139
+ .catch(() => {
140
+ // Error handled by stream consumer
141
+ })
142
+ .finally(() => {
143
+ if (resolvedThreadId) {
144
+ delete this.activeRuns[resolvedThreadId];
145
+ }
146
+ });
147
+ return stream;
148
+ }
149
+ // -- Thread management --
150
+ /**
151
+ * Switch to an existing thread. Fetches thread data from the API.
152
+ * @param threadId - The thread ID to switch to.
153
+ */
154
+ async switchThread(threadId) {
155
+ await this.fetchThread(threadId);
156
+ this.dispatch({ type: "SET_CURRENT_THREAD", threadId });
157
+ }
158
+ /**
159
+ * Start a new thread. Returns the new thread ID.
160
+ * @returns The new placeholder thread ID.
161
+ */
162
+ startNewThread() {
163
+ const threadId = event_accumulator_1.PLACEHOLDER_THREAD_ID;
164
+ this.dispatch({ type: "START_NEW_THREAD", threadId });
165
+ return threadId;
166
+ }
167
+ /**
168
+ * Get a thread from local state.
169
+ * @param threadId - The thread ID.
170
+ * @returns The thread, or undefined if not found.
171
+ */
172
+ getThread(threadId) {
173
+ return this.state.threadMap[threadId]?.thread;
174
+ }
175
+ /**
176
+ * List all threads from the API.
177
+ * @returns An array of TamboThread objects.
178
+ */
179
+ async listThreads() {
180
+ const response = await this.sdkClient.threads.list({
181
+ userKey: this.options.userKey,
182
+ });
183
+ // Convert SDK response to TamboThread format
184
+ return response.threads.map((apiThread) => ({
185
+ id: apiThread.id,
186
+ name: apiThread.name ?? undefined,
187
+ messages: [],
188
+ status: "idle",
189
+ metadata: apiThread.metadata ?? undefined,
190
+ createdAt: apiThread.createdAt,
191
+ updatedAt: apiThread.updatedAt,
192
+ lastRunCancelled: false,
193
+ }));
194
+ }
195
+ /**
196
+ * Fetch a thread from the API and hydrate it into local state.
197
+ * @param threadId - The thread ID to fetch.
198
+ * @returns The fetched thread.
199
+ */
200
+ async fetchThread(threadId) {
201
+ const apiThread = await this.sdkClient.threads.retrieve(threadId);
202
+ // Initialize thread state if not present
203
+ if (!this.state.threadMap[threadId]) {
204
+ this.dispatch({ type: "INIT_THREAD", threadId });
205
+ }
206
+ // Load messages from the API response
207
+ if (apiThread.messages && apiThread.messages.length > 0) {
208
+ this.dispatch({
209
+ type: "LOAD_THREAD_MESSAGES",
210
+ threadId,
211
+ messages: apiThread.messages.map((msg) => ({
212
+ id: msg.id,
213
+ role: msg.role,
214
+ content: msg.content,
215
+ createdAt: msg.createdAt,
216
+ threadId,
217
+ })),
218
+ });
219
+ }
220
+ // Set lastCompletedRunId if available
221
+ if (apiThread.lastCompletedRunId) {
222
+ this.dispatch({
223
+ type: "SET_LAST_COMPLETED_RUN_ID",
224
+ threadId,
225
+ lastCompletedRunId: apiThread.lastCompletedRunId,
226
+ });
227
+ }
228
+ // Update thread name if available
229
+ if (apiThread.name) {
230
+ this.dispatch({
231
+ type: "UPDATE_THREAD_NAME",
232
+ threadId,
233
+ name: apiThread.name,
234
+ });
235
+ }
236
+ const thread = this.getThread(threadId);
237
+ if (!thread) {
238
+ throw new Error(`Failed to hydrate thread ${threadId}`);
239
+ }
240
+ return thread;
241
+ }
242
+ // -- Registration --
243
+ /**
244
+ * Register a single tool.
245
+ * @param tool - The tool to register.
246
+ */
247
+ registerTool(tool) {
248
+ this.toolRegistry[tool.name] = tool;
249
+ }
250
+ /**
251
+ * Register multiple tools at once.
252
+ * @param tools - The tools to register.
253
+ */
254
+ registerTools(tools) {
255
+ for (const tool of tools) {
256
+ this.toolRegistry[tool.name] = tool;
257
+ }
258
+ }
259
+ /**
260
+ * Register a component.
261
+ * @param name - Component name.
262
+ * @param component - The registered component.
263
+ */
264
+ registerComponent(name, component) {
265
+ this.componentList[name] = component;
266
+ }
267
+ // -- State access (useSyncExternalStore-compatible) --
268
+ /**
269
+ * Get the current client state snapshot.
270
+ * @returns The current client state.
271
+ */
272
+ getState() {
273
+ return this.state;
274
+ }
275
+ /**
276
+ * Subscribe to state changes.
277
+ * @param listener - Callback invoked on state change.
278
+ * @returns Unsubscribe function.
279
+ */
280
+ subscribe(listener) {
281
+ this.listeners.add(listener);
282
+ return () => {
283
+ this.listeners.delete(listener);
284
+ };
285
+ }
286
+ // -- Auth --
287
+ /**
288
+ * Compute and return the current auth state. Not stored in state;
289
+ * computed on each call from config.
290
+ * @returns The current auth state.
291
+ */
292
+ getAuthState() {
293
+ const { userKey, userToken } = this.options;
294
+ if (userKey && userToken) {
295
+ return { status: "invalid" };
296
+ }
297
+ if (userKey) {
298
+ return { status: "identified", source: "userKey" };
299
+ }
300
+ if (userToken) {
301
+ // Token exchange would happen here in full implementation
302
+ return { status: "exchanging" };
303
+ }
304
+ return { status: "unauthenticated" };
305
+ }
306
+ // -- Run control --
307
+ /**
308
+ * Cancel the active run on a thread.
309
+ * @param threadId - The thread whose run to cancel. Defaults to current thread.
310
+ */
311
+ async cancelRun(threadId) {
312
+ const tid = threadId ?? this.state.currentThreadId;
313
+ const activeStream = this.activeRuns[tid];
314
+ if (activeStream) {
315
+ activeStream.abort();
316
+ }
317
+ // Also try server-side cancellation
318
+ const threadState = this.state.threadMap[tid];
319
+ const runId = threadState?.streaming.runId;
320
+ if (runId && tid) {
321
+ try {
322
+ await this.sdkClient.threads.runs.delete(runId, { threadId: tid });
323
+ }
324
+ catch (_error) {
325
+ // Server-side cancellation is best-effort
326
+ }
327
+ }
328
+ }
329
+ // -- Thread naming --
330
+ /**
331
+ * Update a thread's name via the API.
332
+ * @param threadId - The thread ID.
333
+ * @param name - The new name.
334
+ */
335
+ async updateThreadName(threadId, name) {
336
+ await this.sdkClient.threads.update(threadId, { name });
337
+ this.dispatch({ type: "UPDATE_THREAD_NAME", threadId, name });
338
+ }
339
+ /**
340
+ * Generate a thread name via the API.
341
+ * @param threadId - The thread ID.
342
+ * @returns The generated name.
343
+ */
344
+ async generateThreadName(threadId) {
345
+ const threadWithName = await this.sdkClient.beta.threads.generateName(threadId);
346
+ const name = threadWithName.name ?? "";
347
+ if (name) {
348
+ this.dispatch({ type: "UPDATE_THREAD_NAME", threadId, name });
349
+ }
350
+ return name;
351
+ }
352
+ // -- MCP --
353
+ /**
354
+ * Connect to an MCP server.
355
+ * @param serverInfo - The MCP server configuration.
356
+ * @returns The connected MCPClient.
357
+ */
358
+ async connectMcpServer(serverInfo) {
359
+ const key = (0, mcp_server_info_1.getMcpServerUniqueKey)(serverInfo);
360
+ const existing = this.mcpClients.get(key);
361
+ if (existing) {
362
+ return existing;
363
+ }
364
+ const mcpClient = await mcp_client_1.MCPClient.create(serverInfo.url, serverInfo.transport, serverInfo.customHeaders, undefined, // authProvider
365
+ undefined);
366
+ this.mcpClients.set(key, mcpClient);
367
+ return mcpClient;
368
+ }
369
+ /**
370
+ * Disconnect from an MCP server.
371
+ * @param serverKey - The server key (from getMcpServerUniqueKey).
372
+ */
373
+ async disconnectMcpServer(serverKey) {
374
+ const client = this.mcpClients.get(serverKey);
375
+ if (client) {
376
+ await client.close();
377
+ this.mcpClients.delete(serverKey);
378
+ }
379
+ }
380
+ /**
381
+ * Get all connected MCP clients.
382
+ * @returns Record of server key to MCPClient.
383
+ */
384
+ getMcpClients() {
385
+ return Object.fromEntries(this.mcpClients.entries());
386
+ }
387
+ /**
388
+ * Get an MCP token for a context key.
389
+ * @param contextKey - The context key.
390
+ * @param threadId - Optional thread ID for session-bound tokens.
391
+ * @returns The MCP access token response.
392
+ */
393
+ async getMcpToken(contextKey, threadId) {
394
+ const response = await this.sdkClient.beta.auth.getMcpToken({
395
+ contextKey,
396
+ threadId,
397
+ });
398
+ return {
399
+ mcpAccessToken: response.mcpAccessToken,
400
+ expiresAt: response.expiresAt,
401
+ hasSession: response.hasSession,
402
+ };
403
+ }
404
+ // -- Suggestions --
405
+ /**
406
+ * List suggestions for a message.
407
+ * @param messageId - The message ID.
408
+ * @param threadId - The thread ID.
409
+ * @returns Array of suggestions.
410
+ */
411
+ async listSuggestions(messageId, threadId) {
412
+ const response = await this.sdkClient.beta.threads.suggestions.list(messageId, { id: threadId });
413
+ return response;
414
+ }
415
+ /**
416
+ * Generate suggestions for a message.
417
+ * @param messageId - The message ID.
418
+ * @param threadId - The thread ID.
419
+ * @param options - Generation options.
420
+ * @param options.maxSuggestions - Maximum number of suggestions to generate.
421
+ * @returns Array of generated suggestions.
422
+ */
423
+ async generateSuggestions(messageId, threadId, options) {
424
+ const response = await this.sdkClient.beta.threads.suggestions.generate(messageId, {
425
+ id: threadId,
426
+ maxSuggestions: options?.maxSuggestions,
427
+ });
428
+ return response;
429
+ }
430
+ // -- Context helpers --
431
+ /**
432
+ * Add a context helper that provides additional context on each run.
433
+ * @param name - Unique name for the helper.
434
+ * @param fn - Function returning context data.
435
+ */
436
+ addContextHelper(name, fn) {
437
+ this.contextHelpers.set(name, fn);
438
+ }
439
+ /**
440
+ * Remove a context helper.
441
+ * @param name - The helper name to remove.
442
+ */
443
+ removeContextHelper(name) {
444
+ this.contextHelpers.delete(name);
445
+ }
446
+ /**
447
+ * Get the underlying SDK client (for advanced use cases).
448
+ * @returns The TamboAI SDK client.
449
+ */
450
+ getSdkClient() {
451
+ return this.sdkClient;
452
+ }
453
+ // -- Private methods --
454
+ /**
455
+ * Dispatch an action to the state reducer and notify listeners.
456
+ * @param action - The action to dispatch.
457
+ */
458
+ dispatch(action) {
459
+ this.state = (0, event_accumulator_1.streamReducer)(this.state, action);
460
+ this.notifyListeners();
461
+ }
462
+ /**
463
+ * Batch subscriber notifications via queueMicrotask to reduce
464
+ * re-renders during high-frequency streaming.
465
+ */
466
+ notifyListeners() {
467
+ if (!this.pendingNotification) {
468
+ this.pendingNotification = true;
469
+ queueMicrotask(() => {
470
+ this.pendingNotification = false;
471
+ for (const listener of this.listeners) {
472
+ listener();
473
+ }
474
+ });
475
+ }
476
+ }
477
+ /**
478
+ * Resolve the base URL from an environment preset.
479
+ * @param environment - The environment to resolve.
480
+ * @returns The resolved base URL.
481
+ */
482
+ resolveBaseUrl(environment) {
483
+ switch (environment) {
484
+ case "staging":
485
+ return "https://api.staging.tambo.co";
486
+ case "production":
487
+ default:
488
+ return "https://api.tambo.co";
489
+ }
490
+ }
491
+ /**
492
+ * Merge context from registered context helpers with provided additional context.
493
+ * @param additionalContext - Context provided by the caller.
494
+ * @returns Merged context object.
495
+ */
496
+ mergeContextForRun(additionalContext) {
497
+ if (this.contextHelpers.size === 0 && !additionalContext) {
498
+ return undefined;
499
+ }
500
+ // Context helpers are gathered synchronously here for the initial merge.
501
+ // Async helpers are not supported in this synchronous path — they should
502
+ // be awaited in the stream processing loop via beforeRun.
503
+ const merged = { ...additionalContext };
504
+ return merged;
505
+ }
506
+ }
507
+ exports.TamboClient = TamboClient;
508
+ //# sourceMappingURL=tambo-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tambo-client.js","sourceRoot":"","sources":["../src/tambo-client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * TamboClient - Framework-agnostic client for Tambo AI\n *\n * Main entry point for the `@tambo-ai/client` package. Manages threads,\n * tool registration, streaming, and state. Compatible with\n * useSyncExternalStore for React integration.\n */\n\nimport TamboAI from \"@tambo-ai/typescript-sdk\";\nimport type { Suggestion } from \"@tambo-ai/typescript-sdk/resources/beta/threads/suggestions\";\n\nimport type { TamboThread } from \"./types/thread\";\nimport type { TamboAuthState } from \"./types/auth\";\nimport type { ToolChoice } from \"./types/tool-choice\";\nimport type { InputMessage } from \"./types/message\";\nimport type {\n TamboTool,\n ComponentRegistry,\n TamboToolRegistry,\n} from \"./model/component-metadata\";\nimport type { McpServerInfo } from \"./model/mcp-server-info\";\nimport { getMcpServerUniqueKey } from \"./model/mcp-server-info\";\nimport { MCPClient } from \"./mcp/mcp-client\";\nimport {\n streamReducer,\n createInitialState,\n isPlaceholderThreadId,\n PLACEHOLDER_THREAD_ID,\n type StreamState,\n type StreamAction,\n type ThreadState,\n} from \"./utils/event-accumulator\";\nimport { TamboStream, type TamboStreamOptions } from \"./tambo-stream\";\n\n/**\n * Function that returns context to be merged into additionalContext before each run.\n */\nexport type ContextHelperFn = () =>\n | Record<string, unknown>\n | Promise<Record<string, unknown>>;\n\n/**\n * Callback invoked before each run starts.\n */\nexport interface BeforeRunContext {\n /** Thread ID (undefined for new threads). */\n threadId: string | undefined;\n /** The message being sent. */\n message: InputMessage;\n /** Frozen copy of registered tools (cannot mutate). */\n tools: Readonly<Record<string, TamboTool>>;\n}\n\n/**\n * Options for configuring TamboClient.\n */\nexport interface TamboClientOptions {\n /** API key for authentication. */\n apiKey: string;\n /** Custom Tambo API URL. Conflicts with `environment`. */\n tamboUrl?: string;\n /** Environment preset. Conflicts with `tamboUrl`. */\n environment?: \"production\" | \"staging\";\n /** User key for identifying the user. */\n userKey?: string;\n /** User token for token-based auth. */\n userToken?: string;\n /** Tools to register on creation. */\n tools?: TamboTool[];\n /** MCP servers to connect on init. */\n mcpServers?: McpServerInfo[];\n /** Callback invoked before each run. */\n beforeRun?: (context: BeforeRunContext) => void | Promise<void>;\n}\n\n/**\n * Options for the `run()` method.\n */\nexport interface RunOptions {\n /** Thread ID to run on. If omitted, creates a new thread. */\n threadId?: string;\n /** Whether to auto-execute tools when the model requests them. */\n autoExecuteTools?: boolean;\n /** Max tool execution rounds (default 10). */\n maxSteps?: number;\n /** How the model should select tools. */\n toolChoice?: ToolChoice;\n /** Enable debug logging. */\n debug?: boolean;\n /** Additional context merged into the message. */\n additionalContext?: Record<string, unknown>;\n /** AbortSignal for cancellation. */\n signal?: AbortSignal;\n /** User message text for optimistic display. */\n userMessageText?: string;\n}\n\n/**\n * Public client state exposed via `getState()`.\n */\nexport interface ClientState {\n /** Map of thread ID to thread state. */\n threadMap: Record<string, ThreadState>;\n /** Currently active thread ID. */\n currentThreadId: string;\n}\n\n// Track active runs per thread to prevent concurrent runs\ntype ActiveRuns = Record<string, TamboStream>;\n\n/**\n * TamboClient manages threads, tool execution, streaming, and state.\n *\n * Compatible with `useSyncExternalStore(client.subscribe, client.getState)`\n * for React integration.\n * @example\n * ```typescript\n * const client = new TamboClient({ apiKey: \"your-api-key\" });\n *\n * // Stream mode\n * const stream = client.run(\"Hello, AI!\");\n * for await (const { event, snapshot } of stream) {\n * console.log(snapshot.messages);\n * }\n *\n * // Promise mode\n * const thread = await client.run(\"Hello, AI!\").thread;\n * console.log(thread.messages);\n * ```\n */\nexport class TamboClient {\n private sdkClient: TamboAI;\n private state: StreamState;\n private listeners = new Set<() => void>();\n private pendingNotification = false;\n private toolRegistry: TamboToolRegistry = {};\n private componentList: ComponentRegistry = {};\n private mcpClients = new Map<string, MCPClient>();\n private activeRuns: ActiveRuns = {};\n private contextHelpers = new Map<string, ContextHelperFn>();\n private readonly options: TamboClientOptions;\n\n /**\n * Create a new TamboClient.\n * @param options - Client configuration.\n */\n constructor(options: TamboClientOptions) {\n if (options.tamboUrl && options.environment) {\n throw new Error(\n \"Cannot specify both 'tamboUrl' and 'environment'. Choose one.\",\n );\n }\n\n if (options.userKey && options.userToken) {\n throw new Error(\n \"Cannot specify both 'userKey' and 'userToken'. Choose one.\",\n );\n }\n\n this.options = options;\n this.state = createInitialState();\n\n // Resolve base URL\n const baseURL =\n options.tamboUrl ?? this.resolveBaseUrl(options.environment);\n\n this.sdkClient = new TamboAI({\n apiKey: options.apiKey,\n baseURL,\n });\n\n // Register initial tools\n if (options.tools) {\n for (const tool of options.tools) {\n this.toolRegistry[tool.name] = tool;\n }\n }\n\n // Connect MCP servers (fire-and-forget)\n if (options.mcpServers) {\n for (const server of options.mcpServers) {\n void this.connectMcpServer(server).catch((err) => {\n console.error(`[TamboClient] Failed to connect MCP server:`, err);\n });\n }\n }\n }\n\n // -- Core operations --\n\n /**\n * Start a new run with a message. Returns a TamboStream immediately.\n *\n * The stream's processing loop runs in the background. Use async iteration\n * to observe events, or `await stream.thread` to get the final result.\n * @param message - The message string or InputMessage object.\n * @param options - Run configuration.\n * @returns A TamboStream for observing the response.\n */\n run(message: string | InputMessage, options: RunOptions = {}): TamboStream {\n const {\n threadId,\n autoExecuteTools = true,\n maxSteps = 10,\n toolChoice,\n debug = false,\n additionalContext,\n signal,\n userMessageText,\n } = options;\n\n // Resolve the actual thread ID\n const resolvedThreadId =\n threadId && !isPlaceholderThreadId(threadId) ? threadId : undefined;\n\n // Check for concurrent runs\n if (resolvedThreadId && this.activeRuns[resolvedThreadId]) {\n throw new Error(\n `A run is already active on thread \"${resolvedThreadId}\". Wait for it to complete or abort it first.`,\n );\n }\n\n // Normalize message to InputMessage\n const inputMessage: InputMessage =\n typeof message === \"string\"\n ? { role: \"user\", content: [{ type: \"text\", text: message }] }\n : message;\n\n // If userMessageText not provided, extract from string messages\n const resolvedUserMessageText =\n userMessageText ?? (typeof message === \"string\" ? message : undefined);\n\n // Get previousRunId from thread state\n const threadState = resolvedThreadId\n ? this.state.threadMap[resolvedThreadId]\n : undefined;\n const previousRunId =\n threadState?.streaming.runId ?? threadState?.lastCompletedRunId;\n\n // Collect context helpers and beforeRun in the stream's processing loop\n const streamOptions: TamboStreamOptions = {\n client: this.sdkClient,\n message: inputMessage,\n threadId: resolvedThreadId,\n userMessageText: resolvedUserMessageText,\n componentList: this.componentList,\n toolRegistry: this.toolRegistry,\n userKey: this.options.userKey,\n previousRunId,\n additionalContext: this.mergeContextForRun(additionalContext),\n toolChoice,\n autoExecuteTools,\n maxSteps,\n debug,\n signal,\n dispatch: (action) => this.dispatch(action),\n getThreadSnapshot: (tid) => this.getThread(tid),\n };\n\n const stream = new TamboStream(streamOptions);\n\n // Track active run\n if (resolvedThreadId) {\n this.activeRuns[resolvedThreadId] = stream;\n }\n\n // Clean up active run tracking when stream completes\n void stream.thread\n .catch(() => {\n // Error handled by stream consumer\n })\n .finally(() => {\n if (resolvedThreadId) {\n delete this.activeRuns[resolvedThreadId];\n }\n });\n\n return stream;\n }\n\n // -- Thread management --\n\n /**\n * Switch to an existing thread. Fetches thread data from the API.\n * @param threadId - The thread ID to switch to.\n */\n async switchThread(threadId: string): Promise<void> {\n await this.fetchThread(threadId);\n this.dispatch({ type: \"SET_CURRENT_THREAD\", threadId });\n }\n\n /**\n * Start a new thread. Returns the new thread ID.\n * @returns The new placeholder thread ID.\n */\n startNewThread(): string {\n const threadId = PLACEHOLDER_THREAD_ID;\n this.dispatch({ type: \"START_NEW_THREAD\", threadId });\n return threadId;\n }\n\n /**\n * Get a thread from local state.\n * @param threadId - The thread ID.\n * @returns The thread, or undefined if not found.\n */\n getThread(threadId: string): TamboThread | undefined {\n return this.state.threadMap[threadId]?.thread;\n }\n\n /**\n * List all threads from the API.\n * @returns An array of TamboThread objects.\n */\n async listThreads(): Promise<TamboThread[]> {\n const response = await this.sdkClient.threads.list({\n userKey: this.options.userKey,\n });\n\n // Convert SDK response to TamboThread format\n return response.threads.map((apiThread) => ({\n id: apiThread.id,\n name: apiThread.name ?? undefined,\n messages: [],\n status: \"idle\" as const,\n metadata: (apiThread.metadata as Record<string, unknown>) ?? undefined,\n createdAt: apiThread.createdAt,\n updatedAt: apiThread.updatedAt,\n lastRunCancelled: false,\n }));\n }\n\n /**\n * Fetch a thread from the API and hydrate it into local state.\n * @param threadId - The thread ID to fetch.\n * @returns The fetched thread.\n */\n async fetchThread(threadId: string): Promise<TamboThread> {\n const apiThread = await this.sdkClient.threads.retrieve(threadId);\n\n // Initialize thread state if not present\n if (!this.state.threadMap[threadId]) {\n this.dispatch({ type: \"INIT_THREAD\", threadId });\n }\n\n // Load messages from the API response\n if (apiThread.messages && apiThread.messages.length > 0) {\n this.dispatch({\n type: \"LOAD_THREAD_MESSAGES\",\n threadId,\n messages: apiThread.messages.map((msg) => ({\n id: msg.id,\n role: msg.role,\n content: msg.content as TamboThread[\"messages\"][0][\"content\"],\n createdAt: msg.createdAt,\n threadId,\n })),\n });\n }\n\n // Set lastCompletedRunId if available\n if (apiThread.lastCompletedRunId) {\n this.dispatch({\n type: \"SET_LAST_COMPLETED_RUN_ID\",\n threadId,\n lastCompletedRunId: apiThread.lastCompletedRunId,\n });\n }\n\n // Update thread name if available\n if (apiThread.name) {\n this.dispatch({\n type: \"UPDATE_THREAD_NAME\",\n threadId,\n name: apiThread.name,\n });\n }\n\n const thread = this.getThread(threadId);\n if (!thread) {\n throw new Error(`Failed to hydrate thread ${threadId}`);\n }\n return thread;\n }\n\n // -- Registration --\n\n /**\n * Register a single tool.\n * @param tool - The tool to register.\n */\n registerTool(tool: TamboTool): void {\n this.toolRegistry[tool.name] = tool;\n }\n\n /**\n * Register multiple tools at once.\n * @param tools - The tools to register.\n */\n registerTools(tools: TamboTool[]): void {\n for (const tool of tools) {\n this.toolRegistry[tool.name] = tool;\n }\n }\n\n /**\n * Register a component.\n * @param name - Component name.\n * @param component - The registered component.\n */\n registerComponent(name: string, component: ComponentRegistry[string]): void {\n this.componentList[name] = component;\n }\n\n // -- State access (useSyncExternalStore-compatible) --\n\n /**\n * Get the current client state snapshot.\n * @returns The current client state.\n */\n getState(): ClientState {\n return this.state;\n }\n\n /**\n * Subscribe to state changes.\n * @param listener - Callback invoked on state change.\n * @returns Unsubscribe function.\n */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n // -- Auth --\n\n /**\n * Compute and return the current auth state. Not stored in state;\n * computed on each call from config.\n * @returns The current auth state.\n */\n getAuthState(): TamboAuthState {\n const { userKey, userToken } = this.options;\n\n if (userKey && userToken) {\n return { status: \"invalid\" };\n }\n if (userKey) {\n return { status: \"identified\", source: \"userKey\" };\n }\n if (userToken) {\n // Token exchange would happen here in full implementation\n return { status: \"exchanging\" };\n }\n return { status: \"unauthenticated\" };\n }\n\n // -- Run control --\n\n /**\n * Cancel the active run on a thread.\n * @param threadId - The thread whose run to cancel. Defaults to current thread.\n */\n async cancelRun(threadId?: string): Promise<void> {\n const tid = threadId ?? this.state.currentThreadId;\n const activeStream = this.activeRuns[tid];\n if (activeStream) {\n activeStream.abort();\n }\n\n // Also try server-side cancellation\n const threadState = this.state.threadMap[tid];\n const runId = threadState?.streaming.runId;\n if (runId && tid) {\n try {\n await this.sdkClient.threads.runs.delete(runId, { threadId: tid });\n } catch (_error) {\n // Server-side cancellation is best-effort\n }\n }\n }\n\n // -- Thread naming --\n\n /**\n * Update a thread's name via the API.\n * @param threadId - The thread ID.\n * @param name - The new name.\n */\n async updateThreadName(threadId: string, name: string): Promise<void> {\n await this.sdkClient.threads.update(threadId, { name });\n this.dispatch({ type: \"UPDATE_THREAD_NAME\", threadId, name });\n }\n\n /**\n * Generate a thread name via the API.\n * @param threadId - The thread ID.\n * @returns The generated name.\n */\n async generateThreadName(threadId: string): Promise<string> {\n const threadWithName =\n await this.sdkClient.beta.threads.generateName(threadId);\n const name = threadWithName.name ?? \"\";\n if (name) {\n this.dispatch({ type: \"UPDATE_THREAD_NAME\", threadId, name });\n }\n return name;\n }\n\n // -- MCP --\n\n /**\n * Connect to an MCP server.\n * @param serverInfo - The MCP server configuration.\n * @returns The connected MCPClient.\n */\n async connectMcpServer(serverInfo: McpServerInfo): Promise<MCPClient> {\n const key = getMcpServerUniqueKey(serverInfo);\n const existing = this.mcpClients.get(key);\n if (existing) {\n return existing;\n }\n\n const mcpClient = await MCPClient.create(\n serverInfo.url,\n serverInfo.transport,\n serverInfo.customHeaders,\n undefined, // authProvider\n undefined, // sessionId\n );\n this.mcpClients.set(key, mcpClient);\n return mcpClient;\n }\n\n /**\n * Disconnect from an MCP server.\n * @param serverKey - The server key (from getMcpServerUniqueKey).\n */\n async disconnectMcpServer(serverKey: string): Promise<void> {\n const client = this.mcpClients.get(serverKey);\n if (client) {\n await client.close();\n this.mcpClients.delete(serverKey);\n }\n }\n\n /**\n * Get all connected MCP clients.\n * @returns Record of server key to MCPClient.\n */\n getMcpClients(): Record<string, MCPClient> {\n return Object.fromEntries(this.mcpClients.entries());\n }\n\n /**\n * Get an MCP token for a context key.\n * @param contextKey - The context key.\n * @param threadId - Optional thread ID for session-bound tokens.\n * @returns The MCP access token response.\n */\n async getMcpToken(\n contextKey?: string,\n threadId?: string,\n ): Promise<{\n mcpAccessToken?: string;\n expiresAt?: number;\n hasSession?: boolean;\n }> {\n const response = await this.sdkClient.beta.auth.getMcpToken({\n contextKey,\n threadId,\n });\n return {\n mcpAccessToken: response.mcpAccessToken,\n expiresAt: response.expiresAt,\n hasSession: response.hasSession,\n };\n }\n\n // -- Suggestions --\n\n /**\n * List suggestions for a message.\n * @param messageId - The message ID.\n * @param threadId - The thread ID.\n * @returns Array of suggestions.\n */\n async listSuggestions(\n messageId: string,\n threadId: string,\n ): Promise<Suggestion[]> {\n const response = await this.sdkClient.beta.threads.suggestions.list(\n messageId,\n { id: threadId },\n );\n return response;\n }\n\n /**\n * Generate suggestions for a message.\n * @param messageId - The message ID.\n * @param threadId - The thread ID.\n * @param options - Generation options.\n * @param options.maxSuggestions - Maximum number of suggestions to generate.\n * @returns Array of generated suggestions.\n */\n async generateSuggestions(\n messageId: string,\n threadId: string,\n options?: { maxSuggestions?: number },\n ): Promise<Suggestion[]> {\n const response = await this.sdkClient.beta.threads.suggestions.generate(\n messageId,\n {\n id: threadId,\n maxSuggestions: options?.maxSuggestions,\n },\n );\n return response;\n }\n\n // -- Context helpers --\n\n /**\n * Add a context helper that provides additional context on each run.\n * @param name - Unique name for the helper.\n * @param fn - Function returning context data.\n */\n addContextHelper(name: string, fn: ContextHelperFn): void {\n this.contextHelpers.set(name, fn);\n }\n\n /**\n * Remove a context helper.\n * @param name - The helper name to remove.\n */\n removeContextHelper(name: string): void {\n this.contextHelpers.delete(name);\n }\n\n /**\n * Get the underlying SDK client (for advanced use cases).\n * @returns The TamboAI SDK client.\n */\n getSdkClient(): TamboAI {\n return this.sdkClient;\n }\n\n // -- Private methods --\n\n /**\n * Dispatch an action to the state reducer and notify listeners.\n * @param action - The action to dispatch.\n */\n private dispatch(action: StreamAction): void {\n this.state = streamReducer(this.state, action);\n this.notifyListeners();\n }\n\n /**\n * Batch subscriber notifications via queueMicrotask to reduce\n * re-renders during high-frequency streaming.\n */\n private notifyListeners(): void {\n if (!this.pendingNotification) {\n this.pendingNotification = true;\n queueMicrotask(() => {\n this.pendingNotification = false;\n for (const listener of this.listeners) {\n listener();\n }\n });\n }\n }\n\n /**\n * Resolve the base URL from an environment preset.\n * @param environment - The environment to resolve.\n * @returns The resolved base URL.\n */\n private resolveBaseUrl(environment?: \"production\" | \"staging\"): string {\n switch (environment) {\n case \"staging\":\n return \"https://api.staging.tambo.co\";\n case \"production\":\n default:\n return \"https://api.tambo.co\";\n }\n }\n\n /**\n * Merge context from registered context helpers with provided additional context.\n * @param additionalContext - Context provided by the caller.\n * @returns Merged context object.\n */\n private mergeContextForRun(\n additionalContext?: Record<string, unknown>,\n ): Record<string, unknown> | undefined {\n if (this.contextHelpers.size === 0 && !additionalContext) {\n return undefined;\n }\n\n // Context helpers are gathered synchronously here for the initial merge.\n // Async helpers are not supported in this synchronous path — they should\n // be awaited in the stream processing loop via beforeRun.\n const merged: Record<string, unknown> = { ...additionalContext };\n return merged;\n }\n}\n"]}