@yeaft/webchat-agent 0.1.561 → 0.1.563
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.
- package/package.json +1 -1
- package/unify/engine.js +12 -0
- package/unify/llm/adapter.js +23 -0
- package/unify/llm/anthropic.js +52 -8
- package/unify/llm/chat-completions.js +50 -7
- package/unify/web-bridge.js +3 -0
package/package.json
CHANGED
package/unify/engine.js
CHANGED
|
@@ -661,6 +661,13 @@ export class Engine {
|
|
|
661
661
|
const toolCalls = [];
|
|
662
662
|
let stopReason = 'end_turn';
|
|
663
663
|
const totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
664
|
+
// task-344: capture redacted raw request / raw response for debug panel.
|
|
665
|
+
let rawRequest = null;
|
|
666
|
+
let rawResponse = null;
|
|
667
|
+
const captureRawExchange = (exchange) => {
|
|
668
|
+
if (exchange?.rawRequest) rawRequest = exchange.rawRequest;
|
|
669
|
+
if (exchange?.rawResponse) rawResponse = exchange.rawResponse;
|
|
670
|
+
};
|
|
664
671
|
|
|
665
672
|
yield { type: 'turn_start', turnNumber };
|
|
666
673
|
|
|
@@ -678,6 +685,7 @@ export class Engine {
|
|
|
678
685
|
maxTokens: this.#config.maxOutputTokens || 16384,
|
|
679
686
|
effort: resolvedEffort,
|
|
680
687
|
signal,
|
|
688
|
+
onRawExchange: captureRawExchange,
|
|
681
689
|
})) {
|
|
682
690
|
switch (event.type) {
|
|
683
691
|
case 'text_delta':
|
|
@@ -730,6 +738,8 @@ export class Engine {
|
|
|
730
738
|
latencyMs,
|
|
731
739
|
ttfbMs,
|
|
732
740
|
stopReason: 'error',
|
|
741
|
+
rawRequest,
|
|
742
|
+
rawResponse,
|
|
733
743
|
};
|
|
734
744
|
|
|
735
745
|
// ─── task-325a: abort short-circuit ────────────────
|
|
@@ -806,6 +816,8 @@ export class Engine {
|
|
|
806
816
|
latencyMs,
|
|
807
817
|
ttfbMs,
|
|
808
818
|
stopReason,
|
|
819
|
+
rawRequest,
|
|
820
|
+
rawResponse,
|
|
809
821
|
};
|
|
810
822
|
|
|
811
823
|
// Append assistant message to conversation
|
package/unify/llm/adapter.js
CHANGED
|
@@ -101,6 +101,29 @@ export class LLMAbortError extends Error {
|
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
// ─── task-344: Raw payload redaction helper ────────────────────
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Redact sensitive headers (API keys / bearer tokens) from a raw request
|
|
108
|
+
* shape before exposing it to debug UI. Always returns a NEW object — never
|
|
109
|
+
* mutates the input.
|
|
110
|
+
*
|
|
111
|
+
* @param {{ url: string, method: string, headers: object, body: any }} req
|
|
112
|
+
* @returns {{ url: string, method: string, headers: object, body: any }}
|
|
113
|
+
*/
|
|
114
|
+
export function redactRawRequest(req) {
|
|
115
|
+
if (!req || typeof req !== 'object') return req;
|
|
116
|
+
const headers = { ...(req.headers || {}) };
|
|
117
|
+
// Common auth headers
|
|
118
|
+
for (const k of Object.keys(headers)) {
|
|
119
|
+
const lower = k.toLowerCase();
|
|
120
|
+
if (lower === 'x-api-key' || lower === 'authorization' || lower === 'api-key') {
|
|
121
|
+
headers[k] = '***';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { url: req.url, method: req.method, headers, body: req.body };
|
|
125
|
+
}
|
|
126
|
+
|
|
104
127
|
// ─── Base Class ────────────────────────────────────────────────
|
|
105
128
|
|
|
106
129
|
/**
|
package/unify/llm/anthropic.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
LLMContextError,
|
|
14
14
|
LLMServerError,
|
|
15
15
|
LLMAbortError,
|
|
16
|
+
redactRawRequest,
|
|
16
17
|
} from './adapter.js';
|
|
17
18
|
import {
|
|
18
19
|
normalizeEffort,
|
|
@@ -139,7 +140,7 @@ export class AnthropicAdapter extends LLMAdapter {
|
|
|
139
140
|
* @param {{ model: string, system: string, messages: import('./adapter.js').UnifiedMessage[], tools?: import('./adapter.js').UnifiedToolDef[], maxTokens?: number, effort?: 'low'|'medium'|'high'|'max', signal?: AbortSignal }} params
|
|
140
141
|
* @returns {AsyncGenerator<import('./adapter.js').StreamEvent>}
|
|
141
142
|
*/
|
|
142
|
-
async *stream({ model, system, messages, tools, maxTokens = 16384, effort, signal }) {
|
|
143
|
+
async *stream({ model, system, messages, tools, maxTokens = 16384, effort, signal, onRawExchange }) {
|
|
143
144
|
if (signal?.aborted) throw new LLMAbortError();
|
|
144
145
|
|
|
145
146
|
const body = {
|
|
@@ -173,19 +174,40 @@ export class AnthropicAdapter extends LLMAdapter {
|
|
|
173
174
|
const translatedTools = this.#translateTools(tools);
|
|
174
175
|
if (translatedTools) body.tools = translatedTools;
|
|
175
176
|
|
|
176
|
-
const
|
|
177
|
+
const url = `${this.#baseUrl}/v1/messages`;
|
|
178
|
+
const headers = {
|
|
179
|
+
'Content-Type': 'application/json',
|
|
180
|
+
'x-api-key': this.#apiKey,
|
|
181
|
+
'anthropic-version': API_VERSION,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// task-344: expose raw request (redacted) for debug panel.
|
|
185
|
+
const rawRequest = redactRawRequest({ url, method: 'POST', headers, body });
|
|
186
|
+
|
|
187
|
+
const response = await fetch(url, {
|
|
177
188
|
method: 'POST',
|
|
178
|
-
headers
|
|
179
|
-
'Content-Type': 'application/json',
|
|
180
|
-
'x-api-key': this.#apiKey,
|
|
181
|
-
'anthropic-version': API_VERSION,
|
|
182
|
-
},
|
|
189
|
+
headers,
|
|
183
190
|
body: JSON.stringify(body),
|
|
184
191
|
signal,
|
|
185
192
|
});
|
|
186
193
|
|
|
187
194
|
if (!response.ok) {
|
|
188
195
|
const errorBody = await response.text();
|
|
196
|
+
// task-344: capture error response too, then throw.
|
|
197
|
+
if (onRawExchange) {
|
|
198
|
+
try {
|
|
199
|
+
onRawExchange({
|
|
200
|
+
rawRequest,
|
|
201
|
+
rawResponse: {
|
|
202
|
+
status: response.status,
|
|
203
|
+
headers: response.headers && typeof response.headers.entries === 'function'
|
|
204
|
+
? Object.fromEntries(response.headers.entries())
|
|
205
|
+
: {},
|
|
206
|
+
body: errorBody,
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
} catch { /* ignore */ }
|
|
210
|
+
}
|
|
189
211
|
throw this.#classifyError(response.status, errorBody);
|
|
190
212
|
}
|
|
191
213
|
|
|
@@ -196,13 +218,21 @@ export class AnthropicAdapter extends LLMAdapter {
|
|
|
196
218
|
let currentToolCallId = null;
|
|
197
219
|
let currentToolName = null;
|
|
198
220
|
let currentToolInput = '';
|
|
221
|
+
// task-344: accumulate raw SSE body for debug exposure.
|
|
222
|
+
let rawSseBody = '';
|
|
223
|
+
const responseHeaders = response.headers && typeof response.headers.entries === 'function'
|
|
224
|
+
? Object.fromEntries(response.headers.entries())
|
|
225
|
+
: {};
|
|
226
|
+
const responseStatus = response.status;
|
|
199
227
|
|
|
200
228
|
try {
|
|
201
229
|
while (true) {
|
|
202
230
|
const { done, value } = await reader.read();
|
|
203
231
|
if (done) break;
|
|
204
232
|
|
|
205
|
-
|
|
233
|
+
const chunkText = decoder.decode(value, { stream: true });
|
|
234
|
+
buffer += chunkText;
|
|
235
|
+
rawSseBody += chunkText;
|
|
206
236
|
const lines = buffer.split('\n');
|
|
207
237
|
buffer = lines.pop() || ''; // Keep incomplete line
|
|
208
238
|
|
|
@@ -292,6 +322,20 @@ export class AnthropicAdapter extends LLMAdapter {
|
|
|
292
322
|
}
|
|
293
323
|
} finally {
|
|
294
324
|
reader.releaseLock();
|
|
325
|
+
// task-344: emit raw exchange after stream completes (or errors).
|
|
326
|
+
if (onRawExchange) {
|
|
327
|
+
try {
|
|
328
|
+
onRawExchange({
|
|
329
|
+
rawRequest,
|
|
330
|
+
rawResponse: {
|
|
331
|
+
status: responseStatus,
|
|
332
|
+
headers: responseHeaders,
|
|
333
|
+
body: rawSseBody,
|
|
334
|
+
format: 'sse',
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
} catch { /* ignore */ }
|
|
338
|
+
}
|
|
295
339
|
}
|
|
296
340
|
}
|
|
297
341
|
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
LLMContextError,
|
|
28
28
|
LLMServerError,
|
|
29
29
|
LLMAbortError,
|
|
30
|
+
redactRawRequest,
|
|
30
31
|
} from './adapter.js';
|
|
31
32
|
import {
|
|
32
33
|
normalizeEffort,
|
|
@@ -199,7 +200,7 @@ export class ChatCompletionsAdapter extends LLMAdapter {
|
|
|
199
200
|
* @param {{ model: string, system: string, messages: import('./adapter.js').UnifiedMessage[], tools?: import('./adapter.js').UnifiedToolDef[], maxTokens?: number, effort?: 'low'|'medium'|'high'|'max', extraBody?: object, signal?: AbortSignal }} params
|
|
200
201
|
* @returns {AsyncGenerator<import('./adapter.js').StreamEvent>}
|
|
201
202
|
*/
|
|
202
|
-
async *stream({ model, system, messages, tools, maxTokens = 16384, effort, extraBody, signal }) {
|
|
203
|
+
async *stream({ model, system, messages, tools, maxTokens = 16384, effort, extraBody, signal, onRawExchange }) {
|
|
203
204
|
if (signal?.aborted) throw new LLMAbortError();
|
|
204
205
|
|
|
205
206
|
const body = {
|
|
@@ -231,18 +232,38 @@ export class ChatCompletionsAdapter extends LLMAdapter {
|
|
|
231
232
|
// extraBody allows callers to pass through any additional/override parameters
|
|
232
233
|
if (extraBody) Object.assign(body, extraBody);
|
|
233
234
|
|
|
234
|
-
const
|
|
235
|
+
const url = `${this.#baseUrl}/chat/completions`;
|
|
236
|
+
const headers = {
|
|
237
|
+
'Content-Type': 'application/json',
|
|
238
|
+
'Authorization': `Bearer ${this.#apiKey}`,
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// task-344: expose raw request (redacted) for debug panel.
|
|
242
|
+
const rawRequest = redactRawRequest({ url, method: 'POST', headers, body });
|
|
243
|
+
|
|
244
|
+
const response = await fetch(url, {
|
|
235
245
|
method: 'POST',
|
|
236
|
-
headers
|
|
237
|
-
'Content-Type': 'application/json',
|
|
238
|
-
'Authorization': `Bearer ${this.#apiKey}`,
|
|
239
|
-
},
|
|
246
|
+
headers,
|
|
240
247
|
body: JSON.stringify(body),
|
|
241
248
|
signal,
|
|
242
249
|
});
|
|
243
250
|
|
|
244
251
|
if (!response.ok) {
|
|
245
252
|
const errorBody = await response.text();
|
|
253
|
+
if (onRawExchange) {
|
|
254
|
+
try {
|
|
255
|
+
onRawExchange({
|
|
256
|
+
rawRequest,
|
|
257
|
+
rawResponse: {
|
|
258
|
+
status: response.status,
|
|
259
|
+
headers: response.headers && typeof response.headers.entries === 'function'
|
|
260
|
+
? Object.fromEntries(response.headers.entries())
|
|
261
|
+
: {},
|
|
262
|
+
body: errorBody,
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
} catch { /* ignore */ }
|
|
266
|
+
}
|
|
246
267
|
throw this.#classifyError(response.status, errorBody);
|
|
247
268
|
}
|
|
248
269
|
|
|
@@ -250,6 +271,12 @@ export class ChatCompletionsAdapter extends LLMAdapter {
|
|
|
250
271
|
const reader = response.body.getReader();
|
|
251
272
|
const decoder = new TextDecoder();
|
|
252
273
|
let buffer = '';
|
|
274
|
+
// task-344: accumulate raw SSE body + headers/status for debug exposure.
|
|
275
|
+
let rawSseBody = '';
|
|
276
|
+
const responseHeaders = response.headers && typeof response.headers.entries === 'function'
|
|
277
|
+
? Object.fromEntries(response.headers.entries())
|
|
278
|
+
: {};
|
|
279
|
+
const responseStatus = response.status;
|
|
253
280
|
|
|
254
281
|
// Tool call accumulation — Chat Completions sends tool args as fragments
|
|
255
282
|
// keyed by index within the delta.tool_calls array
|
|
@@ -261,7 +288,9 @@ export class ChatCompletionsAdapter extends LLMAdapter {
|
|
|
261
288
|
const { done, value } = await reader.read();
|
|
262
289
|
if (done) break;
|
|
263
290
|
|
|
264
|
-
|
|
291
|
+
const chunkText = decoder.decode(value, { stream: true });
|
|
292
|
+
buffer += chunkText;
|
|
293
|
+
rawSseBody += chunkText;
|
|
265
294
|
const lines = buffer.split('\n');
|
|
266
295
|
buffer = lines.pop() || '';
|
|
267
296
|
|
|
@@ -345,6 +374,20 @@ export class ChatCompletionsAdapter extends LLMAdapter {
|
|
|
345
374
|
}
|
|
346
375
|
} finally {
|
|
347
376
|
reader.releaseLock();
|
|
377
|
+
// task-344: emit raw exchange after stream completes.
|
|
378
|
+
if (onRawExchange) {
|
|
379
|
+
try {
|
|
380
|
+
onRawExchange({
|
|
381
|
+
rawRequest,
|
|
382
|
+
rawResponse: {
|
|
383
|
+
status: responseStatus,
|
|
384
|
+
headers: responseHeaders,
|
|
385
|
+
body: rawSseBody,
|
|
386
|
+
format: 'sse',
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
} catch { /* ignore */ }
|
|
390
|
+
}
|
|
348
391
|
}
|
|
349
392
|
}
|
|
350
393
|
|
package/unify/web-bridge.js
CHANGED
|
@@ -898,6 +898,9 @@ function handleEngineEvent(event, threadId, hctx) {
|
|
|
898
898
|
latencyMs: event.latencyMs,
|
|
899
899
|
ttfbMs: event.ttfbMs,
|
|
900
900
|
stopReason: event.stopReason,
|
|
901
|
+
// task-344: forward raw request / response (redacted) to web debug panel.
|
|
902
|
+
rawRequest: event.rawRequest,
|
|
903
|
+
rawResponse: event.rawResponse,
|
|
901
904
|
threadId,
|
|
902
905
|
});
|
|
903
906
|
break;
|