mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-03 15:43:45 +00:00
Refactor to support multiple instances inside single opencode process (#2360)
This release has a bunch of minor breaking changes if you are using opencode plugins or sdk 1. storage events have been removed (we might bring this back but had some issues) 2. concept of `app` is gone - there is a new concept called `project` and endpoints to list projects and get the current project 3. plugin receives `directory` which is cwd and `worktree` which is where the root of the project is if it's a git repo 4. the session.chat function has been renamed to session.prompt in sdk. it no longer requires model to be passed in (model is now an object) 5. every endpoint takes an optional `directory` parameter to operate as though opencode is running in that directory
This commit is contained in:
@@ -20,7 +20,6 @@ import PROMPT_INITIALIZE from "../session/prompt/initialize.txt"
|
||||
import PROMPT_PLAN from "../session/prompt/plan.txt"
|
||||
import BUILD_SWITCH from "../session/prompt/build-switch.txt"
|
||||
|
||||
import { App } from "../app/app"
|
||||
import { Bus } from "../bus"
|
||||
import { Config } from "../config/config"
|
||||
import { Flag } from "../flag/flag"
|
||||
@@ -43,6 +42,8 @@ import { ReadTool } from "../tool/read"
|
||||
import { mergeDeep, pipe, splitWhen } from "remeda"
|
||||
import { ToolRegistry } from "../tool/registry"
|
||||
import { Plugin } from "../plugin"
|
||||
import { Project } from "../project/project"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Agent } from "../agent/agent"
|
||||
import { Permission } from "../permission"
|
||||
import { Wildcard } from "../util/wildcard"
|
||||
@@ -70,6 +71,8 @@ export namespace Session {
|
||||
export const Info = z
|
||||
.object({
|
||||
id: Identifier.schema("session"),
|
||||
projectID: z.string(),
|
||||
directory: z.string(),
|
||||
parentID: Identifier.schema("session").optional(),
|
||||
share: z
|
||||
.object({
|
||||
@@ -134,11 +137,8 @@ export namespace Session {
|
||||
),
|
||||
}
|
||||
|
||||
const state = App.state(
|
||||
"session",
|
||||
const state = Instance.state(
|
||||
() => {
|
||||
const sessions = new Map<string, Info>()
|
||||
const messages = new Map<string, MessageV2.Info[]>()
|
||||
const pending = new Map<string, AbortController>()
|
||||
const autoCompacting = new Map<string, boolean>()
|
||||
const queued = new Map<
|
||||
@@ -153,8 +153,6 @@ export namespace Session {
|
||||
>()
|
||||
|
||||
return {
|
||||
sessions,
|
||||
messages,
|
||||
pending,
|
||||
autoCompacting,
|
||||
queued,
|
||||
@@ -168,19 +166,28 @@ export namespace Session {
|
||||
)
|
||||
|
||||
export async function create(parentID?: string, title?: string) {
|
||||
const result: Info = {
|
||||
id: Identifier.descending("session"),
|
||||
version: Installation.VERSION,
|
||||
return createNext({
|
||||
parentID,
|
||||
title: title ?? createDefaultTitle(!!parentID),
|
||||
directory: Instance.directory,
|
||||
title,
|
||||
})
|
||||
}
|
||||
|
||||
export async function createNext(input: { id?: string; title?: string; parentID?: string; directory: string }) {
|
||||
const result: Info = {
|
||||
id: Identifier.descending("session", input.id),
|
||||
version: Installation.VERSION,
|
||||
projectID: Instance.project.id,
|
||||
directory: input.directory,
|
||||
parentID: input.parentID,
|
||||
title: input.title ?? createDefaultTitle(!!input.parentID),
|
||||
time: {
|
||||
created: Date.now(),
|
||||
updated: Date.now(),
|
||||
},
|
||||
}
|
||||
log.info("created", result)
|
||||
state().sessions.set(result.id, result)
|
||||
await Storage.writeJSON("session/info/" + result.id, result)
|
||||
await Storage.write(["session", Instance.project.id, result.id], result)
|
||||
const cfg = await Config.get()
|
||||
if (!result.parentID && (Flag.OPENCODE_AUTO_SHARE || cfg.share === "auto"))
|
||||
share(result.id)
|
||||
@@ -199,17 +206,12 @@ export namespace Session {
|
||||
}
|
||||
|
||||
export async function get(id: string) {
|
||||
const result = state().sessions.get(id)
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
const read = await Storage.readJSON<Info>("session/info/" + id)
|
||||
state().sessions.set(id, read)
|
||||
const read = await Storage.read<Info>(["session", Instance.project.id, id])
|
||||
return read as Info
|
||||
}
|
||||
|
||||
export async function getShare(id: string) {
|
||||
return Storage.readJSON<ShareInfo>("session/share/" + id)
|
||||
return Storage.read<ShareInfo>(["share", id])
|
||||
}
|
||||
|
||||
export async function share(id: string) {
|
||||
@@ -226,7 +228,7 @@ export namespace Session {
|
||||
url: share.url,
|
||||
}
|
||||
})
|
||||
await Storage.writeJSON<ShareInfo>("session/share/" + id, share)
|
||||
await Storage.write(["share", id], share)
|
||||
await Share.sync("session/info/" + id, session)
|
||||
for (const msg of await messages(id)) {
|
||||
await Share.sync("session/message/" + id + "/" + msg.info.id, msg.info)
|
||||
@@ -240,7 +242,7 @@ export namespace Session {
|
||||
export async function unshare(id: string) {
|
||||
const share = await getShare(id)
|
||||
if (!share) return
|
||||
await Storage.remove("session/share/" + id)
|
||||
await Storage.remove(["share", id])
|
||||
await update(id, (draft) => {
|
||||
draft.share = undefined
|
||||
})
|
||||
@@ -248,17 +250,15 @@ export namespace Session {
|
||||
}
|
||||
|
||||
export async function update(id: string, editor: (session: Info) => void) {
|
||||
const { sessions } = state()
|
||||
const session = await get(id)
|
||||
if (!session) return
|
||||
editor(session)
|
||||
session.time.updated = Date.now()
|
||||
sessions.set(id, session)
|
||||
await Storage.writeJSON("session/info/" + id, session)
|
||||
Bus.publish(Event.Updated, {
|
||||
info: session,
|
||||
const project = Instance.project
|
||||
const result = await Storage.update<Info>(["session", project.id, id], (draft) => {
|
||||
editor(draft)
|
||||
draft.time.updated = Date.now()
|
||||
})
|
||||
return session
|
||||
Bus.publish(Event.Updated, {
|
||||
info: result,
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
export async function messages(sessionID: string) {
|
||||
@@ -266,11 +266,11 @@ export namespace Session {
|
||||
info: MessageV2.Info
|
||||
parts: MessageV2.Part[]
|
||||
}[]
|
||||
for (const p of await Storage.list("session/message/" + sessionID)) {
|
||||
const read = await Storage.readJSON<MessageV2.Info>(p)
|
||||
for (const p of await Storage.list(["message", sessionID])) {
|
||||
const read = await Storage.read<MessageV2.Info>(p)
|
||||
result.push({
|
||||
info: read,
|
||||
parts: await getParts(sessionID, read.id),
|
||||
parts: await getParts(read.id),
|
||||
})
|
||||
}
|
||||
result.sort((a, b) => (a.info.id > b.info.id ? 1 : -1))
|
||||
@@ -279,15 +279,15 @@ export namespace Session {
|
||||
|
||||
export async function getMessage(sessionID: string, messageID: string) {
|
||||
return {
|
||||
info: await Storage.readJSON<MessageV2.Info>("session/message/" + sessionID + "/" + messageID),
|
||||
parts: await getParts(sessionID, messageID),
|
||||
info: await Storage.read<MessageV2.Info>(["message", sessionID, messageID]),
|
||||
parts: await getParts(messageID),
|
||||
}
|
||||
}
|
||||
|
||||
export async function getParts(sessionID: string, messageID: string) {
|
||||
export async function getParts(messageID: string) {
|
||||
const result = [] as MessageV2.Part[]
|
||||
for (const item of await Storage.list("session/part/" + sessionID + "/" + messageID)) {
|
||||
const read = await Storage.readJSON<MessageV2.Part>(item)
|
||||
for (const item of await Storage.list(["part", messageID])) {
|
||||
const read = await Storage.read<MessageV2.Part>(item)
|
||||
result.push(read)
|
||||
}
|
||||
result.sort((a, b) => (a.id > b.id ? 1 : -1))
|
||||
@@ -295,17 +295,17 @@ export namespace Session {
|
||||
}
|
||||
|
||||
export async function* list() {
|
||||
for (const item of await Storage.list("session/info")) {
|
||||
const sessionID = path.basename(item, ".json")
|
||||
yield get(sessionID)
|
||||
const project = Instance.project
|
||||
for (const item of await Storage.list(["session", project.id])) {
|
||||
yield Storage.read<Info>(item)
|
||||
}
|
||||
}
|
||||
|
||||
export async function children(parentID: string) {
|
||||
const project = Instance.project
|
||||
const result = [] as Session.Info[]
|
||||
for (const item of await Storage.list("session/info")) {
|
||||
const sessionID = path.basename(item, ".json")
|
||||
const session = await get(sessionID)
|
||||
for (const item of await Storage.list(["session", project.id])) {
|
||||
const session = await Storage.read<Info>(item)
|
||||
if (session.parentID !== parentID) continue
|
||||
result.push(session)
|
||||
}
|
||||
@@ -324,6 +324,7 @@ export namespace Session {
|
||||
}
|
||||
|
||||
export async function remove(sessionID: string, emitEvent = true) {
|
||||
const project = Instance.project
|
||||
try {
|
||||
abort(sessionID)
|
||||
const session = await get(sessionID)
|
||||
@@ -331,10 +332,13 @@ export namespace Session {
|
||||
await remove(child.id, false)
|
||||
}
|
||||
await unshare(sessionID).catch(() => {})
|
||||
await Storage.remove(`session/info/${sessionID}`).catch(() => {})
|
||||
await Storage.removeDir(`session/message/${sessionID}/`).catch(() => {})
|
||||
state().sessions.delete(sessionID)
|
||||
state().messages.delete(sessionID)
|
||||
for (const msg of await Storage.list(["message", sessionID])) {
|
||||
for (const part of await Storage.list(["part", msg.at(-1)!])) {
|
||||
await Storage.remove(part)
|
||||
}
|
||||
await Storage.remove(msg)
|
||||
}
|
||||
await Storage.remove(["session", project.id, sessionID])
|
||||
if (emitEvent) {
|
||||
Bus.publish(Event.Deleted, {
|
||||
info: session,
|
||||
@@ -346,25 +350,29 @@ export namespace Session {
|
||||
}
|
||||
|
||||
async function updateMessage(msg: MessageV2.Info) {
|
||||
await Storage.writeJSON("session/message/" + msg.sessionID + "/" + msg.id, msg)
|
||||
await Storage.write(["message", msg.sessionID, msg.id], msg)
|
||||
Bus.publish(MessageV2.Event.Updated, {
|
||||
info: msg,
|
||||
})
|
||||
}
|
||||
|
||||
async function updatePart(part: MessageV2.Part) {
|
||||
await Storage.writeJSON(["session", "part", part.sessionID, part.messageID, part.id].join("/"), part)
|
||||
await Storage.write(["part", part.messageID, part.id], part)
|
||||
Bus.publish(MessageV2.Event.PartUpdated, {
|
||||
part,
|
||||
})
|
||||
return part
|
||||
}
|
||||
|
||||
export const ChatInput = z.object({
|
||||
export const PromptInput = z.object({
|
||||
sessionID: Identifier.schema("session"),
|
||||
messageID: Identifier.schema("message").optional(),
|
||||
providerID: z.string(),
|
||||
modelID: z.string(),
|
||||
model: z
|
||||
.object({
|
||||
providerID: z.string(),
|
||||
modelID: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
agent: z.string().optional(),
|
||||
system: z.string().optional(),
|
||||
tools: z.record(z.boolean()).optional(),
|
||||
@@ -403,10 +411,10 @@ export namespace Session {
|
||||
]),
|
||||
),
|
||||
})
|
||||
export type ChatInput = z.infer<typeof ChatInput>
|
||||
export type ChatInput = z.infer<typeof PromptInput>
|
||||
|
||||
export async function chat(
|
||||
input: z.infer<typeof ChatInput>,
|
||||
export async function prompt(
|
||||
input: z.infer<typeof PromptInput>,
|
||||
): Promise<{ info: MessageV2.Assistant; parts: MessageV2.Part[] }> {
|
||||
const l = log.clone().tag("session", input.sessionID)
|
||||
l.info("chatting")
|
||||
@@ -421,7 +429,7 @@ export namespace Session {
|
||||
const [preserve, remove] = splitWhen(msgs, (x) => x.info.id === messageID)
|
||||
msgs = preserve
|
||||
for (const msg of remove) {
|
||||
await Storage.remove(`session/message/${input.sessionID}/${msg.info.id}`)
|
||||
await Storage.remove(["message", input.sessionID, msg.info.id])
|
||||
await Bus.publish(MessageV2.Event.Removed, { sessionID: input.sessionID, messageID: msg.info.id })
|
||||
}
|
||||
const last = preserve.at(-1)
|
||||
@@ -430,7 +438,7 @@ export namespace Session {
|
||||
const [preserveParts, removeParts] = splitWhen(last.parts, (x) => x.id === partID)
|
||||
last.parts = preserveParts
|
||||
for (const part of removeParts) {
|
||||
await Storage.remove(`session/part/${input.sessionID}/${last.info.id}/${part.id}`)
|
||||
await Storage.remove(["part", last.info.id, part.id])
|
||||
await Bus.publish(MessageV2.Event.PartRemoved, {
|
||||
sessionID: input.sessionID,
|
||||
messageID: last.info.id,
|
||||
@@ -451,7 +459,6 @@ export namespace Session {
|
||||
},
|
||||
}
|
||||
|
||||
const app = App.info()
|
||||
const userParts = await Promise.all(
|
||||
input.parts.map(async (part): Promise<MessageV2.Part[]> => {
|
||||
if (part.type === "file") {
|
||||
@@ -649,7 +656,16 @@ export namespace Session {
|
||||
})
|
||||
}
|
||||
|
||||
const model = await Provider.getModel(input.providerID, input.modelID)
|
||||
const agent = await Agent.get(inputAgent)
|
||||
const model = await (async () => {
|
||||
if (input.model) {
|
||||
return input.model
|
||||
}
|
||||
if (agent.model) {
|
||||
return agent.model
|
||||
}
|
||||
return Provider.defaultModel()
|
||||
})().then((x) => Provider.getModel(x.providerID, x.modelID))
|
||||
let msgs = await messages(input.sessionID)
|
||||
|
||||
const previous = msgs.filter((x) => x.info.role === "assistant").at(-1)?.info as MessageV2.Assistant
|
||||
@@ -664,10 +680,10 @@ export namespace Session {
|
||||
|
||||
await summarize({
|
||||
sessionID: input.sessionID,
|
||||
providerID: input.providerID,
|
||||
modelID: input.modelID,
|
||||
providerID: model.providerID,
|
||||
modelID: model.info.id,
|
||||
})
|
||||
return chat(input)
|
||||
return prompt(input)
|
||||
}
|
||||
}
|
||||
using abort = lock(input.sessionID)
|
||||
@@ -676,17 +692,17 @@ export namespace Session {
|
||||
if (lastSummary) msgs = msgs.filter((msg) => msg.info.id >= lastSummary.info.id)
|
||||
|
||||
if (msgs.filter((m) => m.info.role === "user").length === 1 && !session.parentID && isDefaultTitle(session.title)) {
|
||||
const small = (await Provider.getSmallModel(input.providerID)) ?? model
|
||||
const small = (await Provider.getSmallModel(model.providerID)) ?? model
|
||||
generateText({
|
||||
maxOutputTokens: small.info.reasoning ? 1024 : 20,
|
||||
providerOptions: {
|
||||
[input.providerID]: {
|
||||
[model.providerID]: {
|
||||
...small.info.options,
|
||||
...ProviderTransform.options(input.providerID, small.info.id, input.sessionID),
|
||||
...ProviderTransform.options(small.providerID, small.modelID, input.sessionID),
|
||||
},
|
||||
},
|
||||
messages: [
|
||||
...SystemPrompt.title(input.providerID).map(
|
||||
...SystemPrompt.title(model.providerID).map(
|
||||
(x): ModelMessage => ({
|
||||
role: "system",
|
||||
content: x,
|
||||
@@ -721,7 +737,6 @@ export namespace Session {
|
||||
})
|
||||
}
|
||||
|
||||
const agent = await Agent.get(inputAgent)
|
||||
if (agent.name === "plan") {
|
||||
msgs.at(-1)?.parts.push({
|
||||
id: Identifier.ascending("part"),
|
||||
@@ -744,12 +759,12 @@ export namespace Session {
|
||||
synthetic: true,
|
||||
})
|
||||
}
|
||||
let system = SystemPrompt.header(input.providerID)
|
||||
let system = SystemPrompt.header(model.providerID)
|
||||
system.push(
|
||||
...(() => {
|
||||
if (input.system) return [input.system]
|
||||
if (agent.prompt) return [agent.prompt]
|
||||
return SystemPrompt.provider(input.modelID)
|
||||
return SystemPrompt.provider(model.modelID)
|
||||
})(),
|
||||
)
|
||||
system.push(...(await SystemPrompt.environment()))
|
||||
@@ -764,8 +779,8 @@ export namespace Session {
|
||||
system,
|
||||
mode: inputAgent,
|
||||
path: {
|
||||
cwd: app.path.cwd,
|
||||
root: app.path.root,
|
||||
cwd: Instance.directory,
|
||||
root: Instance.worktree,
|
||||
},
|
||||
cost: 0,
|
||||
tokens: {
|
||||
@@ -774,8 +789,8 @@ export namespace Session {
|
||||
reasoning: 0,
|
||||
cache: { read: 0, write: 0 },
|
||||
},
|
||||
modelID: input.modelID,
|
||||
providerID: input.providerID,
|
||||
modelID: model.modelID,
|
||||
providerID: model.providerID,
|
||||
time: {
|
||||
created: Date.now(),
|
||||
},
|
||||
@@ -784,7 +799,7 @@ export namespace Session {
|
||||
await updateMessage(assistantMsg)
|
||||
await using _ = defer(async () => {
|
||||
if (assistantMsg.time.completed) return
|
||||
await Storage.remove(`session/message/${input.sessionID}/${assistantMsg.id}`)
|
||||
await Storage.remove(["session", "message", input.sessionID, assistantMsg.id])
|
||||
await Bus.publish(MessageV2.Event.Removed, { sessionID: input.sessionID, messageID: assistantMsg.id })
|
||||
})
|
||||
const tools: Record<string, AITool> = {}
|
||||
@@ -793,10 +808,10 @@ export namespace Session {
|
||||
|
||||
const enabledTools = pipe(
|
||||
agent.tools,
|
||||
mergeDeep(await ToolRegistry.enabled(input.providerID, input.modelID, agent)),
|
||||
mergeDeep(await ToolRegistry.enabled(model.providerID, model.modelID, agent)),
|
||||
mergeDeep(input.tools ?? {}),
|
||||
)
|
||||
for (const item of await ToolRegistry.tools(input.providerID, input.modelID)) {
|
||||
for (const item of await ToolRegistry.tools(model.providerID, model.modelID)) {
|
||||
if (Wildcard.all(item.id, enabledTools) === false) continue
|
||||
tools[item.id] = tool({
|
||||
id: item.id as any,
|
||||
@@ -906,16 +921,16 @@ export namespace Session {
|
||||
"chat.params",
|
||||
{
|
||||
model: model.info,
|
||||
provider: await Provider.getProvider(input.providerID),
|
||||
provider: await Provider.getProvider(model.providerID),
|
||||
message: userMsg,
|
||||
},
|
||||
{
|
||||
temperature: model.info.temperature
|
||||
? (agent.temperature ?? ProviderTransform.temperature(input.providerID, input.modelID))
|
||||
? (agent.temperature ?? ProviderTransform.temperature(model.providerID, model.modelID))
|
||||
: undefined,
|
||||
topP: agent.topP ?? ProviderTransform.topP(input.providerID, input.modelID),
|
||||
topP: agent.topP ?? ProviderTransform.topP(model.providerID, model.modelID),
|
||||
options: {
|
||||
...ProviderTransform.options(input.providerID, input.modelID, input.sessionID),
|
||||
...ProviderTransform.options(model.providerID, model.modelID, input.sessionID),
|
||||
...model.info.options,
|
||||
...agent.options,
|
||||
},
|
||||
@@ -949,8 +964,8 @@ export namespace Session {
|
||||
role: "assistant",
|
||||
system,
|
||||
path: {
|
||||
cwd: app.path.cwd,
|
||||
root: app.path.root,
|
||||
cwd: Instance.directory,
|
||||
root: Instance.worktree,
|
||||
},
|
||||
cost: 0,
|
||||
tokens: {
|
||||
@@ -959,8 +974,8 @@ export namespace Session {
|
||||
reasoning: 0,
|
||||
cache: { read: 0, write: 0 },
|
||||
},
|
||||
modelID: input.modelID,
|
||||
providerID: input.providerID,
|
||||
modelID: model.modelID,
|
||||
providerID: model.providerID,
|
||||
mode: inputAgent,
|
||||
time: {
|
||||
created: Date.now(),
|
||||
@@ -984,7 +999,7 @@ export namespace Session {
|
||||
}
|
||||
},
|
||||
headers:
|
||||
input.providerID === "opencode"
|
||||
model.providerID === "opencode"
|
||||
? {
|
||||
"x-opencode-session": input.sessionID,
|
||||
"x-opencode-request": userMsg.id,
|
||||
@@ -1007,7 +1022,7 @@ export namespace Session {
|
||||
return false
|
||||
},
|
||||
providerOptions: {
|
||||
[input.providerID]: params.options,
|
||||
[model.providerID]: params.options,
|
||||
},
|
||||
temperature: params.temperature,
|
||||
topP: params.topP,
|
||||
@@ -1028,7 +1043,7 @@ export namespace Session {
|
||||
async transformParams(args) {
|
||||
if (args.type === "stream") {
|
||||
// @ts-expect-error
|
||||
args.params.prompt = ProviderTransform.message(args.params.prompt, input.providerID, input.modelID)
|
||||
args.params.prompt = ProviderTransform.message(args.params.prompt, model.providerID, model.modelID)
|
||||
}
|
||||
return args.params
|
||||
},
|
||||
@@ -1041,7 +1056,7 @@ export namespace Session {
|
||||
const unprocessed = queued.find((x) => !x.processed)
|
||||
if (unprocessed) {
|
||||
unprocessed.processed = true
|
||||
return chat(unprocessed.input)
|
||||
return prompt(unprocessed.input)
|
||||
}
|
||||
for (const item of queued) {
|
||||
item.callback(result)
|
||||
@@ -1084,8 +1099,8 @@ export namespace Session {
|
||||
mode: input.agent,
|
||||
cost: 0,
|
||||
path: {
|
||||
cwd: App.info().path.cwd,
|
||||
root: App.info().path.root,
|
||||
cwd: Instance.directory,
|
||||
root: Instance.worktree,
|
||||
},
|
||||
time: {
|
||||
created: Date.now(),
|
||||
@@ -1119,7 +1134,6 @@ export namespace Session {
|
||||
},
|
||||
}
|
||||
await updatePart(part)
|
||||
const app = App.info()
|
||||
const shell = process.env["SHELL"] ?? "bash"
|
||||
const shellName = path.basename(shell)
|
||||
|
||||
@@ -1139,7 +1153,7 @@ export namespace Session {
|
||||
const args = isFishOrNu ? ["-c", script] : ["-c", "-l", script]
|
||||
|
||||
const proc = spawn(shell, args, {
|
||||
cwd: app.path.cwd,
|
||||
cwd: Instance.directory,
|
||||
signal: abort.signal,
|
||||
detached: true,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
@@ -1218,16 +1232,9 @@ export namespace Session {
|
||||
const fileRegex = /@([^\s]+)/g
|
||||
|
||||
export async function command(input: CommandInput) {
|
||||
log.info("command", input)
|
||||
const command = await Command.get(input.command)
|
||||
const agent = command.agent ?? input.agent ?? "build"
|
||||
const fmtModel = (model: { providerID: string; modelID: string }) => `${model.providerID}/${model.modelID}`
|
||||
|
||||
const model =
|
||||
command.model ??
|
||||
(command.agent && (await Agent.get(command.agent).then((x) => (x.model ? fmtModel(x.model) : undefined)))) ??
|
||||
input.model ??
|
||||
(input.agent && (await Agent.get(input.agent).then((x) => (x.model ? fmtModel(x.model) : undefined)))) ??
|
||||
fmtModel(await Provider.defaultModel())
|
||||
|
||||
let template = command.template.replace("$ARGUMENTS", input.arguments)
|
||||
|
||||
@@ -1257,13 +1264,11 @@ export namespace Session {
|
||||
},
|
||||
] as ChatInput["parts"]
|
||||
|
||||
const app = App.info()
|
||||
|
||||
for (const match of fileMatches) {
|
||||
const filename = match[1]
|
||||
const filepath = filename.startsWith("~/")
|
||||
? path.join(os.homedir(), filename.slice(2))
|
||||
: path.join(app.path.cwd, filename)
|
||||
: path.join(Instance.worktree, filename)
|
||||
|
||||
parts.push({
|
||||
type: "file",
|
||||
@@ -1273,10 +1278,18 @@ export namespace Session {
|
||||
})
|
||||
}
|
||||
|
||||
return chat({
|
||||
return prompt({
|
||||
sessionID: input.sessionID,
|
||||
messageID: input.messageID,
|
||||
...Provider.parseModel(model!),
|
||||
model: (() => {
|
||||
if (input.model) {
|
||||
return Provider.parseModel(input.model)
|
||||
}
|
||||
if (command.model) {
|
||||
return Provider.parseModel(command.model)
|
||||
}
|
||||
return undefined
|
||||
})(),
|
||||
agent,
|
||||
parts,
|
||||
})
|
||||
@@ -1550,7 +1563,7 @@ export namespace Session {
|
||||
error: assistantMsg.error,
|
||||
})
|
||||
}
|
||||
const p = await getParts(assistantMsg.sessionID, assistantMsg.id)
|
||||
const p = await getParts(assistantMsg.id)
|
||||
for (const part of p) {
|
||||
if (part.type === "tool" && part.state.status !== "completed" && part.state.status !== "error") {
|
||||
updatePart({
|
||||
@@ -1642,9 +1655,8 @@ export namespace Session {
|
||||
const lastSummary = msgs.findLast((msg) => msg.info.role === "assistant" && msg.info.summary === true)
|
||||
const filtered = msgs.filter((msg) => !lastSummary || msg.info.id >= lastSummary.info.id)
|
||||
const model = await Provider.getModel(input.providerID, input.modelID)
|
||||
const app = App.info()
|
||||
const system = [
|
||||
...SystemPrompt.summarize(input.providerID),
|
||||
...SystemPrompt.summarize(model.providerID),
|
||||
...(await SystemPrompt.environment()),
|
||||
...(await SystemPrompt.custom()),
|
||||
]
|
||||
@@ -1656,13 +1668,13 @@ export namespace Session {
|
||||
system,
|
||||
mode: "build",
|
||||
path: {
|
||||
cwd: app.path.cwd,
|
||||
root: app.path.root,
|
||||
cwd: Instance.directory,
|
||||
root: Instance.worktree,
|
||||
},
|
||||
summary: true,
|
||||
cost: 0,
|
||||
modelID: input.modelID,
|
||||
providerID: input.providerID,
|
||||
providerID: model.providerID,
|
||||
tokens: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
@@ -1771,20 +1783,21 @@ export namespace Session {
|
||||
providerID: string
|
||||
messageID: string
|
||||
}) {
|
||||
const app = App.info()
|
||||
await Session.chat({
|
||||
await Session.prompt({
|
||||
sessionID: input.sessionID,
|
||||
messageID: input.messageID,
|
||||
providerID: input.providerID,
|
||||
modelID: input.modelID,
|
||||
model: {
|
||||
providerID: input.providerID,
|
||||
modelID: input.modelID,
|
||||
},
|
||||
parts: [
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
type: "text",
|
||||
text: PROMPT_INITIALIZE.replace("${path}", app.path.root),
|
||||
text: PROMPT_INITIALIZE.replace("${path}", Instance.worktree),
|
||||
},
|
||||
],
|
||||
})
|
||||
await App.initialize()
|
||||
await Project.setInitialized(Instance.project.id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { App } from "../app/app"
|
||||
import { Ripgrep } from "../file/ripgrep"
|
||||
import { Global } from "../global"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
import { Config } from "../config/config"
|
||||
|
||||
import { Instance } from "../project/instance"
|
||||
import path from "path"
|
||||
import os from "os"
|
||||
|
||||
@@ -30,21 +31,21 @@ export namespace SystemPrompt {
|
||||
}
|
||||
|
||||
export async function environment() {
|
||||
const app = App.info()
|
||||
const project = Instance.project
|
||||
return [
|
||||
[
|
||||
`Here is some useful information about the environment you are running in:`,
|
||||
`<env>`,
|
||||
` Working directory: ${app.path.cwd}`,
|
||||
` Is directory a git repo: ${app.git ? "yes" : "no"}`,
|
||||
` Working directory: ${Instance.directory}`,
|
||||
` Is directory a git repo: ${project.vcs === "git" ? "yes" : "no"}`,
|
||||
` Platform: ${process.platform}`,
|
||||
` Today's date: ${new Date().toDateString()}`,
|
||||
`</env>`,
|
||||
`<project>`,
|
||||
` ${
|
||||
app.git
|
||||
project.vcs === "git"
|
||||
? await Ripgrep.tree({
|
||||
cwd: app.path.cwd,
|
||||
cwd: Instance.directory,
|
||||
limit: 200,
|
||||
})
|
||||
: ""
|
||||
@@ -65,12 +66,11 @@ export namespace SystemPrompt {
|
||||
]
|
||||
|
||||
export async function custom() {
|
||||
const { cwd, root } = App.info().path
|
||||
const config = await Config.get()
|
||||
const paths = new Set<string>()
|
||||
|
||||
for (const localRuleFile of LOCAL_RULE_FILES) {
|
||||
const matches = await Filesystem.findUp(localRuleFile, cwd, root)
|
||||
const matches = await Filesystem.findUp(localRuleFile, Instance.directory, Instance.worktree)
|
||||
if (matches.length > 0) {
|
||||
matches.forEach((path) => paths.add(path))
|
||||
break
|
||||
@@ -99,7 +99,7 @@ export namespace SystemPrompt {
|
||||
}),
|
||||
).catch(() => [])
|
||||
} else {
|
||||
matches = await Filesystem.globUp(instruction, cwd, root).catch(() => [])
|
||||
matches = await Filesystem.globUp(instruction, Instance.directory, Instance.worktree).catch(() => [])
|
||||
}
|
||||
matches.forEach((path) => paths.add(path))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user