@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/processor.ts
CHANGED
|
@@ -101,6 +101,40 @@ export namespace SessionProcessor {
|
|
|
101
101
|
let snapshot: string | undefined;
|
|
102
102
|
let blocked = false;
|
|
103
103
|
let attempt = 0;
|
|
104
|
+
let needsCompaction = false;
|
|
105
|
+
|
|
106
|
+
// Providers may send tool-call / tool-result without tool-input-start
|
|
107
|
+
// (provider-executed tools, some OpenAI-compatible gateways); the part
|
|
108
|
+
// must still exist or the call is silently dropped (upstream #24151).
|
|
109
|
+
async function ensureToolCall(
|
|
110
|
+
id: string,
|
|
111
|
+
toolName: string,
|
|
112
|
+
providerExecuted?: boolean
|
|
113
|
+
) {
|
|
114
|
+
const existing = toolcalls[id];
|
|
115
|
+
if (existing) {
|
|
116
|
+
if (!providerExecuted || existing.metadata?.providerExecuted)
|
|
117
|
+
return existing;
|
|
118
|
+
const part = (await Session.updatePart({
|
|
119
|
+
...existing,
|
|
120
|
+
metadata: { ...existing.metadata, providerExecuted: true },
|
|
121
|
+
})) as MessageV2.ToolPart;
|
|
122
|
+
toolcalls[id] = part;
|
|
123
|
+
return part;
|
|
124
|
+
}
|
|
125
|
+
const part = (await Session.updatePart({
|
|
126
|
+
id: Identifier.ascending('part'),
|
|
127
|
+
messageID: input.assistantMessage.id,
|
|
128
|
+
sessionID: input.assistantMessage.sessionID,
|
|
129
|
+
type: 'tool',
|
|
130
|
+
tool: toolName,
|
|
131
|
+
callID: id,
|
|
132
|
+
state: { status: 'pending', input: {}, raw: '' },
|
|
133
|
+
metadata: providerExecuted ? { providerExecuted: true } : undefined,
|
|
134
|
+
})) as MessageV2.ToolPart;
|
|
135
|
+
toolcalls[id] = part;
|
|
136
|
+
return part;
|
|
137
|
+
}
|
|
104
138
|
|
|
105
139
|
const result = {
|
|
106
140
|
get message() {
|
|
@@ -112,9 +146,11 @@ export namespace SessionProcessor {
|
|
|
112
146
|
async process(fn: () => StreamTextResult<Record<string, AITool>, never>) {
|
|
113
147
|
log.info(() => ({ message: 'process' }));
|
|
114
148
|
while (true) {
|
|
149
|
+
// Outside the try so cleanup can finalize parts left open by an
|
|
150
|
+
// interrupted stream (upstream #16776).
|
|
151
|
+
let currentText: MessageV2.TextPart | undefined;
|
|
152
|
+
let reasoningMap: Record<string, MessageV2.ReasoningPart> = {};
|
|
115
153
|
try {
|
|
116
|
-
let currentText: MessageV2.TextPart | undefined;
|
|
117
|
-
let reasoningMap: Record<string, MessageV2.ReasoningPart> = {};
|
|
118
154
|
const stream = fn();
|
|
119
155
|
|
|
120
156
|
for await (const value of stream.stream) {
|
|
@@ -192,19 +228,36 @@ export namespace SessionProcessor {
|
|
|
192
228
|
break;
|
|
193
229
|
|
|
194
230
|
case 'tool-call': {
|
|
195
|
-
const match =
|
|
196
|
-
|
|
231
|
+
const match = await ensureToolCall(
|
|
232
|
+
value.toolCallId,
|
|
233
|
+
value.toolName,
|
|
234
|
+
value.providerExecuted
|
|
235
|
+
);
|
|
236
|
+
{
|
|
237
|
+
// Invalid tool calls carry the raw string; the part schema
|
|
238
|
+
// needs a record (upstream #14862).
|
|
239
|
+
const toolInput =
|
|
240
|
+
value.input &&
|
|
241
|
+
typeof value.input === 'object' &&
|
|
242
|
+
!Array.isArray(value.input)
|
|
243
|
+
? (value.input as Record<string, any>)
|
|
244
|
+
: { value: value.input };
|
|
197
245
|
const part = await Session.updatePart({
|
|
198
246
|
...match,
|
|
199
247
|
tool: value.toolName,
|
|
200
|
-
state:
|
|
201
|
-
status
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
248
|
+
state:
|
|
249
|
+
match.state.status === 'running'
|
|
250
|
+
? { ...match.state, input: toolInput }
|
|
251
|
+
: {
|
|
252
|
+
status: 'running',
|
|
253
|
+
input: toolInput,
|
|
254
|
+
time: {
|
|
255
|
+
start: Date.now(),
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
metadata: match.metadata?.providerExecuted
|
|
259
|
+
? { ...value.providerMetadata, providerExecuted: true }
|
|
260
|
+
: value.providerMetadata,
|
|
208
261
|
});
|
|
209
262
|
toolcalls[value.toolCallId] = part as MessageV2.ToolPart;
|
|
210
263
|
|
|
@@ -230,17 +283,53 @@ export namespace SessionProcessor {
|
|
|
230
283
|
break;
|
|
231
284
|
}
|
|
232
285
|
case 'tool-result': {
|
|
233
|
-
const match =
|
|
234
|
-
|
|
286
|
+
const match = await ensureToolCall(
|
|
287
|
+
value.toolCallId,
|
|
288
|
+
value.toolName,
|
|
289
|
+
value.providerExecuted
|
|
290
|
+
);
|
|
291
|
+
if (
|
|
292
|
+
match.state.status === 'running' ||
|
|
293
|
+
match.state.status === 'pending'
|
|
294
|
+
) {
|
|
295
|
+
const output = value.output as unknown;
|
|
296
|
+
const toolInput =
|
|
297
|
+
value.input &&
|
|
298
|
+
typeof value.input === 'object' &&
|
|
299
|
+
!Array.isArray(value.input)
|
|
300
|
+
? (value.input as Record<string, any>)
|
|
301
|
+
: { value: value.input };
|
|
302
|
+
const time = {
|
|
303
|
+
start:
|
|
304
|
+
match.state.status === 'running'
|
|
305
|
+
? match.state.time.start
|
|
306
|
+
: Date.now(),
|
|
307
|
+
end: Date.now(),
|
|
308
|
+
};
|
|
235
309
|
await Session.updatePart({
|
|
236
310
|
...match,
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
|
|
311
|
+
// Provider-executed tools return raw values rather than
|
|
312
|
+
// a Tool.Result.
|
|
313
|
+
state:
|
|
314
|
+
output &&
|
|
315
|
+
typeof output === 'object' &&
|
|
316
|
+
typeof (output as { output?: unknown }).output ===
|
|
317
|
+
'string'
|
|
318
|
+
? Tool.toState(output as Tool.Result, {
|
|
319
|
+
input: toolInput,
|
|
320
|
+
time,
|
|
321
|
+
})
|
|
322
|
+
: {
|
|
323
|
+
status: 'completed',
|
|
324
|
+
input: toolInput,
|
|
325
|
+
output:
|
|
326
|
+
typeof output === 'string'
|
|
327
|
+
? output
|
|
328
|
+
: (JSON.stringify(output) ?? ''),
|
|
329
|
+
title: '',
|
|
330
|
+
metadata: {},
|
|
331
|
+
time,
|
|
332
|
+
},
|
|
244
333
|
});
|
|
245
334
|
|
|
246
335
|
delete toolcalls[value.toolCallId];
|
|
@@ -558,7 +647,7 @@ export namespace SessionProcessor {
|
|
|
558
647
|
if (currentText) {
|
|
559
648
|
currentText.text = currentText.text.trimEnd();
|
|
560
649
|
currentText.time = {
|
|
561
|
-
start: Date.now(),
|
|
650
|
+
start: currentText.time?.start ?? Date.now(),
|
|
562
651
|
end: Date.now(),
|
|
563
652
|
};
|
|
564
653
|
if (value.providerMetadata)
|
|
@@ -611,130 +700,162 @@ export namespace SessionProcessor {
|
|
|
611
700
|
).toObject();
|
|
612
701
|
}
|
|
613
702
|
|
|
614
|
-
//
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
input.
|
|
631
|
-
|
|
632
|
-
)
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
(
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
703
|
+
// Overflow is handled by compacting the session and trying again,
|
|
704
|
+
// not by retrying the same oversized request (upstream #14707).
|
|
705
|
+
if (error?.name === 'ContextOverflowError') {
|
|
706
|
+
SessionRetry.clearRetryState(input.sessionID);
|
|
707
|
+
if (
|
|
708
|
+
config.disableAutocompact &&
|
|
709
|
+
!input.assistantMessage.summary
|
|
710
|
+
) {
|
|
711
|
+
input.assistantMessage.error = error;
|
|
712
|
+
Bus.publish(Session.Event.Error, {
|
|
713
|
+
sessionID: input.assistantMessage.sessionID,
|
|
714
|
+
error: input.assistantMessage.error,
|
|
715
|
+
});
|
|
716
|
+
} else {
|
|
717
|
+
log.info(() => ({
|
|
718
|
+
message: 'context overflow, requesting compaction',
|
|
719
|
+
providerID: input.providerID,
|
|
720
|
+
modelID: input.model.id,
|
|
721
|
+
}));
|
|
722
|
+
needsCompaction = true;
|
|
723
|
+
}
|
|
724
|
+
} else {
|
|
725
|
+
// Check if error is retryable (APIError, SocketConnectionError, TimeoutError)
|
|
726
|
+
const isRetryableAPIError =
|
|
727
|
+
error?.name === 'APIError' && error.data.isRetryable;
|
|
728
|
+
const isRetryableSocketError =
|
|
729
|
+
error?.name === 'SocketConnectionError' &&
|
|
730
|
+
error.data.isRetryable &&
|
|
731
|
+
attempt < SessionRetry.SOCKET_ERROR_MAX_RETRIES;
|
|
732
|
+
const isRetryableTimeoutError =
|
|
733
|
+
error?.name === 'TimeoutError' &&
|
|
734
|
+
error.data.isRetryable &&
|
|
735
|
+
attempt < SessionRetry.TIMEOUT_MAX_RETRIES;
|
|
736
|
+
|
|
737
|
+
// For API errors (rate limits), check if we're within the retry timeout
|
|
738
|
+
// See: https://github.com/link-assistant/agent/issues/157
|
|
739
|
+
const retryCheck = isRetryableAPIError
|
|
740
|
+
? SessionRetry.shouldRetry(
|
|
741
|
+
input.sessionID,
|
|
742
|
+
error.data.statusCode?.toString() ?? 'unknown'
|
|
743
|
+
)
|
|
744
|
+
: { shouldRetry: true, elapsedTime: 0, maxTime: 0 };
|
|
745
|
+
|
|
746
|
+
let delay: number | undefined;
|
|
747
|
+
if (
|
|
748
|
+
(isRetryableAPIError && retryCheck.shouldRetry) ||
|
|
749
|
+
isRetryableSocketError ||
|
|
750
|
+
isRetryableTimeoutError
|
|
751
|
+
) {
|
|
752
|
+
attempt++;
|
|
753
|
+
// Use error-specific delay calculation
|
|
754
|
+
// SessionRetry.delay may throw RetryTimeoutExceededError if retry-after exceeds timeout
|
|
755
|
+
try {
|
|
756
|
+
delay =
|
|
757
|
+
error?.name === 'SocketConnectionError'
|
|
758
|
+
? SessionRetry.socketErrorDelay(attempt)
|
|
759
|
+
: error?.name === 'TimeoutError'
|
|
760
|
+
? SessionRetry.timeoutDelay(attempt)
|
|
761
|
+
: SessionRetry.delay(error, attempt);
|
|
762
|
+
} catch (delayError) {
|
|
763
|
+
// If retry-after exceeds LINK_ASSISTANT_AGENT_RETRY_TIMEOUT, fail immediately
|
|
764
|
+
if (
|
|
765
|
+
delayError instanceof SessionRetry.RetryTimeoutExceededError
|
|
766
|
+
) {
|
|
767
|
+
log.error(() => ({
|
|
768
|
+
message:
|
|
769
|
+
'retry-after exceeds timeout, failing immediately',
|
|
668
770
|
retryAfterMs: delayError.retryAfterMs,
|
|
669
771
|
maxTimeoutMs: delayError.maxTimeoutMs,
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
error
|
|
675
|
-
|
|
676
|
-
|
|
772
|
+
}));
|
|
773
|
+
// Report it like any other final error below. Leaving
|
|
774
|
+
// the loop with `break` skipped the cleanup and returned
|
|
775
|
+
// undefined, so the prompt loop started another step.
|
|
776
|
+
error = new MessageV2.RetryTimeoutExceededError(
|
|
777
|
+
{
|
|
778
|
+
message: delayError.message,
|
|
779
|
+
isRetryable: false,
|
|
780
|
+
retryAfterMs: delayError.retryAfterMs,
|
|
781
|
+
maxTimeoutMs: delayError.maxTimeoutMs,
|
|
782
|
+
},
|
|
783
|
+
{ cause: delayError }
|
|
784
|
+
).toObject();
|
|
785
|
+
} else throw delayError;
|
|
677
786
|
}
|
|
678
|
-
throw delayError;
|
|
679
787
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
// Clear retry state on non-retryable error
|
|
701
|
-
SessionRetry.clearRetryState(input.sessionID);
|
|
788
|
+
if (delay !== undefined) {
|
|
789
|
+
log.info(() => ({
|
|
790
|
+
message: 'retrying',
|
|
791
|
+
errorType: error?.name,
|
|
792
|
+
attempt,
|
|
793
|
+
delay,
|
|
794
|
+
elapsedRetryTime: retryCheck.elapsedTime,
|
|
795
|
+
maxRetryTime: retryCheck.maxTime,
|
|
796
|
+
}));
|
|
797
|
+
SessionStatus.set(input.sessionID, {
|
|
798
|
+
type: 'retry',
|
|
799
|
+
attempt,
|
|
800
|
+
message: error.data.message,
|
|
801
|
+
next: Date.now() + delay,
|
|
802
|
+
});
|
|
803
|
+
// Update retry state to track total time
|
|
804
|
+
SessionRetry.updateRetryState(input.sessionID, delay);
|
|
805
|
+
await SessionRetry.sleep(delay, input.abort).catch(() => {});
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
702
808
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
809
|
+
// Clear retry state on non-retryable error
|
|
810
|
+
SessionRetry.clearRetryState(input.sessionID);
|
|
811
|
+
|
|
812
|
+
// Detect model-not-supported errors from provider response body.
|
|
813
|
+
// OpenCode and similar proxies return HTTP 401 with
|
|
814
|
+
// {"type":"ModelError","message":"Model X not supported"}.
|
|
815
|
+
// Without this check the error looks like an auth failure (401),
|
|
816
|
+
// but it's actually a model-availability issue.
|
|
817
|
+
// See: https://github.com/link-assistant/agent/issues/208
|
|
818
|
+
if (
|
|
819
|
+
error?.name === 'APIError' &&
|
|
820
|
+
error.data.statusCode === 401 &&
|
|
715
821
|
error.data.responseBody
|
|
716
|
-
)
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
822
|
+
) {
|
|
823
|
+
const isModelError = isModelNotSupportedError(
|
|
824
|
+
error.data.responseBody
|
|
825
|
+
);
|
|
826
|
+
if (isModelError) {
|
|
827
|
+
log.error(() => ({
|
|
828
|
+
message:
|
|
829
|
+
'model not supported by provider — this is NOT an auth error',
|
|
830
|
+
hint: 'The model was found in the local cache but the provider rejected it. The model may have been removed or is temporarily unavailable.',
|
|
831
|
+
providerID: input.providerID,
|
|
832
|
+
modelID: input.model.id,
|
|
833
|
+
statusCode: error.data.statusCode,
|
|
834
|
+
responseBody: error.data.responseBody,
|
|
835
|
+
suggestion:
|
|
836
|
+
'Try a different model or check the provider status. Use --model <provider>/<model-id> to specify an alternative.',
|
|
837
|
+
issue: 'https://github.com/link-assistant/agent/issues/208',
|
|
838
|
+
}));
|
|
839
|
+
}
|
|
730
840
|
}
|
|
731
|
-
}
|
|
732
841
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
842
|
+
input.assistantMessage.error = error;
|
|
843
|
+
Bus.publish(Session.Event.Error, {
|
|
844
|
+
sessionID: input.assistantMessage.sessionID,
|
|
845
|
+
error: input.assistantMessage.error,
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
const end = Date.now();
|
|
850
|
+
if (currentText && currentText.time?.end === undefined) {
|
|
851
|
+
currentText.text = currentText.text.trimEnd();
|
|
852
|
+
currentText.time = { start: currentText.time?.start ?? end, end };
|
|
853
|
+
await Session.updatePart(currentText);
|
|
854
|
+
}
|
|
855
|
+
for (const part of Object.values(reasoningMap)) {
|
|
856
|
+
part.text = part.text.trimEnd();
|
|
857
|
+
part.time = { start: part.time.start ?? end, end };
|
|
858
|
+
await Session.updatePart(part);
|
|
738
859
|
}
|
|
739
860
|
const p = await MessageV2.parts(input.assistantMessage.id);
|
|
740
861
|
for (const part of p) {
|
|
@@ -743,15 +864,27 @@ export namespace SessionProcessor {
|
|
|
743
864
|
part.state.status !== 'completed' &&
|
|
744
865
|
part.state.status !== 'error'
|
|
745
866
|
) {
|
|
867
|
+
// Keep the real start and flag the part so the loop does not
|
|
868
|
+
// treat an aborted call as one to continue from (upstream
|
|
869
|
+
// #16776).
|
|
746
870
|
await Session.updatePart({
|
|
747
871
|
...part,
|
|
748
872
|
state: {
|
|
749
873
|
...part.state,
|
|
750
874
|
status: 'error',
|
|
751
875
|
error: 'Tool execution aborted',
|
|
876
|
+
metadata: {
|
|
877
|
+
...(part.state.status === 'running'
|
|
878
|
+
? part.state.metadata
|
|
879
|
+
: undefined),
|
|
880
|
+
interrupted: true,
|
|
881
|
+
},
|
|
752
882
|
time: {
|
|
753
|
-
start:
|
|
754
|
-
|
|
883
|
+
start:
|
|
884
|
+
part.state.status === 'running'
|
|
885
|
+
? part.state.time.start
|
|
886
|
+
: end,
|
|
887
|
+
end,
|
|
755
888
|
},
|
|
756
889
|
},
|
|
757
890
|
});
|
|
@@ -760,6 +893,7 @@ export namespace SessionProcessor {
|
|
|
760
893
|
input.assistantMessage.time.completed = Date.now();
|
|
761
894
|
await Session.updateMessage(input.assistantMessage);
|
|
762
895
|
if (blocked) return 'stop';
|
|
896
|
+
if (needsCompaction) return 'compact';
|
|
763
897
|
if (input.assistantMessage.error) return 'stop';
|
|
764
898
|
return 'continue';
|
|
765
899
|
}
|