Skip to content
Open
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
9 changes: 9 additions & 0 deletions packages/opencode/src/provider/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,15 @@ export namespace Provider {
},
}
},
dify: async () => {
const difyLog = Log.create({ service: "dify" })
return {
autoload: false,
async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
return (sdk as any)(modelID, { ...options, logger: difyLog })
},
}
},
}

export const Model = z
Expand Down
85 changes: 85 additions & 0 deletions packages/opencode/src/session/dify-headers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Config } from "@/config/config"
import { Session } from "@/session"
import type { Provider } from "@/provider/provider"
import type { Agent } from "@/agent/agent"
import type { MessageV2 } from "./message-v2"

export type DifyPartMetadata = {
difyWorkflowData?: { conversationId?: string }
}

function findLastCompactionIndex(msgs: MessageV2.WithParts[]): number | undefined {
for (let i = msgs.length - 1; i >= 0; i--) {
const info = msgs[i].info
if (
info.role === "assistant" &&
(info.summary === true || info.mode === "compaction" || info.agent === "compaction")
) {
return i
}
}
return undefined
}

function findConversationId(msgs: MessageV2.WithParts[]): string | undefined {
for (let i = msgs.length - 1; i >= 0; i--) {
const msg = msgs[i]
if (msg.info.role !== "assistant") continue

for (const part of msg.parts) {
if ("metadata" in part && part.metadata) {
const meta = part.metadata as DifyPartMetadata
if (meta.difyWorkflowData?.conversationId) {
return meta.difyWorkflowData.conversationId
}
}
}
}
return undefined
}

export type ApplyDifyHeadersOptions = {
headers: Record<string, string>
provider: Provider.Info
model: Provider.Model
agent: Agent.Info
sessionID: string
user: MessageV2.User
}

export async function applyDifyHeaders(opts: ApplyDifyHeadersOptions): Promise<void> {
const { headers: h, sessionID, user } = opts
const currentUserId = user.id
const providerOptions = opts.provider.options?.headers
const modelHeaders = opts.model.headers ?? {}
const isCompactionRequest = opts.agent.name === "compaction"

Object.assign(h, providerOptions ?? {}, modelHeaders)
if (!h["user-id"]) {
const config = await Config.get()
h["user-id"] = config.username ?? "unknown"
}

const msgs = await Session.messages({ sessionID, limit: 100 })
const compactionIndex = findLastCompactionIndex(msgs)
const conversationId = findConversationId(msgs)

if (compactionIndex === undefined || compactionIndex !== msgs.length - 3) {
if (conversationId) h["chat-id"] = conversationId
return
}

const isCurrentSynthetic = (opts.user as any).parts?.some((p: any) => p.type === "text" && p.synthetic) ?? false

/**
* Determines if we should reset the chat-id (start a new conversation).
* - true: Normal request after compaction/summary with a real (non-synthetic) user message.
* In this case, do NOT set chat-id to avoid inheriting old conversation.
* - false: Compaction request OR synthetic user message → preserve existing chat-id.
*/
const shouldResetChatId = !isCompactionRequest && !isCurrentSynthetic

if (!shouldResetChatId && conversationId) {
h["chat-id"] = conversationId
}
}
12 changes: 12 additions & 0 deletions packages/opencode/src/session/llm.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Installation } from "@/installation"
import { Provider } from "@/provider/provider"
import { Log } from "@/util/log"
import { applyDifyHeaders } from "./dify-headers"
import {
streamText,
wrapLanguageModel,
Expand Down Expand Up @@ -144,6 +145,17 @@ export namespace LLM {
},
)

if (input.model.providerID === "dify") {
await applyDifyHeaders({
headers: headers as Record<string, string>,
provider,
model: input.model,
agent: input.agent,
sessionID: input.sessionID,
user: input.user,
})
}

const maxOutputTokens =
isCodex || provider.id.includes("github-copilot") ? undefined : ProviderTransform.maxOutputTokens(input.model)

Expand Down
Loading