dify-ai-provider
Version:
Dify provider for Vercel AI SDK
1,033 lines (1,022 loc) • 38.4 kB
JavaScript
"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
});