Merge branch 'dev' into project
This commit is contained in:
@@ -10,13 +10,16 @@ export namespace Agent {
|
||||
export const Info = z
|
||||
.object({
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
mode: z.union([z.literal("subagent"), z.literal("primary"), z.literal("all")]),
|
||||
topP: z.number().optional(),
|
||||
temperature: z.number().optional(),
|
||||
model: z
|
||||
.object({
|
||||
modelID: z.string(),
|
||||
providerID: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
description: z.string(),
|
||||
prompt: z.string().optional(),
|
||||
tools: z.record(z.boolean()),
|
||||
})
|
||||
@@ -24,48 +27,61 @@ export namespace Agent {
|
||||
ref: "Agent",
|
||||
})
|
||||
export type Info = z.infer<typeof Info>
|
||||
const state = Instance.state(
|
||||
async () => {
|
||||
const cfg = await Config.get()
|
||||
const result: Record<string, Info> = {
|
||||
general: {
|
||||
name: "general",
|
||||
description:
|
||||
"General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you.",
|
||||
tools: {
|
||||
todoread: false,
|
||||
todowrite: false,
|
||||
},
|
||||
|
||||
const state = Instance.state(async () => {
|
||||
const cfg = await Config.get()
|
||||
const result: Record<string, Info> = {
|
||||
general: {
|
||||
name: "general",
|
||||
description:
|
||||
"General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you.",
|
||||
tools: {
|
||||
todoread: false,
|
||||
todowrite: false,
|
||||
},
|
||||
mode: "subagent",
|
||||
},
|
||||
build: {
|
||||
name: "build",
|
||||
tools: {},
|
||||
mode: "primary",
|
||||
},
|
||||
plan: {
|
||||
name: "plan",
|
||||
tools: {
|
||||
write: false,
|
||||
edit: false,
|
||||
patch: false,
|
||||
},
|
||||
mode: "primary",
|
||||
},
|
||||
}
|
||||
for (const [key, value] of Object.entries(cfg.agent ?? {})) {
|
||||
if (value.disable) {
|
||||
delete result[key]
|
||||
continue
|
||||
}
|
||||
for (const [key, value] of Object.entries(cfg.agent ?? {})) {
|
||||
if (value.disable) {
|
||||
delete result[key]
|
||||
continue
|
||||
let item = result[key]
|
||||
if (!item)
|
||||
item = result[key] = {
|
||||
name: key,
|
||||
mode: "all",
|
||||
tools: {},
|
||||
}
|
||||
let item = result[key]
|
||||
if (!item)
|
||||
item = result[key] = {
|
||||
name: key,
|
||||
description: "",
|
||||
tools: {
|
||||
todowrite: false,
|
||||
todoread: false,
|
||||
},
|
||||
}
|
||||
const model = value.model ?? cfg.model
|
||||
if (model) item.model = Provider.parseModel(model)
|
||||
if (value.prompt) item.prompt = value.prompt
|
||||
if (value.tools)
|
||||
item.tools = {
|
||||
...item.tools,
|
||||
...value.tools,
|
||||
}
|
||||
if (value.description) item.description = value.description
|
||||
}
|
||||
return result
|
||||
},
|
||||
)
|
||||
if (value.model) item.model = Provider.parseModel(value.model)
|
||||
if (value.prompt) item.prompt = value.prompt
|
||||
if (value.tools)
|
||||
item.tools = {
|
||||
...item.tools,
|
||||
...value.tools,
|
||||
}
|
||||
if (value.description) item.description = value.description
|
||||
if (value.temperature != undefined) item.temperature = value.temperature
|
||||
if (value.top_p != undefined) item.topP = value.top_p
|
||||
if (value.mode) item.mode = value.mode
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
export async function get(agent: string) {
|
||||
return state().then((x) => x[agent])
|
||||
|
||||
@@ -643,7 +643,7 @@ export const GithubRunCommand = cmd({
|
||||
messageID: Identifier.ascending("message"),
|
||||
providerID,
|
||||
modelID,
|
||||
mode: "build",
|
||||
agent: "build",
|
||||
parts: [
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
|
||||
@@ -8,8 +8,8 @@ import { Flag } from "../../flag/flag"
|
||||
import { Config } from "../../config/config"
|
||||
import { bootstrap } from "../bootstrap"
|
||||
import { MessageV2 } from "../../session/message-v2"
|
||||
import { Mode } from "../../session/mode"
|
||||
import { Identifier } from "../../id/id"
|
||||
import { Agent } from "../../agent/agent"
|
||||
|
||||
const TOOL: Record<string, [string, string]> = {
|
||||
todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
|
||||
@@ -54,9 +54,9 @@ export const RunCommand = cmd({
|
||||
alias: ["m"],
|
||||
describe: "model to use in the format of provider/model",
|
||||
})
|
||||
.option("mode", {
|
||||
.option("agent", {
|
||||
type: "string",
|
||||
describe: "mode to use",
|
||||
describe: "agent to use",
|
||||
})
|
||||
},
|
||||
handler: async (args) => {
|
||||
@@ -103,8 +103,19 @@ export const RunCommand = cmd({
|
||||
}
|
||||
UI.empty()
|
||||
|
||||
const mode = args.mode ? await Mode.get(args.mode) : await Mode.list().then((x) => x[0])
|
||||
const { providerID, modelID } = args.model ? Provider.parseModel(args.model) : mode.model ?? await Provider.defaultModel()
|
||||
const agent = await (async () => {
|
||||
if (args.agent) return Agent.get(args.agent)
|
||||
const build = Agent.get("build")
|
||||
if (build) return build
|
||||
return Agent.list().then((x) => x[0])
|
||||
})()
|
||||
|
||||
const { providerID, modelID } = await (() => {
|
||||
if (args.model) return Provider.parseModel(args.model)
|
||||
if (agent.model) return agent.model
|
||||
return Provider.defaultModel()
|
||||
})()
|
||||
|
||||
UI.println(UI.Style.TEXT_NORMAL_BOLD + "@ ", UI.Style.TEXT_NORMAL + `${providerID}/${modelID}`)
|
||||
UI.empty()
|
||||
|
||||
@@ -157,14 +168,17 @@ export const RunCommand = cmd({
|
||||
UI.error(err)
|
||||
})
|
||||
|
||||
|
||||
const messageID = Identifier.ascending("message")
|
||||
const result = await Session.chat({
|
||||
sessionID: session.id,
|
||||
messageID,
|
||||
providerID,
|
||||
modelID,
|
||||
mode: mode.name,
|
||||
...(agent.model
|
||||
? agent.model
|
||||
: {
|
||||
providerID,
|
||||
modelID,
|
||||
}),
|
||||
agent: agent.name,
|
||||
parts: [
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
|
||||
@@ -11,8 +11,8 @@ import { Config } from "../../config/config"
|
||||
import { Bus } from "../../bus"
|
||||
import { Log } from "../../util/log"
|
||||
import { FileWatcher } from "../../file/watch"
|
||||
import { Mode } from "../../session/mode"
|
||||
import { Ide } from "../../ide"
|
||||
import { Agent } from "../../agent/agent"
|
||||
|
||||
declare global {
|
||||
const OPENCODE_TUI_PATH: string
|
||||
@@ -115,7 +115,7 @@ export const TuiCommand = cmd({
|
||||
CGO_ENABLED: "0",
|
||||
OPENCODE_SERVER: server.url.toString(),
|
||||
OPENCODE_APP_INFO: JSON.stringify(app),
|
||||
OPENCODE_MODES: JSON.stringify(await Mode.list()),
|
||||
OPENCODE_AGENTS: JSON.stringify(await Agent.list()),
|
||||
},
|
||||
onExit: () => {
|
||||
server.stop()
|
||||
|
||||
@@ -18,108 +18,120 @@ import { Instance } from "../project/instance"
|
||||
export namespace Config {
|
||||
const log = Log.create({ service: "config" })
|
||||
|
||||
export const state = Instance.state(
|
||||
async () => {
|
||||
const auth = await Auth.all()
|
||||
let result = await global()
|
||||
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
||||
const found = await Filesystem.findUp(file, Instance.directory, Instance.worktree)
|
||||
for (const resolved of found.toReversed()) {
|
||||
result = mergeDeep(result, await loadFile(resolved))
|
||||
}
|
||||
export const state = Instance.state(async () => {
|
||||
const auth = await Auth.all()
|
||||
let result = await global()
|
||||
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
||||
const found = await Filesystem.findUp(file, Instance.directory, Instance.worktree)
|
||||
for (const resolved of found.toReversed()) {
|
||||
result = mergeDeep(result, await loadFile(resolved))
|
||||
}
|
||||
}
|
||||
|
||||
// Override with custom config if provided
|
||||
if (Flag.OPENCODE_CONFIG) {
|
||||
result = mergeDeep(result, await loadFile(Flag.OPENCODE_CONFIG))
|
||||
log.debug("loaded custom config", { path: Flag.OPENCODE_CONFIG })
|
||||
// Override with custom config if provided
|
||||
if (Flag.OPENCODE_CONFIG) {
|
||||
result = mergeDeep(result, await loadFile(Flag.OPENCODE_CONFIG))
|
||||
log.debug("loaded custom config", { path: Flag.OPENCODE_CONFIG })
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(auth)) {
|
||||
if (value.type === "wellknown") {
|
||||
process.env[value.key] = value.token
|
||||
const wellknown = await fetch(`${key}/.well-known/opencode`).then((x) => x.json())
|
||||
result = mergeDeep(result, await load(JSON.stringify(wellknown.config ?? {}), process.cwd()))
|
||||
}
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(auth)) {
|
||||
if (value.type === "wellknown") {
|
||||
process.env[value.key] = value.token
|
||||
const wellknown = await fetch(`${key}/.well-known/opencode`).then((x) => x.json())
|
||||
result = mergeDeep(result, await load(JSON.stringify(wellknown.config ?? {}), process.cwd()))
|
||||
}
|
||||
result.agent = result.agent || {}
|
||||
const markdownAgents = [
|
||||
...(await Filesystem.globUp("agent/*.md", Global.Path.config, Global.Path.config)),
|
||||
...(await Filesystem.globUp(".opencode/agent/*.md", Instance.directory, Instance.worktree)),
|
||||
]
|
||||
for (const item of markdownAgents) {
|
||||
const content = await Bun.file(item).text()
|
||||
const md = matter(content)
|
||||
if (!md.data) continue
|
||||
|
||||
const config = {
|
||||
name: path.basename(item, ".md"),
|
||||
...md.data,
|
||||
prompt: md.content.trim(),
|
||||
}
|
||||
|
||||
result.agent = result.agent || {}
|
||||
const markdownAgents = [
|
||||
...(await Filesystem.globUp("agent/*.md", Global.Path.config, Global.Path.config)),
|
||||
...(await Filesystem.globUp(".opencode/agent/*.md", Instance.directory, Instance.worktree)),
|
||||
]
|
||||
for (const item of markdownAgents) {
|
||||
const content = await Bun.file(item).text()
|
||||
const md = matter(content)
|
||||
if (!md.data) continue
|
||||
|
||||
const config = {
|
||||
name: path.basename(item, ".md"),
|
||||
...md.data,
|
||||
prompt: md.content.trim(),
|
||||
}
|
||||
const parsed = Agent.safeParse(config)
|
||||
if (parsed.success) {
|
||||
result.agent = mergeDeep(result.agent, {
|
||||
[config.name]: parsed.data,
|
||||
})
|
||||
continue
|
||||
}
|
||||
throw new InvalidError({ path: item }, { cause: parsed.error })
|
||||
const parsed = Agent.safeParse(config)
|
||||
if (parsed.success) {
|
||||
result.agent = mergeDeep(result.agent, {
|
||||
[config.name]: parsed.data,
|
||||
})
|
||||
continue
|
||||
}
|
||||
throw new InvalidError({ path: item }, { cause: parsed.error })
|
||||
}
|
||||
|
||||
// Load mode markdown files
|
||||
result.mode = result.mode || {}
|
||||
const markdownModes = [
|
||||
...(await Filesystem.globUp("mode/*.md", Global.Path.config, Global.Path.config)),
|
||||
...(await Filesystem.globUp(".opencode/mode/*.md", Instance.directory, Instance.worktree)),
|
||||
]
|
||||
for (const item of markdownModes) {
|
||||
const content = await Bun.file(item).text()
|
||||
const md = matter(content)
|
||||
if (!md.data) continue
|
||||
// Load mode markdown files
|
||||
result.mode = result.mode || {}
|
||||
const markdownModes = [
|
||||
...(await Filesystem.globUp("mode/*.md", Global.Path.config, Global.Path.config)),
|
||||
...(await Filesystem.globUp(".opencode/mode/*.md", Instance.directory, Instance.worktree)),
|
||||
]
|
||||
for (const item of markdownModes) {
|
||||
const content = await Bun.file(item).text()
|
||||
const md = matter(content)
|
||||
if (!md.data) continue
|
||||
|
||||
const config = {
|
||||
name: path.basename(item, ".md"),
|
||||
...md.data,
|
||||
prompt: md.content.trim(),
|
||||
}
|
||||
const parsed = Mode.safeParse(config)
|
||||
if (parsed.success) {
|
||||
result.mode = mergeDeep(result.mode, {
|
||||
[config.name]: parsed.data,
|
||||
})
|
||||
continue
|
||||
}
|
||||
throw new InvalidError({ path: item }, { cause: parsed.error })
|
||||
const config = {
|
||||
name: path.basename(item, ".md"),
|
||||
...md.data,
|
||||
prompt: md.content.trim(),
|
||||
}
|
||||
|
||||
result.plugin = result.plugin || []
|
||||
result.plugin.push(
|
||||
...[
|
||||
...(await Filesystem.globUp("plugin/*.ts", Global.Path.config, Global.Path.config)),
|
||||
...(await Filesystem.globUp(".opencode/plugin/*.ts", Instance.directory, Instance.worktree)),
|
||||
].map((x) => "file://" + x),
|
||||
)
|
||||
|
||||
// Handle migration from autoshare to share field
|
||||
if (result.autoshare === true && !result.share) {
|
||||
result.share = "auto"
|
||||
}
|
||||
if (result.keybinds?.messages_revert && !result.keybinds.messages_undo) {
|
||||
result.keybinds.messages_undo = result.keybinds.messages_revert
|
||||
const parsed = Agent.safeParse(config)
|
||||
if (parsed.success) {
|
||||
result.agent = mergeDeep(result.mode, {
|
||||
[config.name]: {
|
||||
...parsed.data,
|
||||
mode: "primary" as const,
|
||||
},
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.username) {
|
||||
const os = await import("os")
|
||||
result.username = os.userInfo().username
|
||||
}
|
||||
if (!result.username) {
|
||||
const os = await import("os")
|
||||
result.username = os.userInfo().username
|
||||
}
|
||||
|
||||
log.info("loaded", result)
|
||||
result.plugin = result.plugin || []
|
||||
result.plugin.push(
|
||||
...[
|
||||
...(await Filesystem.globUp("plugin/*.ts", Global.Path.config, Global.Path.config)),
|
||||
...(await Filesystem.globUp(".opencode/plugin/*.ts", Instance.directory, Instance.worktree)),
|
||||
].map((x) => "file://" + x),
|
||||
)
|
||||
|
||||
return result
|
||||
},
|
||||
)
|
||||
// Handle migration from autoshare to share field
|
||||
if (result.autoshare === true && !result.share) {
|
||||
result.share = "auto"
|
||||
}
|
||||
if (result.keybinds?.messages_revert && !result.keybinds.messages_undo) {
|
||||
result.keybinds.messages_undo = result.keybinds.messages_revert
|
||||
}
|
||||
|
||||
// Handle migration from autoshare to share field
|
||||
if (result.autoshare === true && !result.share) {
|
||||
result.share = "auto"
|
||||
}
|
||||
if (result.keybinds?.messages_revert && !result.keybinds.messages_undo) {
|
||||
result.keybinds.messages_undo = result.keybinds.messages_revert
|
||||
}
|
||||
if (result.keybinds?.switch_mode && !result.keybinds.switch_agent) {
|
||||
result.keybinds.switch_agent = result.keybinds.switch_mode
|
||||
}
|
||||
if (result.keybinds?.switch_mode_reverse && !result.keybinds.switch_agent_reverse) {
|
||||
result.keybinds.switch_agent_reverse = result.keybinds.switch_mode_reverse
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
export const McpLocal = z
|
||||
.object({
|
||||
@@ -151,7 +163,7 @@ export namespace Config {
|
||||
export const Mcp = z.discriminatedUnion("type", [McpLocal, McpRemote])
|
||||
export type Mcp = z.infer<typeof Mcp>
|
||||
|
||||
export const Mode = z
|
||||
export const Agent = z
|
||||
.object({
|
||||
model: z.string().optional(),
|
||||
temperature: z.number().optional(),
|
||||
@@ -159,24 +171,26 @@ export namespace Config {
|
||||
prompt: z.string().optional(),
|
||||
tools: z.record(z.string(), z.boolean()).optional(),
|
||||
disable: z.boolean().optional(),
|
||||
description: z.string().optional().describe("Description of when to use the agent"),
|
||||
mode: z.union([z.literal("subagent"), z.literal("primary"), z.literal("all")]).optional(),
|
||||
})
|
||||
.openapi({
|
||||
ref: "ModeConfig",
|
||||
ref: "AgentConfig",
|
||||
})
|
||||
export type Mode = z.infer<typeof Mode>
|
||||
|
||||
export const Agent = Mode.extend({
|
||||
description: z.string(),
|
||||
}).openapi({
|
||||
ref: "AgentConfig",
|
||||
})
|
||||
export type Agent = z.infer<typeof Agent>
|
||||
|
||||
export const Keybinds = z
|
||||
.object({
|
||||
leader: z.string().optional().default("ctrl+x").describe("Leader key for keybind combinations"),
|
||||
app_help: z.string().optional().default("<leader>h").describe("Show help dialog"),
|
||||
switch_mode: z.string().optional().default("tab").describe("Next mode"),
|
||||
switch_mode_reverse: z.string().optional().default("shift+tab").describe("Previous Mode"),
|
||||
switch_mode: z.string().optional().default("none").describe("@deprecated use switch_agent. Next mode"),
|
||||
switch_mode_reverse: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("none")
|
||||
.describe("@deprecated use switch_agent_reverse. Previous mode"),
|
||||
switch_agent: z.string().optional().default("tab").describe("Next agent"),
|
||||
switch_agent_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
|
||||
editor_open: z.string().optional().default("<leader>e").describe("Open external editor"),
|
||||
session_export: z.string().optional().default("<leader>x").describe("Export session to editor"),
|
||||
session_new: z.string().optional().default("<leader>n").describe("Create a new session"),
|
||||
@@ -259,19 +273,21 @@ export namespace Config {
|
||||
.describe("Custom username to display in conversations instead of system username"),
|
||||
mode: z
|
||||
.object({
|
||||
build: Mode.optional(),
|
||||
plan: Mode.optional(),
|
||||
build: Agent.optional(),
|
||||
plan: Agent.optional(),
|
||||
})
|
||||
.catchall(Mode)
|
||||
.catchall(Agent)
|
||||
.optional()
|
||||
.describe("Modes configuration, see https://opencode.ai/docs/modes"),
|
||||
.describe("@deprecated Use `agent` field instead."),
|
||||
agent: z
|
||||
.object({
|
||||
plan: Agent.optional(),
|
||||
build: Agent.optional(),
|
||||
general: Agent.optional(),
|
||||
})
|
||||
.catchall(Agent)
|
||||
.optional()
|
||||
.describe("Modes configuration, see https://opencode.ai/docs/modes"),
|
||||
.describe("Agent configuration, see https://opencode.ai/docs/agent"),
|
||||
provider: z
|
||||
.record(
|
||||
ModelsDev.Provider.partial()
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import "zod-openapi/extend"
|
||||
import { Trace } from "./trace"
|
||||
Trace.init()
|
||||
import yargs from "yargs"
|
||||
import { hideBin } from "yargs/helpers"
|
||||
import { RunCommand } from "./cli/cmd/run"
|
||||
@@ -18,9 +20,6 @@ import { DebugCommand } from "./cli/cmd/debug"
|
||||
import { StatsCommand } from "./cli/cmd/stats"
|
||||
import { McpCommand } from "./cli/cmd/mcp"
|
||||
import { GithubCommand } from "./cli/cmd/github"
|
||||
import { Trace } from "./trace"
|
||||
|
||||
Trace.init()
|
||||
|
||||
const cancel = new AbortController()
|
||||
|
||||
|
||||
@@ -16,11 +16,11 @@ import { Config } from "../config/config"
|
||||
import { File } from "../file"
|
||||
import { LSP } from "../lsp"
|
||||
import { MessageV2 } from "../session/message-v2"
|
||||
import { Mode } from "../session/mode"
|
||||
import { callTui, TuiRoute } from "./tui"
|
||||
import { Permission } from "../permission"
|
||||
import { lazy } from "../util/lazy"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Agent } from "../agent/agent"
|
||||
|
||||
const ERRORS = {
|
||||
400: {
|
||||
@@ -221,7 +221,7 @@ export namespace Server {
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/session/:sessionID",
|
||||
"/session/:id",
|
||||
describeRoute({
|
||||
description: "Get session",
|
||||
operationId: "session.get",
|
||||
@@ -871,23 +871,23 @@ export namespace Server {
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/mode",
|
||||
"/agent",
|
||||
describeRoute({
|
||||
description: "List all modes",
|
||||
operationId: "app.modes",
|
||||
description: "List all agents",
|
||||
operationId: "app.agents",
|
||||
responses: {
|
||||
200: {
|
||||
description: "List of modes",
|
||||
description: "List of agents",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Mode.Info.array()),
|
||||
schema: resolver(Agent.Info.array()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const modes = await Mode.list()
|
||||
const modes = await Agent.list()
|
||||
return c.json(modes)
|
||||
},
|
||||
)
|
||||
@@ -1026,7 +1026,7 @@ export namespace Server {
|
||||
.post(
|
||||
"/tui/execute-command",
|
||||
describeRoute({
|
||||
description: "Execute a TUI command (e.g. switch_mode)",
|
||||
description: "Execute a TUI command (e.g. switch_agent)",
|
||||
operationId: "tui.executeCommand",
|
||||
responses: {
|
||||
200: {
|
||||
|
||||
@@ -34,15 +34,18 @@ import { NamedError } from "../util/error"
|
||||
import { SystemPrompt } from "./system"
|
||||
import { FileTime } from "../file/time"
|
||||
import { MessageV2 } from "./message-v2"
|
||||
import { Mode } from "./mode"
|
||||
import { LSP } from "../lsp"
|
||||
import { ReadTool } from "../tool/read"
|
||||
import { mergeDeep, pipe, splitWhen } from "remeda"
|
||||
import { ToolRegistry } from "../tool/registry"
|
||||
import { Plugin } from "../plugin"
|
||||
<<<<<<< HEAD
|
||||
import { Project } from "../project/project"
|
||||
|
||||
import { Instance } from "../project/instance"
|
||||
=======
|
||||
import { Agent } from "../agent/agent"
|
||||
>>>>>>> dev
|
||||
|
||||
export namespace Session {
|
||||
const log = Log.create({ service: "session" })
|
||||
@@ -362,7 +365,7 @@ export namespace Session {
|
||||
messageID: Identifier.schema("message").optional(),
|
||||
providerID: z.string(),
|
||||
modelID: z.string(),
|
||||
mode: z.string().optional(),
|
||||
agent: z.string().optional(),
|
||||
system: z.string().optional(),
|
||||
tools: z.record(z.boolean()).optional(),
|
||||
parts: z.array(
|
||||
@@ -387,6 +390,16 @@ export namespace Session {
|
||||
.openapi({
|
||||
ref: "FilePartInput",
|
||||
}),
|
||||
MessageV2.AgentPart.omit({
|
||||
messageID: true,
|
||||
sessionID: true,
|
||||
})
|
||||
.partial({
|
||||
id: true,
|
||||
})
|
||||
.openapi({
|
||||
ref: "AgentPartInput",
|
||||
}),
|
||||
]),
|
||||
),
|
||||
})
|
||||
@@ -398,7 +411,7 @@ export namespace Session {
|
||||
const l = log.clone().tag("session", input.sessionID)
|
||||
l.info("chatting")
|
||||
|
||||
const inputMode = input.mode ?? "build"
|
||||
const inputAgent = input.agent ?? "build"
|
||||
|
||||
// Process revert cleanup first, before creating new messages
|
||||
const session = await get(input.sessionID)
|
||||
@@ -570,6 +583,28 @@ export namespace Session {
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
if (part.type === "agent") {
|
||||
return [
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
...part,
|
||||
messageID: userMsg.id,
|
||||
sessionID: input.sessionID,
|
||||
},
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: userMsg.id,
|
||||
sessionID: input.sessionID,
|
||||
type: "text",
|
||||
synthetic: true,
|
||||
text:
|
||||
"Use the above message and context to generate a prompt and call the task tool with subagent: " +
|
||||
part.name,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
@@ -580,7 +615,7 @@ export namespace Session {
|
||||
]
|
||||
}),
|
||||
).then((x) => x.flat())
|
||||
if (inputMode === "plan")
|
||||
if (inputAgent === "plan")
|
||||
userParts.push({
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: userMsg.id,
|
||||
@@ -687,12 +722,12 @@ export namespace Session {
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
const mode = await Mode.get(inputMode)
|
||||
const agent = await Agent.get(inputAgent)
|
||||
let system = SystemPrompt.header(input.providerID)
|
||||
system.push(
|
||||
...(() => {
|
||||
if (input.system) return [input.system]
|
||||
if (mode.prompt) return [mode.prompt]
|
||||
if (agent.prompt) return [agent.prompt]
|
||||
return SystemPrompt.provider(input.modelID)
|
||||
})(),
|
||||
)
|
||||
@@ -706,7 +741,7 @@ export namespace Session {
|
||||
id: Identifier.ascending("message"),
|
||||
role: "assistant",
|
||||
system,
|
||||
mode: inputMode,
|
||||
mode: inputAgent,
|
||||
path: {
|
||||
cwd: Instance.directory,
|
||||
root: Instance.worktree,
|
||||
@@ -731,7 +766,7 @@ export namespace Session {
|
||||
const processor = createProcessor(assistantMsg, model.info)
|
||||
|
||||
const enabledTools = pipe(
|
||||
mode.tools,
|
||||
agent.tools,
|
||||
mergeDeep(await ToolRegistry.enabled(input.providerID, input.modelID)),
|
||||
mergeDeep(input.tools ?? {}),
|
||||
)
|
||||
@@ -822,9 +857,9 @@ export namespace Session {
|
||||
|
||||
const params = {
|
||||
temperature: model.info.temperature
|
||||
? (mode.temperature ?? ProviderTransform.temperature(input.providerID, input.modelID))
|
||||
? (agent.temperature ?? ProviderTransform.temperature(input.providerID, input.modelID))
|
||||
: undefined,
|
||||
topP: mode.topP ?? ProviderTransform.topP(input.providerID, input.modelID),
|
||||
topP: agent.topP ?? ProviderTransform.topP(input.providerID, input.modelID),
|
||||
}
|
||||
await Plugin.trigger(
|
||||
"chat.params",
|
||||
@@ -876,7 +911,7 @@ export namespace Session {
|
||||
},
|
||||
modelID: input.modelID,
|
||||
providerID: input.providerID,
|
||||
mode: inputMode,
|
||||
mode: inputAgent,
|
||||
time: {
|
||||
created: Date.now(),
|
||||
},
|
||||
|
||||
@@ -172,6 +172,21 @@ export namespace MessageV2 {
|
||||
})
|
||||
export type FilePart = z.infer<typeof FilePart>
|
||||
|
||||
export const AgentPart = PartBase.extend({
|
||||
type: z.literal("agent"),
|
||||
name: z.string(),
|
||||
source: z
|
||||
.object({
|
||||
value: z.string(),
|
||||
start: z.number().int(),
|
||||
end: z.number().int(),
|
||||
})
|
||||
.optional(),
|
||||
}).openapi({
|
||||
ref: "AgentPart",
|
||||
})
|
||||
export type AgentPart = z.infer<typeof AgentPart>
|
||||
|
||||
export const StepStartPart = PartBase.extend({
|
||||
type: z.literal("step-start"),
|
||||
}).openapi({
|
||||
@@ -212,7 +227,16 @@ export namespace MessageV2 {
|
||||
export type User = z.infer<typeof User>
|
||||
|
||||
export const Part = z
|
||||
.discriminatedUnion("type", [TextPart, FilePart, ToolPart, StepStartPart, StepFinishPart, SnapshotPart, PatchPart])
|
||||
.discriminatedUnion("type", [
|
||||
TextPart,
|
||||
FilePart,
|
||||
ToolPart,
|
||||
StepStartPart,
|
||||
StepFinishPart,
|
||||
SnapshotPart,
|
||||
PatchPart,
|
||||
AgentPart,
|
||||
])
|
||||
.openapi({
|
||||
ref: "Part",
|
||||
})
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import { Config } from "../config/config"
|
||||
import z from "zod"
|
||||
import { Provider } from "../provider/provider"
|
||||
import { Instance } from "../project/instance"
|
||||
|
||||
export namespace Mode {
|
||||
export const Info = z
|
||||
.object({
|
||||
name: z.string(),
|
||||
temperature: z.number().optional(),
|
||||
topP: z.number().optional(),
|
||||
model: z
|
||||
.object({
|
||||
modelID: z.string(),
|
||||
providerID: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
prompt: z.string().optional(),
|
||||
tools: z.record(z.boolean()),
|
||||
})
|
||||
.openapi({
|
||||
ref: "Mode",
|
||||
})
|
||||
export type Info = z.infer<typeof Info>
|
||||
const state = Instance.state(async () => {
|
||||
const cfg = await Config.get()
|
||||
const model = cfg.model ? Provider.parseModel(cfg.model) : undefined
|
||||
const result: Record<string, Info> = {
|
||||
build: {
|
||||
model,
|
||||
name: "build",
|
||||
tools: {},
|
||||
},
|
||||
plan: {
|
||||
name: "plan",
|
||||
model,
|
||||
tools: {
|
||||
write: false,
|
||||
edit: false,
|
||||
patch: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
for (const [key, value] of Object.entries(cfg.mode ?? {})) {
|
||||
if (value.disable) continue
|
||||
let item = result[key]
|
||||
if (!item)
|
||||
item = result[key] = {
|
||||
name: key,
|
||||
tools: {},
|
||||
}
|
||||
item.name = key
|
||||
if (value.model) item.model = Provider.parseModel(value.model)
|
||||
if (value.prompt) item.prompt = value.prompt
|
||||
if (value.temperature != undefined) item.temperature = value.temperature
|
||||
if (value.top_p != undefined) item.topP = value.top_p
|
||||
if (value.tools)
|
||||
item.tools = {
|
||||
...value.tools,
|
||||
...item.tools,
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
export async function get(mode: string) {
|
||||
return state().then((x) => x[mode])
|
||||
}
|
||||
|
||||
export async function list() {
|
||||
return state().then((x) => Object.values(x))
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,13 @@ import { Identifier } from "../id/id"
|
||||
import { Agent } from "../agent/agent"
|
||||
|
||||
export const TaskTool = Tool.define("task", async () => {
|
||||
const agents = await Agent.list()
|
||||
const description = DESCRIPTION.replace("{agents}", agents.map((a) => `- ${a.name}: ${a.description}`).join("\n"))
|
||||
const agents = await Agent.list().then((x) => x.filter((a) => a.mode !== "primary"))
|
||||
const description = DESCRIPTION.replace(
|
||||
"{agents}",
|
||||
agents
|
||||
.map((a) => `- ${a.name}: ${a.description ?? "This subagent should only be called manually by the user."}`)
|
||||
.join("\n"),
|
||||
)
|
||||
return {
|
||||
description,
|
||||
parameters: z.object({
|
||||
@@ -51,11 +56,12 @@ export const TaskTool = Tool.define("task", async () => {
|
||||
sessionID: session.id,
|
||||
modelID: model.modelID,
|
||||
providerID: model.providerID,
|
||||
mode: msg.info.mode,
|
||||
system: agent.prompt,
|
||||
agent: agent.name,
|
||||
tools: {
|
||||
...agent.tools,
|
||||
todowrite: false,
|
||||
todoread: false,
|
||||
task: false,
|
||||
...agent.tools,
|
||||
},
|
||||
parts: [
|
||||
{
|
||||
|
||||
@@ -1,53 +1,17 @@
|
||||
import { Global } from "../global"
|
||||
import { Installation } from "../installation"
|
||||
import path from "path"
|
||||
import { NodeSDK } from "@opentelemetry/sdk-node"
|
||||
import { FetchInstrumentation } from "@opentelemetry/instrumentation-fetch"
|
||||
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"
|
||||
|
||||
export namespace Trace {
|
||||
export function init() {
|
||||
if (!Installation.isDev()) return
|
||||
const writer = Bun.file(path.join(Global.Path.data, "log", "fetch.log")).writer()
|
||||
const sdk = new NodeSDK({
|
||||
serviceName: "opencode",
|
||||
instrumentations: [new FetchInstrumentation()],
|
||||
traceExporter: new OTLPTraceExporter({
|
||||
url: "http://localhost:4318/v1/traces", // or your OTLP endpoint
|
||||
}),
|
||||
})
|
||||
|
||||
const originalFetch = globalThis.fetch
|
||||
// @ts-expect-error
|
||||
globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url
|
||||
const method = init?.method || "GET"
|
||||
|
||||
const urlObj = new URL(url)
|
||||
|
||||
writer.write(`\n${method} ${urlObj.pathname}${urlObj.search} HTTP/1.1\n`)
|
||||
writer.write(`Host: ${urlObj.host}\n`)
|
||||
|
||||
if (init?.headers) {
|
||||
if (init.headers instanceof Headers) {
|
||||
init.headers.forEach((value, key) => {
|
||||
writer.write(`${key}: ${value}\n`)
|
||||
})
|
||||
} else {
|
||||
for (const [key, value] of Object.entries(init.headers)) {
|
||||
writer.write(`${key}: ${value}\n`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (init?.body) {
|
||||
writer.write(`\n${init.body}`)
|
||||
}
|
||||
writer.flush()
|
||||
const response = await originalFetch(input, init)
|
||||
const clonedResponse = response.clone()
|
||||
writer.write(`\nHTTP/1.1 ${response.status} ${response.statusText}\n`)
|
||||
response.headers.forEach((value, key) => {
|
||||
writer.write(`${key}: ${value}\n`)
|
||||
})
|
||||
if (clonedResponse.body) {
|
||||
clonedResponse.text().then(async (x) => {
|
||||
writer.write(`\n${x}\n`)
|
||||
})
|
||||
}
|
||||
writer.flush()
|
||||
|
||||
return response
|
||||
}
|
||||
sdk.start()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user