{"id":50872714,"url":"https://github.com/atompi/changate","last_synced_at":"2026-06-15T06:36:32.976Z","repository":{"id":356442035,"uuid":"1221634291","full_name":"atompi/changate","owner":"atompi","description":"Channel Gateway for Feishu and AI Agents.","archived":false,"fork":false,"pushed_at":"2026-05-30T13:14:18.000Z","size":139,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-30T15:08:18.375Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/atompi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-26T13:36:22.000Z","updated_at":"2026-05-30T13:14:22.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/atompi/changate","commit_stats":null,"previous_names":["atompi/changate"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/atompi/changate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atompi%2Fchangate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atompi%2Fchangate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atompi%2Fchangate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atompi%2Fchangate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/atompi","download_url":"https://codeload.github.com/atompi/changate/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atompi%2Fchangate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34351451,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-15T02:00:07.085Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-15T06:36:27.873Z","updated_at":"2026-06-15T06:36:32.971Z","avatar_url":"https://github.com/atompi.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Changate\n\nChannel Gateway for Feishu (Lark) and AI Agent Services.\n\n## Overview\n\nChangate acts as a bridge between Feishu (Lark) applications and AI Agent services. It receives message callbacks from Feishu, forwards them to backend AI Agents (via LiteLLM proxy), and sends the Agent responses back to Feishu.\n\n```mermaid\nflowchart LR\n    A[\"Feishu/\u003cbr/\u003eLark\"]\n    B[\"Changate\"]\n    C[\"LiteLLM\u003cbr/\u003eProxy\"]\n    A --\u003e|POST /feishu/:app| B\n    B --\u003e|response| A\n    B --\u003e|async\u003cbr/\u003ePOST /v1/responses or /v1/chat/completions| C\n    C --\u003e|response| B\n```\n\n## Features\n\n- **ETCD Configuration Management**: Centralized multi-app configuration via ETCD with per-user agent override support\n- **Multi-Agent Support**: Supports both OpenResponses and ChatCompletions API types via LiteLLM proxy\n- **Message Encryption**: AES-256-CBC encryption for callback content\n- **Signature Verification**: HMAC-SHA256 signature validation\n- **Async Processing**: Agent requests executed asynchronously to prevent Feishu callback timeout\n- **Session Persistence**: Configure `user` parameter for stable Agent sessions\n- **Image Processing**: Download Feishu images, base64 encode, and send to Agent\n- **File Reply**: Upload local files from Agent response (`MEDIA:/path/to/file`) to Feishu\n- **Agent Client Caching**: LRU+TTL cache to reduce repeated creation overhead\n- **MCP Tools**: Pass MCP server information to LiteLLM proxy via `tools` configuration\n- **Retry Logic**: Exponential backoff retry for transient network failures and 5xx errors\n- **@Mention Gating**: In group/channel/thread chats, the bot only replies when explicitly @-mentioned. 1-on-1 chats reply to every message. The bot's `@_user_xxx` placeholder is stripped from text before forwarding to the Agent. The bot is identified by `Mention.type == \"bot\"` AND `Mention.Name == app.bot_name` (configured per app in ETCD).\n\n## Tech Stack\n\n- **Language**: Go 1.26+\n- **Framework**: Gin Web Framework\n- **Configuration**: Viper\n\n## Project Structure\n\n```\nchangate/\n├── cmd/\n│   └── server/\n│       └── main.go               # Entry point\n├── internal/\n│   ├── agent/\n│   │   ├── client.go             # Client interface + NewClient factory\n│   │   ├── agent_http.go         # Unified HTTP client + builders\n│   │   └── agent_test.go         # Unit tests\n│   ├── config/\n│   │   ├── config.go              # Config structs + Load function\n│   │   └── etcd_loader.go        # ETCD config loader\n│   ├── etcd/\n│   │   └── client.go              # ETCD client\n│   ├── feishu/\n│   │   └── client.go              # Feishu API client\n│   ├── handler/\n│   │   ├── callback.go            # Callback handling logic\n│   │   ├── agent_cache.go         # LRU+TTL Agent client cache\n│   │   └── agent_cache_test.go    # Cache tests\n│   ├── model/\n│   │   ├── agent.go               # Agent response models\n│   │   ├── event.go               # Event data models\n│   │   └── model_test.go          # Model tests\n│   └── router/\n│       └── router.go             # Gin router setup\n└── pkg/\n    ├── crypto/\n    │   └── aes.go                 # AES encryption/decryption\n    ├── logger/\n    │   └── logger.go              # Structured logging\n    └── retry/\n        └── retry.go               # Retry logic\n```\n\n## Quick Start\n\n### Requirements\n\n- Go 1.26+\n- Feishu App (with Bot enabled)\n- LiteLLM Proxy (supporting `/v1/responses` or `/v1/chat/completions`)\n\n### Build\n\n```bash\ngit clone https://github.com/atompi/changate.git\ncd changate\ngo build -o changate ./cmd/server\n```\n\n### Configuration\n\nEdit `config/config.yaml`:\n\n```yaml\nserver:\n  host: \"0.0.0.0\"\n  port: 8080\n  read_timeout: 30s\n  write_timeout: 30s\n\nlog_level: \"debug\"\n\netcd:\n  endpoints:\n    - \"http://127.0.0.1:2379\"\n  timeout: 5s\n  root_path: \"/changate\"\n```\n\n#### ETCD Configuration Structure\n\n| Path | Description |\n|------|-------------|\n| `/changate/\u003capp_name\u003e` | App-level config (enabled + default agent) |\n| `/changate/\u003capp_name\u003e/\u003cuser_id\u003e` | User-level config (enabled + agent override) |\n\n**App Config Example**:\n```json\n{\n  \"enabled\": true,\n  \"app_id\": \"cli_xxxxxxxx\",\n  \"app_secret\": \"xxxxxxxx\",\n  \"encrypt_key\": \"xxxxxxxx\",\n  \"verify_token\": \"xxxxxxxx\",\n  \"feishu_base_url\": \"https://open.feishu.cn\",\n  \"max_concurrent\": 100,\n  \"timeout\": 120,\n  \"bot_name\": \"MyBot\",\n  \"agent\": {\n      \"type\": \"ChatCompletions\",\n      \"base_url\": \"https://litellm-proxy.example.com\",\n      \"api_path\": \"/v1/chat/completions\",\n      \"timeout\": 120,\n      \"max_retries\": 3,\n      \"retry_base_delay\": \"100ms\",\n      \"model\": \"sf/Qwen/Qwen3-30B-A3B\",\n      \"token\": \"sk-xxxxxxxx\",\n      \"user\": \"default\",\n      \"system_prompt\": \"\",\n      \"tool_choice\": \"auto\",\n      \"tools\": [\n        {\n          \"type\": \"mcp\",\n          \"server_url\": \"litellm_proxy/mcp/wiki_mcp\",\n          \"server_label\": \"wiki_search\",\n          \"require_approval\": \"never\"\n        }\n      ]\n    }\n}\n```\n\n**User Config Example**:\n```json\n{\n  \"enabled\": true,\n  \"agent\": {\n    \"type\": \"OpenResponses\",\n    \"base_url\": \"https://litellm-proxy.example.com\",\n    \"max_retries\": 3,\n    \"retry_base_delay\": \"100ms\",\n    \"model\": \"minimax/MiniMax-M2.7\",\n    \"token\": \"sk-xxxxxxxx\",\n    \"user\": \"bob\",\n    \"tools\": [\n      {\n        \"type\": \"mcp\",\n        \"server_url\": \"litellm_proxy/mcp/wiki_mcp\",\n        \"server_label\": \"wiki\",\n        \"require_approval\": \"never\"\n      }\n    ]\n  }\n}\n```\n\n### Run\n\n```bash\n./changate server --config config/config.yaml\n```\n\n### Feishu App Setup\n\n1. Create a Feishu app and enable Bot functionality\n2. Configure event subscription:\n   - Enable `im.message.receive_v1` (receive messages)\n   - Set callback URL to `https://your-domain.com/feishu/app1`\n\n## API Endpoints\n\n### Callback\n\n```\nPOST /feishu/:appName\n```\n\nReceives Feishu message callbacks.\n\n**Headers**:\n- `X-Lark-Signature`: HMAC-SHA256 signature\n- `X-Lark-Request-Timestamp`: Timestamp\n\n**Response**:\n- URL verification: `{\"challenge\": \"xxx\"}`\n- Message handling: `{\"code\": 0}`\n\n### Health Check\n\n```\nGET /health\n```\n\nReturns `{\"status\": \"ok\"}`.\n\n## LiteLLM Proxy API\n\n### Chat Completions API\n\n```json\n{\n  \"model\": \"sf/Qwen/Qwen3-30B-A3B\",\n  \"messages\": [\n    {\"role\": \"user\", \"content\": [\n        {\"type\": \"text\", \"text\": \"user message\"},\n        {\"type\": \"image_url\", \"image_url\": {\"url\": \"data:image/png;base64,...\"}}\n    ]}\n  ],\n  \"tools\": [\n    {\n      \"type\": \"mcp\",\n      \"server_url\": \"litellm_proxy/mcp/wiki_mcp\",\n      \"server_label\": \"wiki_search\",\n      \"require_approval\": \"never\"\n    }\n  ],\n  \"user\": \"user-identifier\",\n  \"tool_choice\": \"auto\"\n}\n```\n\n### Responses API\n\n```json\n{\n  \"model\": \"minimax/MiniMax-M2.7\",\n  \"input\": [\n    {\"role\": \"user\", \"content\": [\n        {\"type\": \"input_text\", \"text\": \"user message\"},\n        {\"type\": \"input_image\", \"image_url\": \"data:image/png;base64,...\"}\n    ]}\n  ],\n  \"tools\": [\n    {\n      \"type\": \"mcp\",\n      \"server_url\": \"litellm_proxy/mcp/wiki_mcp\",\n      \"server_label\": \"wiki\",\n      \"require_approval\": \"never\"\n    }\n  ],\n  \"user\": \"user-identifier\",\n  \"tool_choice\": \"required\"\n}\n```\n\n## Message Processing\n\n### Text Messages\n\n1. **Receive callback**: Changate receives Feishu callback\n2. **Decrypt \u0026 Verify**: Decrypt body and verify signature if `encrypt_key` configured\n3. **Parse message**: Extract content and message ID\n4. **Async processing**:\n   - Serialize content to Agent API format\n   - Send reply to Feishu user after Agent responds\n5. **Immediate response**: Return `{\"code\": 0}` immediately to avoid timeout\n\n### Image Messages\n\n1. **Receive callback**: Extract `image_key`\n2. **Download image**: `GET /open-apis/im/v1/messages/{message_id}/resources/{file_key}?type=image`\n3. **Base64 encode**: Convert to `data:image/png;base64,...`\n4. **Send to Agent**: `{\"type\": \"input_image\", \"image_url\": \"data:image/png;base64,...\"}`\n\n### File Reply\n\nWhen Agent response contains `MEDIA:/path/to/file.png`:\n\n1. Extract file path\n2. Read local file\n3. Upload to Feishu: `POST /open-apis/im/v1/files` (multipart/form-data)\n4. Send file message to user\n\n## Logging\n\nStructured logging with levels:\n\n- `debug`: Request/response bodies (when `log_level: debug`)\n- `info`: General information\n- `warn`: Warning information (includes retry attempts)\n- `error`: Error information\n\n## Testing\n\n```bash\ngo test ./...\ngo test -cover ./...\n```\n\n## License\n\n[MIT](./LICENSE)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatompi%2Fchangate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fatompi%2Fchangate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatompi%2Fchangate/lists"}