@matthesketh/utopia-ai 0.0.5 → 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.
@@ -75,7 +75,7 @@ function googleAdapter(config) {
75
75
  const textParts = parts.filter((p) => p.text).map((p) => p.text);
76
76
  const fnCalls = parts.filter((p) => p.functionCall);
77
77
  const toolCalls = fnCalls.map((p) => ({
78
- id: `call_${crypto.randomUUID()}`,
78
+ id: `call_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
79
79
  name: p.functionCall.name,
80
80
  arguments: p.functionCall.args ?? {}
81
81
  }));
@@ -41,7 +41,7 @@ function googleAdapter(config) {
41
41
  const textParts = parts.filter((p) => p.text).map((p) => p.text);
42
42
  const fnCalls = parts.filter((p) => p.functionCall);
43
43
  const toolCalls = fnCalls.map((p) => ({
44
- id: `call_${crypto.randomUUID()}`,
44
+ id: `call_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
45
45
  name: p.functionCall.name,
46
46
  arguments: p.functionCall.args ?? {}
47
47
  }));
@@ -44,7 +44,8 @@ function ollamaAdapter(config = {}) {
44
44
  const response = await fetch(`${baseURL}/api/chat`, {
45
45
  method: "POST",
46
46
  headers: { "Content-Type": "application/json" },
47
- body: JSON.stringify(body)
47
+ body: JSON.stringify(body),
48
+ signal: AbortSignal.timeout(6e4)
48
49
  });
49
50
  if (!response.ok) {
50
51
  const text = await response.text();
@@ -88,7 +89,8 @@ function ollamaAdapter(config = {}) {
88
89
  const response = await fetch(`${baseURL}/api/chat`, {
89
90
  method: "POST",
90
91
  headers: { "Content-Type": "application/json" },
91
- body: JSON.stringify(body)
92
+ body: JSON.stringify(body),
93
+ signal: AbortSignal.timeout(6e4)
92
94
  });
93
95
  if (!response.ok) {
94
96
  const text = await response.text();
@@ -105,7 +107,12 @@ function ollamaAdapter(config = {}) {
105
107
  buffer = lines.pop() ?? "";
106
108
  for (const line of lines) {
107
109
  if (!line.trim()) continue;
108
- const data = JSON.parse(line);
110
+ let data;
111
+ try {
112
+ data = JSON.parse(line);
113
+ } catch {
114
+ continue;
115
+ }
109
116
  yield {
110
117
  delta: data.message?.content ?? "",
111
118
  finishReason: data.done ? "stop" : void 0,
@@ -20,7 +20,8 @@ function ollamaAdapter(config = {}) {
20
20
  const response = await fetch(`${baseURL}/api/chat`, {
21
21
  method: "POST",
22
22
  headers: { "Content-Type": "application/json" },
23
- body: JSON.stringify(body)
23
+ body: JSON.stringify(body),
24
+ signal: AbortSignal.timeout(6e4)
24
25
  });
25
26
  if (!response.ok) {
26
27
  const text = await response.text();
@@ -64,7 +65,8 @@ function ollamaAdapter(config = {}) {
64
65
  const response = await fetch(`${baseURL}/api/chat`, {
65
66
  method: "POST",
66
67
  headers: { "Content-Type": "application/json" },
67
- body: JSON.stringify(body)
68
+ body: JSON.stringify(body),
69
+ signal: AbortSignal.timeout(6e4)
68
70
  });
69
71
  if (!response.ok) {
70
72
  const text = await response.text();
@@ -81,7 +83,12 @@ function ollamaAdapter(config = {}) {
81
83
  buffer = lines.pop() ?? "";
82
84
  for (const line of lines) {
83
85
  if (!line.trim()) continue;
84
- const data = JSON.parse(line);
86
+ let data;
87
+ try {
88
+ data = JSON.parse(line);
89
+ } catch {
90
+ continue;
91
+ }
85
92
  yield {
86
93
  delta: data.message?.content ?? "",
87
94
  finishReason: data.done ? "stop" : void 0,
package/dist/index.cjs CHANGED
@@ -207,14 +207,17 @@ async function streamSSE(res, stream, options) {
207
207
  "Connection": "keep-alive",
208
208
  "X-Accel-Buffering": "no"
209
209
  });
210
- for await (const chunk of stream) {
211
- options?.onChunk?.(chunk);
212
- res.write(`data: ${JSON.stringify(chunk)}
210
+ try {
211
+ for await (const chunk of stream) {
212
+ options?.onChunk?.(chunk);
213
+ res.write(`data: ${JSON.stringify(chunk)}
213
214
 
214
215
  `);
216
+ }
217
+ res.write("data: [DONE]\n\n");
218
+ } finally {
219
+ res.end();
215
220
  }
216
- res.write("data: [DONE]\n\n");
217
- res.end();
218
221
  }
219
222
  async function collectStream(stream) {
220
223
  let result = "";
@@ -227,22 +230,27 @@ async function* parseSSEStream(response) {
227
230
  const reader = response.body.getReader();
228
231
  const decoder = new TextDecoder();
229
232
  let buffer = "";
230
- while (true) {
231
- const { done, value } = await reader.read();
232
- if (done) break;
233
- buffer += decoder.decode(value, { stream: true });
234
- const lines = buffer.split("\n");
235
- buffer = lines.pop() ?? "";
236
- for (const line of lines) {
237
- if (line.startsWith("data: ")) {
238
- const data = line.slice(6).trim();
239
- if (data === "[DONE]") return;
240
- try {
241
- yield JSON.parse(data);
242
- } catch {
233
+ try {
234
+ while (true) {
235
+ const { done, value } = await reader.read();
236
+ if (done) break;
237
+ buffer += decoder.decode(value, { stream: true });
238
+ const lines = buffer.split("\n");
239
+ buffer = lines.pop() ?? "";
240
+ for (const line of lines) {
241
+ if (line.startsWith("data: ")) {
242
+ const data = line.slice(6).trim();
243
+ if (data === "[DONE]") return;
244
+ try {
245
+ yield JSON.parse(data);
246
+ } catch {
247
+ }
243
248
  }
244
249
  }
245
250
  }
251
+ } finally {
252
+ reader.cancel().catch(() => {
253
+ });
246
254
  }
247
255
  }
248
256
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.js CHANGED
@@ -178,14 +178,17 @@ async function streamSSE(res, stream, options) {
178
178
  "Connection": "keep-alive",
179
179
  "X-Accel-Buffering": "no"
180
180
  });
181
- for await (const chunk of stream) {
182
- options?.onChunk?.(chunk);
183
- res.write(`data: ${JSON.stringify(chunk)}
181
+ try {
182
+ for await (const chunk of stream) {
183
+ options?.onChunk?.(chunk);
184
+ res.write(`data: ${JSON.stringify(chunk)}
184
185
 
185
186
  `);
187
+ }
188
+ res.write("data: [DONE]\n\n");
189
+ } finally {
190
+ res.end();
186
191
  }
187
- res.write("data: [DONE]\n\n");
188
- res.end();
189
192
  }
190
193
  async function collectStream(stream) {
191
194
  let result = "";
@@ -198,22 +201,27 @@ async function* parseSSEStream(response) {
198
201
  const reader = response.body.getReader();
199
202
  const decoder = new TextDecoder();
200
203
  let buffer = "";
201
- while (true) {
202
- const { done, value } = await reader.read();
203
- if (done) break;
204
- buffer += decoder.decode(value, { stream: true });
205
- const lines = buffer.split("\n");
206
- buffer = lines.pop() ?? "";
207
- for (const line of lines) {
208
- if (line.startsWith("data: ")) {
209
- const data = line.slice(6).trim();
210
- if (data === "[DONE]") return;
211
- try {
212
- yield JSON.parse(data);
213
- } catch {
204
+ try {
205
+ while (true) {
206
+ const { done, value } = await reader.read();
207
+ if (done) break;
208
+ buffer += decoder.decode(value, { stream: true });
209
+ const lines = buffer.split("\n");
210
+ buffer = lines.pop() ?? "";
211
+ for (const line of lines) {
212
+ if (line.startsWith("data: ")) {
213
+ const data = line.slice(6).trim();
214
+ if (data === "[DONE]") return;
215
+ try {
216
+ yield JSON.parse(data);
217
+ } catch {
218
+ }
214
219
  }
215
220
  }
216
221
  }
222
+ } finally {
223
+ reader.cancel().catch(() => {
224
+ });
217
225
  }
218
226
  }
219
227
  export {
@@ -139,7 +139,8 @@ function createMCPServer(config) {
139
139
  return { handleRequest, info: () => serverInfo };
140
140
  }
141
141
  function matchesTemplate(pattern, uri) {
142
- const regex = pattern.replace(/\{[^}]+\}/g, "([^/]+)");
142
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
143
+ const regex = escaped.replace(/\\\{[^\\}]+\\\}/g, "([^/]+)");
143
144
  return new RegExp(`^${regex}$`).test(uri);
144
145
  }
145
146
  function makeError(code, message) {
@@ -162,7 +163,8 @@ function createMCPClient(config) {
162
163
  "Content-Type": "application/json",
163
164
  ...config.headers
164
165
  },
165
- body: JSON.stringify(request)
166
+ body: JSON.stringify(request),
167
+ signal: AbortSignal.timeout(3e4)
166
168
  });
167
169
  if (!response.ok) {
168
170
  const text = await response.text();
package/dist/mcp/index.js CHANGED
@@ -111,7 +111,8 @@ function createMCPServer(config) {
111
111
  return { handleRequest, info: () => serverInfo };
112
112
  }
113
113
  function matchesTemplate(pattern, uri) {
114
- const regex = pattern.replace(/\{[^}]+\}/g, "([^/]+)");
114
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
115
+ const regex = escaped.replace(/\\\{[^\\}]+\\\}/g, "([^/]+)");
115
116
  return new RegExp(`^${regex}$`).test(uri);
116
117
  }
117
118
  function makeError(code, message) {
@@ -134,7 +135,8 @@ function createMCPClient(config) {
134
135
  "Content-Type": "application/json",
135
136
  ...config.headers
136
137
  },
137
- body: JSON.stringify(request)
138
+ body: JSON.stringify(request),
139
+ signal: AbortSignal.timeout(3e4)
138
140
  });
139
141
  if (!response.ok) {
140
142
  const text = await response.text();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matthesketh/utopia-ai",
3
- "version": "0.0.5",
3
+ "version": "0.1.0",
4
4
  "description": "AI adapters and MCP support for UtopiaJS",
5
5
  "type": "module",
6
6
  "license": "MIT",