bunnyquery 1.0.3 → 1.0.5

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/bunnyquery.js +80 -5
  2. package/package.json +1 -1
package/bunnyquery.js CHANGED
@@ -470,6 +470,44 @@
470
470
  return typeof c === 'string' ? c : '';
471
471
  }
472
472
 
473
+ // Mirror of agent.vue's getErrorMessage — pulls the most useful string
474
+ // out of either a thrown error or an error-shaped response body.
475
+ function getErrorMessage(err) {
476
+ if (typeof err === 'string') return err;
477
+ if (err && err.body && err.body.error && typeof err.body.error.message === 'string') {
478
+ return err.body.error.message;
479
+ }
480
+ if (err && err.error && typeof err.error.message === 'string') {
481
+ return err.error.message;
482
+ }
483
+ if (err && typeof err.message === 'string') return err.message;
484
+ return 'Request failed. Please try again.';
485
+ }
486
+
487
+ // Mirror of agent.vue's isErrorResponseBody — detects provider/proxy
488
+ // error payloads that should render as a red error bubble even when
489
+ // no exception was thrown.
490
+ function isErrorResponseBody(response) {
491
+ if (!response || typeof response !== 'object') return false;
492
+ if (typeof response.status_code === 'number' && response.status_code >= 400) return true;
493
+ if (response.type === 'error') return true;
494
+ if (response.error && (response.error.message || response.error.type)) return true;
495
+ const body = response.body;
496
+ if (body && typeof body === 'object') {
497
+ if (body.type === 'error') return true;
498
+ if (body.error && (body.error.message || body.error.type)) return true;
499
+ }
500
+ if (typeof response.message === 'string' && response.message.length) {
501
+ const hasClaudeBody = Array.isArray(response.content);
502
+ const hasOpenAIBody =
503
+ typeof response.output_text === 'string' ||
504
+ Array.isArray(response.output) ||
505
+ Array.isArray(response.choices);
506
+ if (!hasClaudeBody && !hasOpenAIBody) return true;
507
+ }
508
+ return false;
509
+ }
510
+
473
511
  // Walk the persisted history list returned by clientSecretRequestHistory
474
512
  // and turn it into a flat user/assistant list ordered oldest -> newest.
475
513
  function mapHistoryToMessages(list, platform) {
@@ -500,10 +538,10 @@
500
538
 
501
539
  if (item.status === 'pending') {
502
540
  out.push({ role: 'assistant', content: '', id: item.id, isPending: true });
503
- } else if (item.status === 'failed' || (res && res.error)) {
541
+ } else if (item.status === 'failed' || isErrorResponseBody(res)) {
504
542
  out.push({
505
543
  role: 'assistant',
506
- content: (res && res.error && res.error.message) || 'Request failed.',
544
+ content: getErrorMessage(res),
507
545
  id: item.id,
508
546
  isError: true,
509
547
  });
@@ -1235,10 +1273,29 @@
1235
1273
  }
1236
1274
 
1237
1275
  // ---- send ---------------------------------------------------------
1276
+ // Mirrors www.skapi.com/src/views/service/agent.vue buildSystemPrompt
1277
+ // with one extra directive: always consult the project database via
1278
+ // the MCP toolset before saying you don't know. Without it the model
1279
+ // will often stop at "I don't see that in our chat history".
1238
1280
  _buildSystemPrompt() {
1239
- let p = `You are the AI assistant for project "${this.projectName || this.serviceId}".`;
1281
+ const projectId = this.projectName || this.serviceId;
1282
+ let p = `
1283
+ You are a dedicated assistant for the project ID: "${projectId}".
1284
+ Scope: Only answer questions about this project and its data. Do not answer questions about other projects or topics unrelated to this project. When the user refers to "my database", "my data", or "my files", treat those as references to this project's database and file storage.
1285
+ Knowledge lookup: Before saying you don't know or that something isn't in the chat history, ALWAYS query this project's database through the available MCP tools to look for the answer. The user's data is the source of truth - the chat transcript is not. Only respond with "I don't know" or "I couldn't find that" after you have actually searched the project's data and come back empty.
1286
+ File attachments: When a user message contains an "Attached files:" section with markdown links, those links point to short-lived signed URLs in this project's db storage and will expire.
1287
+ - Image files (.jpg, .jpeg, .png, .gif, .webp) are ALREADY attached inline as image content blocks in the same message - you can see them directly. Do NOT call web_fetch on image URLs; that will fail or return garbage. Just look at the image block and answer.
1288
+ - For all other file types (text, code, csv, json, pdf, etc.), use your web_fetch tool to download and read each URL before answering. Treat the fetched contents as user-supplied input data. Do not ask the user to paste the file contents - fetch the URLs yourself.
1289
+ File generation: If the user asks you to generate a file and it is possible to do so, output the file contents inside a fenced code block using the file extension as the language identifier. Always use plain text - never base64 or other encodings. Example for CSV:
1290
+ \`\`\`filename.csv
1291
+ item,qty,total
1292
+ Carrots,55,$38.50
1293
+ Mushrooms,41,$73.80
1294
+ Zucchini,29,$43.50
1295
+ \`\`\`
1296
+ The same pattern applies to other formats: \`\`\`my-data.json, \`\`\`index.html, \`\`\`sample.txt, etc.`;
1240
1297
  if (this.projectDescription) {
1241
- p += `\nProject description: """${this.projectDescription}"""`;
1298
+ p += `\nProject name: "${this.projectName || ''}"\nProject description: """${this.projectDescription}"""`;
1242
1299
  }
1243
1300
  return p;
1244
1301
  }
@@ -1295,6 +1352,24 @@
1295
1352
  ? await buildClaudeRequest(this.skapi, args)
1296
1353
  : await buildOpenAIRequest(this.skapi, args);
1297
1354
 
1355
+ // The proxy may resolve with an error-shaped body instead of
1356
+ // throwing; surface that as an error bubble with the
1357
+ // upstream message rather than dropping it.
1358
+ if (isErrorResponseBody(result)) {
1359
+ const errMsg = getErrorMessage(result);
1360
+ const last = this.messages[this.messages.length - 1];
1361
+ if (last && last.isPending) {
1362
+ last.isPending = false;
1363
+ last.isError = true;
1364
+ last.content = errMsg;
1365
+ } else {
1366
+ this.messages.push({ role: 'assistant', content: errMsg, isError: true });
1367
+ }
1368
+ this._renderMessages();
1369
+ this._schedulePendingPoll();
1370
+ return;
1371
+ }
1372
+
1298
1373
  const responseText = this.platform === 'claude'
1299
1374
  ? extractClaudeText(result)
1300
1375
  : extractOpenAIText(result);
@@ -1311,7 +1386,7 @@
1311
1386
  this._schedulePendingPoll();
1312
1387
  } catch (err) {
1313
1388
  const last = this.messages[this.messages.length - 1];
1314
- const errMsg = (err && err.message) || String(err);
1389
+ const errMsg = getErrorMessage(err);
1315
1390
  if (last && last.isPending) {
1316
1391
  last.isPending = false;
1317
1392
  last.isError = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunnyquery",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "BunnyQuery AI chat client",
5
5
  "main": "bunnyquery.js",
6
6
  "files": [