Skip to content

Commit 6f39d2a

Browse files
committed
Fix Kilo and OpenCode transcript handling
1 parent 77a222d commit 6f39d2a

3 files changed

Lines changed: 78 additions & 7 deletions

File tree

apps/server/src/orchestration/Layers/ProviderCommandReactor.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { TextGeneration } from "../../git/Services/TextGeneration.ts";
4444
import { ProviderService } from "../../provider/Services/ProviderService.ts";
4545
import { clearWorkspaceIndexCache } from "../../workspaceEntries.ts";
4646
import {
47+
buildPriorTranscriptBootstrapText,
4748
buildForkBootstrapText,
4849
buildHandoffBootstrapText,
4950
hasNativeAssistantMessagesBefore,
@@ -793,6 +794,11 @@ const make = Effect.gen(function* () {
793794
if (!thread) {
794795
return;
795796
}
797+
const activeSessionBeforeEnsure = yield* providerService
798+
.listSessions()
799+
.pipe(
800+
Effect.map((sessions) => sessions.find((session) => session.threadId === input.threadId)),
801+
);
796802
yield* ensureSessionForThread(input.threadId, input.createdAt, {
797803
...(input.modelSelection !== undefined ? { modelSelection: input.modelSelection } : {}),
798804
...(input.providerOptions !== undefined ? { providerOptions: input.providerOptions } : {}),
@@ -825,13 +831,29 @@ const make = Effect.gen(function* () {
825831
shouldBootstrapSidechatContext && availableBootstrapChars > 0
826832
? buildForkBootstrapText(thread, availableBootstrapChars)
827833
: null;
834+
const selectedProvider =
835+
input.modelSelection?.provider ??
836+
threadModelSelections.get(input.threadId)?.provider ??
837+
thread.session?.providerName ??
838+
thread.modelSelection.provider;
839+
const shouldBootstrapPriorTranscriptContext =
840+
(selectedProvider === "kilo" || selectedProvider === "opencode") &&
841+
activeSessionBeforeEnsure === undefined &&
842+
!handoffBootstrapText &&
843+
!sidechatBootstrapText;
844+
const priorTranscriptBootstrapText =
845+
shouldBootstrapPriorTranscriptContext && availableBootstrapChars > 0
846+
? buildPriorTranscriptBootstrapText(thread, input.messageId, availableBootstrapChars)
847+
: null;
828848
const boundaryMessageText = thread.sidechatSourceThreadId
829849
? wrapSidechatInput(input.messageText)
830850
: input.messageText;
831851
const providerInput = handoffBootstrapText
832852
? `<handoff_context>\n${handoffBootstrapText}\n</handoff_context>\n\n<latest_user_message>\n${boundaryMessageText}\n</latest_user_message>`
833853
: sidechatBootstrapText
834854
? `<sidechat_context>\n${sidechatBootstrapText}\n</sidechat_context>\n\n${boundaryMessageText}`
855+
: priorTranscriptBootstrapText
856+
? `<thread_context>\n${priorTranscriptBootstrapText}\n</thread_context>\n\n<latest_user_message>\n${boundaryMessageText}\n</latest_user_message>`
835857
: boundaryMessageText;
836858
const normalizedInput = toNonEmptyProviderInput(providerInput);
837859
const normalizedAttachments = input.attachments ?? [];

apps/server/src/orchestration/handoff.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,24 @@ export function hasNativeAssistantMessagesBefore(
7373
});
7474
}
7575

76+
export function listPriorTranscriptMessages(
77+
thread: Pick<OrchestrationThread, "messages">,
78+
currentMessageId: string,
79+
): ReadonlyArray<OrchestrationMessage> {
80+
const currentIndex = thread.messages.findIndex((message) => message.id === currentMessageId);
81+
if (currentIndex <= 0) {
82+
return [];
83+
}
84+
85+
return thread.messages.slice(0, currentIndex).filter((message) => {
86+
return (
87+
(message.role === "user" || message.role === "assistant") &&
88+
message.streaming === false &&
89+
normalizeMessageText(message.text).length > 0
90+
);
91+
});
92+
}
93+
7694
function buildImportedMessagesBootstrapText(input: {
7795
thread: Pick<OrchestrationThread, "title" | "branch" | "worktreePath">;
7896
importedMessages: ReadonlyArray<OrchestrationMessage>;
@@ -143,6 +161,25 @@ export function buildHandoffBootstrapText(
143161
});
144162
}
145163

164+
export function buildPriorTranscriptBootstrapText(
165+
thread: Pick<OrchestrationThread, "title" | "branch" | "worktreePath" | "messages">,
166+
currentMessageId: string,
167+
maxChars = HANDOFF_BOOTSTRAP_CHAR_BUDGET,
168+
): string | null {
169+
const priorMessages = listPriorTranscriptMessages(thread, currentMessageId);
170+
if (priorMessages.length === 0) {
171+
return null;
172+
}
173+
174+
return buildImportedMessagesBootstrapText({
175+
thread,
176+
importedMessages: priorMessages,
177+
intro:
178+
"This provider session may have been restarted without native conversation state. Use this prior DP Code transcript as context for the latest user message.",
179+
maxChars,
180+
});
181+
}
182+
146183
export function buildForkBootstrapText(
147184
thread: Pick<OrchestrationThread, "title" | "branch" | "worktreePath" | "messages">,
148185
maxChars = HANDOFF_BOOTSTRAP_CHAR_BUDGET,

apps/server/src/provider/Layers/OpenCodeAdapter.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1755,9 +1755,18 @@ export function makeOpenCodeAdapterLive(options?: OpenCodeAdapterLiveOptions) {
17551755
if (text === undefined) {
17561756
return;
17571757
}
1758-
const previousText = context.emittedTextByPartId.get(part.id);
1758+
const nextTextItemId =
1759+
turnId && part.type === "text" ? openCodeNextTextItemId(turnId) : undefined;
1760+
const itemId =
1761+
nextTextItemId && context.emittedTextByPartId.has(nextTextItemId)
1762+
? nextTextItemId
1763+
: part.id;
1764+
const previousText = context.emittedTextByPartId.get(itemId);
17591765
const { latestText, deltaToEmit } = mergeOpenCodeAssistantText(previousText, text);
1760-
context.emittedTextByPartId.set(part.id, latestText);
1766+
context.emittedTextByPartId.set(itemId, latestText);
1767+
if (itemId !== part.id) {
1768+
context.emittedTextByPartId.set(part.id, latestText);
1769+
}
17611770
if (latestText !== text) {
17621771
context.partById.set(
17631772
part.id,
@@ -1771,7 +1780,7 @@ export function makeOpenCodeAdapterLive(options?: OpenCodeAdapterLiveOptions) {
17711780
...buildEventBase({
17721781
threadId: context.session.threadId,
17731782
turnId,
1774-
itemId: part.id,
1783+
itemId,
17751784
createdAt:
17761785
part.type === "text" || part.type === "reasoning"
17771786
? isoFromEpochMs(part.time?.start)
@@ -1789,9 +1798,12 @@ export function makeOpenCodeAdapterLive(options?: OpenCodeAdapterLiveOptions) {
17891798
if (
17901799
part.type === "text" &&
17911800
part.time?.end !== undefined &&
1792-
!context.completedAssistantPartIds.has(part.id)
1801+
!context.completedAssistantPartIds.has(itemId)
17931802
) {
1794-
context.completedAssistantPartIds.add(part.id);
1803+
context.completedAssistantPartIds.add(itemId);
1804+
if (itemId !== part.id) {
1805+
context.completedAssistantPartIds.add(part.id);
1806+
}
17951807
const proposedPlanMarkdown =
17961808
context.activeInteractionMode === "plan"
17971809
? extractProposedPlanMarkdown(latestText)
@@ -1801,7 +1813,7 @@ export function makeOpenCodeAdapterLive(options?: OpenCodeAdapterLiveOptions) {
18011813
...buildEventBase({
18021814
threadId: context.session.threadId,
18031815
turnId,
1804-
itemId: part.id,
1816+
itemId,
18051817
createdAt: isoFromEpochMs(part.time.end),
18061818
raw,
18071819
}),
@@ -1815,7 +1827,7 @@ export function makeOpenCodeAdapterLive(options?: OpenCodeAdapterLiveOptions) {
18151827
...buildEventBase({
18161828
threadId: context.session.threadId,
18171829
turnId,
1818-
itemId: part.id,
1830+
itemId,
18191831
createdAt: isoFromEpochMs(part.time.end),
18201832
raw,
18211833
}),

0 commit comments

Comments
 (0)