@link-assistant/agent 0.26.15 → 0.27.1
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/README.md +49 -7
- package/package.json +51 -42
- package/src/agent/agent.ts +12 -7
- package/src/auth/azure-login.ts +241 -0
- package/src/auth/plugins.ts +9 -0
- package/src/auth/provider-logins.ts +804 -0
- package/src/cli/argv.ts +1 -1
- package/src/cli/cmd/auth.ts +4 -0
- package/src/cli/cmd/models.ts +68 -13
- package/src/cli/cmd/run.ts +7 -1
- package/src/cli/continuous-mode.js +38 -12
- package/src/cli/event-handler.js +3 -1
- package/src/cli/model-config.js +27 -8
- package/src/cli/run-options.js +144 -78
- package/src/config/config.ts +306 -105
- package/src/config/defaults.ts +13 -5
- package/src/config/file-config.ts +16 -0
- package/src/file/ripgrep.ts +38 -5
- package/src/file/time.ts +86 -9
- package/src/index.js +42 -33
- package/src/mcp/index.ts +370 -106
- package/src/permission/index.ts +22 -0
- package/src/project/instance.ts +5 -1
- package/src/project/project.ts +35 -2
- package/src/provider/env-template.ts +115 -0
- package/src/provider/extra-providers.ts +347 -0
- package/src/provider/free-tier-error.ts +1 -1
- package/src/provider/kilo-deprecation.ts +30 -0
- package/src/provider/kilo.ts +44 -7
- package/src/provider/model-discovery.ts +690 -0
- package/src/provider/models.ts +11 -1
- package/src/provider/parity-loaders.ts +595 -0
- package/src/provider/provider.ts +309 -203
- package/src/provider/retry-fetch.ts +5 -0
- package/src/provider/transform.ts +168 -17
- package/src/session/compaction.ts +18 -2
- package/src/session/index.ts +30 -3
- package/src/session/message-v2.ts +318 -32
- package/src/session/processor.ts +274 -140
- package/src/session/prompt.ts +154 -128
- package/src/session/retry.ts +26 -13
- package/src/session/summary.ts +4 -1
- package/src/tool/bash.ts +232 -45
- package/src/tool/bash.txt +1 -1
- package/src/tool/codesearch.ts +15 -78
- package/src/tool/edit.ts +111 -30
- package/src/tool/glob.ts +2 -1
- package/src/tool/grep.ts +175 -57
- package/src/tool/ls.ts +2 -1
- package/src/tool/multiedit.ts +3 -1
- package/src/tool/read.ts +169 -41
- package/src/tool/task.ts +26 -18
- package/src/tool/tool.ts +4 -1
- package/src/tool/webfetch.ts +68 -56
- package/src/tool/webfetch.txt +4 -4
- package/src/tool/websearch.ts +20 -75
- package/src/tool/websearch.txt +2 -0
- package/src/tool/write.ts +23 -6
- package/src/util/bom.ts +32 -0
- package/src/util/exa-mcp.ts +82 -0
- package/src/util/legacy-error-names.ts +39 -0
- package/src/util/read-bounded.ts +32 -0
- package/src/util/sse-usage-extractor.ts +67 -13
- package/src/util/timeout.ts +3 -4
- package/src/util/verbose-fetch.ts +40 -1
package/src/session/prompt.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
+
import { Permission } from '../permission';
|
|
2
3
|
import os from 'os';
|
|
3
4
|
import fs from 'fs/promises';
|
|
4
5
|
import z from 'zod';
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
wrapLanguageModel,
|
|
18
19
|
isStepCount,
|
|
19
20
|
jsonSchema,
|
|
21
|
+
asSchema,
|
|
20
22
|
} from 'ai';
|
|
21
23
|
import { SessionCompaction } from './compaction';
|
|
22
24
|
import { Instance } from '../project/instance';
|
|
@@ -33,7 +35,6 @@ import { mergeDeep, pipe } from 'remeda';
|
|
|
33
35
|
import { ToolRegistry } from '../tool/registry';
|
|
34
36
|
import { Wildcard } from '../util/wildcard';
|
|
35
37
|
import { MCP } from '../mcp';
|
|
36
|
-
import { withTimeout } from '../util/timeout';
|
|
37
38
|
import { ReadTool } from '../tool/read';
|
|
38
39
|
import { ListTool } from '../tool/ls';
|
|
39
40
|
import { FileTime } from '../file/time';
|
|
@@ -102,7 +103,7 @@ export namespace SessionPrompt {
|
|
|
102
103
|
abort: AbortController;
|
|
103
104
|
callbacks: {
|
|
104
105
|
resolve(input: MessageV2.WithParts): void;
|
|
105
|
-
reject(): void;
|
|
106
|
+
reject(reason?: unknown): void;
|
|
106
107
|
}[];
|
|
107
108
|
}
|
|
108
109
|
> = {};
|
|
@@ -274,14 +275,47 @@ export namespace SessionPrompt {
|
|
|
274
275
|
return controller.signal;
|
|
275
276
|
}
|
|
276
277
|
|
|
278
|
+
// Tool calls whose results must go back to the model. Provider-executed
|
|
279
|
+
// tools already have their result in the response, and tools interrupted by
|
|
280
|
+
// an abort or retry are not pending work (upstream #14707, #16776).
|
|
281
|
+
export function hasToolCalls(parts: MessageV2.Part[]) {
|
|
282
|
+
return parts.some(
|
|
283
|
+
(part) =>
|
|
284
|
+
part.type === 'tool' &&
|
|
285
|
+
!part.metadata?.providerExecuted &&
|
|
286
|
+
!(
|
|
287
|
+
part.state.status === 'error' &&
|
|
288
|
+
part.state.metadata?.interrupted === true
|
|
289
|
+
)
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Whether a finished step still has tool results to send back.
|
|
294
|
+
// --legacy-loop-exit / LINK_ASSISTANT_AGENT_LEGACY_LOOP_EXIT: only an
|
|
295
|
+
// 'unknown' finish was checked for tool calls before (issue #194).
|
|
296
|
+
export function toolCallsPending(
|
|
297
|
+
finish: string | undefined,
|
|
298
|
+
parts: MessageV2.Part[],
|
|
299
|
+
legacy: boolean = config.legacyLoopExit
|
|
300
|
+
) {
|
|
301
|
+
if (legacy && finish !== 'unknown') return false;
|
|
302
|
+
return hasToolCalls(parts);
|
|
303
|
+
}
|
|
304
|
+
|
|
277
305
|
export function cancel(sessionID: string) {
|
|
278
306
|
log.info(() => ({ message: 'cancel', sessionID }));
|
|
279
307
|
const s = state();
|
|
280
308
|
const match = s[sessionID];
|
|
281
|
-
if (!match)
|
|
309
|
+
if (!match) {
|
|
310
|
+
SessionStatus.set(sessionID, { type: 'idle' });
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
282
313
|
match.abort.abort();
|
|
314
|
+
Permission.clearSession(sessionID);
|
|
315
|
+
// Queued callers get a real AbortError instead of `undefined`
|
|
316
|
+
// (upstream #16776).
|
|
283
317
|
for (const item of match.callbacks) {
|
|
284
|
-
item.reject();
|
|
318
|
+
item.reject(new DOMException('Aborted', 'AbortError'));
|
|
285
319
|
}
|
|
286
320
|
delete s[sessionID];
|
|
287
321
|
SessionStatus.set(sessionID, { type: 'idle' });
|
|
@@ -333,87 +367,67 @@ export namespace SessionPrompt {
|
|
|
333
367
|
|
|
334
368
|
// Check if we should exit the agentic loop
|
|
335
369
|
// We exit when: the assistant has a finish reason, it's not 'tool-calls',
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
-
|
|
370
|
+
// it made no tool calls whose results still have to be sent back, and
|
|
371
|
+
// the assistant message is newer than the last user message. Providers
|
|
372
|
+
// report finish 'stop' (or nothing) for turns with tool calls, so the
|
|
373
|
+
// finish reason alone is not enough (upstream #14707, #194).
|
|
374
|
+
const lastAssistantParts = lastAssistant
|
|
375
|
+
? msgs.find((m) => m.info.id === lastAssistant!.id)?.parts
|
|
376
|
+
: undefined;
|
|
377
|
+
const finishedTurn =
|
|
378
|
+
!!lastAssistant?.finish &&
|
|
339
379
|
lastAssistant.finish !== 'tool-calls' &&
|
|
340
|
-
lastUser.id < lastAssistant.id
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
380
|
+
lastUser.id < lastAssistant.id;
|
|
381
|
+
const toolCallsMade = toolCallsPending(
|
|
382
|
+
lastAssistant?.finish,
|
|
383
|
+
lastAssistantParts ?? []
|
|
384
|
+
);
|
|
385
|
+
if (finishedTurn && toolCallsMade) {
|
|
386
|
+
log.info(() => ({
|
|
387
|
+
message: 'continuing loop - tool calls detected',
|
|
388
|
+
sessionID,
|
|
389
|
+
finishReason: lastAssistant!.finish,
|
|
390
|
+
}));
|
|
391
|
+
}
|
|
392
|
+
if (lastAssistant && finishedTurn && !toolCallsMade) {
|
|
393
|
+
// SAFETY CHECK for issue #196: Detect zero-token responses as provider failures
|
|
394
|
+
// When all tokens are 0 and finish reason is 'unknown', this indicates the provider
|
|
395
|
+
// returned an empty/error response (e.g., rate limit, model unavailable, API failure).
|
|
396
|
+
// Turns with tool calls never get here (#239).
|
|
397
|
+
const tokens = lastAssistant.tokens;
|
|
398
|
+
if (
|
|
399
|
+
lastAssistant.finish === 'unknown' &&
|
|
400
|
+
tokens.input === 0 &&
|
|
401
|
+
tokens.output === 0 &&
|
|
402
|
+
tokens.reasoning === 0
|
|
403
|
+
) {
|
|
404
|
+
const errorMessage =
|
|
405
|
+
`Provider returned zero tokens with unknown finish reason. ` +
|
|
406
|
+
`Requested model: ${lastAssistant.model?.requestedModelID ?? 'unknown'} ` +
|
|
407
|
+
`(provider: ${lastAssistant.model?.providerID ?? 'unknown'}). ` +
|
|
408
|
+
`Responded model: ${lastAssistant.model?.respondedModelID ?? 'unknown'}. ` +
|
|
409
|
+
`This usually indicates the provider failed to process the request. ` +
|
|
410
|
+
`Check provider status, model availability, and API keys.`;
|
|
411
|
+
log.error(() => ({
|
|
412
|
+
message: errorMessage,
|
|
413
|
+
sessionID,
|
|
414
|
+
finishReason: lastAssistant.finish,
|
|
415
|
+
tokens,
|
|
416
|
+
cost: lastAssistant.cost,
|
|
417
|
+
model: lastAssistant.model,
|
|
418
|
+
issue: 'https://github.com/link-assistant/agent/issues/198',
|
|
419
|
+
}));
|
|
420
|
+
// Publish error event so JSON standard output includes it (#198)
|
|
421
|
+
Bus.publish(Session.Event.Error, {
|
|
422
|
+
sessionID,
|
|
423
|
+
error: new NamedError.Unknown({
|
|
381
424
|
message: errorMessage,
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
tokens,
|
|
385
|
-
cost: lastAssistant.cost,
|
|
386
|
-
model: lastAssistant.model,
|
|
387
|
-
issue: 'https://github.com/link-assistant/agent/issues/198',
|
|
388
|
-
}));
|
|
389
|
-
// Publish error event so JSON standard output includes it (#198)
|
|
390
|
-
Bus.publish(Session.Event.Error, {
|
|
391
|
-
sessionID,
|
|
392
|
-
error: new NamedError.Unknown({
|
|
393
|
-
message: errorMessage,
|
|
394
|
-
}).toObject(),
|
|
395
|
-
});
|
|
396
|
-
break;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
if (hasToolCalls) {
|
|
400
|
-
log.info(() => ({
|
|
401
|
-
message:
|
|
402
|
-
'continuing loop despite unknown finish reason - tool calls detected',
|
|
403
|
-
sessionID,
|
|
404
|
-
finishReason: lastAssistant.finish,
|
|
405
|
-
zeroTokens: tokens.input === 0 && tokens.output === 0,
|
|
406
|
-
hint: 'Provider returned undefined finishReason but made tool calls',
|
|
407
|
-
}));
|
|
408
|
-
// Don't break - continue the loop to handle tool call results
|
|
409
|
-
} else {
|
|
410
|
-
log.info(() => ({ message: 'exiting loop', sessionID }));
|
|
411
|
-
break;
|
|
412
|
-
}
|
|
413
|
-
} else {
|
|
414
|
-
log.info(() => ({ message: 'exiting loop', sessionID }));
|
|
425
|
+
}).toObject(),
|
|
426
|
+
});
|
|
415
427
|
break;
|
|
416
428
|
}
|
|
429
|
+
log.info(() => ({ message: 'exiting loop', sessionID }));
|
|
430
|
+
break;
|
|
417
431
|
}
|
|
418
432
|
|
|
419
433
|
step++;
|
|
@@ -867,7 +881,8 @@ export namespace SessionPrompt {
|
|
|
867
881
|
}
|
|
868
882
|
|
|
869
883
|
return false;
|
|
870
|
-
})
|
|
884
|
+
}),
|
|
885
|
+
{ providerID: model.providerID, modelID: model.modelID }
|
|
871
886
|
);
|
|
872
887
|
// Defensive check: ensure modelMessages is iterable (AI SDK 6.0.1 compatibility fix)
|
|
873
888
|
const safeModelMessages = Array.isArray(modelMessages)
|
|
@@ -998,8 +1013,10 @@ export namespace SessionPrompt {
|
|
|
998
1013
|
maxRetries: 0,
|
|
999
1014
|
activeTools: Object.keys(tools).filter((x) => x !== 'invalid'),
|
|
1000
1015
|
maxOutputTokens: capOutputTokensToContext({
|
|
1016
|
+
// The first argument is the npm package: passing the provider ID
|
|
1017
|
+
// skipped the Anthropic thinking-budget adjustment (#335).
|
|
1001
1018
|
baseMaxOutput: ProviderTransform.maxOutputTokens(
|
|
1002
|
-
model.
|
|
1019
|
+
model.npm ?? '',
|
|
1003
1020
|
params.options,
|
|
1004
1021
|
model.info?.limit?.output ?? 100000,
|
|
1005
1022
|
OUTPUT_TOKEN_MAX
|
|
@@ -1029,7 +1046,8 @@ export namespace SessionPrompt {
|
|
|
1029
1046
|
args.params.prompt = ProviderTransform.message(
|
|
1030
1047
|
args.params.prompt,
|
|
1031
1048
|
model.providerID,
|
|
1032
|
-
model.modelID
|
|
1049
|
+
model.modelID,
|
|
1050
|
+
model.npm
|
|
1033
1051
|
);
|
|
1034
1052
|
}
|
|
1035
1053
|
return args.params;
|
|
@@ -1040,6 +1058,27 @@ export namespace SessionPrompt {
|
|
|
1040
1058
|
})
|
|
1041
1059
|
);
|
|
1042
1060
|
if (result === 'stop') break;
|
|
1061
|
+
if (result === 'compact') {
|
|
1062
|
+
// Overflowing again straight after a compaction means the summary
|
|
1063
|
+
// plus the latest message still do not fit; compacting once more
|
|
1064
|
+
// would loop forever (upstream #14707).
|
|
1065
|
+
if (lastFinished?.summary) {
|
|
1066
|
+
processor.message.error = new MessageV2.ContextOverflowError({
|
|
1067
|
+
message:
|
|
1068
|
+
'Context still exceeds the model limit after compaction - start a new session or use a model with a larger context window',
|
|
1069
|
+
}).toObject();
|
|
1070
|
+
await Session.updateMessage(processor.message);
|
|
1071
|
+
Bus.publish(Session.Event.Error, {
|
|
1072
|
+
sessionID,
|
|
1073
|
+
error: processor.message.error,
|
|
1074
|
+
});
|
|
1075
|
+
break;
|
|
1076
|
+
}
|
|
1077
|
+
await SessionCompaction.create({
|
|
1078
|
+
sessionID,
|
|
1079
|
+
model: lastUser.model,
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1043
1082
|
continue;
|
|
1044
1083
|
}
|
|
1045
1084
|
SessionCompaction.prune({ sessionID });
|
|
@@ -1208,56 +1247,43 @@ export namespace SessionPrompt {
|
|
|
1208
1247
|
if (Wildcard.all(key, enabledTools) === false) continue;
|
|
1209
1248
|
const execute = item.execute;
|
|
1210
1249
|
if (!execute) continue;
|
|
1250
|
+
// MCP schemas go through the same provider fixes as built-in tools
|
|
1251
|
+
// (e.g. Moonshot/Kimi rejects `$ref` siblings and tuple `items`).
|
|
1252
|
+
// See: upstream 528fb1d404 (#24730) and b7b734f51f (#11984).
|
|
1253
|
+
const inputSchema = item.inputSchema;
|
|
1254
|
+
item.inputSchema = jsonSchema(async () =>
|
|
1255
|
+
ProviderTransform.schema(
|
|
1256
|
+
input.model.providerID,
|
|
1257
|
+
input.model.modelID,
|
|
1258
|
+
(await asSchema(inputSchema).jsonSchema) as any
|
|
1259
|
+
)
|
|
1260
|
+
);
|
|
1211
1261
|
item.execute = async (args, opts) => {
|
|
1212
|
-
// Get timeout for this specific tool
|
|
1213
1262
|
const timeout = await MCP.getToolTimeout(key);
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
);
|
|
1231
|
-
}
|
|
1232
|
-
throw error;
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
const textParts: string[] = [];
|
|
1236
|
-
const attachments: MessageV2.FilePart[] = [];
|
|
1237
|
-
|
|
1238
|
-
for (const item of result.content) {
|
|
1239
|
-
if (item.type === 'text') {
|
|
1240
|
-
textParts.push(item.text);
|
|
1241
|
-
} else if (item.type === 'image') {
|
|
1242
|
-
attachments.push({
|
|
1243
|
-
id: Identifier.ascending('part'),
|
|
1244
|
-
sessionID: input.sessionID,
|
|
1245
|
-
messageID: input.processor.message.id,
|
|
1246
|
-
type: 'file',
|
|
1247
|
-
mime: item.mimeType,
|
|
1248
|
-
url: `data:${item.mimeType};base64,${item.data}`,
|
|
1249
|
-
});
|
|
1250
|
-
}
|
|
1251
|
-
// Add support for other types if needed
|
|
1252
|
-
}
|
|
1263
|
+
const result = await MCP.callWithTimeout(
|
|
1264
|
+
key,
|
|
1265
|
+
(abortSignal) => execute(args, { ...opts, abortSignal }),
|
|
1266
|
+
timeout,
|
|
1267
|
+
opts?.abortSignal
|
|
1268
|
+
);
|
|
1269
|
+
const normalized = MCP.normalizeResult(result);
|
|
1270
|
+
const attachments: MessageV2.FilePart[] = normalized.attachments.map(
|
|
1271
|
+
(attachment) => ({
|
|
1272
|
+
id: Identifier.ascending('part'),
|
|
1273
|
+
sessionID: input.sessionID,
|
|
1274
|
+
messageID: input.processor.message.id,
|
|
1275
|
+
type: 'file',
|
|
1276
|
+
...attachment,
|
|
1277
|
+
})
|
|
1278
|
+
);
|
|
1253
1279
|
|
|
1254
1280
|
return {
|
|
1255
1281
|
title: '',
|
|
1256
|
-
metadata:
|
|
1257
|
-
output:
|
|
1282
|
+
metadata: normalized.metadata,
|
|
1283
|
+
output: normalized.output,
|
|
1258
1284
|
attachments,
|
|
1259
|
-
content:
|
|
1260
|
-
isError:
|
|
1285
|
+
content: normalized.content, // directly return content to preserve ordering when outputting to model
|
|
1286
|
+
isError: normalized.isError,
|
|
1261
1287
|
};
|
|
1262
1288
|
};
|
|
1263
1289
|
item.toModelOutput = Tool.toModelOutput;
|
package/src/session/retry.ts
CHANGED
|
@@ -8,6 +8,13 @@ export namespace SessionRetry {
|
|
|
8
8
|
export const RETRY_INITIAL_DELAY = 2000;
|
|
9
9
|
export const RETRY_BACKOFF_FACTOR = 2;
|
|
10
10
|
export const RETRY_MAX_DELAY_NO_HEADERS = 30_000; // 30 seconds
|
|
11
|
+
// setTimeout fires immediately for anything above 2^31 - 1 ms, which turns a
|
|
12
|
+
// long retry-after into a tight retry loop (upstream #19224).
|
|
13
|
+
export const RETRY_MAX_DELAY = 2_147_483_647;
|
|
14
|
+
|
|
15
|
+
function cap(delay: number): number {
|
|
16
|
+
return Math.min(delay, RETRY_MAX_DELAY);
|
|
17
|
+
}
|
|
11
18
|
|
|
12
19
|
// Maximum delay for a single retry attempt when NO retry-after header (default: 20 minutes)
|
|
13
20
|
// This caps exponential backoff when headers are not available
|
|
@@ -122,17 +129,23 @@ export namespace SessionRetry {
|
|
|
122
129
|
|
|
123
130
|
export async function sleep(ms: number, signal: AbortSignal): Promise<void> {
|
|
124
131
|
return new Promise((resolve, reject) => {
|
|
125
|
-
|
|
132
|
+
if (signal.aborted) {
|
|
133
|
+
reject(new DOMException('Aborted', 'AbortError'));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const onAbort = () => {
|
|
137
|
+
clearTimeout(timeout);
|
|
138
|
+
reject(new DOMException('Aborted', 'AbortError'));
|
|
139
|
+
};
|
|
140
|
+
const timeout = setTimeout(() => {
|
|
141
|
+
// Drop the listener once the delay elapses so retries do not pile
|
|
142
|
+
// listeners onto the turn's abort signal (issue #335).
|
|
143
|
+
signal.removeEventListener('abort', onAbort);
|
|
144
|
+
resolve();
|
|
145
|
+
}, cap(ms));
|
|
126
146
|
// Prevent sleep timer from keeping event loop alive (#213)
|
|
127
147
|
if (timeout.unref) timeout.unref();
|
|
128
|
-
signal.addEventListener(
|
|
129
|
-
'abort',
|
|
130
|
-
() => {
|
|
131
|
-
clearTimeout(timeout);
|
|
132
|
-
reject(new DOMException('Aborted', 'AbortError'));
|
|
133
|
-
},
|
|
134
|
-
{ once: true }
|
|
135
|
-
);
|
|
148
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
136
149
|
});
|
|
137
150
|
}
|
|
138
151
|
|
|
@@ -142,7 +155,7 @@ export namespace SessionRetry {
|
|
|
142
155
|
*/
|
|
143
156
|
function addJitter(delay: number): number {
|
|
144
157
|
const jitter = Math.random() * 0.1 * delay;
|
|
145
|
-
return Math.round(delay + jitter);
|
|
158
|
+
return cap(Math.round(delay + jitter));
|
|
146
159
|
}
|
|
147
160
|
|
|
148
161
|
/**
|
|
@@ -273,9 +286,9 @@ export namespace SessionRetry {
|
|
|
273
286
|
* Uses exponential backoff: 1s, 2s, 4s, etc.
|
|
274
287
|
*/
|
|
275
288
|
export function socketErrorDelay(attempt: number): number {
|
|
276
|
-
return (
|
|
289
|
+
return cap(
|
|
277
290
|
SOCKET_ERROR_INITIAL_DELAY *
|
|
278
|
-
|
|
291
|
+
Math.pow(SOCKET_ERROR_BACKOFF_FACTOR, attempt - 1)
|
|
279
292
|
);
|
|
280
293
|
}
|
|
281
294
|
|
|
@@ -286,6 +299,6 @@ export namespace SessionRetry {
|
|
|
286
299
|
*/
|
|
287
300
|
export function timeoutDelay(attempt: number): number {
|
|
288
301
|
const index = Math.min(attempt - 1, TIMEOUT_DELAYS.length - 1);
|
|
289
|
-
return TIMEOUT_DELAYS[index];
|
|
302
|
+
return cap(TIMEOUT_DELAYS[index]);
|
|
290
303
|
}
|
|
291
304
|
}
|
package/src/session/summary.ts
CHANGED
|
@@ -281,7 +281,10 @@ export namespace SessionSummary {
|
|
|
281
281
|
?.parts.findLast((p) => p.type === 'text')?.text;
|
|
282
282
|
if (!summary || diffs.length > 0) {
|
|
283
283
|
// Pre-convert messages to ModelMessage format (async in AI SDK 6.0+)
|
|
284
|
-
const modelMessages = await MessageV2.toModelMessage(messages
|
|
284
|
+
const modelMessages = await MessageV2.toModelMessage(messages, {
|
|
285
|
+
providerID: model.providerID,
|
|
286
|
+
modelID: model.modelID,
|
|
287
|
+
});
|
|
285
288
|
const conversationContent = JSON.stringify(modelMessages);
|
|
286
289
|
|
|
287
290
|
if (isVerbose()) {
|