pull/5462/head
Dax Raad 2025-12-12 15:10:49 -05:00
parent a1c20e3e00
commit 167709c48e
6 changed files with 63 additions and 63 deletions

View File

@ -13,7 +13,7 @@ export namespace Agent {
name: z.string(),
description: z.string().optional(),
mode: z.enum(["subagent", "primary", "all"]),
builtIn: z.boolean(),
internal: z.boolean(),
topP: z.number().optional(),
temperature: z.number().optional(),
color: z.string().optional(),
@ -112,7 +112,7 @@ export namespace Agent {
options: {},
permission: agentPermission,
mode: "subagent",
builtIn: true,
internal: true,
},
explore: {
name: "explore",
@ -147,7 +147,7 @@ export namespace Agent {
options: {},
permission: agentPermission,
mode: "subagent",
builtIn: true,
internal: true,
},
build: {
name: "build",
@ -155,13 +155,13 @@ export namespace Agent {
options: {},
permission: agentPermission,
mode: "primary",
builtIn: true,
internal: true,
},
summary: {
name: "summary",
mode: "subagent",
options: {},
builtIn: true,
internal: true,
permission: agentPermission,
prompt: `You are a title generator. You output ONLY a thread title. Nothing else.
@ -210,7 +210,7 @@ Your output must be:
...defaultTools,
},
mode: "primary",
builtIn: true,
internal: true,
},
}
for (const [key, value] of Object.entries(cfg.agent ?? {})) {
@ -226,7 +226,7 @@ Your output must be:
permission: agentPermission,
options: {},
tools: {},
builtIn: false,
internal: false,
}
const {
name,

View File

@ -91,10 +91,10 @@ export namespace SessionCompaction {
providerID: string
modelID: string
}
agent: string
abort: AbortSignal
auto: boolean
}) {
const agent = await Agent.get("compaction")
const model = await Provider.getModel(input.model.providerID, input.model.modelID)
const system = [...SystemPrompt.compaction(model.providerID)]
const msg = (await Session.updateMessage({
@ -102,7 +102,8 @@ export namespace SessionCompaction {
role: "assistant",
parentID: input.parentID,
sessionID: input.sessionID,
mode: input.agent,
mode: "compaction",
agent: "compaction",
summary: true,
path: {
cwd: Instance.directory,
@ -127,7 +128,6 @@ export namespace SessionCompaction {
model: model,
abort: input.abort,
})
const agent = await Agent.get(input.agent)
const result = await processor.process({
user: input.messages.findLast((m) => m.info.id === input.parentID)!.info as MessageV2.User,
agent,

View File

@ -9,6 +9,7 @@ import type { Agent } from "@/agent/agent"
import type { MessageV2 } from "./message-v2"
import { Plugin } from "@/plugin"
import { SystemPrompt } from "./system"
import { ToolRegistry } from "@/tool/registry"
export namespace LLM {
const log = Log.create({ service: "llm" })
@ -43,7 +44,12 @@ export namespace LLM {
})
const [language, cfg] = await Promise.all([Provider.getLanguage(input.model), Config.get()])
const [first, ...rest] = [...SystemPrompt.header(input.model.providerID), ...input.system]
const [first, ...rest] = [
...SystemPrompt.header(input.model.providerID),
...(input.agent.prompt ?? SystemPrompt.provider(input.model)),
...input.system,
...(input.user.system ? [input.user.system] : []),
]
const system = [first, rest.join("\n")]
const params = await Plugin.trigger(
@ -80,6 +86,8 @@ export namespace LLM {
OUTPUT_TOKEN_MAX,
)
const tools = await resolveTools(input)
return streamText({
onError(error) {
l.error("stream error", {
@ -88,7 +96,7 @@ export namespace LLM {
},
async experimental_repairToolCall(failed) {
const lower = failed.toolCall.toolName.toLowerCase()
if (lower !== failed.toolCall.toolName && input.tools[lower]) {
if (lower !== failed.toolCall.toolName && tools[lower]) {
l.info("repairing tool call", {
tool: failed.toolCall.toolName,
repaired: lower,
@ -110,8 +118,8 @@ export namespace LLM {
temperature: params.temperature,
topP: params.topP,
providerOptions: ProviderTransform.providerOptions(input.model, params.options),
activeTools: Object.keys(input.tools).filter((x) => x !== "invalid"),
tools: input.tools,
activeTools: Object.keys(tools).filter((x) => x !== "invalid"),
tools,
maxOutputTokens,
abortSignal: input.abort,
headers: {
@ -151,4 +159,16 @@ export namespace LLM {
experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry },
})
}
async function resolveTools(input: Pick<StreamInput, "tools" | "agent" | "user">) {
const enabled = pipe(
input.agent.tools,
mergeDeep(await ToolRegistry.enabled(input.agent)),
mergeDeep(input.user.tools ?? {}),
)
for (const [key, value] of Object.entries(enabled)) {
if (value === false) delete input.tools[key]
}
return input.tools
}
}

View File

@ -348,7 +348,11 @@ export namespace MessageV2 {
parentID: z.string(),
modelID: z.string(),
providerID: z.string(),
/**
* @deprecated
*/
mode: z.string(),
agent: z.string(),
path: z.object({
cwd: z.string(),
root: z.string(),

View File

@ -5,24 +5,22 @@ import z from "zod"
import { Identifier } from "../id/id"
import { MessageV2 } from "./message-v2"
import { Log } from "../util/log"
import { Flag } from "../flag/flag"
import { SessionRevert } from "./revert"
import { Session } from "."
import { Agent } from "../agent/agent"
import { Provider } from "../provider/provider"
import { generateText, type ModelMessage, type Tool as AITool, tool, jsonSchema } from "ai"
import { type Tool as AITool, tool, jsonSchema } from "ai"
import { SessionCompaction } from "./compaction"
import { Instance } from "../project/instance"
import { Bus } from "../bus"
import { ProviderTransform } from "../provider/transform"
import { SystemPrompt } from "./system"
import { Plugin } from "../plugin"
import PROMPT_PLAN from "../session/prompt/plan.txt"
import BUILD_SWITCH from "../session/prompt/build-switch.txt"
import MAX_STEPS from "../session/prompt/max-steps.txt"
import { defer } from "../util/defer"
import { clone, mergeDeep, pipe } from "remeda"
import { mergeDeep, pipe } from "remeda"
import { ToolRegistry } from "../tool/registry"
import { Wildcard } from "../util/wildcard"
import { MCP } from "../mcp"
@ -36,7 +34,6 @@ import { Command } from "../command"
import { $, fileURLToPath } from "bun"
import { ConfigMarkdown } from "../config/markdown"
import { SessionSummary } from "./summary"
import { Config } from "../config/config"
import { NamedError } from "@opencode-ai/util/error"
import { fn } from "@/util/fn"
import { SessionProcessor } from "./processor"
@ -89,8 +86,8 @@ export namespace SessionPrompt {
.optional(),
agent: z.string().optional(),
noReply: z.boolean().optional(),
system: z.string().optional(),
tools: z.record(z.string(), z.boolean()).optional(),
system: z.string().optional(),
parts: z.array(
z.discriminatedUnion("type", [
MessageV2.TextPart.omit({
@ -138,6 +135,20 @@ export namespace SessionPrompt {
})
export type PromptInput = z.infer<typeof PromptInput>
export const prompt = fn(PromptInput, async (input) => {
const session = await Session.get(input.sessionID)
await SessionRevert.cleanup(session)
const message = await createUserMessage(input)
await Session.touch(input.sessionID)
if (input.noReply === true) {
return message
}
return loop(input.sessionID)
})
export async function resolvePromptParts(template: string): Promise<PromptInput["parts"]> {
const parts: PromptInput["parts"] = [
{
@ -189,20 +200,6 @@ export namespace SessionPrompt {
return parts
}
export const prompt = fn(PromptInput, async (input) => {
const session = await Session.get(input.sessionID)
await SessionRevert.cleanup(session)
const message = await createUserMessage(input)
await Session.touch(input.sessionID)
if (input.noReply === true) {
return message
}
return loop(input.sessionID)
})
function start(sessionID: string) {
const s = state()
if (s[sessionID]) return
@ -296,6 +293,7 @@ export namespace SessionPrompt {
parentID: lastUser.id,
sessionID,
mode: task.agent,
agent: task.agent,
path: {
cwd: Instance.directory,
root: Instance.worktree,
@ -406,7 +404,6 @@ export namespace SessionPrompt {
messages: msgs,
parentID: lastUser.id,
abort,
agent: lastUser.agent,
model: {
providerID: model.providerID,
modelID: model.id,
@ -448,6 +445,7 @@ export namespace SessionPrompt {
parentID: lastUser.id,
role: "assistant",
mode: agent.name,
agent: agent.name,
path: {
cwd: Instance.directory,
root: Instance.worktree,
@ -470,11 +468,6 @@ export namespace SessionPrompt {
model,
abort,
})
const system = await resolveSystemPrompt({
model,
agent,
system: lastUser.system,
})
const tools = await resolveTools({
agent,
sessionID,
@ -495,7 +488,7 @@ export namespace SessionPrompt {
agent,
abort,
sessionID,
system,
system: [...(await SystemPrompt.environment()), ...(await SystemPrompt.custom())],
messages: [
...MessageV2.toModelMessage(msgs),
...(isLastStep
@ -532,21 +525,6 @@ export namespace SessionPrompt {
return Provider.defaultModel()
}
async function resolveSystemPrompt(input: { system?: string; agent: Agent.Info; model: Provider.Model }) {
using _ = log.time("system")
let system = []
system.push(
...(() => {
if (input.system) return [input.system]
if (input.agent.prompt) return [input.agent.prompt]
return SystemPrompt.provider(input.model)
})(),
)
system.push(...(await SystemPrompt.environment()))
system.push(...(await SystemPrompt.custom()))
return system
}
async function resolveTools(input: {
agent: Agent.Info
model: Provider.Model
@ -561,7 +539,6 @@ export namespace SessionPrompt {
mergeDeep(await ToolRegistry.enabled(input.agent)),
mergeDeep(input.tools ?? {}),
)
for (const item of await ToolRegistry.tools(input.model.providerID)) {
if (Wildcard.all(item.id, enabledTools) === false) continue
const schema = ProviderTransform.schema(input.model, z.toJSONSchema(item.parameters))
@ -625,7 +602,6 @@ export namespace SessionPrompt {
},
})
}
for (const [key, item] of Object.entries(await MCP.tools())) {
if (Wildcard.all(key, enabledTools) === false) continue
const execute = item.execute
@ -704,7 +680,6 @@ export namespace SessionPrompt {
created: Date.now(),
},
tools: input.tools,
system: input.system,
agent: agent.name,
model: input.model ?? agent.model ?? (await lastModel(input.sessionID)),
}
@ -995,7 +970,7 @@ export namespace SessionPrompt {
synthetic: true,
})
}
const wasPlan = input.messages.some((msg) => msg.info.role === "assistant" && msg.info.mode === "plan")
const wasPlan = input.messages.some((msg) => msg.info.role === "assistant" && msg.info.agent === "plan")
if (wasPlan && input.agent.name === "build") {
userMessage.parts.push({
id: Identifier.ascending("part"),
@ -1057,6 +1032,7 @@ export namespace SessionPrompt {
sessionID: input.sessionID,
parentID: userMsg.id,
mode: input.agent,
agent: input.agent,
cost: 0,
path: {
cwd: Instance.directory,

View File

@ -122,7 +122,7 @@ export namespace SystemPrompt {
export function compaction(providerID: string) {
switch (providerID) {
case "anthropic":
return [PROMPT_ANTHROPIC_SPOOF.trim(), PROMPT_COMPACTION]
return [PROMPT_COMPACTION]
default:
return [PROMPT_COMPACTION]
}
@ -131,7 +131,7 @@ export namespace SystemPrompt {
export function summarize(providerID: string) {
switch (providerID) {
case "anthropic":
return [PROMPT_ANTHROPIC_SPOOF.trim(), PROMPT_SUMMARIZE]
return [PROMPT_SUMMARIZE]
default:
return [PROMPT_SUMMARIZE]
}
@ -140,7 +140,7 @@ export namespace SystemPrompt {
export function title(providerID: string) {
switch (providerID) {
case "anthropic":
return [PROMPT_ANTHROPIC_SPOOF.trim(), PROMPT_TITLE]
return [PROMPT_TITLE]
default:
return [PROMPT_TITLE]
}