@jxsuite/server 4.2.0 → 4.2.2

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 (2) hide show
  1. package/package.json +11 -11
  2. package/src/ai-api.ts +65 -17
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/server",
3
- "version": "4.2.0",
3
+ "version": "4.2.2",
4
4
  "description": "Jx development server with live reload, server-side proxy, and studio integration",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -42,21 +42,21 @@
42
42
  "dependencies": {
43
43
  "@jxsuite/catalog": "^0.1.0",
44
44
  "@jxsuite/collab": "^0.9.0",
45
- "@jxsuite/compiler": "^4.0.0",
46
- "@jxsuite/create": "^1.3.10",
47
- "@jxsuite/import": "^0.40.2",
48
- "@jxsuite/protocol": "^2.3.0",
49
- "@jxsuite/runtime": "^4.0.0",
45
+ "@jxsuite/compiler": "^4.0.1",
46
+ "@jxsuite/create": "^1.3.11",
47
+ "@jxsuite/import": "^0.40.3",
48
+ "@jxsuite/protocol": "^2.3.1",
49
+ "@jxsuite/runtime": "^4.0.1",
50
50
  "@jxsuite/schema": "^2.2.0",
51
- "@jxsuite/site": "^2.0.0",
52
- "@jxsuite/starters": "^1.8.0",
51
+ "@jxsuite/site": "^2.0.1",
52
+ "@jxsuite/starters": "^1.8.1",
53
53
  "chokidar": "^5.0.0",
54
- "kysely": "^0.29.5",
55
- "zod": "^4.6.4"
54
+ "kysely": "^0.29.6",
55
+ "zod": "^4.6.5"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@jxsuite/auth": "^0.6.0",
59
59
  "@jxsuite/connector": "^0.6.0",
60
- "@jxsuite/parser": "^1.8.0"
60
+ "@jxsuite/parser": "^1.8.1"
61
61
  }
62
62
  }
package/src/ai-api.ts CHANGED
@@ -150,6 +150,48 @@ function writeSSE(controller: ReadableStreamDefaultController, event: unknown):
150
150
  controller.enqueue(new TextEncoder().encode(`data: ${data}\n\n`));
151
151
  }
152
152
 
153
+ /**
154
+ * Extract a human-readable message from an upstream error body, falling back to the raw body (or
155
+ * `fallback` when the body is empty).
156
+ *
157
+ * OpenAI-compatible providers return `{ error: "..." }` or `{ error: { message: "..." } }`.
158
+ * Cloudflare's own REST API — what a BYOK base URL pointed at Workers AI actually returns — uses a
159
+ * different envelope, `{ success: false, errors: [{ code, message }] }`, with no top-level `error`
160
+ * key at all. Without this branch that shape parses as valid JSON with nothing matched, and the
161
+ * caller was left showing the whole raw body (e.g. the full `{"success":false,"errors":[...]}`
162
+ * blob) instead of the human sentence inside it.
163
+ */
164
+ function extractUpstreamErrorMessage(rawBody: string, fallback: string): string {
165
+ if (!rawBody) {
166
+ return fallback;
167
+ }
168
+ try {
169
+ const { error, errors } = JSON.parse(rawBody) as {
170
+ error?: string | { message?: string };
171
+ errors?: { code?: number; message?: string }[];
172
+ };
173
+ if (typeof error === "string") {
174
+ return error;
175
+ }
176
+ if (error) {
177
+ const { message } = error;
178
+ if (message) {
179
+ return message;
180
+ }
181
+ }
182
+ const [first] = errors ?? [];
183
+ if (first) {
184
+ const { message } = first;
185
+ if (message) {
186
+ return message;
187
+ }
188
+ }
189
+ } catch {
190
+ /* Not JSON — use the raw body. */
191
+ }
192
+ return rawBody;
193
+ }
194
+
153
195
  /**
154
196
  * Write a failure response for the non-streaming endpoints.
155
197
  *
@@ -265,20 +307,7 @@ export async function handleChat(req: Request): Promise<Response> {
265
307
  } catch {
266
308
  /* Ignore */
267
309
  }
268
- // Parse the upstream JSON error body (OpenAI returns { error: { message: "..." } },
269
- // While some compatible providers return { error: "..." }). Extract a clean message
270
- // Instead of embedding the raw JSON in the error text.
271
- let cleanMessage = errorBody || response.statusText;
272
- try {
273
- const parsed = JSON.parse(errorBody) as { error?: string | { message?: string } };
274
- if (typeof parsed.error === "string") {
275
- cleanMessage = parsed.error;
276
- } else if (parsed.error?.message) {
277
- cleanMessage = parsed.error.message;
278
- }
279
- } catch {
280
- /* Not JSON — use the raw body. */
281
- }
310
+ const cleanMessage = extractUpstreamErrorMessage(errorBody, response.statusText);
282
311
  writeSSE(controller, {
283
312
  code: String(response.status),
284
313
  message: cleanMessage,
@@ -499,9 +528,22 @@ export async function handleModels(req: Request): Promise<Response> {
499
528
 
500
529
  if (!upstreamResp.ok) {
501
530
  // Upstream failed — return defaults with configured flag so user can still try.
531
+ let errorBody = "";
532
+ try {
533
+ errorBody = await upstreamResp.text();
534
+ } catch {
535
+ /* Ignore */
536
+ }
537
+ const upstreamMessage = extractUpstreamErrorMessage(errorBody, upstreamResp.statusText);
502
538
  const defaults = [{ id: "gpt-4o", name: "GPT-4o", contextWindow: 128_000 }];
503
539
  return Response.json(
504
- { models: defaults, configured: true, managed: false, upstreamError: upstreamResp.status },
540
+ {
541
+ models: defaults,
542
+ configured: true,
543
+ managed: false,
544
+ upstreamError: upstreamResp.status,
545
+ upstreamMessage,
546
+ },
505
547
  {
506
548
  headers: { "Content-Type": "application/json" },
507
549
  },
@@ -529,11 +571,17 @@ export async function handleModels(req: Request): Promise<Response> {
529
571
  headers: { "Content-Type": "application/json" },
530
572
  },
531
573
  );
532
- } catch {
574
+ } catch (error) {
533
575
  // Network error → return defaults.
534
576
  const defaults = [{ id: "gpt-4o", name: "GPT-4o", contextWindow: 128_000 }];
535
577
  return Response.json(
536
- { models: defaults, configured: true, managed: false, upstreamError: "network" },
578
+ {
579
+ models: defaults,
580
+ configured: true,
581
+ managed: false,
582
+ upstreamError: "network",
583
+ upstreamMessage: (error as Error).message,
584
+ },
537
585
  {
538
586
  headers: { "Content-Type": "application/json" },
539
587
  },