UNPKG

dify-ai-provider

Version:
1,033 lines (1,022 loc) 38.4 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { createDifyProvider: () => createDifyProvider, difyProvider: () => difyProvider }); module.exports = __toCommonJS(index_exports); // src/dify-provider.ts var import_provider_utils2 = require("@ai-sdk/provider-utils"); // src/dify-chat-language-model.ts var import_provider = require("@ai-sdk/provider"); var import_provider_utils = require("@ai-sdk/provider-utils"); // src/dify-chat-settings.ts var defaultLogger = { debug: (message, extra) => console.debug(`[dify-ai-provider] ${message}`, extra != null ? extra : ""), info: (message, extra) => console.log(`[dify-ai-provider] ${message}`, extra != null ? extra : ""), warn: (message, extra) => console.warn(`[dify-ai-provider] ${message}`, extra != null ? extra : ""), error: (message, extra) => console.error(`[dify-ai-provider] ${message}`, extra != null ? extra : "") }; // src/dify-chat-schema.ts var import_zod = require("zod"); var completionResponseSchema = import_zod.z.object({ id: import_zod.z.string(), answer: import_zod.z.string(), task_id: import_zod.z.string(), conversation_id: import_zod.z.string(), message_id: import_zod.z.string(), metadata: import_zod.z.object({ usage: import_zod.z.object({ completion_tokens: import_zod.z.number(), prompt_tokens: import_zod.z.number(), total_tokens: import_zod.z.number() }) }) }); var errorResponseSchema = import_zod.z.object({ code: import_zod.z.string(), message: import_zod.z.string(), status: import_zod.z.number() }); var difyStreamEventBase = import_zod.z.object({ event: import_zod.z.string(), conversation_id: import_zod.z.string().optional(), message_id: import_zod.z.string().optional(), task_id: import_zod.z.string().optional(), created_at: import_zod.z.number().optional() }).passthrough(); var workflowStartedSchema = difyStreamEventBase.extend({ event: import_zod.z.literal("workflow_started"), workflow_run_id: import_zod.z.string(), data: import_zod.z.object({ id: import_zod.z.string(), workflow_id: import_zod.z.string(), created_at: import_zod.z.number() }).passthrough() }); var workflowFinishedSchema = difyStreamEventBase.extend({ event: import_zod.z.literal("workflow_finished"), workflow_run_id: import_zod.z.string(), task_id: import_zod.z.string().optional(), data: import_zod.z.object({ id: import_zod.z.string(), workflow_id: import_zod.z.string(), outputs: import_zod.z.record(import_zod.z.unknown()).optional(), status: import_zod.z.string().optional(), elapsed_time: import_zod.z.number().optional(), total_tokens: import_zod.z.number().optional(), total_steps: import_zod.z.string().optional(), created_at: import_zod.z.number().optional(), finished_at: import_zod.z.number().optional() }).passthrough() }); var nodeStartedSchema = difyStreamEventBase.extend({ event: import_zod.z.literal("node_started"), workflow_run_id: import_zod.z.string(), data: import_zod.z.object({ id: import_zod.z.string(), node_id: import_zod.z.string(), node_type: import_zod.z.string() }).passthrough() }); var nodeFinishedSchema = difyStreamEventBase.extend({ event: import_zod.z.literal("node_finished"), workflow_run_id: import_zod.z.string(), data: import_zod.z.object({ id: import_zod.z.string(), node_id: import_zod.z.string(), node_type: import_zod.z.string() }).passthrough() }); var messageSchema = difyStreamEventBase.extend({ event: import_zod.z.literal("message"), id: import_zod.z.string().optional(), answer: import_zod.z.string(), from_variable_selector: import_zod.z.array(import_zod.z.string()).optional() }); var messageEndSchema = difyStreamEventBase.extend({ event: import_zod.z.literal("message_end"), id: import_zod.z.string().optional(), metadata: import_zod.z.object({ usage: import_zod.z.object({ prompt_tokens: import_zod.z.number().optional(), completion_tokens: import_zod.z.number().optional(), total_tokens: import_zod.z.number().optional(), prompt_unit_price: import_zod.z.string().optional(), prompt_price_unit: import_zod.z.string().optional(), prompt_price: import_zod.z.string().optional(), completion_unit_price: import_zod.z.string().optional(), completion_price_unit: import_zod.z.string().optional(), completion_price: import_zod.z.string().optional(), total_price: import_zod.z.string().optional(), currency: import_zod.z.string().optional(), latency: import_zod.z.number().optional() }).passthrough(), retriever_resources: import_zod.z.array(import_zod.z.unknown()).optional() }).passthrough(), files: import_zod.z.nullable(import_zod.z.array(import_zod.z.unknown())).optional() }); var ttsMessageSchema = difyStreamEventBase.extend({ event: import_zod.z.literal("tts_message"), audio: import_zod.z.string() }); var ttsMessageEndSchema = difyStreamEventBase.extend({ event: import_zod.z.literal("tts_message_end"), audio: import_zod.z.string() }); var agentMessageSchema = difyStreamEventBase.extend({ event: import_zod.z.literal("agent_message"), answer: import_zod.z.string() }); var agentThoughtSchema = difyStreamEventBase.extend({ event: import_zod.z.literal("agent_thought"), id: import_zod.z.string(), position: import_zod.z.number(), thought: import_zod.z.string(), observation: import_zod.z.string(), tool: import_zod.z.string(), tool_labels: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional(), tool_input: import_zod.z.string(), message_files: import_zod.z.array(import_zod.z.unknown()) }); var messageReplaceSchema = difyStreamEventBase.extend({ event: import_zod.z.literal("message_replace"), answer: import_zod.z.string() }); var messageFileSchema = difyStreamEventBase.extend({ event: import_zod.z.literal("message_file"), id: import_zod.z.string(), type: import_zod.z.string(), belongs_to: import_zod.z.string(), url: import_zod.z.string() }); var pingSchema = difyStreamEventBase.extend({ event: import_zod.z.literal("ping") }); var errorSchema = difyStreamEventBase.extend({ event: import_zod.z.literal("error"), status: import_zod.z.number().optional(), code: import_zod.z.string().optional(), message: import_zod.z.string().optional() }); var difyStreamEventSchema = import_zod.z.discriminatedUnion("event", [ workflowStartedSchema, workflowFinishedSchema, nodeStartedSchema, nodeFinishedSchema, messageSchema, messageEndSchema, ttsMessageSchema, ttsMessageEndSchema, agentThoughtSchema, agentMessageSchema, messageReplaceSchema, messageFileSchema, pingSchema, errorSchema ]).or(difyStreamEventBase); // src/stream-parser.ts var ThinkTagParser = class { constructor() { this.state = "text"; this.buffer = ""; this.accumulated = ""; this.hasStarted = false; this.closed = false; // Once </think> is seen, treat all subsequent <think> as plain text this.textAccumulated = ""; this.inString = false; this.escape = false; } feed(chunk) { const raw = []; this.accumulated += chunk; for (let i = 0; i < chunk.length; i++) { const ch = chunk[i]; if (!this.hasStarted) { if (ch.trim() === "") continue; this.hasStarted = true; } if (this.inString) { if (this.escape) { this.escape = false; raw.push({ type: "text", content: ch }); } else if (ch === "\\") { this.escape = true; raw.push({ type: "text", content: ch }); } else if (ch === '"') { this.inString = false; raw.push({ type: "text", content: ch }); } else { raw.push({ type: "text", content: ch }); } continue; } if (ch === '"') { this.inString = true; raw.push({ type: "text", content: ch }); continue; } switch (this.state) { case "text": if (ch === "<" && !this.closed) { this.state = "maybe-open"; this.buffer = "<"; } else { raw.push({ type: "text", content: ch }); } break; case "maybe-open": this.buffer += ch; if ("<think>".startsWith(this.buffer)) { if (this.buffer === "<think>") { this.state = "reasoning"; this.buffer = ""; } } else { raw.push({ type: "text", content: this.buffer }); this.buffer = ""; this.state = "text"; } break; case "reasoning": if (ch === "<") { this.state = "maybe-close"; this.buffer = "<"; } else { raw.push({ type: "reasoning", content: ch }); } break; case "maybe-close": this.buffer += ch; if ("</think>".startsWith(this.buffer)) { if (this.buffer === "</think>") { this.state = "text"; this.buffer = ""; this.closed = true; } } else { raw.push({ type: "reasoning", content: this.buffer }); this.buffer = ""; this.state = "reasoning"; } break; } } const merged = []; for (const seg of raw) { const last = merged[merged.length - 1]; if (last && last.type === seg.type) { last.content += seg.content; } else { merged.push({ ...seg }); } } for (const seg of merged) { if (seg.type === "text") { this.textAccumulated += seg.content; } } return merged; } flush() { if (!this.buffer) return []; const type = this.state === "maybe-close" ? "reasoning" : "text"; const seg = [{ type, content: this.buffer }]; if (type === "text") { this.textAccumulated += this.buffer; } this.buffer = ""; return seg; } reset(text) { this.accumulated = ""; this.buffer = ""; this.state = "text"; this.closed = false; this.hasStarted = false; this.inString = false; this.escape = false; this.feed(text); } getTextWithoutThink() { return this.textAccumulated.trim(); } }; function parseToolCalls(text, toolNames, genId) { const toolCalls = []; let cleanText = text; if (!toolNames.length) return { toolCalls, cleanText }; const lowerToolNames = toolNames.map((n) => n.toLowerCase()); const matchedBlocks = []; let depth = 0; let inString = false; let escape = false; let startIdx = -1; for (let i = 0; i < text.length; i++) { const ch = text[i]; if (escape) { escape = false; continue; } if (ch === "\\") { escape = true; continue; } if (ch === '"') { inString = !inString; continue; } if (inString) continue; if (ch === "{") { if (depth === 0) startIdx = i; depth++; } else if (ch === "}") { depth--; if (depth < 0) depth = 0; if (depth === 0 && startIdx !== -1) { const jsonCandidate = text.substring(startIdx, i + 1); try { const parsed = JSON.parse(jsonCandidate); if (parsed && typeof parsed === "object" && "name" in parsed && "arguments" in parsed) { const toolName = String(parsed.name).toLowerCase(); const originalIdx = lowerToolNames.indexOf(toolName); if (originalIdx !== -1) { toolCalls.push({ id: genId(), name: toolNames[originalIdx], input: JSON.stringify(parsed.arguments) }); matchedBlocks.push(jsonCandidate); } } } catch (e) { } startIdx = -1; } } } for (const block of matchedBlocks) { cleanText = cleanText.replace(block, ""); } return { toolCalls, cleanText: cleanText.trim() }; } function placeholder(schema) { if (!schema) return '"..."'; const t = schema.type; if (t === "array") { const item = schema.items ? placeholder(schema.items) : '"..."'; return `[${item}]`; } if (t === "object") { const props = schema.properties; if (!props) return "{}"; const entries = Object.entries(props).slice(0, 3).map(([k, v]) => `"${k}": ${placeholder(v)}`); return `{${entries.join(", ")}}`; } if (t === "number" || t === "integer") return "0"; if (t === "boolean") return "true"; return '"..."'; } var TOOL_SHORT_DESCRIPTIONS = { question: "Ask the user a question", bash: "Execute a bash command", read: "Read a file", glob: "Find files by pattern", grep: "Search file contents with regex", edit: "Edit a file with string replacement", write: "Write/create a file", task: "Launch a sub-agent for complex tasks", webfetch: "Fetch content from a URL", todowrite: "Create/manage a task list", skill: "Load a specialized skill" }; function getShortDescription(name, description) { if (TOOL_SHORT_DESCRIPTIONS[name]) return TOOL_SHORT_DESCRIPTIONS[name]; if (!description) return name; return description.replace(/[\r\n]+/g, " ").split(/[.!?。]/)[0].trim().substring(0, 60); } function formatToolsPrompt(tools) { if (!(tools == null ? void 0 : tools.length)) { return { prompt: "", toolNames: [] }; } const defs = tools.filter((t) => t.type === "function").map((t) => ({ name: t.name, description: t.description, parameters: t.inputSchema })); if (!defs.length) return { prompt: "", toolNames: [] }; const toolNames = defs.map((d) => d.name); const toolDescriptions = defs.map((t) => { const params = t.parameters; const paramList = (params == null ? void 0 : params.properties) ? Object.entries(params.properties).map(([k, v]) => `"${k}": ${placeholder(v)}`).join(", ") : ""; const shortDesc = getShortDescription(t.name, t.description); return `- ${t.name}: ${shortDesc} {"name": "${t.name}", "arguments": {${paramList}}}`; }); const prompt = ` # Tools Call tools with JSON format: ${toolDescriptions.join("\n")}`; return { prompt, toolNames }; } async function uploadFileToDify(data, mimeType, filename, user, baseURL, headers, logger) { try { let blob; if (typeof data === "string") { const buffer = Buffer.from(data, "base64"); blob = new Blob([buffer], { type: mimeType }); } else { blob = new Blob([data], { type: mimeType }); } const form = new FormData(); form.append("file", blob, filename); form.append("user", user); const { "content-type": _, "Content-Type": __, ...authHeaders } = headers(); const res = await fetch(`${baseURL}/files/upload`, { method: "POST", headers: { ...authHeaders }, body: form }); if (!res.ok) { const errBody = await res.text().catch(() => ""); logger == null ? void 0 : logger.error("Dify file upload failed", { status: res.status, statusText: res.statusText, body: errBody }); return void 0; } const json = await res.json(); return json.id; } catch (e) { logger == null ? void 0 : logger.error("Dify file upload error", { error: String(e) }); return void 0; } } async function extractFileAttachments(messages, userId, baseURL, headers, logger) { const uploadPromises = []; for (const msg of messages) { if (!Array.isArray(msg.content)) continue; for (const part of msg.content) { if (!part || typeof part !== "object" || part.type !== "file") continue; const mimeType = part.mediaType || part.mimeType || ""; if (!mimeType) continue; let uploadData; const filename = part.filename || `file.${mimeType.split("/")[1] || "bin"}`; if (typeof part.data === "string") { const m = part.data.match(/^data:[^;]+;base64,(.+)$/); uploadData = m ? m[1] : part.data; } else if (part.data instanceof Uint8Array) { uploadData = part.data; } if (uploadData) { const task = uploadFileToDify(uploadData, mimeType, filename, userId, baseURL, headers, logger).then((uploadId) => { if (uploadId) { return { type: mimeType.startsWith("image/") ? "image" : "document", transfer_method: "local_file", upload_file_id: uploadId }; } else { logger == null ? void 0 : logger.warn("Dify file upload skipped, attachment will be missing", { filename, mimeType }); return void 0; } }); uploadPromises.push(task); } } } const results = await Promise.all(uploadPromises); return results.filter((item) => item !== void 0); } // src/dify-chat-language-model.ts function formatSystemPrompt(raw) { if (!raw) return ""; const trimmed = raw.trim(); const envTagMatch = trimmed.match(/<env>([\s\S]*?)<\/env>/); const dirsTagMatch = trimmed.match(/<directories>([\s\S]*?)<\/directories>/); const identityMatch = trimmed.match(/^You are\b[^.]*\./m); if (envTagMatch || identityMatch || dirsTagMatch) { const parts = []; if (identityMatch) { parts.push(identityMatch[0]); } if (envTagMatch) { parts.push(`<env>${envTagMatch[1]}</env>`); } if (dirsTagMatch) { parts.push(`<directories>${dirsTagMatch[1]}</directories>`); } return parts.join("\n\n\n\n"); } return trimmed; } function isRecord(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } function getStringField(record, keys) { for (const key of keys) { const value = record[key]; if (typeof value === "string" && value) return value; } return void 0; } function buildRetrieverResourceSources(resources, metadata) { if (!Array.isArray(resources)) return []; return resources.map((resource, index) => { var _a, _b; const record = isRecord(resource) ? resource : {}; const id = (_a = getStringField(record, ["segment_id", "document_id", "id"])) != null ? _a : `retriever-resource-${index + 1}`; const title = (_b = getStringField(record, ["document_name", "title", "dataset_name", "segment_id", "id"])) != null ? _b : `Retriever resource ${index + 1}`; const filename = getStringField(record, ["document_name", "filename"]); return { type: "source", sourceType: "document", id, mediaType: "text/plain", title, ...filename ? { filename } : {}, providerMetadata: { difyWorkflowData: { ...metadata, retrieverResource: resource } } }; }); } var DifyChatLanguageModel = class { constructor(modelId, settings, config) { this.settings = settings; this.specificationVersion = "v2"; this.supportedUrls = {}; var _a; this.modelId = modelId; this.config = config; this.generateId = import_provider_utils.generateId; this.chatMessagesEndpoint = `${this.config.baseURL}/chat-messages`; this.logger = settings.logger === false ? void 0 : (_a = settings.logger) != null ? _a : defaultLogger; if (!this.settings.responseMode) { this.settings.responseMode = "streaming"; } } createErrorHandler() { return (0, import_provider_utils.createJsonErrorResponseHandler)({ errorSchema: errorResponseSchema, errorToMessage: (data) => { var _a; (_a = this.logger) == null ? void 0 : _a.error("Dify API error", data); return `Dify API error: ${data.message}`; } }); } get provider() { return this.config.provider; } async doGenerate(options) { var _a, _b; const { abortSignal } = options; const { body: requestBody, toolNames, requestHeaders } = await this.getRequestBody(options); const body = { ...requestBody, response_mode: "blocking" }; if (this.settings.logMessages) { (_a = this.logger) == null ? void 0 : _a.info("", { body }); } const { responseHeaders, value: data } = await (0, import_provider_utils.postJsonToApi)({ url: this.chatMessagesEndpoint, headers: (0, import_provider_utils.combineHeaders)(this.config.headers(), requestHeaders != null ? requestHeaders : options.headers), body, abortSignal, failedResponseHandler: this.createErrorHandler(), successfulResponseHandler: (0, import_provider_utils.createJsonResponseHandler)( completionResponseSchema ), fetch: this.config.fetch }); const typedData = data; if (this.settings.logMessages) { (_b = this.logger) == null ? void 0 : _b.info("", { answer: typedData.answer, conversation_id: typedData.conversation_id }); } const content = []; if (typedData.answer) { const { toolCalls, cleanText } = parseToolCalls(typedData.answer, toolNames, this.generateId); if (cleanText) { content.push({ type: "text", text: cleanText }); } for (const tc of toolCalls) { content.push({ type: "tool-call", toolCallId: tc.id, toolName: tc.name, input: tc.input }); } } const hasToolCalls = content.some((c) => c.type === "tool-call"); return { content, finishReason: hasToolCalls ? "tool-calls" : "stop", usage: { inputTokens: typedData.metadata.usage.prompt_tokens, outputTokens: typedData.metadata.usage.completion_tokens, totalTokens: typedData.metadata.usage.total_tokens }, warnings: [], providerMetadata: { difyWorkflowData: { conversationId: typedData.conversation_id, messageId: typedData.message_id } }, request: { body: JSON.stringify(body) }, response: { id: typedData.id, timestamp: /* @__PURE__ */ new Date(), headers: responseHeaders } }; } async doStream(options) { var _a, _b; if (this.settings.logMessages) { (_a = this.logger) == null ? void 0 : _a.info("", { options }); } const { abortSignal } = options; const { body: requestBody, toolNames, requestHeaders } = await this.getRequestBody(options); const body = { ...requestBody, response_mode: "streaming" }; if (this.settings.logMessages) { (_b = this.logger) == null ? void 0 : _b.info("", { body }); } const { responseHeaders, value: responseStream } = await (0, import_provider_utils.postJsonToApi)({ url: this.chatMessagesEndpoint, headers: (0, import_provider_utils.combineHeaders)(this.config.headers(), requestHeaders != null ? requestHeaders : options.headers), body, failedResponseHandler: this.createErrorHandler(), successfulResponseHandler: (0, import_provider_utils.createEventSourceResponseHandler)( difyStreamEventSchema ), abortSignal, fetch: this.config.fetch }); let conversationId; let messageId; let taskId; let isActiveText = false; let isActiveReasoning = false; const thinkParser = new ThinkTagParser(); const genId = this.generateId; const logger = this.logger; const logMessages = this.settings.logMessages; function buildDifyWorkflowData() { const meta = {}; if (conversationId) meta.conversationId = conversationId; if (messageId) meta.messageId = messageId; if (taskId) meta.taskId = taskId; return meta; } function buildDifyMetadata() { const meta = buildDifyWorkflowData(); return Object.keys(meta).length ? { providerMetadata: { difyWorkflowData: meta } } : {}; } function emitSegments(segments, controller) { for (const seg of segments) { if (seg.type === "reasoning") { if (isActiveText) { controller.enqueue({ type: "text-end", id: "0", ...buildDifyMetadata() }); isActiveText = false; } if (!isActiveReasoning) { isActiveReasoning = true; controller.enqueue({ type: "reasoning-start", id: "r0", ...buildDifyMetadata() }); } controller.enqueue({ type: "reasoning-delta", id: "r0", delta: seg.content, ...buildDifyMetadata() }); } else if (seg.content) { if (isActiveReasoning) { controller.enqueue({ type: "reasoning-end", id: "r0", ...buildDifyMetadata() }); isActiveReasoning = false; } if (!isActiveText) { isActiveText = true; controller.enqueue({ type: "text-start", id: "0", ...buildDifyMetadata() }); } controller.enqueue({ type: "text-delta", id: "0", delta: seg.content, ...buildDifyMetadata() }); } } } return { stream: responseStream.pipeThrough( new TransformStream({ transform(chunk, controller) { var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j, _k; if (!chunk.success) { logger == null ? void 0 : logger.error("Dify stream parse error", { error: String(chunk.error) }); controller.enqueue({ type: "error", error: chunk.error }); return; } const data = chunk.value; if (logMessages) { logger == null ? void 0 : logger.info("", { data }); } if (data.conversation_id) conversationId = data.conversation_id; if (data.message_id) messageId = data.message_id; if (data.task_id) taskId = data.task_id; switch (data.event) { case "workflow_finished": case "message_end": { let inputTokens = 0; let outputTokens = 0; let totalTokens = 0; if (data.event === "workflow_finished") { const dataUsage = data.data || {}; inputTokens = 0; outputTokens = (_a2 = dataUsage.total_tokens) != null ? _a2 : 0; totalTokens = (_b2 = dataUsage.total_tokens) != null ? _b2 : 0; } else { const usageData = ((_c = data.metadata) == null ? void 0 : _c.usage) || {}; inputTokens = (_d = usageData.prompt_tokens) != null ? _d : 0; outputTokens = (_e = usageData.completion_tokens) != null ? _e : 0; totalTokens = (_f = usageData.total_tokens) != null ? _f : 0; } const textWithoutThink = thinkParser.getTextWithoutThink(); const { toolCalls } = parseToolCalls(textWithoutThink, toolNames, genId); const hasToolCalls = toolCalls.length > 0; if (logMessages) { logger == null ? void 0 : logger.info("", { toolCalls }); } emitSegments(thinkParser.flush(), controller); if (isActiveReasoning) { controller.enqueue({ type: "reasoning-end", id: "r0", ...buildDifyMetadata() }); isActiveReasoning = false; } if (isActiveText) { controller.enqueue({ type: "text-end", id: "0", ...buildDifyMetadata() }); isActiveText = false; } else if (!hasToolCalls) { controller.enqueue({ type: "text-start", id: "0", ...buildDifyMetadata() }); controller.enqueue({ type: "text-end", id: "0", ...buildDifyMetadata() }); } if (data.event === "message_end") { for (const sourcePart of buildRetrieverResourceSources( (_g = data.metadata) == null ? void 0 : _g.retriever_resources, buildDifyWorkflowData() )) { controller.enqueue(sourcePart); } } for (const tc of toolCalls) { controller.enqueue({ type: "tool-input-start", id: tc.id, toolName: tc.name, ...buildDifyMetadata() }); controller.enqueue({ type: "tool-input-delta", id: tc.id, delta: tc.input, ...buildDifyMetadata() }); controller.enqueue({ type: "tool-input-end", id: tc.id, ...buildDifyMetadata() }); controller.enqueue({ type: "tool-call", toolCallId: tc.id, toolName: tc.name, input: tc.input, ...buildDifyMetadata() }); } controller.enqueue({ type: "finish", finishReason: hasToolCalls ? "tool-calls" : "stop", ...buildDifyMetadata(), usage: { inputTokens, outputTokens, totalTokens } }); break; } case "message": case "agent_message": { if ("answer" in data && typeof data.answer === "string" && data.answer) { emitSegments(thinkParser.feed(data.answer), controller); if ("id" in data && typeof data.id === "string") { controller.enqueue({ type: "response-metadata", id: data.id }); } } break; } // agent_thought events are summaries of agent_message chunks. // Dify sends: agent_thought(empty) → agent_message × N → agent_thought(full) → message_end // The agent_message events already contain all content (including <think> tags), // so agent_thought is redundant and must be ignored to avoid duplicate output. case "agent_thought": break; case "message_replace": { if ("answer" in data && typeof data.answer === "string" && data.answer) { thinkParser.reset(data.answer); if (isActiveText) { controller.enqueue({ type: "text-end", id: "0", ...buildDifyMetadata() }); isActiveText = false; } controller.enqueue({ type: "text-start", id: "0", ...buildDifyMetadata() }); controller.enqueue({ type: "text-delta", id: "0", delta: data.answer, ...buildDifyMetadata() }); isActiveText = true; } break; } case "message_file": case "ping": break; case "error": { const err = data; const msg = (_i = (_h = err.message) != null ? _h : err.msg) != null ? _i : "Unknown Dify error"; const code = (_k = (_j = err.code) != null ? _j : err.status) != null ? _k : ""; logger == null ? void 0 : logger.error("Dify stream error", { code, message: msg }); controller.enqueue({ type: "error", error: new Error(`Dify error${code ? ` (${code})` : ""}: ${msg}`) }); break; } } } }) ), request: { body: JSON.stringify(body) }, response: { headers: responseHeaders } }; } /** * Get the request body for the Dify API */ async getRequestBody(options) { var _a, _b, _c, _d, _e; const opts = options; const messages = (_a = options.prompt) != null ? _a : opts.messages; if (!messages || !messages.length) { (_b = this.logger) == null ? void 0 : _b.error("No messages provided", { prompt: options.prompt }); throw new import_provider.APICallError({ message: "No messages provided", url: this.chatMessagesEndpoint, requestBodyValues: options }); } const conversationId = this.findConversationId(options.headers, messages); const query = this.buildQuery(messages, conversationId); if (!query) { (_c = this.logger) == null ? void 0 : _c.error("No user message found", { messageCount: messages.length, roles: messages.map((m) => m.role) }); throw new import_provider.APICallError({ message: "No user message found", url: this.chatMessagesEndpoint, requestBodyValues: { messageCount: messages.length } }); } const userId = (_e = (_d = options.headers) == null ? void 0 : _d["user-id"]) != null ? _e : "you_should_pass_user-id"; const { "chat-id": _h1, "user-id": _h2, ...cleanHeaders } = options.headers || {}; const files = await extractFileAttachments(messages, userId, this.config.baseURL, this.config.headers, this.logger); const systemPrompt = this.extractSystemPrompt(messages); const injectTools = this.settings.injectToolsPrompt !== false; const { prompt: generatedPrompt, toolNames } = formatToolsPrompt(options.tools); const toolsPrompt = injectTools ? generatedPrompt : ""; const isNewConversation = !conversationId; const parts = [ isNewConversation ? systemPrompt : "", query, isNewConversation ? toolsPrompt : "" ].filter(Boolean); const finalQuery = parts.join("\n\n"); const inputs = this.settings.inputs || {}; const requestBody = { inputs, query: finalQuery, response_mode: this.settings.responseMode, conversation_id: conversationId, user: userId, ...files.length ? { files } : {} }; return { body: requestBody, toolNames, requestHeaders: cleanHeaders }; } findConversationId(headers, messages) { var _a, _b; if (headers == null ? void 0 : headers["chat-id"]) { return headers["chat-id"]; } for (let i = messages.length - 1; i >= 0; i--) { const msg = messages[i]; if (Array.isArray(msg.content)) { for (const part of msg.content) { const meta = ((_a = part == null ? void 0 : part.providerMetadata) == null ? void 0 : _a.difyWorkflowData) || ((_b = part == null ? void 0 : part.callProviderMetadata) == null ? void 0 : _b.difyWorkflowData); if (meta == null ? void 0 : meta.conversationId) { return meta.conversationId; } } } } return void 0; } buildQuery(messages, conversationId) { let startIndex = 0; if (conversationId) { startIndex = messages.length; for (let i = messages.length - 1; i >= 0; i--) { const msg = messages[i]; if (msg.role === "tool") { startIndex = i; continue; } if (msg.role === "user") { startIndex = i; break; } break; } } return messages.slice(startIndex).filter((msg) => msg.role === "user" || msg.role === "tool" || !conversationId && msg.role === "assistant").map((msg) => { if (msg.role === "tool") return this.extractToolResult(msg); return this.extractTextFromMessage(msg); }).filter(Boolean).join("\n\n"); } extractSystemPrompt(messages) { const systemMessage = messages.find((msg) => msg.role === "system"); const rawSystemPrompt = systemMessage ? this.extractTextFromMessage(systemMessage) : ""; return formatSystemPrompt(rawSystemPrompt); } extractTextFromMessage(msg) { if (typeof msg.content === "string") { return msg.content; } if (Array.isArray(msg.content)) { return msg.content.map((part) => { if (typeof part === "string") return part; if ((part == null ? void 0 : part.type) === "text" && part.text) return part.text; return ""; }).filter(Boolean).join(" "); } return ""; } extractToolResult(msg) { var _a, _b, _c; const content = msg.content; if (!Array.isArray(content)) return ""; const results = []; for (const part of content) { if ((part == null ? void 0 : part.type) === "tool-result") { const name = part.toolName || "tool"; const raw = (_c = (_b = (_a = part.output) != null ? _a : part.result) != null ? _b : part.content) != null ? _c : part.text; const output = raw !== void 0 && typeof raw === "object" && raw !== null && "value" in raw ? raw.value : raw; const result = output === void 0 ? "[completed]" : typeof output === "string" ? output : JSON.stringify(output); results.push(`[${name} result]: ${result}`); } } return results.join("\n"); } }; // src/dify-provider.ts function createDifyProvider(options = {}) { const createChatModel = (modelId, settings = {}) => new DifyChatLanguageModel(modelId, settings, { provider: "dify.chat", baseURL: options.baseURL || "https://api.dify.ai/v1", headers: () => ({ Authorization: `Bearer ${(0, import_provider_utils2.loadApiKey)({ apiKey: settings.apiKey, environmentVariableName: "DIFY_API_KEY", description: "Dify API Key" })}`, "Content-Type": "application/json", ...options.headers }) }); const provider = function(modelId, settings) { if (new.target) { throw new Error( "The model factory function cannot be called with the new keyword." ); } return createChatModel(modelId, settings); }; provider.chat = createChatModel; return provider; } var difyProvider = createDifyProvider(); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { createDifyProvider, difyProvider });