Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions packages/opencode/src/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ export namespace Agent {
const cfg = await Config.get()
const defaultModel = input.model ?? (await Provider.defaultModel())
const model = await Provider.getModel(defaultModel.providerID, defaultModel.modelID)
const provider = await Provider.getProvider(defaultModel.providerID)
const language = await Provider.getLanguage(model)

const system = [PROMPT_GENERATE]
Expand Down Expand Up @@ -317,22 +318,33 @@ export namespace Agent {
}),
} satisfies Parameters<typeof generateObject>[0]

const ctx = {
model,
provider,
}

if (defaultModel.providerID === "openai" && (await Auth.get(defaultModel.providerID))?.type === "oauth") {
const result = streamObject({
const call = {
...params,
providerOptions: ProviderTransform.providerOptions(model, {
instructions: SystemPrompt.instructions(),
store: false,
}),
onError: () => {},
})
}
await Plugin.trigger("llm.request.before", { ...ctx, type: "stream" }, { params: call })
const result = streamObject(call)
for await (const part of result.fullStream) {
await Plugin.trigger("llm.stream.chunk", { ...ctx, type: "stream" }, { part })
if (part.type === "error") throw part.error
}
await Plugin.trigger("llm.response.after", { ...ctx, type: "stream" }, { result: result.object })
return result.object
}

await Plugin.trigger("llm.request.before", { ...ctx, type: "generate" }, { params })
const result = await generateObject(params)
await Plugin.trigger("llm.response.after", { ...ctx, type: "generate" }, { result: result.object })
return result.object
}
}
27 changes: 25 additions & 2 deletions packages/opencode/src/session/llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,17 @@ export namespace LLM {
})
}

return streamText({
const ctx = {
sessionID: input.sessionID,
agent: input.agent,
model: input.model,
provider,
message: input.user,
requestID: input.user.id,
type: "stream",
}

const call = {
onError(error) {
l.error("stream error", {
error,
Expand Down Expand Up @@ -262,7 +272,20 @@ export namespace LLM {
sessionId: input.sessionID,
},
},
})
}

await Plugin.trigger("llm.request.before", ctx, { params: call })

const stream = await streamText(call)
const full = stream.fullStream
const fullStream = (async function* () {
for await (const part of full) {
await Plugin.trigger("llm.stream.chunk", ctx, { part })
yield part
}
})()

return { ...stream, fullStream }
}

async function resolveTools(input: Pick<StreamInput, "tools" | "agent" | "user">) {
Expand Down
24 changes: 24 additions & 0 deletions packages/plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ export type ProviderContext = {
options: Record<string, any>
}

export type LlmPhase = "generate" | "stream"

export type LlmContext = {
sessionID?: string
agent?: unknown
model?: Model
provider?: unknown
message?: UserMessage
requestID?: string
type: LlmPhase
}

export type PluginInput = {
client: ReturnType<typeof createOpencodeClient>
project: Project
Expand Down Expand Up @@ -176,6 +188,18 @@ export interface Hooks {
input: { sessionID: string; agent: string; model: Model; provider: ProviderContext; message: UserMessage },
output: { headers: Record<string, string> },
) => Promise<void>
/**
* Last hook before the request is sent to the provider
*/
"llm.request.before"?: (input: LlmContext, output: { params: Record<string, unknown> }) => Promise<void>
/**
* First hook after a non-stream response is received
*/
"llm.response.after"?: (input: LlmContext, output: { result: unknown }) => Promise<void>
/**
* Stream chunk hook before opencode processing
*/
"llm.stream.chunk"?: (input: LlmContext, output: { part: unknown }) => Promise<void>
"permission.ask"?: (input: Permission, output: { status: "ask" | "deny" | "allow" }) => Promise<void>
"command.execute.before"?: (
input: { command: string; sessionID: string; arguments: string },
Expand Down
Loading