@link-assistant/agent 0.16.10 → 0.16.12
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/src/provider/provider.ts +90 -3
package/package.json
CHANGED
package/src/provider/provider.ts
CHANGED
|
@@ -1201,15 +1201,23 @@ export namespace Provider {
|
|
|
1201
1201
|
sessionID: provider.id,
|
|
1202
1202
|
});
|
|
1203
1203
|
|
|
1204
|
-
// Wrap fetch with verbose HTTP logging
|
|
1205
|
-
//
|
|
1204
|
+
// Wrap fetch with verbose HTTP logging for debugging provider issues.
|
|
1205
|
+
// IMPORTANT: The verbose check is done at call time (not SDK creation time)
|
|
1206
|
+
// because the SDK is cached and Flag.OPENCODE_VERBOSE may change after creation.
|
|
1207
|
+
// When verbose is disabled, the wrapper is a no-op passthrough with negligible overhead.
|
|
1206
1208
|
// See: https://github.com/link-assistant/agent/issues/200
|
|
1207
|
-
|
|
1209
|
+
// See: https://github.com/link-assistant/agent/issues/206
|
|
1210
|
+
{
|
|
1208
1211
|
const innerFetch = options['fetch'];
|
|
1209
1212
|
options['fetch'] = async (
|
|
1210
1213
|
input: RequestInfo | URL,
|
|
1211
1214
|
init?: RequestInit
|
|
1212
1215
|
): Promise<Response> => {
|
|
1216
|
+
// Check verbose flag at call time — not at SDK creation time
|
|
1217
|
+
if (!Flag.OPENCODE_VERBOSE) {
|
|
1218
|
+
return innerFetch(input, init);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1213
1221
|
const url =
|
|
1214
1222
|
typeof input === 'string'
|
|
1215
1223
|
? input
|
|
@@ -1289,6 +1297,85 @@ export namespace Provider {
|
|
|
1289
1297
|
responseHeaders: Object.fromEntries(response.headers.entries()),
|
|
1290
1298
|
}));
|
|
1291
1299
|
|
|
1300
|
+
// Log response body for debugging provider failures
|
|
1301
|
+
// For streaming responses (SSE/event-stream), tee() the stream so the AI SDK
|
|
1302
|
+
// still receives the full stream while we asynchronously log a preview.
|
|
1303
|
+
// For non-streaming responses, buffer the body and reconstruct the Response.
|
|
1304
|
+
// See: https://github.com/link-assistant/agent/issues/204
|
|
1305
|
+
const responseBodyMaxChars = 4000;
|
|
1306
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
1307
|
+
const isStreaming =
|
|
1308
|
+
contentType.includes('event-stream') ||
|
|
1309
|
+
contentType.includes('octet-stream');
|
|
1310
|
+
|
|
1311
|
+
if (response.body) {
|
|
1312
|
+
if (isStreaming) {
|
|
1313
|
+
// Tee the stream: one copy for AI SDK, one for logging
|
|
1314
|
+
const [sdkStream, logStream] = response.body.tee();
|
|
1315
|
+
|
|
1316
|
+
// Consume log stream asynchronously (does not block SDK)
|
|
1317
|
+
(async () => {
|
|
1318
|
+
try {
|
|
1319
|
+
const reader = logStream.getReader();
|
|
1320
|
+
const decoder = new TextDecoder();
|
|
1321
|
+
let bodyPreview = '';
|
|
1322
|
+
let truncated = false;
|
|
1323
|
+
while (true) {
|
|
1324
|
+
const { done, value } = await reader.read();
|
|
1325
|
+
if (done) break;
|
|
1326
|
+
if (!truncated) {
|
|
1327
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
1328
|
+
bodyPreview += chunk;
|
|
1329
|
+
if (bodyPreview.length > responseBodyMaxChars) {
|
|
1330
|
+
bodyPreview = bodyPreview.slice(
|
|
1331
|
+
0,
|
|
1332
|
+
responseBodyMaxChars
|
|
1333
|
+
);
|
|
1334
|
+
truncated = true;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
log.info(() => ({
|
|
1339
|
+
message: 'HTTP response body (stream)',
|
|
1340
|
+
providerID: provider.id,
|
|
1341
|
+
url,
|
|
1342
|
+
bodyPreview: truncated
|
|
1343
|
+
? bodyPreview + `... [truncated]`
|
|
1344
|
+
: bodyPreview,
|
|
1345
|
+
}));
|
|
1346
|
+
} catch {
|
|
1347
|
+
// Ignore logging errors — do not affect the SDK stream
|
|
1348
|
+
}
|
|
1349
|
+
})();
|
|
1350
|
+
|
|
1351
|
+
// Return response with the SDK's copy of the stream
|
|
1352
|
+
return new Response(sdkStream, {
|
|
1353
|
+
status: response.status,
|
|
1354
|
+
statusText: response.statusText,
|
|
1355
|
+
headers: response.headers,
|
|
1356
|
+
});
|
|
1357
|
+
} else {
|
|
1358
|
+
// Non-streaming: buffer body, log it, reconstruct Response
|
|
1359
|
+
const bodyText = await response.text();
|
|
1360
|
+
const bodyPreview =
|
|
1361
|
+
bodyText.length > responseBodyMaxChars
|
|
1362
|
+
? bodyText.slice(0, responseBodyMaxChars) +
|
|
1363
|
+
`... [truncated, total ${bodyText.length} chars]`
|
|
1364
|
+
: bodyText;
|
|
1365
|
+
log.info(() => ({
|
|
1366
|
+
message: 'HTTP response body',
|
|
1367
|
+
providerID: provider.id,
|
|
1368
|
+
url,
|
|
1369
|
+
bodyPreview,
|
|
1370
|
+
}));
|
|
1371
|
+
return new Response(bodyText, {
|
|
1372
|
+
status: response.status,
|
|
1373
|
+
statusText: response.statusText,
|
|
1374
|
+
headers: response.headers,
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1292
1379
|
return response;
|
|
1293
1380
|
} catch (error) {
|
|
1294
1381
|
const durationMs = Date.now() - startMs;
|