@ifzai/connect2dify
Version:
A comprehensive TypeScript client for the Dify API, providing a clean and modular interface for chat, workflow, file management, and application interactions.
673 lines (524 loc) โข 18.8 kB
Markdown
# @ifzai/connect2dify
A comprehensive TypeScript client for the Dify API, providing a clean and modular interface for chat, workflow, file management, and application interactions.
## Features
- ๐ **Modular Architecture**: Organized into focused API modules (Chat, Conversation, Workflow, File, App)
- ๐ **Streaming Support**: Built-in support for real-time streaming responses
- ๐ช **TypeScript**: Full type safety with comprehensive type definitions
- ๐ **Universal**: Works in both browser and Node.js environments
- ๐ฆ **Tree Shakable**: Import only the modules you need
- ๐ง **Configurable**: Flexible configuration options
- ๐ **Backward Compatible**: Deprecated methods maintain compatibility
## Installation
```bash
npm install @ifzai/connect2dify
# or
pnpm add @ifzai/connect2dify
# or
yarn add @ifzai/connect2dify
```
## Quick Start
### Basic Usage
```typescript
import { DifyClient } from '@ifzai/connect2dify';
const client = new DifyClient({
baseUrl: 'https://api.dify.ai',
apiKey: 'your-api-key',
});
// Send a chat message (agent-chat apps only support streaming)
const response = await client.chat.sendMessage({
user: 'user-123',
query: 'Hello, how can you help me?',
response_mode: 'streaming',
inputs: {}, // Required for agent-chat applications
});
console.log(response.answer);
```
### Streaming Responses
```typescript
// Chat streaming example
const streamParams = {
user: 'user-123',
query: 'Tell me a story',
response_mode: 'streaming' as const,
};
const result = await client.chat.sendMessage(streamParams);
if (Array.isArray(result)) {
// Handle streaming chunks
for (const chunk of result) {
console.log(chunk.answer);
}
}
```
### Workflow Execution
```typescript
// Execute a workflow in blocking mode
const workflowResponse = await client.workflow.runWorkflow({
inputs: {
query: 'What is artificial intelligence?',
},
user: 'user-123',
response_mode: 'blocking',
});
console.log('Workflow result:', workflowResponse.data.outputs);
// Execute a workflow with streaming for real-time output
const streamingWorkflow = await client.workflow.runWorkflow({
inputs: {
query: 'Tell me a short story',
},
user: 'user-123',
response_mode: 'streaming',
chunkCompletionCallback: (chunk) => {
if (chunk.event === 'text_chunk' && chunk.data?.text) {
process.stdout.write(chunk.data.text); // Real-time text streaming
}
},
});
```
### Using Individual API Modules
```typescript
import { ChatAPI, WorkflowAPI } from '@ifzai/connect2dify';
const config = {
baseUrl: 'https://api.dify.ai',
apiKey: 'your-api-key',
};
const chatAPI = new ChatAPI(config);
const workflowAPI = new WorkflowAPI(config);
// Use APIs independently
const chatResponse = await chatAPI.sendMessage({
user: 'user-123',
query: 'Hello!',
});
const workflowResponse = await workflowAPI.runWorkflow({
user: 'user-123',
inputs: { text: 'Process this' },
});
```
## Workflow Examples
### Basic Workflow Execution
```typescript
import { DifyClient } from '@ifzai/connect2dify';
const client = new DifyClient({
baseUrl: 'https://api.dify.ai/v1',
apiKey: 'your-workflow-api-key',
});
// Execute workflow in blocking mode
const response = await client.workflow.runWorkflow({
inputs: {
query: 'What is the weather like today?',
},
user: 'user-123',
response_mode: 'blocking',
});
console.log('Workflow result:', response.data.outputs);
```
### Streaming Workflow with Real-time Output
```typescript
// Execute workflow with real-time streaming
const streamingResponse = await client.workflow.runWorkflow({
inputs: {
query: 'Tell me a short story about a robot.',
},
user: 'user-123',
response_mode: 'streaming',
chunkCompletionCallback: (chunk) => {
// Handle different event types
if (chunk.event === 'workflow_started') {
console.log('๐ฌ Workflow started...');
} else if (chunk.event === 'node_started') {
console.log(`๐ง Node started: ${chunk.data?.title || 'Unknown'}`);
} else if (chunk.event === 'text_chunk' && chunk.data?.text) {
// This is the actual streaming text - display it word by word
process.stdout.write(chunk.data.text);
} else if (chunk.event === 'node_finished') {
console.log(`โ
Node finished: ${chunk.data?.title || 'Unknown'}`);
} else if (chunk.event === 'workflow_finished') {
console.log('\n๐ Workflow finished!');
}
},
});
console.log('Streaming completed!');
```
### Workflow with Custom Inputs
```typescript
// Execute workflow with custom parameters
const customResponse = await client.workflow.runWorkflow({
inputs: {
query: 'How do I cook pasta step by step?',
language: 'en',
detail_level: 'comprehensive',
// Add any custom inputs your workflow expects
},
user: 'user-123',
response_mode: 'blocking',
});
console.log('Custom workflow result:', customResponse.data.outputs);
```
### Retrieving Workflow Information
```typescript
// First execute a workflow to get a workflow_run_id
const runResponse = await client.workflow.runWorkflow({
inputs: { query: 'Hello, this is a test.' },
user: 'user-123',
response_mode: 'blocking',
});
// Then get detailed information about the workflow run
if (runResponse.workflow_run_id) {
const workflowInfo = await client.workflow.getWorkflow({
workflow_run_id: runResponse.workflow_run_id,
});
console.log('Workflow Details:');
console.log(`- Status: ${workflowInfo.status}`);
console.log(`- Total Steps: ${workflowInfo.total_steps}`);
console.log(`- Total Tokens: ${workflowInfo.total_tokens}`);
console.log(`- Execution Time: ${workflowInfo.elapsed_time}s`);
}
```
### File Upload with Workflow
```typescript
// Upload a file first
const fileResponse = await client.file.uploadFile({
file: fileBuffer, // or File object in browser
user: 'user-123',
});
// Use the uploaded file in a workflow
const workflowResponse = await client.workflow.runWorkflow({
inputs: {
query: 'Analyze this document',
},
user: 'user-123',
response_mode: 'blocking',
files: [
{
type: 'document',
transfer_method: 'local_file',
upload_file_id: fileResponse.id,
},
],
});
```
## API Reference
### DifyClient
The main client class that provides access to all API modules:
```typescript
const client = new DifyClient({
baseUrl: 'https://api.dify.ai/v1', // Required: Your Dify API base URL
apiKey: 'your-api-key', // Required: Your API key
defaultResponseMode: 'blocking', // Optional: 'blocking' | 'streaming'
defaultUser: 'default-user', // Optional: Default user ID for requests
requestOptions: {
// Optional: Additional request configuration
extraHeaders: {
'Custom-Header': 'value',
'X-Source': 'my-app',
},
timeout: 30000, // Request timeout in milliseconds
},
});
```
#### Configuration Options
| Option | Type | Required | Default | Description |
| ----------------------------- | --------------------------- | -------- | ------------ | ------------------------------------------------------- |
| `baseUrl` | string | โ
| - | Your Dify API base URL (e.g., `https://api.dify.ai/v1`) |
| `apiKey` | string | โ
| - | Your application API key from Dify dashboard |
| `defaultResponseMode` | `'blocking' \| 'streaming'` | โ | `'blocking'` | Default response mode for API calls |
| `defaultUser` | string | โ | - | Default user ID to use when not specified in requests |
| `requestOptions.extraHeaders` | Record<string, string> | โ | `{}` | Additional HTTP headers to include in requests |
| `requestOptions.timeout` | number | โ | `60000` | Request timeout in milliseconds |
#### Environment Variables
You can also configure the client using environment variables:
```bash
# .env file
DIFY_API_BASE_URL=https://api.dify.ai/v1
DIFY_API_KEY=your-api-key-here
DIFY_DEFAULT_USER=default-user-id
```
```typescript
// Use environment variables
const client = new DifyClient({
baseUrl: process.env.DIFY_API_BASE_URL || 'https://api.dify.ai/v1',
apiKey: process.env.DIFY_API_KEY,
defaultUser: process.env.DIFY_DEFAULT_USER,
});
```
### Chat API (`client.chat`)
- `sendMessage(params)` - Send a chat message (agent-chat apps only support streaming mode)
- `getMessages(params)` - Get conversation messages
- `createMessageFeedback(params)` - Provide feedback on a message
- `getMessageSuggests(params)` - Get suggested questions
- `stopMessageResponse(params)` - Stop a message response
### Conversation API (`client.conversation`)
- `getConversations(params)` - List conversations
- `deleteConversation(params)` - Delete a conversation
- `renameConversation(params)` - Rename a conversation
### Workflow API (`client.workflow`)
- `runWorkflow(params)` - Execute a workflow (supports both blocking and streaming)
- `getWorkflow(params)` - Get workflow details
- `stopWorkflowTask(params)` - Stop a workflow task
- `getWorkflowLogs(params)` - Get workflow execution logs
### File API (`client.file`)
- `uploadFile(params)` - Upload a file
- `audioToText(params)` - Convert audio to text
### App API (`client.app`)
- `getParameters()` - Get application parameters
- `getInfo()` - Get application information
- `getMeta()` - Get application metadata
- `sendCompletionMessage(params)` - Send completion message (supports both blocking and streaming)
- `stopCompletionMessage(params)` - Stop completion message
## Environment Support
This package works in both browser and Node.js environments using modern web APIs:
- โ
Node.js 18+ (native fetch and ReadableStream support)
- โ
Modern browsers (Chrome 66+, Firefox 65+, Safari 12+)
- โ
Edge runtime environments (Vercel Edge, Cloudflare Workers)
- โ Node.js < 18 (requires fetch polyfill)
## Error Handling
### Basic Error Handling
```typescript
try {
const response = await client.chat.sendMessage({
user: 'user-123',
query: 'Hello!',
});
console.log('Success:', response.answer);
} catch (error) {
console.error('API Error:', error.message);
// Handle specific error types
if (error.message.includes('unauthorized')) {
console.error('Check your API key');
} else if (error.message.includes('quota')) {
console.error('API quota exceeded');
}
}
```
### Workflow Error Handling
```typescript
try {
const response = await client.workflow.runWorkflow({
inputs: { query: 'Process this data' },
user: 'user-123',
response_mode: 'blocking',
});
// Check workflow execution status
if (response.data.status === 'failed') {
console.error('Workflow failed:', response.data.error);
return;
}
console.log('Workflow succeeded:', response.data.outputs);
} catch (error) {
console.error('Workflow execution error:', error.message);
// Handle specific workflow errors
if (error.message.includes('workflow not found')) {
console.error('Check your workflow ID and permissions');
} else if (error.message.includes('invalid inputs')) {
console.error('Check your input parameters match workflow requirements');
}
}
```
### Streaming Error Handling
```typescript
try {
const result = await client.workflow.runWorkflow({
inputs: { query: 'Tell me a story' },
user: 'user-123',
response_mode: 'streaming',
chunkCompletionCallback: (chunk) => {
// Handle streaming errors
if (chunk.event === 'error') {
console.error('Streaming error:', chunk.data?.error);
return;
}
// Process successful chunks
if (chunk.event === 'text_chunk' && chunk.data?.text) {
process.stdout.write(chunk.data.text);
}
},
});
} catch (error) {
if (error.message.includes('Streaming is not supported')) {
// Fallback to blocking mode
console.log('Falling back to blocking mode...');
const response = await client.workflow.runWorkflow({
inputs: { query: 'Tell me a story' },
user: 'user-123',
response_mode: 'blocking',
});
console.log(response.data.outputs);
} else {
console.error('Streaming workflow failed:', error.message);
}
}
```
## Migration from Previous Versions
The new modular structure provides a cleaner API while maintaining backward compatibility:
```typescript
// Old way (still works, but deprecated)
const response = await client.sendMessage(params);
const workflowResult = await client.runWorkflow(params);
// New way (recommended)
const response = await client.chat.sendMessage(params);
const workflowResult = await client.workflow.runWorkflow(params);
```
## Running Examples
This package includes comprehensive examples demonstrating real-world usage patterns. All examples use actual Dify API endpoints and showcase different functionality.
### Prerequisites
Before running examples, ensure you have:
1. **Node.js 18+** installed
2. **Valid Dify API credentials** (API key and base URL)
3. **Dependencies installed**: `pnpm install`
### Available Examples
#### 1. Comprehensive Workflow Demo
**Location**: `examples/comprehensive-workflow-demo.ts`
**Run Command**:
```bash
npm run example:workflow
```
**What it demonstrates**:
- โ
Blocking workflow execution with timing
- ๐ Streaming workflow execution with real-time text output
- โ๏ธ Custom input parameters
- ๐ Workflow run information retrieval
- โ
Parameter validation
- ๐ฌ Event handling for different workflow stages
**Sample Output**:
```
๐ฏ Comprehensive Dify Workflow Demo
===================================
๐ Test 1: Blocking Workflow Execution
--------------------------------------
โ
Blocking execution completed in 6.852s
Response structure:
- Task ID: 95cc6b94-22ea-468d-94f8-8ecd46601fc1
- Status: succeeded
- Total Tokens: 268
๐ Test 2: Streaming Workflow Execution
---------------------------------------
๐ Starting streaming workflow...
๐ Streaming output (word by word):
Here's a short story about a curious robot... [streaming text appears here]
โ
Streaming execution completed in 13.708s
- Total chunks received: 428
- Event type distribution: { text_chunk: 420, workflow_started: 1, ... }
```
#### 2. API Debugging Tool
**Location**: `examples/debug-api.ts`
**Run Command**:
```bash
npm run debug:api
```
**What it demonstrates**:
- ๐ App info and parameters retrieval
- ๐ฌ Chat completion API usage
- ๐ง Workflow API with detailed logging
- ๐ Error analysis and troubleshooting
#### 3. Raw API Testing
**Location**: `examples/raw-api-test.js`
**Run Command**:
```bash
npm run test:raw
```
**What it demonstrates**:
- ๐งช Low-level API testing with raw fetch calls
- ๐ก Direct HTTP request/response analysis
- ๐ง API connectivity troubleshooting
- โก Bypasses client library for pure API testing
### Configuring Examples
Most examples include API credentials that you'll need to update:
```typescript
// Update these values in the example files
const client = new DifyClient({
baseUrl: 'https://api.dify.ai/v1', // Your Dify instance URL
apiKey: 'your-api-key-here', // Your workflow/app API key
});
const userId = 'your-user-id'; // Your user identifier
```
### Example Output Features
The examples showcase:
- **Real-time Streaming**: Watch AI responses generate word-by-word
- **Performance Metrics**: Execution time, token usage, chunk counts
- **Event Tracking**: Workflow progression through different stages
- **Error Handling**: Robust error catching and user-friendly messages
- **Type Safety**: Full TypeScript integration with proper typing
### Creating Custom Examples
You can create your own examples by following this pattern:
```typescript
import { DifyClient } from '../src/index.js';
async function myCustomExample() {
const client = new DifyClient({
baseUrl: 'https://api.dify.ai/v1',
apiKey: 'your-api-key',
});
try {
const response = await client.workflow.runWorkflow({
inputs: { query: 'Your custom input' },
user: 'your-user-id',
response_mode: 'streaming',
chunkCompletionCallback: (chunk) => {
// Handle streaming chunks
console.log('Received chunk:', chunk.event);
},
});
console.log('Success:', response);
} catch (error) {
console.error('Error:', error.message);
}
}
myCustomExample().catch(console.error);
```
### Troubleshooting Examples
If examples fail to run:
1. **Check API credentials** - Ensure your API key and base URL are correct
2. **Verify network connectivity** - Test if you can reach the Dify API
3. **Review console output** - Examples provide detailed error messages
4. **Check API quotas** - Ensure you have sufficient API credits
5. **Validate workflow configuration** - Make sure your workflow accepts the input parameters
## Setup
Install the dependencies:
```bash
pnpm install
```
## Get started
Build the library:
```bash
pnpm build
```
Build the library in watch mode:
```bash
pnpm dev
```
## Commit Convention
This project follows [Conventional Commits](https://www.conventionalcommits.org/) specification. All commit messages must be formatted as:
```
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
```
### Types
- **feat**: A new feature
- **fix**: A bug fix
- **docs**: Documentation only changes
- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
- **refactor**: A code change that neither fixes a bug nor adds a feature
- **perf**: A code change that improves performance
- **test**: Adding missing tests or correcting existing tests
- **build**: Changes that affect the build system or external dependencies
- **ci**: Changes to our CI configuration files and scripts
- **chore**: Other changes that don't modify src or test files
- **revert**: Reverts a previous commit
### Examples
```bash
feat: add user authentication
fix: resolve memory leak in data processing
docs: update API documentation
style: format code with prettier
refactor: extract utility functions
perf: optimize database queries
test: add unit tests for user service
build: update dependencies
ci: add automated testing workflow
chore: update .gitignore
```
### Git Hooks
This project uses [Husky](https://typicode.github.io/husky/) to run git hooks:
- **pre-commit**: Runs code formatting and tests
- **commit-msg**: Validates commit message format
The hooks will automatically run when you commit. If they fail, the commit will be rejected.