@vibes.diy/api-svc 2.2.7 → 2.2.9
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/cf-serve.d.ts +3 -1
- package/cf-serve.js +3 -2
- package/cf-serve.js.map +1 -1
- package/create-handler.d.ts +3 -1
- package/default-llm-request.d.ts +4 -0
- package/default-llm-request.js +2 -1
- package/default-llm-request.js.map +1 -1
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/intern/ensure-app-metadata.d.ts +13 -0
- package/intern/ensure-app-metadata.js +94 -0
- package/intern/ensure-app-metadata.js.map +1 -0
- package/intern/ensure-chat-id.js +21 -2
- package/intern/ensure-chat-id.js.map +1 -1
- package/intern/pre-allocate.d.ts +1 -0
- package/intern/pre-allocate.js +1 -0
- package/intern/pre-allocate.js.map +1 -1
- package/intern/recovery.d.ts +37 -0
- package/intern/recovery.js +101 -0
- package/intern/recovery.js.map +1 -0
- package/package.json +13 -13
- package/public/app-documents.js +2 -2
- package/public/app-documents.js.map +1 -1
- package/public/ensure-app-settings.js +52 -1
- package/public/ensure-app-settings.js.map +1 -1
- package/public/ensure-app-slug-item.js +22 -1
- package/public/ensure-app-slug-item.js.map +1 -1
- package/public/prompt-chat-section.d.ts +41 -2
- package/public/prompt-chat-section.js +394 -60
- package/public/prompt-chat-section.js.map +1 -1
- package/svc-ws-send-provider.d.ts +1 -0
- package/svc-ws-send-provider.js +1 -0
- package/svc-ws-send-provider.js.map +1 -1
- package/types.d.ts +3 -1
|
@@ -6,10 +6,11 @@ import { type } from "arktype";
|
|
|
6
6
|
import { checkAuth } from "../check-auth.js";
|
|
7
7
|
import { unwrapMsgBase, wrapMsgBase } from "../unwrap-msg-base.js";
|
|
8
8
|
import { and, desc, eq } from "drizzle-orm/sql/expressions";
|
|
9
|
-
import { applyEdits, createStatsCollector, createLineStream, createDataStream, createSseStream, createDeltaStream, createSectionsStream, isBlockEnd, isCodeBegin, isCodeEnd, isCodeLine, isToplevelLine, parseFenceBody, FileSystemRef, isBlockStreamMsg, isBlockImage, } from "@vibes.diy/call-ai-v2";
|
|
10
|
-
import { makeBaseSystemPrompt, resolveEffectiveModel } from "@vibes.diy/prompts";
|
|
9
|
+
import { applyEdits, createStatsCollector, createLineStream, createDataStream, createSseStream, createDeltaStream, createSectionsStream, isBlockEnd, isCodeBegin, isCodeEnd, isCodeLine, isDeltaLine, isToplevelLine, parseFenceBody, FileSystemRef, isBlockStreamMsg, isBlockImage, } from "@vibes.diy/call-ai-v2";
|
|
10
|
+
import { getRecoveryAddendum, getRecoveryStitchAddendum, makeBaseSystemPrompt, resolveEffectiveModel } from "@vibes.diy/prompts";
|
|
11
11
|
import { ensureAppSlugItem } from "./ensure-app-slug-item.js";
|
|
12
12
|
import { getModelDefaults } from "../intern/get-model-defaults.js";
|
|
13
|
+
import { buildRecoveryRequest, buildTruncatedEvent, shouldAttemptRecovery, updateRecoveryCounter, } from "../intern/recovery.js";
|
|
13
14
|
export function createPromptAssetFetch(deps) {
|
|
14
15
|
return async (url, _init) => {
|
|
15
16
|
const uri = URI.from(url);
|
|
@@ -94,6 +95,97 @@ export function resolveCodeBlocksToFileSystem(blocks, seed) {
|
|
|
94
95
|
}
|
|
95
96
|
return result;
|
|
96
97
|
}
|
|
98
|
+
function normalizeFilename(rawPath) {
|
|
99
|
+
const path = rawPath ?? "App.jsx";
|
|
100
|
+
return path.startsWith("/") ? path : `/${path}`;
|
|
101
|
+
}
|
|
102
|
+
function searchPrefixOf(search) {
|
|
103
|
+
const firstLine = search.split("\n").find((l) => l.trim().length > 0) ?? "";
|
|
104
|
+
return firstLine.length > 80 ? `${firstLine.slice(0, 80)}…` : firstLine;
|
|
105
|
+
}
|
|
106
|
+
export function createStreamingResolver(deps) {
|
|
107
|
+
const vfs = new Map();
|
|
108
|
+
const seedFor = (filename, rawPath) => {
|
|
109
|
+
return deps.seed.get(filename) ?? deps.seed.get(rawPath) ?? "";
|
|
110
|
+
};
|
|
111
|
+
return {
|
|
112
|
+
observeBlock(block) {
|
|
113
|
+
const rawPath = block.begin.path ?? "App.jsx";
|
|
114
|
+
const filename = normalizeFilename(rawPath);
|
|
115
|
+
const current = vfs.has(filename) ? (vfs.get(filename) ?? "") : seedFor(filename, rawPath);
|
|
116
|
+
const parsed = parseFenceBody(block.lines.map((l) => l.line));
|
|
117
|
+
const errors = [];
|
|
118
|
+
for (const fenceErr of parsed.errors) {
|
|
119
|
+
const evt = {
|
|
120
|
+
chatId: deps.chatId,
|
|
121
|
+
promptId: deps.promptId,
|
|
122
|
+
blockId: block.end.blockId,
|
|
123
|
+
sectionId: block.end.sectionId,
|
|
124
|
+
kind: "fence-parse",
|
|
125
|
+
reason: fenceErr.kind,
|
|
126
|
+
};
|
|
127
|
+
deps.onApplyError(evt);
|
|
128
|
+
errors.push(evt);
|
|
129
|
+
}
|
|
130
|
+
const applied = applyEdits(current, parsed.edits);
|
|
131
|
+
for (const applyErr of applied.errors) {
|
|
132
|
+
const evt = {
|
|
133
|
+
chatId: deps.chatId,
|
|
134
|
+
promptId: deps.promptId,
|
|
135
|
+
blockId: block.end.blockId,
|
|
136
|
+
sectionId: block.end.sectionId,
|
|
137
|
+
kind: "apply",
|
|
138
|
+
reason: applyErr.reason,
|
|
139
|
+
searchPrefix: searchPrefixOf(applyErr.search),
|
|
140
|
+
};
|
|
141
|
+
deps.onApplyError(evt);
|
|
142
|
+
errors.push(evt);
|
|
143
|
+
}
|
|
144
|
+
vfs.set(filename, applied.content);
|
|
145
|
+
return { path: filename, errors };
|
|
146
|
+
},
|
|
147
|
+
getVfs() {
|
|
148
|
+
return new Map(vfs);
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
export function createBlockAccumulator() {
|
|
153
|
+
const open = new Map();
|
|
154
|
+
return {
|
|
155
|
+
ingest(msg) {
|
|
156
|
+
if (isCodeBegin(msg)) {
|
|
157
|
+
open.set(msg.blockId, { begin: msg, lines: [] });
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
if (isCodeLine(msg)) {
|
|
161
|
+
open.get(msg.blockId)?.lines.push(msg);
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
if (isCodeEnd(msg)) {
|
|
165
|
+
const acc = open.get(msg.blockId);
|
|
166
|
+
if (!acc)
|
|
167
|
+
return undefined;
|
|
168
|
+
open.delete(msg.blockId);
|
|
169
|
+
return { begin: acc.begin, lines: acc.lines, end: msg };
|
|
170
|
+
}
|
|
171
|
+
return undefined;
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
export function logApplyError(logger, evt) {
|
|
176
|
+
logger
|
|
177
|
+
.Debug()
|
|
178
|
+
.Any({
|
|
179
|
+
chatId: evt.chatId,
|
|
180
|
+
promptId: evt.promptId,
|
|
181
|
+
blockId: evt.blockId,
|
|
182
|
+
sectionId: evt.sectionId,
|
|
183
|
+
kind: evt.kind,
|
|
184
|
+
reason: evt.reason,
|
|
185
|
+
...(evt.searchPrefix === undefined ? {} : { searchPrefix: evt.searchPrefix }),
|
|
186
|
+
})
|
|
187
|
+
.Msg("apply-error");
|
|
188
|
+
}
|
|
97
189
|
async function appendBlockEvent({ ctx, vctx, req, promptId, blockSeq, evt, emitMode = "store", }) {
|
|
98
190
|
if (isBlockImage(evt)) {
|
|
99
191
|
const imgEvt = evt;
|
|
@@ -156,6 +248,7 @@ export async function handlePromptContext({ vctx, req, resChat, promptId, blockS
|
|
|
156
248
|
const code = [];
|
|
157
249
|
const sections = [];
|
|
158
250
|
const collectedMsgs = [...iCollectedMsgs];
|
|
251
|
+
const blockAcc = createBlockAccumulator();
|
|
159
252
|
for (const msg of collectedMsgs) {
|
|
160
253
|
if (!isBlockStreamMsg(msg)) {
|
|
161
254
|
continue;
|
|
@@ -172,25 +265,9 @@ export async function handlePromptContext({ vctx, req, resChat, promptId, blockS
|
|
|
172
265
|
sqlVal = sections[sections.length - 1];
|
|
173
266
|
}
|
|
174
267
|
sqlVal.blocks.push(msg);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
else if (isCodeLine(msg)) {
|
|
179
|
-
if (code.length === 0) {
|
|
180
|
-
console.warn("Received code line without a preceding code begin:", msg);
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
code[code.length - 1].lines.push(msg);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
else if (isCodeEnd(msg)) {
|
|
187
|
-
if (code.length === 0) {
|
|
188
|
-
console.warn("Received code end without a preceding code begin:", msg);
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
code[code.length - 1].end = msg;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
268
|
+
const closed = blockAcc.ingest(msg);
|
|
269
|
+
if (closed)
|
|
270
|
+
code.push({ begin: closed.begin, lines: [...closed.lines], end: closed.end });
|
|
194
271
|
}
|
|
195
272
|
if (code.length > 0 && (resChat.mode === "chat" || isPromptFSStyle(resChat.mode))) {
|
|
196
273
|
let resolvedFileSystem;
|
|
@@ -325,10 +402,13 @@ async function injectSystemPrompt(vctx, chatId, model) {
|
|
|
325
402
|
}
|
|
326
403
|
const conversationMessages = reconstructConversationMessages(allSectionMsgs);
|
|
327
404
|
const { skills, title } = await loadActiveSettings(vctx, chatId);
|
|
405
|
+
const priorFs = await loadPriorFileSystem(vctx, chatId);
|
|
406
|
+
const isInitial = priorFs.size === 0;
|
|
328
407
|
const systemPrompt = await exception2Result(async () => makeBaseSystemPrompt(await resolveEffectiveModel({ model }, {}), {
|
|
329
408
|
skills,
|
|
330
409
|
title,
|
|
331
410
|
demoData: false,
|
|
411
|
+
variant: isInitial ? "initial" : "continuation",
|
|
332
412
|
pkgBaseUrl: promptsPkgBaseUrl(vctx.params.pkgRepos.workspace),
|
|
333
413
|
fetch: createPromptAssetFetch({ fetchAsset: vctx.fetchAsset }),
|
|
334
414
|
}));
|
|
@@ -483,9 +563,10 @@ async function handlerLlmRequest({ scope, ctx, vctx, blockSeq, req, resChat, pro
|
|
|
483
563
|
...(isInitialTurn && req.mode === "chat" ? { verbosity: "low" } : {}),
|
|
484
564
|
...(req.mode === "img" ? { modalities: ["text", "image"] } : {}),
|
|
485
565
|
};
|
|
566
|
+
const abort = new AbortController();
|
|
486
567
|
const res = await scope
|
|
487
568
|
.evalResult(async () => {
|
|
488
|
-
const res = await vctx.llmRequest(llmReq);
|
|
569
|
+
const res = await vctx.llmRequest(llmReq, { signal: abort.signal });
|
|
489
570
|
if (!res.ok) {
|
|
490
571
|
return Result.Err(`LLM request failed with status ${res.status} :${llmReq.model} : ${res.statusText}`);
|
|
491
572
|
}
|
|
@@ -495,7 +576,7 @@ async function handlerLlmRequest({ scope, ctx, vctx, blockSeq, req, resChat, pro
|
|
|
495
576
|
return Result.Ok(res);
|
|
496
577
|
})
|
|
497
578
|
.do();
|
|
498
|
-
return { res, blockSeq };
|
|
579
|
+
return { res, blockSeq, llmReq, abort };
|
|
499
580
|
}
|
|
500
581
|
async function handleProdiaImageRequest({ scope, ctx, vctx, req, promptId, blockSeq, resolvedModel, }) {
|
|
501
582
|
const prodiaToken = vctx.prodiaToken;
|
|
@@ -697,27 +778,17 @@ async function handleEndMsg({ collectedMsgs, vctx, req, ctx, resChat, promptId,
|
|
|
697
778
|
}
|
|
698
779
|
return Result.Ok(blockSeq);
|
|
699
780
|
}
|
|
700
|
-
async function handleLlmResponse({ scope, vctx, req, ctx, res, resChat, promptId, blockSeq, }) {
|
|
781
|
+
async function handleLlmResponse({ scope, vctx, req, ctx, res, resChat, promptId, blockSeq, llmReq, abort, }) {
|
|
701
782
|
await scope
|
|
702
783
|
.evalResult(async () => {
|
|
703
|
-
const pipeline = res
|
|
704
|
-
.body.pipeThrough(createStatsCollector(promptId, 1000))
|
|
705
|
-
.pipeThrough(createLineStream(promptId))
|
|
706
|
-
.pipeThrough(createDataStream(promptId))
|
|
707
|
-
.pipeThrough(createSseStream(promptId))
|
|
708
|
-
.pipeThrough(createDeltaStream(promptId, () => vctx.sthis.nextId(12).str))
|
|
709
|
-
.pipeThrough(createSectionsStream(promptId, () => vctx.sthis.nextId(12).str));
|
|
710
|
-
const reader = pipeline.getReader();
|
|
711
784
|
let collectedMsgs;
|
|
712
|
-
let chatCtx;
|
|
713
785
|
for (const conn of vctx.connections) {
|
|
714
786
|
const tChatCtx = conn.chatIds.get(req.chatId);
|
|
715
787
|
if (tChatCtx) {
|
|
716
|
-
|
|
717
|
-
const promptIdCtx = chatCtx.promptIds.get(promptId);
|
|
788
|
+
const promptIdCtx = tChatCtx.promptIds.get(promptId);
|
|
718
789
|
if (!promptIdCtx) {
|
|
719
790
|
collectedMsgs = [];
|
|
720
|
-
|
|
791
|
+
tChatCtx.promptIds.set(promptId, {
|
|
721
792
|
blocks: collectedMsgs,
|
|
722
793
|
promptId,
|
|
723
794
|
type: "vibes.diy.section-event",
|
|
@@ -734,40 +805,301 @@ async function handleLlmResponse({ scope, vctx, req, ctx, res, resChat, promptId
|
|
|
734
805
|
if (!collectedMsgs) {
|
|
735
806
|
return Result.Err(`Chat context not found for chatId: ${req.chatId}`);
|
|
736
807
|
}
|
|
808
|
+
const seedForResolver = await loadPriorFileSystem(vctx, req.chatId);
|
|
809
|
+
const resolverLogger = ensureLogger(vctx.sthis, "streamingResolver");
|
|
810
|
+
const recoveryLogger = ensureLogger(vctx.sthis, "applyRecovery");
|
|
811
|
+
const streamingResolver = createStreamingResolver({
|
|
812
|
+
chatId: req.chatId,
|
|
813
|
+
promptId,
|
|
814
|
+
seed: seedForResolver,
|
|
815
|
+
onApplyError: (evt) => logApplyError(resolverLogger, evt),
|
|
816
|
+
});
|
|
817
|
+
const blockAcc = createBlockAccumulator();
|
|
818
|
+
let recoveryCounter = { consecutiveFruitless: 0 };
|
|
819
|
+
let isRecoveryStream = false;
|
|
820
|
+
let currentRes = res;
|
|
821
|
+
let currentAbort = abort;
|
|
737
822
|
while (true) {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
823
|
+
let streamMadeProgress = false;
|
|
824
|
+
const partialBuffer = { text: "", safeCut: 0, lastReplaceFileLines: undefined };
|
|
825
|
+
const captureDeltas = new TransformStream({
|
|
826
|
+
transform(msg, controller) {
|
|
827
|
+
if (isDeltaLine(msg))
|
|
828
|
+
partialBuffer.text += msg.content;
|
|
829
|
+
controller.enqueue(msg);
|
|
830
|
+
},
|
|
831
|
+
});
|
|
832
|
+
const pipeline = currentRes
|
|
833
|
+
.body.pipeThrough(createStatsCollector(promptId, 1000))
|
|
834
|
+
.pipeThrough(createLineStream(promptId))
|
|
835
|
+
.pipeThrough(createDataStream(promptId))
|
|
836
|
+
.pipeThrough(createSseStream(promptId))
|
|
837
|
+
.pipeThrough(createDeltaStream(promptId, () => vctx.sthis.nextId(12).str))
|
|
838
|
+
.pipeThrough(captureDeltas)
|
|
839
|
+
.pipeThrough(createSectionsStream(promptId, () => vctx.sthis.nextId(12).str));
|
|
840
|
+
const reader = pipeline.getReader();
|
|
841
|
+
let recoverHint = null;
|
|
842
|
+
let drainOnly = false;
|
|
843
|
+
readLoop: while (true) {
|
|
844
|
+
const rRead = await exception2Result(() => reader.read());
|
|
845
|
+
if (rRead.isErr()) {
|
|
846
|
+
const intentional = drainOnly || currentAbort.signal.aborted;
|
|
847
|
+
if (!intentional) {
|
|
848
|
+
recoveryLogger
|
|
849
|
+
.Info()
|
|
850
|
+
.Any("event", {
|
|
851
|
+
chatId: req.chatId,
|
|
852
|
+
promptId,
|
|
853
|
+
err: String(rRead.Err()),
|
|
854
|
+
})
|
|
855
|
+
.Msg("upstream-read-failed");
|
|
856
|
+
}
|
|
857
|
+
break readLoop;
|
|
858
|
+
}
|
|
859
|
+
const { done, value } = rRead.Ok();
|
|
860
|
+
if (done)
|
|
861
|
+
break readLoop;
|
|
862
|
+
if (drainOnly)
|
|
744
863
|
continue;
|
|
864
|
+
if (!isBlockEnd(value)) {
|
|
865
|
+
if (!isBlockStreamMsg(value))
|
|
866
|
+
continue;
|
|
867
|
+
const closed = blockAcc.ingest(value);
|
|
868
|
+
const applyResult = closed ? streamingResolver.observeBlock(closed) : undefined;
|
|
869
|
+
const isFailedCodeEnd = closed !== undefined && applyResult !== undefined && applyResult.errors.length > 0;
|
|
870
|
+
if (isFailedCodeEnd) {
|
|
871
|
+
const first = applyResult.errors[0];
|
|
872
|
+
const truncateEvt = buildTruncatedEvent({
|
|
873
|
+
closed,
|
|
874
|
+
firstError: first,
|
|
875
|
+
errorCount: applyResult.errors.length,
|
|
876
|
+
promptId,
|
|
877
|
+
blockSeq,
|
|
878
|
+
now: new Date(),
|
|
879
|
+
});
|
|
880
|
+
collectedMsgs.push(truncateEvt);
|
|
881
|
+
const r = await appendBlockEvent({
|
|
882
|
+
ctx,
|
|
883
|
+
vctx,
|
|
884
|
+
req,
|
|
885
|
+
promptId,
|
|
886
|
+
blockSeq: blockSeq++,
|
|
887
|
+
evt: truncateEvt,
|
|
888
|
+
emitMode: "emit-only",
|
|
889
|
+
});
|
|
890
|
+
if (r.isErr()) {
|
|
891
|
+
return Result.Err(r);
|
|
892
|
+
}
|
|
893
|
+
recoverHint = {
|
|
894
|
+
partial: partialBuffer.text.slice(0, partialBuffer.safeCut),
|
|
895
|
+
focusPath: applyResult.path,
|
|
896
|
+
blockId: closed.end.blockId,
|
|
897
|
+
sectionId: closed.end.sectionId,
|
|
898
|
+
reason: first.reason,
|
|
899
|
+
kind: first.kind,
|
|
900
|
+
errorCount: applyResult.errors.length,
|
|
901
|
+
lastReplaceFileLines: partialBuffer.lastReplaceFileLines,
|
|
902
|
+
};
|
|
903
|
+
currentAbort.abort();
|
|
904
|
+
drainOnly = true;
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
collectedMsgs.push(value);
|
|
908
|
+
const r = await appendBlockEvent({
|
|
909
|
+
ctx,
|
|
910
|
+
vctx,
|
|
911
|
+
req,
|
|
912
|
+
promptId,
|
|
913
|
+
blockSeq: blockSeq++,
|
|
914
|
+
evt: value,
|
|
915
|
+
emitMode: "emit-only",
|
|
916
|
+
});
|
|
917
|
+
if (r.isErr()) {
|
|
918
|
+
return Result.Err(r);
|
|
919
|
+
}
|
|
920
|
+
if (closed !== undefined && applyResult !== undefined && applyResult.errors.length === 0) {
|
|
921
|
+
partialBuffer.safeCut = partialBuffer.text.length;
|
|
922
|
+
streamMadeProgress = true;
|
|
923
|
+
const wasReplace = closed.lines.some((l) => l.line.startsWith("<<<<<<< SEARCH"));
|
|
924
|
+
if (wasReplace) {
|
|
925
|
+
const resolvedText = streamingResolver.getVfs().get(applyResult.path);
|
|
926
|
+
partialBuffer.lastReplaceFileLines = resolvedText ? resolvedText.split("\n").length : undefined;
|
|
927
|
+
}
|
|
928
|
+
else {
|
|
929
|
+
partialBuffer.lastReplaceFileLines = undefined;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
745
932
|
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
ctx,
|
|
933
|
+
else {
|
|
934
|
+
collectedMsgs.push(value);
|
|
935
|
+
const x = await handleEndMsg({ collectedMsgs, vctx, req, ctx, resChat, promptId, value, blockSeq });
|
|
936
|
+
if (x.isErr())
|
|
937
|
+
return Result.Err(x);
|
|
938
|
+
blockSeq = x.Ok();
|
|
939
|
+
collectedMsgs.splice(0, collectedMsgs.length);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
if (isRecoveryStream) {
|
|
943
|
+
recoveryCounter = updateRecoveryCounter(recoveryCounter, { madeProgress: streamMadeProgress });
|
|
944
|
+
recoveryLogger
|
|
945
|
+
.Debug()
|
|
946
|
+
.Any("event", {
|
|
947
|
+
chatId: req.chatId,
|
|
948
|
+
promptId,
|
|
949
|
+
madeProgress: streamMadeProgress,
|
|
950
|
+
consecutiveFruitless: recoveryCounter.consecutiveFruitless,
|
|
951
|
+
})
|
|
952
|
+
.Msg("recovery-stream-end");
|
|
953
|
+
}
|
|
954
|
+
if (recoverHint === null) {
|
|
955
|
+
return Result.Ok();
|
|
956
|
+
}
|
|
957
|
+
if (!shouldAttemptRecovery(recoveryCounter)) {
|
|
958
|
+
recoveryLogger
|
|
959
|
+
.Info()
|
|
960
|
+
.Any("event", {
|
|
961
|
+
chatId: req.chatId,
|
|
962
|
+
promptId,
|
|
963
|
+
blockId: recoverHint.blockId,
|
|
964
|
+
consecutiveFruitless: recoveryCounter.consecutiveFruitless,
|
|
965
|
+
})
|
|
966
|
+
.Msg("recovery-exhausted");
|
|
967
|
+
const exhaustedEnd = {
|
|
968
|
+
type: "block.end",
|
|
969
|
+
blockId: recoverHint.blockId,
|
|
970
|
+
streamId: promptId,
|
|
971
|
+
seq: blockSeq,
|
|
972
|
+
blockNr: 0,
|
|
973
|
+
timestamp: new Date(),
|
|
974
|
+
stats: {
|
|
975
|
+
toplevel: { lines: 0, bytes: 0 },
|
|
976
|
+
code: { lines: 0, bytes: 0 },
|
|
977
|
+
image: { lines: 0, bytes: 0 },
|
|
978
|
+
total: { lines: 0, bytes: 0 },
|
|
979
|
+
},
|
|
980
|
+
usage: {
|
|
981
|
+
given: [],
|
|
982
|
+
calculated: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
|
|
983
|
+
},
|
|
984
|
+
};
|
|
985
|
+
collectedMsgs.push(exhaustedEnd);
|
|
986
|
+
const xEnd = await handleEndMsg({
|
|
987
|
+
collectedMsgs,
|
|
749
988
|
vctx,
|
|
750
989
|
req,
|
|
990
|
+
ctx,
|
|
991
|
+
resChat,
|
|
751
992
|
promptId,
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
emitMode: "emit-only",
|
|
993
|
+
value: exhaustedEnd,
|
|
994
|
+
blockSeq,
|
|
755
995
|
});
|
|
756
|
-
if (
|
|
757
|
-
return Result.Err(
|
|
758
|
-
|
|
996
|
+
if (xEnd.isErr())
|
|
997
|
+
return Result.Err(xEnd);
|
|
998
|
+
return Result.Ok();
|
|
759
999
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
1000
|
+
const stitchMode = recoveryCounter.consecutiveFruitless === 2;
|
|
1001
|
+
recoveryLogger
|
|
1002
|
+
.Debug()
|
|
1003
|
+
.Any("event", {
|
|
1004
|
+
chatId: req.chatId,
|
|
1005
|
+
promptId,
|
|
1006
|
+
blockId: recoverHint.blockId,
|
|
1007
|
+
sectionId: recoverHint.sectionId,
|
|
1008
|
+
path: recoverHint.focusPath,
|
|
1009
|
+
reason: recoverHint.reason,
|
|
1010
|
+
kind: recoverHint.kind,
|
|
1011
|
+
errorCount: recoverHint.errorCount,
|
|
1012
|
+
consecutiveFruitless: recoveryCounter.consecutiveFruitless,
|
|
1013
|
+
mode: stitchMode ? "stitch" : "continue",
|
|
1014
|
+
})
|
|
1015
|
+
.Msg("recovery-start");
|
|
1016
|
+
const pkgBaseUrl = promptsPkgBaseUrl(vctx.params.pkgRepos.workspace);
|
|
1017
|
+
const fetchOverride = createPromptAssetFetch({ fetchAsset: vctx.fetchAsset });
|
|
1018
|
+
const addendum = await exception2Result(() => stitchMode ? getRecoveryStitchAddendum(pkgBaseUrl, fetchOverride) : getRecoveryAddendum(pkgBaseUrl, fetchOverride));
|
|
1019
|
+
if (addendum.isErr()) {
|
|
1020
|
+
recoveryLogger
|
|
1021
|
+
.Info()
|
|
1022
|
+
.Any("event", { chatId: req.chatId, promptId, err: String(addendum.Err()) })
|
|
1023
|
+
.Msg("recovery-addendum-failed");
|
|
1024
|
+
return Result.Ok();
|
|
768
1025
|
}
|
|
1026
|
+
const recReq = buildRecoveryRequest({
|
|
1027
|
+
originalRequest: llmReq,
|
|
1028
|
+
recoveryAddendum: addendum.Ok(),
|
|
1029
|
+
vfs: streamingResolver.getVfs(),
|
|
1030
|
+
focusPath: recoverHint.focusPath,
|
|
1031
|
+
assistantPartial: stitchMode ? undefined : recoverHint.partial,
|
|
1032
|
+
lastReplaceFileLines: stitchMode ? undefined : recoverHint.lastReplaceFileLines,
|
|
1033
|
+
});
|
|
1034
|
+
if (recReq.isErr()) {
|
|
1035
|
+
recoveryLogger
|
|
1036
|
+
.Info()
|
|
1037
|
+
.Any("event", {
|
|
1038
|
+
chatId: req.chatId,
|
|
1039
|
+
promptId,
|
|
1040
|
+
err: String(recReq.Err()),
|
|
1041
|
+
originalMessageCount: llmReq.messages.length,
|
|
1042
|
+
originalRoles: llmReq.messages.map((m) => m.role),
|
|
1043
|
+
})
|
|
1044
|
+
.Msg("recovery-build-failed");
|
|
1045
|
+
return Result.Ok();
|
|
1046
|
+
}
|
|
1047
|
+
const recPayload = recReq.Ok();
|
|
1048
|
+
const recMessageCount = recPayload.messages.length;
|
|
1049
|
+
const recRoles = recPayload.messages.map((m) => m.role);
|
|
1050
|
+
const recModel = recPayload.model;
|
|
1051
|
+
const nextAbort = new AbortController();
|
|
1052
|
+
const rNextRes = await exception2Result(() => vctx.llmRequest({ ...recPayload, headers: llmReq.headers }, { signal: nextAbort.signal }));
|
|
1053
|
+
if (rNextRes.isErr()) {
|
|
1054
|
+
recoveryLogger
|
|
1055
|
+
.Info()
|
|
1056
|
+
.Any("event", {
|
|
1057
|
+
chatId: req.chatId,
|
|
1058
|
+
promptId,
|
|
1059
|
+
err: String(rNextRes.Err()),
|
|
1060
|
+
model: recModel,
|
|
1061
|
+
messageCount: recMessageCount,
|
|
1062
|
+
roles: recRoles,
|
|
1063
|
+
})
|
|
1064
|
+
.Msg("recovery-call-failed");
|
|
1065
|
+
return Result.Ok();
|
|
1066
|
+
}
|
|
1067
|
+
const nextRes = rNextRes.Ok();
|
|
1068
|
+
if (!nextRes.ok || !nextRes.body) {
|
|
1069
|
+
const rBody = await exception2Result(() => nextRes.text());
|
|
1070
|
+
const rawBody = rBody.isOk() ? rBody.Ok() : `<read-failed: ${String(rBody.Err())}>`;
|
|
1071
|
+
const bodySnippet = rawBody.length > 2000 ? `${rawBody.slice(0, 2000)}…[+${rawBody.length - 2000}b]` : rawBody;
|
|
1072
|
+
recoveryLogger
|
|
1073
|
+
.Info()
|
|
1074
|
+
.Any("event", {
|
|
1075
|
+
chatId: req.chatId,
|
|
1076
|
+
promptId,
|
|
1077
|
+
status: nextRes.status,
|
|
1078
|
+
statusText: nextRes.statusText,
|
|
1079
|
+
model: recModel,
|
|
1080
|
+
messageCount: recMessageCount,
|
|
1081
|
+
roles: recRoles,
|
|
1082
|
+
bodySnippet,
|
|
1083
|
+
})
|
|
1084
|
+
.Msg("recovery-call-failed");
|
|
1085
|
+
return Result.Ok();
|
|
1086
|
+
}
|
|
1087
|
+
recoveryLogger
|
|
1088
|
+
.Debug()
|
|
1089
|
+
.Any("event", {
|
|
1090
|
+
chatId: req.chatId,
|
|
1091
|
+
promptId,
|
|
1092
|
+
partialBytes: recoverHint.partial.length,
|
|
1093
|
+
focusPath: recoverHint.focusPath,
|
|
1094
|
+
model: recModel,
|
|
1095
|
+
messageCount: recMessageCount,
|
|
1096
|
+
roles: recRoles,
|
|
1097
|
+
})
|
|
1098
|
+
.Msg("recovery-call-started");
|
|
1099
|
+
currentRes = nextRes;
|
|
1100
|
+
currentAbort = nextAbort;
|
|
1101
|
+
isRecoveryStream = true;
|
|
769
1102
|
}
|
|
770
|
-
return Result.Ok();
|
|
771
1103
|
})
|
|
772
1104
|
.do();
|
|
773
1105
|
return blockSeq;
|
|
@@ -990,6 +1322,8 @@ export const promptChatSection = {
|
|
|
990
1322
|
resChat,
|
|
991
1323
|
promptId,
|
|
992
1324
|
blockSeq: res.blockSeq,
|
|
1325
|
+
llmReq: res.llmReq,
|
|
1326
|
+
abort: res.abort,
|
|
993
1327
|
});
|
|
994
1328
|
return Result.Ok(finalBlockSeq);
|
|
995
1329
|
};
|