@yeaft/webchat-agent 1.0.264 → 1.0.265

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.
@@ -1 +1 @@
1
- {"version":"1.0.264"}
1
+ {"version":"1.0.265"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.264",
3
+ "version": "1.0.265",
4
4
  "description": "Remote worker agent for Yeaft Web Code Agent — connects the native Yeaft engine, CLI providers, and workbench tools",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -77,7 +77,7 @@ export async function archiveOne({ root, scopeDir, message }) {
77
77
  await fs.mkdir(dirname(path), { recursive: true });
78
78
  await fs.writeFile(path, body, 'utf8');
79
79
  const sizeStr = formatSize(body.length);
80
- const preview = body.slice(0, STUB_PREVIEW_LEN).replace(/\s+/g, ' ');
80
+ const preview = body.slice(0, STUB_PREVIEW_LEN).toWellFormed().replace(/\s+/g, ' ');
81
81
  const stub = {
82
82
  role: 'tool',
83
83
  toolCallId,
@@ -419,6 +419,56 @@ export function redactRawRequest(req) {
419
419
  return { url: req.url, method: req.method, headers, body: req.body };
420
420
  }
421
421
 
422
+ /**
423
+ * Return a wire-safe copy of a JSON-compatible value.
424
+ *
425
+ * JavaScript strings may contain lone UTF-16 surrogates. JSON.stringify keeps
426
+ * them as `\\ud800`-style escapes, but strict API servers reject them because
427
+ * they cannot represent Unicode scalar values in UTF-8. Replace only malformed
428
+ * code units with U+FFFD; valid surrogate pairs (including emoji) are kept.
429
+ *
430
+ * @param {any} value
431
+ * @returns {any}
432
+ */
433
+ export function toWellFormedJson(value) {
434
+ // Let the native serializer retain its complete semantics first: toJSON,
435
+ // boxed primitives, non-finite numbers, array holes, and omitted values.
436
+ const serialized = JSON.stringify(value, (_key, child) => {
437
+ if (typeof child === 'string') return child.toWellFormed();
438
+ // JSON.stringify invokes the replacer before unboxing String objects.
439
+ // String#valueOf checks the real [[StringData]] internal slot across realms;
440
+ // unlike instanceof or Object#toString, it cannot be spoofed by a caller.
441
+ if (child && typeof child === 'object') {
442
+ try {
443
+ return String.prototype.valueOf.call(child).toWellFormed();
444
+ } catch (err) {
445
+ if (!(err instanceof TypeError)) throw err;
446
+ }
447
+ }
448
+ return child;
449
+ });
450
+ if (serialized === undefined) return undefined;
451
+
452
+ // The replacer cannot rename object keys. Rebuild only the already-serialized
453
+ // plain JSON tree so malformed keys are fixed too. Null-prototype containers
454
+ // keep an own "__proto__" key as data rather than invoking the legacy setter.
455
+ const normalizeKeys = child => {
456
+ if (Array.isArray(child)) return child.map(normalizeKeys);
457
+ if (!child || typeof child !== 'object') return child;
458
+ const out = Object.create(null);
459
+ for (const [key, nested] of Object.entries(child)) {
460
+ Object.defineProperty(out, key.toWellFormed(), {
461
+ value: normalizeKeys(nested),
462
+ enumerable: true,
463
+ configurable: true,
464
+ writable: true,
465
+ });
466
+ }
467
+ return out;
468
+ };
469
+ return normalizeKeys(JSON.parse(serialized));
470
+ }
471
+
422
472
  /**
423
473
  * Snapshot a Fetch Response's headers into a plain object for the debug
424
474
  * panel. Defensive against polyfilled / mocked Response shapes that don't
@@ -19,6 +19,7 @@ import {
19
19
  redactRawRequest,
20
20
  safeHeaders,
21
21
  SseLineBuffer,
22
+ toWellFormedJson,
22
23
  } from './adapter.js';
23
24
  import {
24
25
  normalizeEffort,
@@ -261,6 +262,7 @@ export class AnthropicAdapter extends LLMAdapter {
261
262
 
262
263
  const translatedTools = this.#translateTools(tools);
263
264
  if (translatedTools) body.tools = translatedTools;
265
+ const wireBody = toWellFormedJson(body);
264
266
 
265
267
  const url = `${this.#baseUrl}/v1/messages`;
266
268
  const headers = this.#headers();
@@ -268,7 +270,7 @@ export class AnthropicAdapter extends LLMAdapter {
268
270
  // Expose the raw request (auth-redacted) for the debug panel. The body
269
271
  // is captured verbatim — never truncated — so "copy request" matches
270
272
  // exactly what we POST to the LLM.
271
- const rawRequest = redactRawRequest({ url, method: 'POST', headers, body });
273
+ const rawRequest = redactRawRequest({ url, method: 'POST', headers, body: wireBody });
272
274
 
273
275
  let response;
274
276
  try {
@@ -276,7 +278,7 @@ export class AnthropicAdapter extends LLMAdapter {
276
278
  response = await fetch(url, {
277
279
  method: 'POST',
278
280
  headers,
279
- body: JSON.stringify(body),
281
+ body: JSON.stringify(wireBody),
280
282
  signal,
281
283
  });
282
284
  } catch (err) {
@@ -544,6 +546,7 @@ export class AnthropicAdapter extends LLMAdapter {
544
546
  if ((thinkingV1Enabled() || effortSource === 'user') && normEffort) {
545
547
  applyAnthropicThinking(body, model, normEffort, effortContext);
546
548
  }
549
+ const wireBody = toWellFormedJson(body);
547
550
 
548
551
  let response;
549
552
  try {
@@ -551,7 +554,7 @@ export class AnthropicAdapter extends LLMAdapter {
551
554
  response = await fetch(`${this.#baseUrl}/v1/messages`, {
552
555
  method: 'POST',
553
556
  headers: this.#headers(),
554
- body: JSON.stringify(body),
557
+ body: JSON.stringify(wireBody),
555
558
  signal,
556
559
  });
557
560
  } catch (err) {
@@ -38,6 +38,7 @@ import {
38
38
  redactRawRequest,
39
39
  safeHeaders,
40
40
  SseLineBuffer,
41
+ toWellFormedJson,
41
42
  } from './adapter.js';
42
43
  import {
43
44
  normalizeEffort,
@@ -187,7 +188,7 @@ export class OpenAIResponsesAdapter extends LLMAdapter {
187
188
  type: 'function_call',
188
189
  call_id: tc.id,
189
190
  name: tc.name,
190
- arguments: JSON.stringify(tc.input ?? {}),
191
+ arguments: JSON.stringify(toWellFormedJson(tc.input ?? {})),
191
192
  });
192
193
  }
193
194
  }
@@ -285,6 +286,7 @@ export class OpenAIResponsesAdapter extends LLMAdapter {
285
286
  }
286
287
 
287
288
  if (extraBody) Object.assign(body, extraBody);
289
+ const wireBody = toWellFormedJson(body);
288
290
 
289
291
  const url = `${this.#baseUrl}/responses`;
290
292
  const headers = {
@@ -294,7 +296,7 @@ export class OpenAIResponsesAdapter extends LLMAdapter {
294
296
 
295
297
  // Expose the raw request (auth-redacted) for the debug panel. See
296
298
  // `redactRawRequest` in adapter.js for the verbatim-design rationale.
297
- const rawRequest = redactRawRequest({ url, method: 'POST', headers, body });
299
+ const rawRequest = redactRawRequest({ url, method: 'POST', headers, body: wireBody });
298
300
 
299
301
  let response;
300
302
  try {
@@ -302,7 +304,7 @@ export class OpenAIResponsesAdapter extends LLMAdapter {
302
304
  response = await fetch(url, {
303
305
  method: 'POST',
304
306
  headers,
305
- body: JSON.stringify(body),
307
+ body: JSON.stringify(wireBody),
306
308
  signal,
307
309
  });
308
310
  } catch (err) {
@@ -540,6 +542,7 @@ export class OpenAIResponsesAdapter extends LLMAdapter {
540
542
  }
541
543
 
542
544
  if (extraBody) Object.assign(body, extraBody);
545
+ const wireBody = toWellFormedJson(body);
543
546
 
544
547
  let response;
545
548
  try {
@@ -550,7 +553,7 @@ export class OpenAIResponsesAdapter extends LLMAdapter {
550
553
  'Content-Type': 'application/json',
551
554
  'Authorization': `Bearer ${this.#apiKey}`,
552
555
  },
553
- body: JSON.stringify(body),
556
+ body: JSON.stringify(wireBody),
554
557
  signal,
555
558
  });
556
559
  } catch (err) {