refactor(core): consolidate references
This commit is contained in:
parent
137992ca85
commit
f76ca58a54
63 changed files with 683 additions and 2722 deletions
|
|
@ -9,6 +9,7 @@ import { useEditorContext } from "../../context/editor"
|
|||
import { useProject } from "../../context/project"
|
||||
import { useSDK } from "../../context/sdk"
|
||||
import { useSync } from "../../context/sync"
|
||||
import { useSyncV2 } from "../../context/sync-v2"
|
||||
import { getScrollAcceleration } from "../../util/scroll"
|
||||
import { useTuiPaths } from "../../context/runtime"
|
||||
import { useTuiConfig } from "../../config"
|
||||
|
|
@ -19,7 +20,6 @@ import { Locale } from "../../util/locale"
|
|||
import type { PromptInfo } from "../../prompt/history"
|
||||
import { useFrecency } from "../../prompt/frecency"
|
||||
import { useBindings, useCommandSlashes, useOpencodeModeStack } from "../../keymap"
|
||||
import type { ReferenceDescriptor } from "@opencode-ai/sdk/v2"
|
||||
import { displayCharAt, mentionTriggerIndex } from "../../prompt/display"
|
||||
|
||||
function removeLineRange(input: string) {
|
||||
|
|
@ -85,6 +85,7 @@ export function Autocomplete(props: {
|
|||
const editor = useEditorContext()
|
||||
const sdk = useSDK()
|
||||
const sync = useSync()
|
||||
const syncV2 = useSyncV2()
|
||||
const project = useProject()
|
||||
const slashes = useCommandSlashes()
|
||||
const modeStack = useOpencodeModeStack()
|
||||
|
|
@ -272,37 +273,12 @@ export function Autocomplete(props: {
|
|||
}
|
||||
}
|
||||
|
||||
function referencePromptText(reference: ReferenceDescriptor) {
|
||||
const problem = reference.kind === "invalid" ? reference.message : undefined
|
||||
return [
|
||||
`Referenced configured reference @${reference.name}.`,
|
||||
...(reference.kind === "local" ? ["Kind: local directory"] : []),
|
||||
...(reference.kind === "git" ? ["Kind: git repository"] : []),
|
||||
...(reference.kind === "invalid" && reference.repository ? [`Repository: ${reference.repository}`] : []),
|
||||
...(reference.kind === "git" ? [`Repository: ${reference.repository}`] : []),
|
||||
...(reference.kind === "git" && reference.branch ? [`Branch/ref: ${reference.branch}`] : []),
|
||||
...(reference.kind === "invalid" ? [] : [`Reference root: ${reference.path}`]),
|
||||
...(problem
|
||||
? [`Problem: ${problem}`]
|
||||
: ["Inspect the configured reference with Read, Glob, and Grep when useful."]),
|
||||
].join("\n")
|
||||
}
|
||||
|
||||
const [references] = createResource(
|
||||
() => project.workspace.current(),
|
||||
async (workspace) => {
|
||||
const result = await sdk.client.reference.list({ workspace })
|
||||
return result.data ?? []
|
||||
},
|
||||
{ initialValue: [] },
|
||||
)
|
||||
|
||||
const referenceMatch = createMemo(() => {
|
||||
if (!store.visible || store.visible === "/") return
|
||||
const { baseQuery } = extractLineRange(search())
|
||||
const slash = baseQuery.indexOf("/")
|
||||
const alias = slash === -1 ? baseQuery : baseQuery.slice(0, slash)
|
||||
return references().find((item) => item.name === alias)
|
||||
return syncV2.data.reference.find((item) => item.name === alias)
|
||||
})
|
||||
|
||||
function normalizeMentionPath(filePath: string) {
|
||||
|
|
@ -435,29 +411,21 @@ export function Autocomplete(props: {
|
|||
})
|
||||
|
||||
const referenceAliases = createMemo(() =>
|
||||
references().map(
|
||||
syncV2.data.reference.map(
|
||||
(reference): AutocompleteOption => ({
|
||||
display: "@" + reference.name,
|
||||
description: reference.kind === "invalid" ? reference.message : " dir",
|
||||
description: " dir",
|
||||
onSelect: () => {
|
||||
if (reference.kind !== "invalid") {
|
||||
insertPart(reference.name, {
|
||||
type: "file",
|
||||
mime: "application/x-directory",
|
||||
filename: reference.name,
|
||||
url: pathToFileURL(reference.path).href,
|
||||
source: {
|
||||
type: "file",
|
||||
text: { start: 0, end: 0, value: "" },
|
||||
path: reference.name,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
insertPart(reference.name, {
|
||||
type: "text",
|
||||
text: referencePromptText(reference),
|
||||
synthetic: true,
|
||||
type: "file",
|
||||
mime: "application/x-directory",
|
||||
filename: reference.name,
|
||||
url: pathToFileURL(reference.path).href,
|
||||
source: {
|
||||
type: "file",
|
||||
text: { start: 0, end: 0, value: "" },
|
||||
path: reference.name,
|
||||
},
|
||||
})
|
||||
},
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useEvent } from "./event"
|
||||
import type {
|
||||
Event,
|
||||
ReferenceInfo,
|
||||
SessionMessage,
|
||||
SessionMessageAssistant,
|
||||
SessionMessageAssistantReasoning,
|
||||
|
|
@ -10,6 +11,8 @@ import type {
|
|||
import { createStore, produce, reconcile } from "solid-js/store"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { useSDK } from "./sdk"
|
||||
import { useProject } from "./project"
|
||||
import { createEffect } from "solid-js"
|
||||
|
||||
function activeAssistant(messages: SessionMessage[]) {
|
||||
const index = messages.findIndex((message) => message.type === "assistant" && !message.time.completed)
|
||||
|
|
@ -60,12 +63,15 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
|
|||
messages: {
|
||||
[sessionID: string]: SessionMessage[]
|
||||
}
|
||||
reference: ReferenceInfo[]
|
||||
}>({
|
||||
messages: {},
|
||||
reference: [],
|
||||
})
|
||||
|
||||
const event = useEvent()
|
||||
const sdk = useSDK()
|
||||
const project = useProject()
|
||||
const applied = new Set<string>()
|
||||
const buffering = new Map<string, Event[]>()
|
||||
const syncing = new Map<string, Promise<void>>()
|
||||
|
|
@ -117,6 +123,17 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
|
|||
return result
|
||||
}
|
||||
|
||||
async function syncReferences(workspace = project.workspace.current()) {
|
||||
const result = await sdk.client.v2.reference.list({ location: { workspace } })
|
||||
if (workspace !== project.workspace.current()) return
|
||||
setStore("reference", reconcile(result.data?.data ?? []))
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
project.workspace.current()
|
||||
void syncReferences()
|
||||
})
|
||||
|
||||
function apply(event: Event) {
|
||||
switch (event.type) {
|
||||
case "session.next.agent.switched":
|
||||
|
|
@ -147,7 +164,6 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
|
|||
text: event.properties.prompt.text,
|
||||
files: event.properties.prompt.files,
|
||||
agents: event.properties.prompt.agents,
|
||||
references: event.properties.prompt.references,
|
||||
time: { created: event.properties.timestamp },
|
||||
})
|
||||
})
|
||||
|
|
@ -163,7 +179,6 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
|
|||
text: event.properties.prompt.text,
|
||||
files: event.properties.prompt.files,
|
||||
agents: event.properties.prompt.agents,
|
||||
references: event.properties.prompt.references,
|
||||
time: { created: event.properties.timeCreated },
|
||||
})
|
||||
})
|
||||
|
|
@ -418,6 +433,9 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext(
|
|||
})
|
||||
})
|
||||
break
|
||||
case "reference.updated":
|
||||
void syncReferences()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,52 @@ function emitTwice(events: ReturnType<typeof createEventSource>, payload: Event)
|
|||
events.emit(event)
|
||||
}
|
||||
|
||||
test("sync v2 refreshes references after updates", async () => {
|
||||
const events = createEventSource()
|
||||
let requests = 0
|
||||
const calls = createFetch((url) => {
|
||||
if (url.pathname !== "/api/reference") return
|
||||
requests++
|
||||
return json({
|
||||
location: { directory, project: { id: "proj_test", directory } },
|
||||
data: requests === 1 ? [] : [{ name: "docs", path: "/docs", source: { type: "local", path: "/docs" } }],
|
||||
})
|
||||
})
|
||||
let sync!: ReturnType<typeof useSyncV2>
|
||||
let ready!: () => void
|
||||
const mounted = new Promise<void>((resolve) => {
|
||||
ready = resolve
|
||||
})
|
||||
|
||||
function Probe() {
|
||||
sync = useSyncV2()
|
||||
onMount(ready)
|
||||
return <box />
|
||||
}
|
||||
|
||||
const app = await testRender(() => (
|
||||
<TestTuiContexts>
|
||||
<SDKProvider url="http://test" directory={directory} events={events.source} fetch={calls.fetch}>
|
||||
<ProjectProvider>
|
||||
<SyncProviderV2>
|
||||
<Probe />
|
||||
</SyncProviderV2>
|
||||
</ProjectProvider>
|
||||
</SDKProvider>
|
||||
</TestTuiContexts>
|
||||
))
|
||||
|
||||
try {
|
||||
await mounted
|
||||
await wait(() => requests === 1)
|
||||
events.emit(global({ id: "evt_reference_1", type: "reference.updated", properties: {} }))
|
||||
await wait(() => sync.data.reference.length === 1)
|
||||
expect(sync.data.reference[0]?.name).toBe("docs")
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
test("sync v2 settles pending tools when a live failure arrives", async () => {
|
||||
const events = createEventSource()
|
||||
const calls = createFetch()
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ export function createFetch(override?: FetchHandler) {
|
|||
if (url.pathname === "/experimental/console") return json({ consoleManagedProviders: [], switchableOrgCount: 0 })
|
||||
if (url.pathname === "/path") return json({ home: "", state: "", config: "", worktree, directory })
|
||||
if (url.pathname === "/project/current") return json({ id: "proj_test" })
|
||||
if (url.pathname === "/api/reference") return json({ location: { directory, project: { id: "proj_test", directory } }, data: [] })
|
||||
if (url.pathname === "/provider") return json({ all: [], default: {}, connected: [] })
|
||||
if (url.pathname === "/session") return json([])
|
||||
if (url.pathname === "/vcs") return json({ branch: "main" })
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue