UNPKG

dify-ai-provider

Version:
385 lines (380 loc) 11.8 kB
// src/dify-provider.ts import { loadApiKey } from "@ai-sdk/provider-utils"; // src/dify-chat-language-model.ts import { APICallError } from "@ai-sdk/provider"; import { combineHeaders, createEventSourceResponseHandler, createJsonErrorResponseHandler, createJsonResponseHandler, generateId, postJsonToApi } from "@ai-sdk/provider-utils"; // src/dify-chat-schema.ts import { z } from "zod"; var completionResponseSchema = z.object({ id: z.string(), answer: z.string(), task_id: z.string(), conversation_id: z.string(), message_id: z.string(), metadata: z.object({ usage: z.object({ completion_tokens: z.number(), prompt_tokens: z.number(), total_tokens: z.number() }) }) }); var errorResponseSchema = z.object({ code: z.string(), message: z.string(), status: z.number() }); var difyStreamEventBase = z.object({ event: z.string(), conversation_id: z.string().optional(), message_id: z.string().optional(), task_id: z.string().optional(), created_at: z.number().optional() }).passthrough(); var workflowStartedSchema = difyStreamEventBase.extend({ event: z.literal("workflow_started"), workflow_run_id: z.string(), data: z.object({ id: z.string(), workflow_id: z.string(), created_at: z.number() }).passthrough() }); var workflowFinishedSchema = difyStreamEventBase.extend({ event: z.literal("workflow_finished"), workflow_run_id: z.string(), data: z.object({ id: z.string(), workflow_id: z.string(), total_tokens: z.number().optional(), created_at: z.number() }).passthrough() }); var nodeStartedSchema = difyStreamEventBase.extend({ event: z.literal("node_started"), workflow_run_id: z.string(), data: z.object({ id: z.string(), node_id: z.string(), node_type: z.string() }).passthrough() }); var nodeFinishedSchema = difyStreamEventBase.extend({ event: z.literal("node_finished"), workflow_run_id: z.string(), data: z.object({ id: z.string(), node_id: z.string(), node_type: z.string() }).passthrough() }); var messageSchema = difyStreamEventBase.extend({ event: z.literal("message"), id: z.string().optional(), answer: z.string(), from_variable_selector: z.array(z.string()).optional() }); var messageEndSchema = difyStreamEventBase.extend({ event: z.literal("message_end"), id: z.string(), metadata: z.object({ usage: z.object({ prompt_tokens: z.number(), completion_tokens: z.number(), total_tokens: z.number() }).passthrough() }).passthrough(), files: z.array(z.unknown()).optional() }); var ttsMessageSchema = difyStreamEventBase.extend({ event: z.literal("tts_message"), audio: z.string() }); var ttsMessageEndSchema = difyStreamEventBase.extend({ event: z.literal("tts_message_end"), audio: z.string() }); var agentMessageSchema = difyStreamEventBase.extend({ event: z.literal("agent_message"), answer: z.string() }); var agentThoughtSchema = difyStreamEventBase.extend({ event: z.literal("agent_thought"), thought: z.string(), observation: z.string(), tool: z.string(), tool_labels: z.record(z.string(), z.string()), tool_input: z.string(), message_files: z.array(z.unknown()) }); var difyStreamEventSchema = z.discriminatedUnion("event", [ workflowStartedSchema, workflowFinishedSchema, nodeStartedSchema, nodeFinishedSchema, messageSchema, messageEndSchema, ttsMessageSchema, ttsMessageEndSchema, agentThoughtSchema, agentMessageSchema ]).or(difyStreamEventBase); // src/dify-chat-language-model.ts var difyFailedResponseHandler = createJsonErrorResponseHandler({ errorSchema: errorResponseSchema, errorToMessage: (data) => { console.log("Dify API error:", data); return `Dify API error: ${data.message}`; } }); var DifyChatLanguageModel = class { constructor(modelId, settings, config) { this.settings = settings; this.specificationVersion = "v1"; this.defaultObjectGenerationMode = void 0; this.modelId = modelId; this.config = config; this.generateId = generateId; this.chatMessagesEndpoint = `${this.config.baseURL}/chat-messages`; if (!this.settings.responseMode) { this.settings.responseMode = "streaming"; } } get provider() { return this.config.provider; } async doGenerate(options) { var _a, _b, _c, _d; const { abortSignal } = options; const requestBody = this.getRequestBody(options); const { responseHeaders, value: data } = await postJsonToApi({ url: this.chatMessagesEndpoint, headers: combineHeaders(this.config.headers(), options.headers), body: requestBody, abortSignal, failedResponseHandler: difyFailedResponseHandler, successfulResponseHandler: createJsonResponseHandler( completionResponseSchema ), fetch: this.config.fetch }); return { text: data.answer, toolCalls: [], // Dify doesn't currently support tool calls finishReason: "stop", // Dify doesn't specify finish reason usage: { promptTokens: ((_b = (_a = data.metadata) == null ? void 0 : _a.usage) == null ? void 0 : _b.prompt_tokens) || 0, completionTokens: ((_d = (_c = data.metadata) == null ? void 0 : _c.usage) == null ? void 0 : _d.completion_tokens) || 0 }, rawCall: this.createRawCall(options), providerMetadata: { difyWorkflowData: { conversationId: data.conversation_id, messageId: data.message_id } }, rawResponse: { headers: responseHeaders, body: data }, request: { body: JSON.stringify(requestBody) }, response: { id: data.id, timestamp: /* @__PURE__ */ new Date() } }; } async doStream(options) { const { abortSignal } = options; const requestBody = this.getRequestBody(options); const body = { ...requestBody, response_mode: "streaming" }; const { responseHeaders, value: responseStream } = await postJsonToApi({ url: this.chatMessagesEndpoint, headers: combineHeaders(this.config.headers(), options.headers), body, failedResponseHandler: difyFailedResponseHandler, successfulResponseHandler: createEventSourceResponseHandler( difyStreamEventSchema ), abortSignal, fetch: this.config.fetch }); let conversationId; let messageId; let taskId; return { stream: responseStream.pipeThrough( new TransformStream({ transform(chunk, controller) { if (!chunk.success) { controller.enqueue({ type: "error", error: chunk.error }); return; } const data = chunk.value; 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 totalTokens = 0; if ("data" in data && data.data && typeof data.data === "object" && "total_tokens" in data.data && typeof data.data.total_tokens === "number") { totalTokens = data.data.total_tokens; } controller.enqueue({ type: "finish", finishReason: "stop", providerMetadata: { difyWorkflowData: { conversationId, messageId, taskId } }, usage: { promptTokens: 0, completionTokens: totalTokens } }); break; } case "message": case "agent_message": { if ("answer" in data && typeof data.answer === "string") { controller.enqueue({ type: "text-delta", textDelta: data.answer }); if ("id" in data && typeof data.id === "string") { controller.enqueue({ type: "response-metadata", id: data.id }); } } break; } } } }) ), rawCall: this.createRawCall(options), rawResponse: { headers: responseHeaders }, request: { body: JSON.stringify(body) } }; } /** * Get the request body for the Dify API */ getRequestBody(options) { var _a, _b, _c; const messages = options.messages || options.prompt; if (!messages || !messages.length) { throw new APICallError({ message: "No messages provided", url: this.chatMessagesEndpoint, requestBodyValues: options }); } const latestMessage = messages[messages.length - 1]; if (latestMessage.role !== "user") { throw new APICallError({ message: "The last message must be a user message", url: this.chatMessagesEndpoint, requestBodyValues: { latestMessageRole: latestMessage.role } }); } const hasAttachments = Array.isArray(latestMessage.content) && latestMessage.content.some((part) => { return typeof part !== "string" && part.type === "image"; }); if (hasAttachments) { throw new APICallError({ message: "Dify provider does not currently support image attachments", url: this.chatMessagesEndpoint, requestBodyValues: { hasAttachments: true } }); } let query = ""; if (typeof latestMessage.content === "string") { query = latestMessage.content; } else if (Array.isArray(latestMessage.content)) { query = latestMessage.content.map((part) => { if (typeof part === "string") { return part; } else if (part.type === "text") { return part.text; } return ""; }).filter(Boolean).join(" "); } const conversationId = (_a = options.headers) == null ? void 0 : _a["chat-id"]; const userId = (_c = (_b = options.headers) == null ? void 0 : _b["user-id"]) != null ? _c : "you_should_pass_user-id"; const { "chat-id": _, "user-id": __, ...cleanHeaders } = options.headers || {}; options.headers = cleanHeaders; return { inputs: this.settings.inputs || {}, query, response_mode: this.settings.responseMode, conversation_id: conversationId, user: userId }; } /** * Create the rawCall object for response */ createRawCall(options) { return { rawPrompt: options.messages || options.prompt, rawSettings: { ...this.settings } }; } supportsUrl(url) { return false; } }; // 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 ${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(); export { createDifyProvider, difyProvider };