Przeglądaj źródła

make single owner of prompt copy/equality

Simon Klee 5 dni temu
rodzic
commit
e80d6557ff

+ 6 - 6
packages/opencode/src/cli/cmd/run/prompt.shared.ts

@@ -46,14 +46,14 @@ export type PromptMove = {
   apply: boolean
 }
 
-function copy(prompt: RunPrompt): RunPrompt {
+export function promptCopy(prompt: RunPrompt): RunPrompt {
   return {
     text: prompt.text,
     parts: structuredClone(prompt.parts),
   }
 }
 
-function same(a: RunPrompt, b: RunPrompt): boolean {
+export function promptSame(a: RunPrompt, b: RunPrompt): boolean {
   return a.text === b.text && JSON.stringify(a.parts) === JSON.stringify(b.parts)
 }
 
@@ -171,10 +171,10 @@ export function promptCycle(
 }
 
 export function createPromptHistory(items?: RunPrompt[]): PromptHistoryState {
-  const list = (items ?? []).filter((item) => item.text.trim().length > 0).map(copy)
+  const list = (items ?? []).filter((item) => item.text.trim().length > 0).map(promptCopy)
   const next: RunPrompt[] = []
   for (const item of list) {
-    if (next.length > 0 && same(next[next.length - 1], item)) {
+    if (next.length > 0 && promptSame(next[next.length - 1], item)) {
       continue
     }
 
@@ -193,8 +193,8 @@ export function pushPromptHistory(state: PromptHistoryState, prompt: RunPrompt):
     return state
   }
 
-  const next = copy(prompt)
-  if (state.items[state.items.length - 1] && same(state.items[state.items.length - 1], next)) {
+  const next = promptCopy(prompt)
+  if (state.items[state.items.length - 1] && promptSame(state.items[state.items.length - 1], next)) {
     return {
       ...state,
       index: null,

+ 3 - 13
packages/opencode/src/cli/cmd/run/session.shared.ts

@@ -5,6 +5,7 @@
 // the current model so the footer can pre-select it.
 import path from "path"
 import { fileURLToPath } from "url"
+import { promptCopy, promptSame } from "./prompt.shared"
 import type { RunInput, RunPrompt } from "./types"
 
 const LIMIT = 200
@@ -23,17 +24,6 @@ export type RunSession = {
   turns: Turn[]
 }
 
-function copy(prompt: RunPrompt): RunPrompt {
-  return {
-    text: prompt.text,
-    parts: structuredClone(prompt.parts),
-  }
-}
-
-function same(a: RunPrompt, b: RunPrompt): boolean {
-  return a.text === b.text && JSON.stringify(a.parts) === JSON.stringify(b.parts)
-}
-
 function fileName(url: string, filename?: string) {
   if (filename) {
     return filename
@@ -175,11 +165,11 @@ export function sessionHistory(session: RunSession, limit = LIMIT): RunPrompt[]
       continue
     }
 
-    if (out[out.length - 1] && same(out[out.length - 1], turn.prompt)) {
+    if (out[out.length - 1] && promptSame(out[out.length - 1], turn.prompt)) {
       continue
     }
 
-    out.push(copy(turn.prompt))
+    out.push(promptCopy(turn.prompt))
   }
 
   return out.slice(-limit)

+ 20 - 4
packages/opencode/test/cli/run/session.shared.test.ts

@@ -133,18 +133,34 @@ describe("run session shared", () => {
     })
   })
 
-  test("dedupes consecutive history entries and drops blank prompts", () => {
+  test("dedupes consecutive history entries, drops blanks, and copies prompt parts", () => {
+    const parts = [
+      {
+        type: "agent" as const,
+        name: "scan",
+        source: {
+          start: 0,
+          end: 5,
+          value: "@scan",
+        },
+      },
+    ]
     const session: RunSession = {
       first: false,
       turns: [
-        { prompt: { text: "one", parts: [] }, provider: "openai", model: "gpt-5", variant: "high" },
-        { prompt: { text: "one", parts: [] }, provider: "openai", model: "gpt-5", variant: "high" },
+        { prompt: { text: "one", parts }, provider: "openai", model: "gpt-5", variant: "high" },
+        { prompt: { text: "one", parts: structuredClone(parts) }, provider: "openai", model: "gpt-5", variant: "high" },
         { prompt: { text: "   ", parts: [] }, provider: "openai", model: "gpt-5", variant: "high" },
         { prompt: { text: "two", parts: [] }, provider: "openai", model: "gpt-5", variant: undefined },
       ],
     }
 
-    expect(sessionHistory(session).map((item) => item.text)).toEqual(["one", "two"])
+    const out = sessionHistory(session)
+
+    expect(out.map((item) => item.text)).toEqual(["one", "two"])
+    expect(out[0]?.parts).toEqual(parts)
+    expect(out[0]?.parts).not.toBe(parts)
+    expect(out[0]?.parts[0]).not.toBe(parts[0])
   })
 
   test("returns the latest matching variant for the active model", () => {