zai-coding-gateway 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. zai_coding_gateway-0.1.0/.gitignore +60 -0
  2. zai_coding_gateway-0.1.0/PKG-INFO +176 -0
  3. zai_coding_gateway-0.1.0/README.md +156 -0
  4. zai_coding_gateway-0.1.0/mcp.json.example +12 -0
  5. zai_coding_gateway-0.1.0/pypi_credentials.env.example +10 -0
  6. zai_coding_gateway-0.1.0/pyproject.toml +49 -0
  7. zai_coding_gateway-0.1.0/scripts/e2e_smoke.sh +13 -0
  8. zai_coding_gateway-0.1.0/scripts/upload_to_pypi.sh +25 -0
  9. zai_coding_gateway-0.1.0/src/zai_coding_gateway/__init__.py +3 -0
  10. zai_coding_gateway-0.1.0/src/zai_coding_gateway/client.py +93 -0
  11. zai_coding_gateway-0.1.0/src/zai_coding_gateway/errors.py +63 -0
  12. zai_coding_gateway-0.1.0/src/zai_coding_gateway/logging_utils.py +32 -0
  13. zai_coding_gateway-0.1.0/src/zai_coding_gateway/main.py +38 -0
  14. zai_coding_gateway-0.1.0/src/zai_coding_gateway/server.py +120 -0
  15. zai_coding_gateway-0.1.0/src/zai_coding_gateway/tools/__init__.py +10 -0
  16. zai_coding_gateway-0.1.0/src/zai_coding_gateway/tools/file_io.py +228 -0
  17. zai_coding_gateway-0.1.0/src/zai_coding_gateway/tools/solve_in_workspace.py +761 -0
  18. zai_coding_gateway-0.1.0/tests/conftest.py +44 -0
  19. zai_coding_gateway-0.1.0/tests/e2e_fixtures/battle_project/README.md +8 -0
  20. zai_coding_gateway-0.1.0/tests/e2e_fixtures/battle_project/src/__init__.py +1 -0
  21. zai_coding_gateway-0.1.0/tests/e2e_fixtures/battle_project/src/calc.py +9 -0
  22. zai_coding_gateway-0.1.0/tests/e2e_fixtures/battle_project/src/main.py +7 -0
  23. zai_coding_gateway-0.1.0/tests/test_client.py +93 -0
  24. zai_coding_gateway-0.1.0/tests/test_e2e.py +36 -0
  25. zai_coding_gateway-0.1.0/tests/test_e2e_battle.py +48 -0
  26. zai_coding_gateway-0.1.0/tests/test_file_io.py +128 -0
  27. zai_coding_gateway-0.1.0/tests/test_solve_in_workspace.py +168 -0
@@ -0,0 +1,60 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtualenv
24
+ .venv/
25
+ venv/
26
+ ENV/
27
+ env/
28
+
29
+ # Pytest
30
+ .pytest_cache/
31
+ .coverage
32
+ htmlcov/
33
+ .tox/
34
+ .nox/
35
+
36
+ # IDE / Editor
37
+ .idea/
38
+ .vscode/
39
+ *.swp
40
+ *.swo
41
+ *~
42
+
43
+ # Cursor
44
+ .cursor/
45
+
46
+ # OS
47
+ .DS_Store
48
+ Thumbs.db
49
+
50
+ # Env (секреты не коммитить)
51
+ .env
52
+ .env.local
53
+ .env.*.local
54
+ pypi_credentials.env
55
+
56
+ # MCP config с ключами (пример — в репо, свой — нет)
57
+ mcp.json
58
+
59
+ # Логи сессий solve_in_workspace (не коммитить)
60
+ workspace_sessions_logs/
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.4
2
+ Name: zai-coding-gateway
3
+ Version: 0.1.0
4
+ Summary: MCP-сервер для решения задач в рабочем пространстве через Z.AI Coding Plan (solve_in_workspace, apply_changes)
5
+ License: MIT
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.11
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Topic :: Software Development
11
+ Requires-Python: >=3.11
12
+ Requires-Dist: mcp>=1.0.0
13
+ Requires-Dist: openai>=1.0.0
14
+ Requires-Dist: pydantic>=2.0.0
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
17
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
18
+ Requires-Dist: respx>=0.20.0; extra == 'dev'
19
+ Description-Content-Type: text/markdown
20
+
21
+ # ZAI Coding Gateway
22
+
23
+ MCP-сервер для решения задач в рабочем пространстве через **Z.AI Coding Plan** endpoint (`api.z.ai/api/coding/paas/v4`). Предоставляет инструменты `solve_in_workspace`, `apply_changes`, `confirm_session_done` для любых MCP-клиентов (Cursor, Anigravity, Claude Code, Cline и др.). Один и тот же сервер можно добавить в любую IDE — поведение не зависит от того, откуда вы его запускаете.
24
+
25
+ ## Требования
26
+
27
+ - Python 3.11+
28
+ - Ключ Z.AI и подписка Coding Plan
29
+
30
+ ## Установка
31
+
32
+ ```bash
33
+ pip install -e .
34
+ # или с тестами:
35
+ pip install -e ".[dev]"
36
+ ```
37
+
38
+ ## Настройка
39
+
40
+ Все настройки задаются **переменными окружения**. Ключ и модель **не хранятся в коде** — их задаёт пользователь в настройках MCP-клиента (например, в Cursor в `mcp.json` в секции `env`).
41
+
42
+ | Переменная | Назначение |
43
+ |----------------|--------------------------------------------------|
44
+ | `ZAI_API_KEY` | Ключ Z.AI (обязательный) |
45
+ | `ZAI_WORK_MODEL` | Необязательно. Модель для генерации кода, рефакторинга и основного цикла сессии. По умолчанию **glm-4.7** (контекст 200K). |
46
+ | `ZAI_FAST_MODEL` | Необязательно. Модель для консолидации и суммаризации внутри сессии. По умолчанию **glm-4.5-air** (контекст 128K, быстрая). |
47
+ | `ZAI_BASE_URL` | По умолчанию `https://api.z.ai/api/coding/paas/v4` |
48
+ | `ZAI_PROJECT_ROOT` | Корень открытого проекта для solve_in_workspace (пути file_paths и сессия). **Чтобы из любой IDE всё работало «как по маслу»**, задайте эту переменную в конфиге MCP так, чтобы она указывала на папку открытого в IDE проекта. Многие IDE при старте MCP подставляют в env переменные вроде `${workspaceFolder}` / `${projectPath}` — тогда один конфиг подходит для Cursor, Anigravity и др. Без неё сервер пробует MCP `roots/list` (многие клиенты пока не поддерживают) или поиск вверх от cwd по маркерам; при неудаче — явная ошибка с просьбой задать ZAI_PROJECT_ROOT. |
49
+ | `ZAI_SESSION_LOGS_DIR` | Опционально. Каталог для логов сессий solve_in_workspace. По умолчанию логи пишутся в проект gateway (для разработчиков). Если нужно получать логи в свой проект — укажите абсолютный путь к каталогу (например `${workspaceFolder}/workspace_sessions_logs`). |
50
+
51
+ ## Работа из любой IDE
52
+
53
+ MCP запускается вашей IDE при открытии проекта. Чтобы пути file_paths/file_path и сессия solve_in_workspace относились к открытому проекту (а не к случайному cwd), в настройках MCP укажите **ZAI_PROJECT_ROOT** в `env`. Тогда не важно, в какой IDE вы работаете (Cursor, Anigravity, другая):
54
+
55
+ - Если IDE при запуске MCP подставляет в env путь к открытому проекту (например через переменную вроде `${workspaceFolder}`, `${workspaceRoot}`, `%projectPath%` и т.п.) — используйте её для `ZAI_PROJECT_ROOT`. Один и тот же конфиг будет работать во всех проектах и во всех таких IDE.
56
+ - Если такой подстановки нет — укажите в конфиге абсолютный путь к корню проекта; для другого проекта можно завести отдельный конфиг или переопределить env в настройках проекта/workspace.
57
+
58
+ Идея: **корень проекта задаёт тот, кто запускает MCP (IDE)**, через env — тогда серверу не нужно угадывать, и поведение едино во всех средах.
59
+
60
+ ## Подключение в Cursor
61
+
62
+ 1. Скопируйте пример конфигурации:
63
+ ```bash
64
+ cp mcp.json.example mcp.json
65
+ ```
66
+ 2. Откройте `mcp.json` и в секции `env` подставьте свой `ZAI_API_KEY`.
67
+ 3. Добавьте содержимое `mcp.json` в настройки MCP Cursor (например, в `~/.cursor/mcp.json` или в настройках проекта в разделе MCP Servers). **При использовании в другом проекте** укажите в `env` путь к корню этого проекта: `"ZAI_PROJECT_ROOT": "/путь/к/проекту"` (Cursor пока не передаёт workspace через MCP roots/list). Формат:
68
+ ```json
69
+ {
70
+ "mcpServers": {
71
+ "zai-coding-gateway": {
72
+ "command": "/путь/к/.venv/bin/python",
73
+ "args": ["-m", "zai_coding_gateway.main", "--stdio"],
74
+ "env": {
75
+ "ZAI_API_KEY": "ваш-ключ",
76
+ "ZAI_PROJECT_ROOT": "/путь/к/корню/проекта"
77
+ }
78
+ }
79
+ }
80
+ }
81
+ ```
82
+ 4. Убедитесь, что в PATH доступен тот же Python, в окружении которого установлен пакет (`pip install -e .`). Либо укажите полный путь к `python` в `command`.
83
+
84
+ ## Модели (work / fast)
85
+
86
+ - Для основного цикла сессии в **solve_in_workspace** (need_more/done/use_tools) используется **work-модель** (по умолчанию **glm-4.7**, контекст 200K).
87
+ - Для внутренней консолидации и суммаризации истории в **solve_in_workspace** используется **fast-модель** (по умолчанию **glm-4.5-air**, контекст 128K).
88
+ - Выбор модели в вызовах MCP недоступен; при необходимости заменить дефолты задайте **ZAI_WORK_MODEL** и/или **ZAI_FAST_MODEL** в env.
89
+
90
+ ## Запуск
91
+
92
+ Сервер запускается MCP-клиентом по stdio (Cursor сам стартует процесс по `mcp.json`). Ручной запуск для отладки:
93
+
94
+ ```bash
95
+ export ZAI_API_KEY="ваш-ключ"
96
+ python -m zai_coding_gateway.main --stdio
97
+ ```
98
+
99
+ ## Инструменты
100
+
101
+ Порядок использования: **solve_in_workspace** → **apply_changes** → при необходимости **confirm_session_done**.
102
+
103
+ ### solve_in_workspace
104
+
105
+ Точка входа: запускает сессию рабочего пространства. Модель получает полный контекст, может запрашивать файлы (need_more), использовать list_dir/grep/glob/read, вернуть отчёт и suggested_changes (в т.ч. предложения по новым файлам). Создание файлов в проекте — только через apply_changes после утверждения архитектором.
106
+
107
+ **Вход:** `instruction` (строка — формулировка задачи), `todos` (список строк), `file_paths` (список путей относительно корня проекта, обязательный начальный контекст), `max_steps` (число, по умолчанию 10).
108
+
109
+ **Выход:** `session_id` (строка), `report` (строка), `suggested_changes` (список объектов: каждый с полем `path` и полем `diff` или `content`), `files_used` (список путей).
110
+
111
+ Лимит: 5 раундов инструментов за сессию. Логи: `workspace_sessions_logs/<session_id>.log` или каталог из `ZAI_SESSION_LOGS_DIR`.
112
+
113
+ ### apply_changes
114
+
115
+ Применяет утверждённые изменения к проекту. Вызывать после solve_in_workspace, когда принято решение по suggested_changes.
116
+
117
+ **Вход:** `session_id` (из ответа solve_in_workspace), `changes` (список объектов: каждый `{path: путь к файлу, content: новое содержимое}` или `{path, diff: unified diff или полный текст}`), `confirm_after` (bool, по умолчанию True — после применения очистить сессию). Пути должны входить в сессию.
118
+
119
+ **Выход:** `session_id`, `applied` (список применённых путей), `cleaned` (bool).
120
+
121
+ ### confirm_session_done
122
+
123
+ Очищает темп-каталог сессии после принятия решения (после apply_changes или при отказе от изменений).
124
+
125
+ **Вход:** `session_id`. **Выход:** `session_id`, `cleaned` (true).
126
+
127
+ ## Режим «архитектор + разработчик»
128
+
129
+ Один поток работы: сформулируйте задачу в **instruction** и **todos**, укажите **file_paths** (начальный контекст — существующие файлы). Запустите **solve_in_workspace** — модель получит контекст, при необходимости запросит ещё файлы или использует list_dir/grep/glob/read, затем вернёт отчёт и suggested_changes (в т.ч. предложения по новым файлам). Все изменения применяются только после вашего утверждения через **apply_changes** (или откажитесь и при необходимости вызовите **confirm_session_done**). Точечное чтение и запись файлов архитектор выполняет средствами IDE.
130
+
131
+ ## Тесты
132
+
133
+ ```bash
134
+ pytest tests/ -v
135
+ ```
136
+
137
+ Тесты используют мок Z.AI API (реальный ключ не нужен). E2E-тесты с реальным API помечены маркером `e2e` и выполняются только при заданном `ZAI_API_KEY`:
138
+
139
+ ```bash
140
+ ZAI_API_KEY=ваш-ключ pytest tests/ -v -m e2e
141
+ # или
142
+ ZAI_API_KEY=ваш-ключ ./scripts/e2e_smoke.sh
143
+ ```
144
+
145
+ **Боевой E2E** (`tests/test_e2e_battle.py`): тестовый проект в `tests/e2e_fixtures/battle_project/`. Тесты проверяют доступ к файлам проекта и solve_in_workspace (структура ответа). Запуск с ключом: `ZAI_API_KEY=... pytest tests/test_e2e_battle.py -v -m e2e`.
146
+
147
+ В CI по умолчанию e2e не запускают.
148
+
149
+ ## Как проверить, что списывается Coding Plan (а не общий биллинг)
150
+
151
+ 1. **При старте MCP** в stderr выводится строка вида:
152
+ `zai-coding-gateway: endpoint = https://api.z.ai/api/coding/paas/v4 (Coding Plan)`
153
+ Если в скобках **Coding Plan** — запросы идут на подписку Coding Plan. Если **общий paas (не Coding Plan)** — используется общий endpoint (без `/coding/` в пути), и списание может идти с общего биллинга.
154
+
155
+ 2. **По умолчанию** (без `ZAI_BASE_URL`) используется `https://api.z.ai/api/coding/paas/v4` — это Coding Plan. Не задавайте `ZAI_BASE_URL`, если хотите использовать только Coding Plan.
156
+
157
+ 3. **Проверка в кабинете Z.AI** — в [использовании/биллинге](https://z.ai) посмотрите, по какому продукту идёт списание (Coding Plan vs общий API).
158
+
159
+ 4. **Кто решает, откуда списывать** — итоговая привязка к Coding Plan или к общему биллингу определяется политикой Z.AI (как правило, по вызываемому endpoint и/или по привязке API-ключа к продукту). Мы со своей стороны гарантируем только то, что запросы уходят на выбранный URL (по умолчанию Coding Plan); ключ вы задаёте в конфиге MCP. При сомнениях — уточните в документации или поддержке Z.AI.
160
+
161
+ ## Диагностика 401 (token expired or incorrect)
162
+
163
+ Если E2E или вызовы из Cursor возвращают `401 - token expired or incorrect`:
164
+
165
+ 1. **Формат ключа** — Z.AI ожидает ключ вида `{id}.{secret}` и заголовок `Authorization: Bearer <ключ>`. Сервер уже отправляет ключ в этом формате; проверьте, что в `ZAI_API_KEY` нет лишних пробелов и кавычек.
166
+ 2. **Подписка Coding Plan** — endpoint `api.z.ai/api/coding/paas/v4` доступен только при активной подписке [GLM Coding Plan](https://docs.z.ai/devpack/overview). Убедитесь, что ключ создан для аккаунта с этой подпиской.
167
+ 3. **Проверка ключа на общем endpoint** — временно задайте `ZAI_BASE_URL=https://api.z.ai/api/paas/v4` и запустите E2E. Если с общим endpoint запрос проходит, а с Coding — 401, значит ключ или аккаунт не привязаны к Coding Plan.
168
+ 4. **Перевыпуск ключа** — в [управлении API-ключами](https://z.ai/manage-apikey/apikey-list) проверьте, что ключ активен; при необходимости создайте новый и подставьте в `ZAI_API_KEY`.
169
+
170
+ ## Транспорт и развёртывание
171
+
172
+ Текущий режим — **stdio** (клиент запускает процесс). Развёртывание на удалённом сервере (StreamableHTTP) планируется в следующей итерации.
173
+
174
+ ## Лицензия
175
+
176
+ MIT.
@@ -0,0 +1,156 @@
1
+ # ZAI Coding Gateway
2
+
3
+ MCP-сервер для решения задач в рабочем пространстве через **Z.AI Coding Plan** endpoint (`api.z.ai/api/coding/paas/v4`). Предоставляет инструменты `solve_in_workspace`, `apply_changes`, `confirm_session_done` для любых MCP-клиентов (Cursor, Anigravity, Claude Code, Cline и др.). Один и тот же сервер можно добавить в любую IDE — поведение не зависит от того, откуда вы его запускаете.
4
+
5
+ ## Требования
6
+
7
+ - Python 3.11+
8
+ - Ключ Z.AI и подписка Coding Plan
9
+
10
+ ## Установка
11
+
12
+ ```bash
13
+ pip install -e .
14
+ # или с тестами:
15
+ pip install -e ".[dev]"
16
+ ```
17
+
18
+ ## Настройка
19
+
20
+ Все настройки задаются **переменными окружения**. Ключ и модель **не хранятся в коде** — их задаёт пользователь в настройках MCP-клиента (например, в Cursor в `mcp.json` в секции `env`).
21
+
22
+ | Переменная | Назначение |
23
+ |----------------|--------------------------------------------------|
24
+ | `ZAI_API_KEY` | Ключ Z.AI (обязательный) |
25
+ | `ZAI_WORK_MODEL` | Необязательно. Модель для генерации кода, рефакторинга и основного цикла сессии. По умолчанию **glm-4.7** (контекст 200K). |
26
+ | `ZAI_FAST_MODEL` | Необязательно. Модель для консолидации и суммаризации внутри сессии. По умолчанию **glm-4.5-air** (контекст 128K, быстрая). |
27
+ | `ZAI_BASE_URL` | По умолчанию `https://api.z.ai/api/coding/paas/v4` |
28
+ | `ZAI_PROJECT_ROOT` | Корень открытого проекта для solve_in_workspace (пути file_paths и сессия). **Чтобы из любой IDE всё работало «как по маслу»**, задайте эту переменную в конфиге MCP так, чтобы она указывала на папку открытого в IDE проекта. Многие IDE при старте MCP подставляют в env переменные вроде `${workspaceFolder}` / `${projectPath}` — тогда один конфиг подходит для Cursor, Anigravity и др. Без неё сервер пробует MCP `roots/list` (многие клиенты пока не поддерживают) или поиск вверх от cwd по маркерам; при неудаче — явная ошибка с просьбой задать ZAI_PROJECT_ROOT. |
29
+ | `ZAI_SESSION_LOGS_DIR` | Опционально. Каталог для логов сессий solve_in_workspace. По умолчанию логи пишутся в проект gateway (для разработчиков). Если нужно получать логи в свой проект — укажите абсолютный путь к каталогу (например `${workspaceFolder}/workspace_sessions_logs`). |
30
+
31
+ ## Работа из любой IDE
32
+
33
+ MCP запускается вашей IDE при открытии проекта. Чтобы пути file_paths/file_path и сессия solve_in_workspace относились к открытому проекту (а не к случайному cwd), в настройках MCP укажите **ZAI_PROJECT_ROOT** в `env`. Тогда не важно, в какой IDE вы работаете (Cursor, Anigravity, другая):
34
+
35
+ - Если IDE при запуске MCP подставляет в env путь к открытому проекту (например через переменную вроде `${workspaceFolder}`, `${workspaceRoot}`, `%projectPath%` и т.п.) — используйте её для `ZAI_PROJECT_ROOT`. Один и тот же конфиг будет работать во всех проектах и во всех таких IDE.
36
+ - Если такой подстановки нет — укажите в конфиге абсолютный путь к корню проекта; для другого проекта можно завести отдельный конфиг или переопределить env в настройках проекта/workspace.
37
+
38
+ Идея: **корень проекта задаёт тот, кто запускает MCP (IDE)**, через env — тогда серверу не нужно угадывать, и поведение едино во всех средах.
39
+
40
+ ## Подключение в Cursor
41
+
42
+ 1. Скопируйте пример конфигурации:
43
+ ```bash
44
+ cp mcp.json.example mcp.json
45
+ ```
46
+ 2. Откройте `mcp.json` и в секции `env` подставьте свой `ZAI_API_KEY`.
47
+ 3. Добавьте содержимое `mcp.json` в настройки MCP Cursor (например, в `~/.cursor/mcp.json` или в настройках проекта в разделе MCP Servers). **При использовании в другом проекте** укажите в `env` путь к корню этого проекта: `"ZAI_PROJECT_ROOT": "/путь/к/проекту"` (Cursor пока не передаёт workspace через MCP roots/list). Формат:
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "zai-coding-gateway": {
52
+ "command": "/путь/к/.venv/bin/python",
53
+ "args": ["-m", "zai_coding_gateway.main", "--stdio"],
54
+ "env": {
55
+ "ZAI_API_KEY": "ваш-ключ",
56
+ "ZAI_PROJECT_ROOT": "/путь/к/корню/проекта"
57
+ }
58
+ }
59
+ }
60
+ }
61
+ ```
62
+ 4. Убедитесь, что в PATH доступен тот же Python, в окружении которого установлен пакет (`pip install -e .`). Либо укажите полный путь к `python` в `command`.
63
+
64
+ ## Модели (work / fast)
65
+
66
+ - Для основного цикла сессии в **solve_in_workspace** (need_more/done/use_tools) используется **work-модель** (по умолчанию **glm-4.7**, контекст 200K).
67
+ - Для внутренней консолидации и суммаризации истории в **solve_in_workspace** используется **fast-модель** (по умолчанию **glm-4.5-air**, контекст 128K).
68
+ - Выбор модели в вызовах MCP недоступен; при необходимости заменить дефолты задайте **ZAI_WORK_MODEL** и/или **ZAI_FAST_MODEL** в env.
69
+
70
+ ## Запуск
71
+
72
+ Сервер запускается MCP-клиентом по stdio (Cursor сам стартует процесс по `mcp.json`). Ручной запуск для отладки:
73
+
74
+ ```bash
75
+ export ZAI_API_KEY="ваш-ключ"
76
+ python -m zai_coding_gateway.main --stdio
77
+ ```
78
+
79
+ ## Инструменты
80
+
81
+ Порядок использования: **solve_in_workspace** → **apply_changes** → при необходимости **confirm_session_done**.
82
+
83
+ ### solve_in_workspace
84
+
85
+ Точка входа: запускает сессию рабочего пространства. Модель получает полный контекст, может запрашивать файлы (need_more), использовать list_dir/grep/glob/read, вернуть отчёт и suggested_changes (в т.ч. предложения по новым файлам). Создание файлов в проекте — только через apply_changes после утверждения архитектором.
86
+
87
+ **Вход:** `instruction` (строка — формулировка задачи), `todos` (список строк), `file_paths` (список путей относительно корня проекта, обязательный начальный контекст), `max_steps` (число, по умолчанию 10).
88
+
89
+ **Выход:** `session_id` (строка), `report` (строка), `suggested_changes` (список объектов: каждый с полем `path` и полем `diff` или `content`), `files_used` (список путей).
90
+
91
+ Лимит: 5 раундов инструментов за сессию. Логи: `workspace_sessions_logs/<session_id>.log` или каталог из `ZAI_SESSION_LOGS_DIR`.
92
+
93
+ ### apply_changes
94
+
95
+ Применяет утверждённые изменения к проекту. Вызывать после solve_in_workspace, когда принято решение по suggested_changes.
96
+
97
+ **Вход:** `session_id` (из ответа solve_in_workspace), `changes` (список объектов: каждый `{path: путь к файлу, content: новое содержимое}` или `{path, diff: unified diff или полный текст}`), `confirm_after` (bool, по умолчанию True — после применения очистить сессию). Пути должны входить в сессию.
98
+
99
+ **Выход:** `session_id`, `applied` (список применённых путей), `cleaned` (bool).
100
+
101
+ ### confirm_session_done
102
+
103
+ Очищает темп-каталог сессии после принятия решения (после apply_changes или при отказе от изменений).
104
+
105
+ **Вход:** `session_id`. **Выход:** `session_id`, `cleaned` (true).
106
+
107
+ ## Режим «архитектор + разработчик»
108
+
109
+ Один поток работы: сформулируйте задачу в **instruction** и **todos**, укажите **file_paths** (начальный контекст — существующие файлы). Запустите **solve_in_workspace** — модель получит контекст, при необходимости запросит ещё файлы или использует list_dir/grep/glob/read, затем вернёт отчёт и suggested_changes (в т.ч. предложения по новым файлам). Все изменения применяются только после вашего утверждения через **apply_changes** (или откажитесь и при необходимости вызовите **confirm_session_done**). Точечное чтение и запись файлов архитектор выполняет средствами IDE.
110
+
111
+ ## Тесты
112
+
113
+ ```bash
114
+ pytest tests/ -v
115
+ ```
116
+
117
+ Тесты используют мок Z.AI API (реальный ключ не нужен). E2E-тесты с реальным API помечены маркером `e2e` и выполняются только при заданном `ZAI_API_KEY`:
118
+
119
+ ```bash
120
+ ZAI_API_KEY=ваш-ключ pytest tests/ -v -m e2e
121
+ # или
122
+ ZAI_API_KEY=ваш-ключ ./scripts/e2e_smoke.sh
123
+ ```
124
+
125
+ **Боевой E2E** (`tests/test_e2e_battle.py`): тестовый проект в `tests/e2e_fixtures/battle_project/`. Тесты проверяют доступ к файлам проекта и solve_in_workspace (структура ответа). Запуск с ключом: `ZAI_API_KEY=... pytest tests/test_e2e_battle.py -v -m e2e`.
126
+
127
+ В CI по умолчанию e2e не запускают.
128
+
129
+ ## Как проверить, что списывается Coding Plan (а не общий биллинг)
130
+
131
+ 1. **При старте MCP** в stderr выводится строка вида:
132
+ `zai-coding-gateway: endpoint = https://api.z.ai/api/coding/paas/v4 (Coding Plan)`
133
+ Если в скобках **Coding Plan** — запросы идут на подписку Coding Plan. Если **общий paas (не Coding Plan)** — используется общий endpoint (без `/coding/` в пути), и списание может идти с общего биллинга.
134
+
135
+ 2. **По умолчанию** (без `ZAI_BASE_URL`) используется `https://api.z.ai/api/coding/paas/v4` — это Coding Plan. Не задавайте `ZAI_BASE_URL`, если хотите использовать только Coding Plan.
136
+
137
+ 3. **Проверка в кабинете Z.AI** — в [использовании/биллинге](https://z.ai) посмотрите, по какому продукту идёт списание (Coding Plan vs общий API).
138
+
139
+ 4. **Кто решает, откуда списывать** — итоговая привязка к Coding Plan или к общему биллингу определяется политикой Z.AI (как правило, по вызываемому endpoint и/или по привязке API-ключа к продукту). Мы со своей стороны гарантируем только то, что запросы уходят на выбранный URL (по умолчанию Coding Plan); ключ вы задаёте в конфиге MCP. При сомнениях — уточните в документации или поддержке Z.AI.
140
+
141
+ ## Диагностика 401 (token expired or incorrect)
142
+
143
+ Если E2E или вызовы из Cursor возвращают `401 - token expired or incorrect`:
144
+
145
+ 1. **Формат ключа** — Z.AI ожидает ключ вида `{id}.{secret}` и заголовок `Authorization: Bearer <ключ>`. Сервер уже отправляет ключ в этом формате; проверьте, что в `ZAI_API_KEY` нет лишних пробелов и кавычек.
146
+ 2. **Подписка Coding Plan** — endpoint `api.z.ai/api/coding/paas/v4` доступен только при активной подписке [GLM Coding Plan](https://docs.z.ai/devpack/overview). Убедитесь, что ключ создан для аккаунта с этой подпиской.
147
+ 3. **Проверка ключа на общем endpoint** — временно задайте `ZAI_BASE_URL=https://api.z.ai/api/paas/v4` и запустите E2E. Если с общим endpoint запрос проходит, а с Coding — 401, значит ключ или аккаунт не привязаны к Coding Plan.
148
+ 4. **Перевыпуск ключа** — в [управлении API-ключами](https://z.ai/manage-apikey/apikey-list) проверьте, что ключ активен; при необходимости создайте новый и подставьте в `ZAI_API_KEY`.
149
+
150
+ ## Транспорт и развёртывание
151
+
152
+ Текущий режим — **stdio** (клиент запускает процесс). Развёртывание на удалённом сервере (StreamableHTTP) планируется в следующей итерации.
153
+
154
+ ## Лицензия
155
+
156
+ MIT.
@@ -0,0 +1,12 @@
1
+ {
2
+ "mcpServers": {
3
+ "zai-coding-gateway": {
4
+ "command": "python",
5
+ "args": ["-m", "zai_coding_gateway.main", "--stdio"],
6
+ "env": {
7
+ "ZAI_API_KEY": "<подставьте свой ключ Z.AI>",
8
+ "ZAI_PROJECT_ROOT": "<рекомендуется: путь к корню открытого проекта; во многих IDE можно использовать переменную типа ${workspaceFolder}>"
9
+ }
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,10 @@
1
+ # Учётные данные для загрузки пакета на PyPI.
2
+ # PyPI больше НЕ принимает пароль от аккаунта — только API token!
3
+ # 1. Создайте токен: https://pypi.org/manage/account/token/ → Add API token
4
+ # 2. Скопируйте этот файл: cp pypi_credentials.env.example pypi_credentials.env
5
+ # 3. Вставьте токен в TWINE_PASSWORD (целиком, вид pypi-AgEIcHlwaS5vcmc...)
6
+ # Логин всегда __token__ (два подчёркивания с обеих сторон), не ваш username.
7
+ # Файл pypi_credentials.env в .gitignore, в репозиторий не попадёт.
8
+
9
+ TWINE_USERNAME=__token__
10
+ TWINE_PASSWORD=pypi-ВСТАВЬТЕ_СЮДА_ВАШ_API_ТОКЕН_С_PYPI_ORG
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "zai-coding-gateway"
7
+ version = "0.1.0"
8
+ description = "MCP-сервер для решения задач в рабочем пространстве через Z.AI Coding Plan (solve_in_workspace, apply_changes)"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = { text = "MIT" }
12
+ authors = []
13
+ classifiers = [
14
+ "License :: OSI Approved :: MIT License",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Topic :: Software Development",
19
+ ]
20
+ dependencies = [
21
+ "mcp>=1.0.0",
22
+ "openai>=1.0.0",
23
+ "pydantic>=2.0.0",
24
+ ]
25
+
26
+ [project.optional-dependencies]
27
+ dev = [
28
+ "pytest>=7.0.0",
29
+ "pytest-asyncio>=0.21.0",
30
+ "respx>=0.20.0",
31
+ ]
32
+
33
+ [project.scripts]
34
+ zai-coding-gateway = "zai_coding_gateway.main:main"
35
+
36
+ [tool.hatch.build.targets.wheel]
37
+ packages = ["src/zai_coding_gateway"]
38
+
39
+ [tool.pytest.ini_options]
40
+ asyncio_mode = "auto"
41
+ testpaths = ["tests"]
42
+ pythonpath = ["src"]
43
+ markers = [
44
+ "e2e: end-to-end test with real Z.AI API (run only when ZAI_API_KEY is set)",
45
+ ]
46
+
47
+ [tool.mypy]
48
+ python_version = "3.11"
49
+ strict = true
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env bash
2
+ # E2E smoke: запуск e2e-тестов только при заданном ZAI_API_KEY.
3
+ # В CI по умолчанию не запускать (нет ключа).
4
+
5
+ set -e
6
+ cd "$(dirname "$0")/.."
7
+
8
+ if [ -z "${ZAI_API_KEY}" ]; then
9
+ echo "ZAI_API_KEY не задан — e2e пропущены."
10
+ exit 0
11
+ fi
12
+
13
+ .venv/bin/python -m pytest tests/ -v -m e2e 2>&1
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env bash
2
+ # Сборка пакета и загрузка на PyPI.
3
+ # Нужен файл pypi_credentials.env с TWINE_USERNAME=__token__ и TWINE_PASSWORD=<API token с pypi.org>.
4
+ set -e
5
+ cd "$(dirname "$0")/.."
6
+ if [ ! -f pypi_credentials.env ]; then
7
+ echo "Создайте pypi_credentials.env (см. pypi_credentials.env.example)."
8
+ exit 1
9
+ fi
10
+ set -a
11
+ source pypi_credentials.env
12
+ set +a
13
+ if [ -z "$TWINE_PASSWORD" ]; then
14
+ echo "В pypi_credentials.env задайте TWINE_PASSWORD (API token с https://pypi.org/manage/account/token/)."
15
+ exit 1
16
+ fi
17
+ if [ "$TWINE_USERNAME" != "__token__" ]; then
18
+ echo "Для API token TWINE_USERNAME должен быть __token__. Сейчас: $TWINE_USERNAME"
19
+ exit 1
20
+ fi
21
+ echo "Сборка пакета..."
22
+ python -m build
23
+ echo "Загрузка на PyPI..."
24
+ twine upload dist/*
25
+ echo "Готово."
@@ -0,0 +1,3 @@
1
+ """ZAI Coding Gateway — MCP-сервер для решения задач в рабочем пространстве через Z.AI Coding Plan."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,93 @@
1
+ """Обёртка над OpenAI-клиентом для Z.AI Coding Plan endpoint."""
2
+
3
+ import os
4
+ from typing import Any
5
+
6
+ from openai import OpenAI
7
+
8
+ from zai_coding_gateway.errors import QuotaExceededError
9
+
10
+
11
+ def _is_quota_error(exc: Exception) -> bool:
12
+ """Определяет по исключению, что это исчерпание квоты (429 / quota / rate limit)."""
13
+ status = getattr(exc, "status_code", None)
14
+ if status == 429:
15
+ return True
16
+ msg = str(exc).lower()
17
+ if "429" in msg or "quota" in msg or "rate limit" in msg or "rate_limit" in msg:
18
+ return True
19
+ return False
20
+
21
+ DEFAULT_BASE_URL = "https://api.z.ai/api/coding/paas/v4"
22
+ DEFAULT_WORK_MODEL = "glm-4.7"
23
+ DEFAULT_FAST_MODEL = "glm-4.5-air"
24
+
25
+
26
+ def _get_env(key: str, default: str | None = None) -> str | None:
27
+ return os.environ.get(key) or default
28
+
29
+
30
+ def get_effective_base_url() -> str:
31
+ """Возвращает URL, который будет использоваться для запросов (из env или дефолт). Для проверки биллинга."""
32
+ return (_get_env("ZAI_BASE_URL") or DEFAULT_BASE_URL).rstrip("/") + "/"
33
+
34
+
35
+ def get_work_model() -> str:
36
+ """Модель для основного цикла сессии solve_in_workspace. По умолчанию glm-4.7."""
37
+ v = (_get_env("ZAI_WORK_MODEL") or "").strip()
38
+ return v or DEFAULT_WORK_MODEL
39
+
40
+
41
+ def get_fast_model() -> str:
42
+ """Модель для консолидации и суммаризации в solve_in_workspace. По умолчанию glm-4.5-air."""
43
+ v = (_get_env("ZAI_FAST_MODEL") or "").strip()
44
+ return v or DEFAULT_FAST_MODEL
45
+
46
+
47
+ def create_client() -> OpenAI:
48
+ """Создаёт OpenAI-клиент с base_url и api_key из ENV. Ключ только из окружения."""
49
+ api_key = _get_env("ZAI_API_KEY")
50
+ if not api_key:
51
+ raise ValueError("ZAI_API_KEY не задан. Укажите ключ в env (например в mcp.json).")
52
+ base_url = get_effective_base_url()
53
+ return OpenAI(api_key=api_key, base_url=base_url)
54
+
55
+
56
+ def chat_completions(
57
+ client: OpenAI,
58
+ messages: list[dict[str, Any]],
59
+ model: str,
60
+ response_format: dict[str, Any] | None = None,
61
+ ) -> dict[str, Any]:
62
+ """
63
+ Вызов chat/completions. При исчерпании квоты выбрасывает QuotaExceededError.
64
+ model: передавать get_work_model() или get_fast_model() в зависимости от сценария.
65
+ """
66
+ kwargs: dict[str, Any] = {"model": model, "messages": messages, "stream": False}
67
+ if response_format is not None:
68
+ kwargs["response_format"] = response_format
69
+ try:
70
+ resp = client.chat.completions.create(**kwargs)
71
+ except QuotaExceededError:
72
+ raise
73
+ except Exception as e: # noqa: BLE001
74
+ if _is_quota_error(e):
75
+ raise QuotaExceededError("QuotaExceeded: wait for refresh cycle") from e
76
+ raise
77
+ return _completion_to_dict(resp)
78
+
79
+
80
+ def _completion_to_dict(resp: Any) -> dict[str, Any]:
81
+ """Преобразует объект Completion в dict для единообразного разбора."""
82
+ return {
83
+ "choices": [
84
+ {
85
+ "message": {
86
+ "content": c.message.content,
87
+ "role": getattr(c.message, "role", "assistant"),
88
+ }
89
+ }
90
+ for c in (resp.choices or [])
91
+ ],
92
+ "usage": getattr(resp, "usage", None),
93
+ }