npm package: @our-llm/sdk
Description: Agent 工作流 SDK，为第三方 Web 开发者提供简洁的 API 来集成工作流执行、文件上传和会话管理功能。
Latest version: 1.2.2

# @our-llm/sdk

Agent 工作流 SDK，为第三方 Web 开发者提供简洁的 API 来集成工作流执行、文件上传和会话管理功能。

## 安装

```bash
npm install @our-llm/sdk
# 或
pnpm add @our-llm/sdk
```

## 快速开始

```typescript
import { OurLLMClient } from '@our-llm/sdk'

// 1. 创建客户端
const client = new OurLLMClient({
  baseUrl: 'https://api.example.com',
})

// 2. 创建会话
const session = client.createSession('workflow-id')

// 3. 监听状态变化
session.on('stateChange', (state) => {
  console.log('消息列表:', state.messages)
  console.log('是否执行中:', state.isExecuting)
})

// 4. 发送消息
await session.send({ content: '你好' })
```

## API 文档

### OurLLMClient

SDK 主入口类。

#### 构造函数

```typescript
const client = new OurLLMClient({
  baseUrl: string,    // 必填，API 服务器地址
  timeout?: number,   // 可选，请求超时时间（毫秒）
  headers?: Record<string, string>,  // 可选，自定义请求头
  tenantSecret?: string, // 可选，租户密钥 (⚠️ 仅用于调试，禁止在生产环境使用)
})
```

#### 方法

##### `getWorkflows(page?: number, pageSize?: number)`

获取工作流列表。

```typescript
// 分页获取工作流，默认 page=1, pageSize=10
const { items, total } = await client.getWorkflows(1, 20)
console.log(`共 ${total} 个工作流`)
```

##### `getWorkflow(id: string)`

获取工作流详情。

```typescript
const workflow = await client.getWorkflow('workflow-id')
console.log(workflow.name, workflow.description)
```

##### `createSession(workflowId: string, options?: SessionOptions)`

创建工作流会话。

```typescript
// 新会话
const session = client.createSession('workflow-id')

// 恢复已有会话
const session = client.createSession('workflow-id', {
  threadId: 'existing-thread-id',
})
```

---

### WorkflowSession

工作流会话类，管理消息状态和执行流程。

#### 方法

##### `on(event, listener)`

监听事件。

```typescript
session.on('start', ({ threadId }) => {
  // 工作流开始执行，获取到 threadId
})

session.on('nodeStart', ({ nodeId }) => {
  // 节点开始执行
})

session.on('nodeEnd', ({ nodeId, variables, messages }) => {
  // 节点执行结束
})

session.on('token', ({ nodeId, content, isReasoning }) => {
  // 收到流式 token（用于打字机效果）
})

session.on('loading', ({ nodeId, message }) => {
  // 收到 loading 事件，用于更新加载状态
  console.log('Loading:', message)
})

session.on('complete', () => {
  // 工作流执行完成
})

session.on('error', ({ message }) => {
  // 发生错误
})

session.on('stateChange', (state) => {
  // 状态变化（推荐使用此事件更新 UI）
  // state.messages: SimpleMessage[]
  // state.isExecuting: boolean
  // state.threadId?: string
  // state.currentNodeId?: string
})
```

##### `off(event, listener)`

移除事件监听。

```typescript
const onToken = (data) => console.log(data)
session.on('token', onToken)

// 不需要时移除监听
session.off('token', onToken)
```

##### `send(input: SendInput)`

发送消息执行工作流。

```typescript
// 发送纯文本
await session.send({
  content: '你好',
})

// 发送带图片的消息（自动压缩上传）
await session.send({
  content: '请分析这张图片',
  images: [imageFile],  // File[]
})

// 发送带文档的消息（自动上传）
await session.send({
  content: '请总结这份文档',
  documents: [pdfFile],  // File[]
})

// 使用已上传的 URL
await session.send({
  content: '你好',
  imageUrls: ['https://example.com/image.jpg'],
  documentInfos: [{
    url: 'https://example.com/doc.pdf',
    filename: 'document.pdf',
    mimeType: 'application/pdf',
  }],
})
```

##### `restore()`

恢复历史会话（需要在创建 session 时传入 threadId）。

```typescript
const session = client.createSession('workflow-id', {
  threadId: 'existing-thread-id',
})
await session.restore()
// 会触发 stateChange 事件，包含历史消息
```

##### `getState()`

获取当前状态快照。

```typescript
const state = session.getState()
console.log(state.messages)
console.log(state.isExecuting)
```

##### `abort()`

中止当前执行。

```typescript
session.abort()
```

---

### UploadService

文件上传服务（通过 `client.upload` 访问）。

##### `uploadFile(params)`

上传文件。

```typescript
const url = await client.upload.uploadFile({
  file: file,
  resourceType: 'conversation',  // 'conversation' | 'workflow' | 'knowledge-base'
  onProgress: (percent) => {
    console.log(`上传进度: ${percent * 100}%`)
  },
})
```

##### `uploadImage(file, resourceType, onProgress?)`

上传图片（自动压缩）。

```typescript
const url = await client.upload.uploadImage(
  imageFile,
  'conversation',
  (percent) => console.log(`${percent * 100}%`),
)
```

---

## 类型定义

### SimpleMessage

消息类型（与后端保持一致）。

```typescript
interface SimpleMessage {
  type: 'human' | 'ai' | 'system'
  content: string | Array<{ type: string; [key: string]: any }>
  id?: string
  images?: string[]
  videos?: string[]
  documents?: Array<{ filename: string; url: string }>
}
```

### SessionState

会话状态类型。

```typescript
interface SessionState {
  threadId?: string
  messages: SimpleMessage[]
  isExecuting: boolean
  currentNodeId?: string
}
```

### SendInput

发送消息输入类型。

```typescript
interface SendInput {
  content?: string
  images?: File[]
  imageUrls?: string[]
  documents?: File[]
  documentInfos?: Array<{
    url: string
    filename: string
    mimeType: string
  }>
}
```

---

## 完整示例

### React 聊天组件

```tsx
import { useState, useEffect, useRef } from 'react'
import { OurLLMClient, type SimpleMessage, type WorkflowSession } from '@our-llm/sdk'

const client = new OurLLMClient({
  baseUrl: 'https://api.example.com',
})

function ChatComponent({ workflowId }: { workflowId: string }) {
  const [messages, setMessages] = useState<SimpleMessage[]>([])
  const [isExecuting, setIsExecuting] = useState(false)
  const [input, setInput] = useState('')
  const sessionRef = useRef<WorkflowSession | null>(null)

  useEffect(() => {
    return () => {
      sessionRef.current?.abort()
    }
  }, [])

  const handleSend = async () => {
    if (!input.trim() || isExecuting) return

    const session = client.createSession(workflowId)
    sessionRef.current = session

    session.on('stateChange', (state) => {
      setMessages([...state.messages])
      setIsExecuting(state.isExecuting)
    })

    session.on('error', ({ message }) => {
      alert(`错误: ${message}`)
    })

    try {
      await session.send({ content: input })
      setInput('')
    } catch (error) {
      console.error(error)
    }
  }

  return (
    <div>
      <div className="messages">
        {messages.map((msg, i) => (
          <div key={i} className={msg.type}>
            {typeof msg.content === 'string' ? msg.content : '...'}
          </div>
        ))}
        {isExecuting && <div>思考中...</div>}
      </div>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        disabled={isExecuting}
      />
      <button onClick={handleSend} disabled={isExecuting}>
        发送
      </button>
    </div>
  )
}
```

---

## 注意事项

1. **图片自动压缩**：通过 `session.send({ images: [...] })` 上传的图片会自动压缩到 1920px 以内，质量 0.8
2. **会话管理**：每次调用 `send()` 都会更新 `threadId`，如需保存会话请在 `start` 事件中获取
3. **错误处理**：建议始终监听 `error` 事件处理异常情况
4. **资源清理**：组件卸载时调用 `session.abort()` 避免内存泄漏