jwadow commited on
Commit ·
f1668d0
1
Parent(s): 9a5fa46
refactor: update kiro_gateway naming to kiro
Browse files- docs/en/ARCHITECTURE.md +18 -18
- docs/ru/ARCHITECTURE.md +18 -18
- kiro/__init__.py +11 -11
- kiro/auth.py +2 -2
- kiro/cache.py +1 -1
- kiro/converters.py +3 -3
- kiro/debug_logger.py +1 -1
- kiro/http_client.py +3 -3
- kiro/parsers.py +1 -1
- kiro/routes.py +9 -9
- kiro/streaming.py +8 -8
- kiro/thinking_parser.py +1 -1
- kiro/utils.py +1 -1
- main.py +5 -5
- tests/README.md +1 -1
- tests/conftest.py +10 -10
- tests/integration/test_full_flow.py +2 -2
- tests/unit/test_auth_manager.py +22 -22
- tests/unit/test_cache.py +2 -2
- tests/unit/test_config.py +26 -26
- tests/unit/test_converters.py +48 -48
- tests/unit/test_debug_logger.py +55 -55
- tests/unit/test_http_client.py +41 -41
- tests/unit/test_main_cli.py +1 -1
- tests/unit/test_parsers.py +1 -1
- tests/unit/test_routes.py +2 -2
- tests/unit/test_streaming.py +51 -51
- tests/unit/test_thinking_parser.py +12 -12
- tests/unit/test_tokenizer.py +6 -6
docs/en/ARCHITECTURE.md
CHANGED
|
@@ -53,7 +53,7 @@ The system acts as a "translator", allowing the use of any tools, libraries, and
|
|
| 53 |
|
| 54 |
## 2. Project Structure
|
| 55 |
|
| 56 |
-
The project is organized as a modular Python package `
|
| 57 |
|
| 58 |
```
|
| 59 |
kiro-gateway/
|
|
@@ -61,7 +61,7 @@ kiro-gateway/
|
|
| 61 |
├── requirements.txt # Python dependencies
|
| 62 |
├── .env.example # Environment configuration example
|
| 63 |
│
|
| 64 |
-
├──
|
| 65 |
│ ├── __init__.py # Package exports, version
|
| 66 |
│ │
|
| 67 |
│ │ # ═══════════════════════════════════════════════════════
|
|
@@ -140,7 +140,7 @@ The `main.py` file is responsible for:
|
|
| 140 |
4. **Error handler registration** — `validation_exception_handler` for 422 errors
|
| 141 |
5. **Route connection** — `app.include_router(router)`
|
| 142 |
|
| 143 |
-
### 3.2. Configuration Module (`
|
| 144 |
|
| 145 |
Centralized storage of all settings:
|
| 146 |
|
|
@@ -167,7 +167,7 @@ Centralized storage of all settings:
|
|
| 167 |
- `get_kiro_q_host(region)` — Q API host
|
| 168 |
- `get_internal_model_id(external_model)` — model name conversion
|
| 169 |
|
| 170 |
-
### 3.3. Pydantic Models (`
|
| 171 |
|
| 172 |
#### Models for `/v1/models`
|
| 173 |
|
|
@@ -198,7 +198,7 @@ Centralized storage of all settings:
|
|
| 198 |
|
| 199 |
### 3.4. State Management Layer
|
| 200 |
|
| 201 |
-
#### KiroAuthManager (`
|
| 202 |
|
| 203 |
**Role:** Stateful singleton encapsulating Kiro token management logic.
|
| 204 |
|
|
@@ -234,7 +234,7 @@ auth_manager = KiroAuthManager(
|
|
| 234 |
token = await auth_manager.get_access_token()
|
| 235 |
```
|
| 236 |
|
| 237 |
-
#### ModelInfoCache (`
|
| 238 |
|
| 239 |
**Role:** Thread-safe storage for model configurations.
|
| 240 |
|
|
@@ -250,7 +250,7 @@ token = await auth_manager.get_access_token()
|
|
| 250 |
- `is_empty()` / `is_stale()` — cache state check
|
| 251 |
- `get_all_model_ids()` — list of all model IDs
|
| 252 |
|
| 253 |
-
### 3.5. Helper Utilities (`
|
| 254 |
|
| 255 |
| Function | Description |
|
| 256 |
|----------|-------------|
|
|
@@ -260,7 +260,7 @@ token = await auth_manager.get_access_token()
|
|
| 260 |
| `generate_conversation_id()` | UUID for conversation |
|
| 261 |
| `generate_tool_call_id()` | ID in format `call_{uuid_hex[:8]}` |
|
| 262 |
|
| 263 |
-
### 3.6. Conversion Layer (`
|
| 264 |
|
| 265 |
#### Message Conversion
|
| 266 |
|
|
@@ -310,7 +310,7 @@ External model names are converted to internal Kiro IDs:
|
|
| 310 |
| `claude-3-7-sonnet-20250219` | `CLAUDE_3_7_SONNET_20250219_V1_0` |
|
| 311 |
| `auto` | `claude-sonnet-4.5` (alias) |
|
| 312 |
|
| 313 |
-
### 3.7. Parsing Layer (`
|
| 314 |
|
| 315 |
#### AwsEventStreamParser
|
| 316 |
|
|
@@ -340,7 +340,7 @@ Advanced AWS SSE format parser with support for:
|
|
| 340 |
| `parse_bracket_tool_calls(response_text)` | Parse `[Called func with args: {...}]` |
|
| 341 |
| `deduplicate_tool_calls(tool_calls)` | Remove duplicate tool calls |
|
| 342 |
|
| 343 |
-
### 3.8. Streaming (`
|
| 344 |
|
| 345 |
#### stream_kiro_to_openai
|
| 346 |
|
|
@@ -357,7 +357,7 @@ Async generator for transforming Kiro stream to OpenAI format.
|
|
| 357 |
|
| 358 |
Collects full response from streaming for non-streaming mode.
|
| 359 |
|
| 360 |
-
### 3.9. HTTP Client (`
|
| 361 |
|
| 362 |
#### KiroHttpClient
|
| 363 |
|
|
@@ -378,7 +378,7 @@ Automatic error handling with exponential backoff:
|
|
| 378 |
|
| 379 |
Supports async context manager (`async with`).
|
| 380 |
|
| 381 |
-
### 3.10. Routes (`
|
| 382 |
|
| 383 |
| Endpoint | Method | Description |
|
| 384 |
|----------|--------|-------------|
|
|
@@ -389,14 +389,14 @@ Supports async context manager (`async with`).
|
|
| 389 |
|
| 390 |
**Authentication:** Bearer token in `Authorization` header
|
| 391 |
|
| 392 |
-
### 3.11. Exception Handling (`
|
| 393 |
|
| 394 |
| Function | Description |
|
| 395 |
|----------|-------------|
|
| 396 |
| `sanitize_validation_errors(errors)` | Convert bytes to strings for JSON serialization |
|
| 397 |
| `validation_exception_handler(request, exc)` | Pydantic validation error handler (422) |
|
| 398 |
|
| 399 |
-
### 3.12. Debug Logging (`
|
| 400 |
|
| 401 |
**Class:** `DebugLogger` (singleton)
|
| 402 |
|
|
@@ -417,7 +417,7 @@ Supports async context manager (`async with`).
|
|
| 417 |
- `response_stream_raw.txt` — raw stream from Kiro
|
| 418 |
- `response_stream_modified.txt` — transformed stream (OpenAI format)
|
| 419 |
|
| 420 |
-
### 3.13. Tokenizer (`
|
| 421 |
|
| 422 |
**Problem:** Kiro API does not return token counts directly. Instead, the API only provides `context_usage_percentage` — the percentage of model context usage.
|
| 423 |
|
|
@@ -749,7 +749,7 @@ The modular architecture allows easy addition of support for other API formats.
|
|
| 749 |
|
| 750 |
2. **Create conversion adapter** — `converters_gemini.py`
|
| 751 |
```python
|
| 752 |
-
from
|
| 753 |
|
| 754 |
def gemini_to_kiro(request: GeminiRequest, ...) -> dict:
|
| 755 |
"""Converts Gemini request to Kiro payload."""
|
|
@@ -769,7 +769,7 @@ The modular architecture allows easy addition of support for other API formats.
|
|
| 769 |
|
| 770 |
3. **Create streaming formatter** — `streaming_gemini.py`
|
| 771 |
```python
|
| 772 |
-
from
|
| 773 |
|
| 774 |
async def stream_to_gemini(response, ...) -> AsyncGenerator[str, None]:
|
| 775 |
"""Formats Kiro events to Gemini SSE."""
|
|
@@ -788,7 +788,7 @@ The modular architecture allows easy addition of support for other API formats.
|
|
| 788 |
|
| 789 |
5. **Connect in main.py**
|
| 790 |
```python
|
| 791 |
-
from
|
| 792 |
app.include_router(gemini_router)
|
| 793 |
```
|
| 794 |
|
|
|
|
| 53 |
|
| 54 |
## 2. Project Structure
|
| 55 |
|
| 56 |
+
The project is organized as a modular Python package `kiro/`:
|
| 57 |
|
| 58 |
```
|
| 59 |
kiro-gateway/
|
|
|
|
| 61 |
├── requirements.txt # Python dependencies
|
| 62 |
├── .env.example # Environment configuration example
|
| 63 |
│
|
| 64 |
+
├── kiro/ # Main package
|
| 65 |
│ ├── __init__.py # Package exports, version
|
| 66 |
│ │
|
| 67 |
│ │ # ═══════════════════════════════════════════════════════
|
|
|
|
| 140 |
4. **Error handler registration** — `validation_exception_handler` for 422 errors
|
| 141 |
5. **Route connection** — `app.include_router(router)`
|
| 142 |
|
| 143 |
+
### 3.2. Configuration Module (`kiro/config.py`)
|
| 144 |
|
| 145 |
Centralized storage of all settings:
|
| 146 |
|
|
|
|
| 167 |
- `get_kiro_q_host(region)` — Q API host
|
| 168 |
- `get_internal_model_id(external_model)` — model name conversion
|
| 169 |
|
| 170 |
+
### 3.3. Pydantic Models (`kiro/models.py`)
|
| 171 |
|
| 172 |
#### Models for `/v1/models`
|
| 173 |
|
|
|
|
| 198 |
|
| 199 |
### 3.4. State Management Layer
|
| 200 |
|
| 201 |
+
#### KiroAuthManager (`kiro/auth.py`)
|
| 202 |
|
| 203 |
**Role:** Stateful singleton encapsulating Kiro token management logic.
|
| 204 |
|
|
|
|
| 234 |
token = await auth_manager.get_access_token()
|
| 235 |
```
|
| 236 |
|
| 237 |
+
#### ModelInfoCache (`kiro/cache.py`)
|
| 238 |
|
| 239 |
**Role:** Thread-safe storage for model configurations.
|
| 240 |
|
|
|
|
| 250 |
- `is_empty()` / `is_stale()` — cache state check
|
| 251 |
- `get_all_model_ids()` — list of all model IDs
|
| 252 |
|
| 253 |
+
### 3.5. Helper Utilities (`kiro/utils.py`)
|
| 254 |
|
| 255 |
| Function | Description |
|
| 256 |
|----------|-------------|
|
|
|
|
| 260 |
| `generate_conversation_id()` | UUID for conversation |
|
| 261 |
| `generate_tool_call_id()` | ID in format `call_{uuid_hex[:8]}` |
|
| 262 |
|
| 263 |
+
### 3.6. Conversion Layer (`kiro/converters.py`)
|
| 264 |
|
| 265 |
#### Message Conversion
|
| 266 |
|
|
|
|
| 310 |
| `claude-3-7-sonnet-20250219` | `CLAUDE_3_7_SONNET_20250219_V1_0` |
|
| 311 |
| `auto` | `claude-sonnet-4.5` (alias) |
|
| 312 |
|
| 313 |
+
### 3.7. Parsing Layer (`kiro/parsers.py`)
|
| 314 |
|
| 315 |
#### AwsEventStreamParser
|
| 316 |
|
|
|
|
| 340 |
| `parse_bracket_tool_calls(response_text)` | Parse `[Called func with args: {...}]` |
|
| 341 |
| `deduplicate_tool_calls(tool_calls)` | Remove duplicate tool calls |
|
| 342 |
|
| 343 |
+
### 3.8. Streaming (`kiro/streaming.py`)
|
| 344 |
|
| 345 |
#### stream_kiro_to_openai
|
| 346 |
|
|
|
|
| 357 |
|
| 358 |
Collects full response from streaming for non-streaming mode.
|
| 359 |
|
| 360 |
+
### 3.9. HTTP Client (`kiro/http_client.py`)
|
| 361 |
|
| 362 |
#### KiroHttpClient
|
| 363 |
|
|
|
|
| 378 |
|
| 379 |
Supports async context manager (`async with`).
|
| 380 |
|
| 381 |
+
### 3.10. Routes (`kiro/routes.py`)
|
| 382 |
|
| 383 |
| Endpoint | Method | Description |
|
| 384 |
|----------|--------|-------------|
|
|
|
|
| 389 |
|
| 390 |
**Authentication:** Bearer token in `Authorization` header
|
| 391 |
|
| 392 |
+
### 3.11. Exception Handling (`kiro/exceptions.py`)
|
| 393 |
|
| 394 |
| Function | Description |
|
| 395 |
|----------|-------------|
|
| 396 |
| `sanitize_validation_errors(errors)` | Convert bytes to strings for JSON serialization |
|
| 397 |
| `validation_exception_handler(request, exc)` | Pydantic validation error handler (422) |
|
| 398 |
|
| 399 |
+
### 3.12. Debug Logging (`kiro/debug_logger.py`)
|
| 400 |
|
| 401 |
**Class:** `DebugLogger` (singleton)
|
| 402 |
|
|
|
|
| 417 |
- `response_stream_raw.txt` — raw stream from Kiro
|
| 418 |
- `response_stream_modified.txt` — transformed stream (OpenAI format)
|
| 419 |
|
| 420 |
+
### 3.13. Tokenizer (`kiro/tokenizer.py`)
|
| 421 |
|
| 422 |
**Problem:** Kiro API does not return token counts directly. Instead, the API only provides `context_usage_percentage` — the percentage of model context usage.
|
| 423 |
|
|
|
|
| 749 |
|
| 750 |
2. **Create conversion adapter** — `converters_gemini.py`
|
| 751 |
```python
|
| 752 |
+
from kiro.converters_core import build_kiro_payload
|
| 753 |
|
| 754 |
def gemini_to_kiro(request: GeminiRequest, ...) -> dict:
|
| 755 |
"""Converts Gemini request to Kiro payload."""
|
|
|
|
| 769 |
|
| 770 |
3. **Create streaming formatter** — `streaming_gemini.py`
|
| 771 |
```python
|
| 772 |
+
from kiro.streaming_core import parse_kiro_stream
|
| 773 |
|
| 774 |
async def stream_to_gemini(response, ...) -> AsyncGenerator[str, None]:
|
| 775 |
"""Formats Kiro events to Gemini SSE."""
|
|
|
|
| 788 |
|
| 789 |
5. **Connect in main.py**
|
| 790 |
```python
|
| 791 |
+
from kiro.routes_gemini import router as gemini_router
|
| 792 |
app.include_router(gemini_router)
|
| 793 |
```
|
| 794 |
|
docs/ru/ARCHITECTURE.md
CHANGED
|
@@ -53,7 +53,7 @@
|
|
| 53 |
|
| 54 |
## 2. Структура Проекта
|
| 55 |
|
| 56 |
-
Проект организован в виде модульного Python-пакета `
|
| 57 |
|
| 58 |
```
|
| 59 |
kiro-gateway/
|
|
@@ -61,7 +61,7 @@ kiro-gateway/
|
|
| 61 |
├── requirements.txt # Зависимости Python
|
| 62 |
├── .env.example # Пример конфигурации окружения
|
| 63 |
│
|
| 64 |
-
├──
|
| 65 |
│ ├── __init__.py # Экспорты пакета, версия
|
| 66 |
│ │
|
| 67 |
│ │ # ═══════════════════════════════════════════════════════
|
|
@@ -140,7 +140,7 @@ kiro-gateway/
|
|
| 140 |
4. **Регистрация обработчиков ошибок** — `validation_exception_handler` для ошибок 422
|
| 141 |
5. **Подключение роутов** — `app.include_router(router)`
|
| 142 |
|
| 143 |
-
### 3.2. Модуль конфигурации (`
|
| 144 |
|
| 145 |
Централизованное хранение всех настроек:
|
| 146 |
|
|
@@ -167,7 +167,7 @@ kiro-gateway/
|
|
| 167 |
- `get_kiro_q_host(region)` — хост Q API
|
| 168 |
- `get_internal_model_id(external_model)` — конвертация имени модели
|
| 169 |
|
| 170 |
-
### 3.3. Pydantic Модели (`
|
| 171 |
|
| 172 |
#### Модели для `/v1/models`
|
| 173 |
|
|
@@ -198,7 +198,7 @@ kiro-gateway/
|
|
| 198 |
|
| 199 |
### 3.4. Управление Состоянием (State Management Layer)
|
| 200 |
|
| 201 |
-
#### KiroAuthManager (`
|
| 202 |
|
| 203 |
**Роль:** Stateful-синглтон, инкапсулирующий логику управления токенами Kiro.
|
| 204 |
|
|
@@ -234,7 +234,7 @@ auth_manager = KiroAuthManager(
|
|
| 234 |
token = await auth_manager.get_access_token()
|
| 235 |
```
|
| 236 |
|
| 237 |
-
#### ModelInfoCache (`
|
| 238 |
|
| 239 |
**Роль:** Потокобезопасное хранилище конфигураций моделей.
|
| 240 |
|
|
@@ -250,7 +250,7 @@ token = await auth_manager.get_access_token()
|
|
| 250 |
- `is_empty()` / `is_stale()` — проверка состояния кэша
|
| 251 |
- `get_all_model_ids()` — список всех ID моделей
|
| 252 |
|
| 253 |
-
### 3.5. Вспомогательные Утилиты (`
|
| 254 |
|
| 255 |
| Функция | Описание |
|
| 256 |
|---------|----------|
|
|
@@ -260,7 +260,7 @@ token = await auth_manager.get_access_token()
|
|
| 260 |
| `generate_conversation_id()` | UUID для разговора |
|
| 261 |
| `generate_tool_call_id()` | ID в формате `call_{uuid_hex[:8]}` |
|
| 262 |
|
| 263 |
-
### 3.6. Слой Конвертации (`
|
| 264 |
|
| 265 |
#### Конвертация сообщений
|
| 266 |
|
|
@@ -310,7 +310,7 @@ OpenAI messages преобразуются в Kiro conversationState:
|
|
| 310 |
| `claude-3-7-sonnet-20250219` | `CLAUDE_3_7_SONNET_20250219_V1_0` |
|
| 311 |
| `auto` | `claude-sonnet-4.5` (алиас) |
|
| 312 |
|
| 313 |
-
### 3.7. Слой Парсинга (`
|
| 314 |
|
| 315 |
#### AwsEventStreamParser
|
| 316 |
|
|
@@ -340,7 +340,7 @@ OpenAI messages преобразуются в Kiro conversationState:
|
|
| 340 |
| `parse_bracket_tool_calls(response_text)` | Парсинг `[Called func with args: {...}]` |
|
| 341 |
| `deduplicate_tool_calls(tool_calls)` | Удаление дубликатов tool calls |
|
| 342 |
|
| 343 |
-
### 3.8. Streaming (`
|
| 344 |
|
| 345 |
#### stream_kiro_to_openai
|
| 346 |
|
|
@@ -357,7 +357,7 @@ OpenAI messages преобразуются в Kiro conversationState:
|
|
| 357 |
|
| 358 |
Собирает полный ответ из streaming потока для non-streaming режима.
|
| 359 |
|
| 360 |
-
### 3.9. HTTP Клиент (`
|
| 361 |
|
| 362 |
#### KiroHttpClient
|
| 363 |
|
|
@@ -378,7 +378,7 @@ OpenAI messages преобразуются в Kiro conversationState:
|
|
| 378 |
|
| 379 |
Поддерживает async context manager (`async with`).
|
| 380 |
|
| 381 |
-
### 3.10. Роуты (`
|
| 382 |
|
| 383 |
| Endpoint | Метод | Описание |
|
| 384 |
|----------|-------|----------|
|
|
@@ -389,14 +389,14 @@ OpenAI messages преобразуются в Kiro conversationState:
|
|
| 389 |
|
| 390 |
**Аутентификация:** Bearer token в заголовке `Authorization`
|
| 391 |
|
| 392 |
-
### 3.11. Обработка Исключений (`
|
| 393 |
|
| 394 |
| Функция | Описание |
|
| 395 |
|---------|----------|
|
| 396 |
| `sanitize_validation_errors(errors)` | Конвертация bytes в строки для JSON-сериализации |
|
| 397 |
| `validation_exception_handler(request, exc)` | Обработчик ошибок валидации Pydantic (422) |
|
| 398 |
|
| 399 |
-
### 3.12. Отладочное Логирование (`
|
| 400 |
|
| 401 |
**Класс:** `DebugLogger` (синглтон)
|
| 402 |
|
|
@@ -417,7 +417,7 @@ OpenAI messages преобразуются в Kiro conversationState:
|
|
| 417 |
- `response_stream_raw.txt` — сырой поток от Kiro
|
| 418 |
- `response_stream_modified.txt` — преобразованный поток (OpenAI формат)
|
| 419 |
|
| 420 |
-
### 3.13. Токенизатор (`
|
| 421 |
|
| 422 |
**Проблема:** Kiro API не возвращает напрямую количество токенов. Вместо этого API предоставляет только `context_usage_percentage` — процент использования контекста модели.
|
| 423 |
|
|
@@ -749,7 +749,7 @@ data: [DONE]
|
|
| 749 |
|
| 750 |
2. **Создать адаптер конвертации** — `converters_gemini.py`
|
| 751 |
```python
|
| 752 |
-
from
|
| 753 |
|
| 754 |
def gemini_to_kiro(request: GeminiRequest, ...) -> dict:
|
| 755 |
"""Конвертирует Gemini запрос в Kiro payload."""
|
|
@@ -769,7 +769,7 @@ data: [DONE]
|
|
| 769 |
|
| 770 |
3. **Создать форматтер streaming** — `streaming_gemini.py`
|
| 771 |
```python
|
| 772 |
-
from
|
| 773 |
|
| 774 |
async def stream_to_gemini(response, ...) -> AsyncGenerator[str, None]:
|
| 775 |
"""Форматирует Kiro события в Gemini SSE."""
|
|
@@ -788,7 +788,7 @@ data: [DONE]
|
|
| 788 |
|
| 789 |
5. **Подключить в main.py**
|
| 790 |
```python
|
| 791 |
-
from
|
| 792 |
app.include_router(gemini_router)
|
| 793 |
```
|
| 794 |
|
|
|
|
| 53 |
|
| 54 |
## 2. Структура Проекта
|
| 55 |
|
| 56 |
+
Проект организован в виде модульного Python-пакета `kiro/`:
|
| 57 |
|
| 58 |
```
|
| 59 |
kiro-gateway/
|
|
|
|
| 61 |
├── requirements.txt # Зависимости Python
|
| 62 |
├── .env.example # Пример конфигурации окружения
|
| 63 |
│
|
| 64 |
+
├── kiro/ # Основной пакет
|
| 65 |
│ ├── __init__.py # Экспорты пакета, версия
|
| 66 |
│ │
|
| 67 |
│ │ # ═══════════════════════════════════════════════════════
|
|
|
|
| 140 |
4. **Регистрация обработчиков ошибок** — `validation_exception_handler` для ошибок 422
|
| 141 |
5. **Подключение роутов** — `app.include_router(router)`
|
| 142 |
|
| 143 |
+
### 3.2. Модуль конфигурации (`kiro/config.py`)
|
| 144 |
|
| 145 |
Централизованное хранение всех настроек:
|
| 146 |
|
|
|
|
| 167 |
- `get_kiro_q_host(region)` — хост Q API
|
| 168 |
- `get_internal_model_id(external_model)` — конвертация имени модели
|
| 169 |
|
| 170 |
+
### 3.3. Pydantic Модели (`kiro/models.py`)
|
| 171 |
|
| 172 |
#### Модели для `/v1/models`
|
| 173 |
|
|
|
|
| 198 |
|
| 199 |
### 3.4. Управление Состоянием (State Management Layer)
|
| 200 |
|
| 201 |
+
#### KiroAuthManager (`kiro/auth.py`)
|
| 202 |
|
| 203 |
**Роль:** Stateful-синглтон, инкапсулирующий логику управления токенами Kiro.
|
| 204 |
|
|
|
|
| 234 |
token = await auth_manager.get_access_token()
|
| 235 |
```
|
| 236 |
|
| 237 |
+
#### ModelInfoCache (`kiro/cache.py`)
|
| 238 |
|
| 239 |
**Роль:** Потокобезопасное хранилище конфигураций моделей.
|
| 240 |
|
|
|
|
| 250 |
- `is_empty()` / `is_stale()` — проверка состояния кэша
|
| 251 |
- `get_all_model_ids()` — список всех ID моделей
|
| 252 |
|
| 253 |
+
### 3.5. Вспомогательные Утилиты (`kiro/utils.py`)
|
| 254 |
|
| 255 |
| Функция | Описание |
|
| 256 |
|---------|----------|
|
|
|
|
| 260 |
| `generate_conversation_id()` | UUID для разговора |
|
| 261 |
| `generate_tool_call_id()` | ID в формате `call_{uuid_hex[:8]}` |
|
| 262 |
|
| 263 |
+
### 3.6. Слой Конвертации (`kiro/converters.py`)
|
| 264 |
|
| 265 |
#### Конвертация сообщений
|
| 266 |
|
|
|
|
| 310 |
| `claude-3-7-sonnet-20250219` | `CLAUDE_3_7_SONNET_20250219_V1_0` |
|
| 311 |
| `auto` | `claude-sonnet-4.5` (алиас) |
|
| 312 |
|
| 313 |
+
### 3.7. Слой Парсинга (`kiro/parsers.py`)
|
| 314 |
|
| 315 |
#### AwsEventStreamParser
|
| 316 |
|
|
|
|
| 340 |
| `parse_bracket_tool_calls(response_text)` | Парсинг `[Called func with args: {...}]` |
|
| 341 |
| `deduplicate_tool_calls(tool_calls)` | Удаление дубликатов tool calls |
|
| 342 |
|
| 343 |
+
### 3.8. Streaming (`kiro/streaming.py`)
|
| 344 |
|
| 345 |
#### stream_kiro_to_openai
|
| 346 |
|
|
|
|
| 357 |
|
| 358 |
Собирает полный ответ из streaming потока для non-streaming режима.
|
| 359 |
|
| 360 |
+
### 3.9. HTTP Клиент (`kiro/http_client.py`)
|
| 361 |
|
| 362 |
#### KiroHttpClient
|
| 363 |
|
|
|
|
| 378 |
|
| 379 |
Поддерживает async context manager (`async with`).
|
| 380 |
|
| 381 |
+
### 3.10. Роуты (`kiro/routes.py`)
|
| 382 |
|
| 383 |
| Endpoint | Метод | Описание |
|
| 384 |
|----------|-------|----------|
|
|
|
|
| 389 |
|
| 390 |
**Аутентификация:** Bearer token в заголовке `Authorization`
|
| 391 |
|
| 392 |
+
### 3.11. Обработка Исключений (`kiro/exceptions.py`)
|
| 393 |
|
| 394 |
| Функция | Описание |
|
| 395 |
|---------|----------|
|
| 396 |
| `sanitize_validation_errors(errors)` | Конвертация bytes в строки для JSON-сериализации |
|
| 397 |
| `validation_exception_handler(request, exc)` | Обработчик ошибок валидации Pydantic (422) |
|
| 398 |
|
| 399 |
+
### 3.12. Отладочное Логирование (`kiro/debug_logger.py`)
|
| 400 |
|
| 401 |
**Класс:** `DebugLogger` (синглтон)
|
| 402 |
|
|
|
|
| 417 |
- `response_stream_raw.txt` — сырой поток от Kiro
|
| 418 |
- `response_stream_modified.txt` — преобразованный поток (OpenAI формат)
|
| 419 |
|
| 420 |
+
### 3.13. Токенизатор (`kiro/tokenizer.py`)
|
| 421 |
|
| 422 |
**Проблема:** Kiro API не возвращает напрямую количество токенов. Вместо этого API предоставляет только `context_usage_percentage` — процент использования контекста модели.
|
| 423 |
|
|
|
|
| 749 |
|
| 750 |
2. **Создать адаптер конвертации** — `converters_gemini.py`
|
| 751 |
```python
|
| 752 |
+
from kiro.converters_core import build_kiro_payload
|
| 753 |
|
| 754 |
def gemini_to_kiro(request: GeminiRequest, ...) -> dict:
|
| 755 |
"""Конвертирует Gemini запрос в Kiro payload."""
|
|
|
|
| 769 |
|
| 770 |
3. **Создать форматтер streaming** — `streaming_gemini.py`
|
| 771 |
```python
|
| 772 |
+
from kiro.streaming_core import parse_kiro_stream
|
| 773 |
|
| 774 |
async def stream_to_gemini(response, ...) -> AsyncGenerator[str, None]:
|
| 775 |
"""Форматирует Kiro события в Gemini SSE."""
|
|
|
|
| 788 |
|
| 789 |
5. **Подключить в main.py**
|
| 790 |
```python
|
| 791 |
+
from kiro.routes_gemini import router as gemini_router
|
| 792 |
app.include_router(gemini_router)
|
| 793 |
```
|
| 794 |
|
kiro/__init__.py
CHANGED
|
@@ -39,18 +39,18 @@ Modules:
|
|
| 39 |
|
| 40 |
# Version is imported from config.py — the single source of truth
|
| 41 |
# This allows changing the version in only one place
|
| 42 |
-
from
|
| 43 |
|
| 44 |
__author__ = "Jwadow"
|
| 45 |
|
| 46 |
# Main components for convenient import
|
| 47 |
-
from
|
| 48 |
-
from
|
| 49 |
-
from
|
| 50 |
-
from
|
| 51 |
|
| 52 |
# Configuration
|
| 53 |
-
from
|
| 54 |
PROXY_API_KEY,
|
| 55 |
REGION,
|
| 56 |
MODEL_MAPPING,
|
|
@@ -59,7 +59,7 @@ from kiro_gateway.config import (
|
|
| 59 |
)
|
| 60 |
|
| 61 |
# Models
|
| 62 |
-
from
|
| 63 |
ChatCompletionRequest,
|
| 64 |
ChatMessage,
|
| 65 |
OpenAIModel,
|
|
@@ -67,26 +67,26 @@ from kiro_gateway.models import (
|
|
| 67 |
)
|
| 68 |
|
| 69 |
# Converters
|
| 70 |
-
from
|
| 71 |
build_kiro_payload,
|
| 72 |
extract_text_content,
|
| 73 |
merge_adjacent_messages,
|
| 74 |
)
|
| 75 |
|
| 76 |
# Parsers
|
| 77 |
-
from
|
| 78 |
AwsEventStreamParser,
|
| 79 |
parse_bracket_tool_calls,
|
| 80 |
)
|
| 81 |
|
| 82 |
# Streaming
|
| 83 |
-
from
|
| 84 |
stream_kiro_to_openai,
|
| 85 |
collect_stream_response,
|
| 86 |
)
|
| 87 |
|
| 88 |
# Exceptions
|
| 89 |
-
from
|
| 90 |
validation_exception_handler,
|
| 91 |
sanitize_validation_errors,
|
| 92 |
)
|
|
|
|
| 39 |
|
| 40 |
# Version is imported from config.py — the single source of truth
|
| 41 |
# This allows changing the version in only one place
|
| 42 |
+
from kiro.config import APP_VERSION as __version__
|
| 43 |
|
| 44 |
__author__ = "Jwadow"
|
| 45 |
|
| 46 |
# Main components for convenient import
|
| 47 |
+
from kiro.auth import KiroAuthManager
|
| 48 |
+
from kiro.cache import ModelInfoCache
|
| 49 |
+
from kiro.http_client import KiroHttpClient
|
| 50 |
+
from kiro.routes import router
|
| 51 |
|
| 52 |
# Configuration
|
| 53 |
+
from kiro.config import (
|
| 54 |
PROXY_API_KEY,
|
| 55 |
REGION,
|
| 56 |
MODEL_MAPPING,
|
|
|
|
| 59 |
)
|
| 60 |
|
| 61 |
# Models
|
| 62 |
+
from kiro.models import (
|
| 63 |
ChatCompletionRequest,
|
| 64 |
ChatMessage,
|
| 65 |
OpenAIModel,
|
|
|
|
| 67 |
)
|
| 68 |
|
| 69 |
# Converters
|
| 70 |
+
from kiro.converters import (
|
| 71 |
build_kiro_payload,
|
| 72 |
extract_text_content,
|
| 73 |
merge_adjacent_messages,
|
| 74 |
)
|
| 75 |
|
| 76 |
# Parsers
|
| 77 |
+
from kiro.parsers import (
|
| 78 |
AwsEventStreamParser,
|
| 79 |
parse_bracket_tool_calls,
|
| 80 |
)
|
| 81 |
|
| 82 |
# Streaming
|
| 83 |
+
from kiro.streaming import (
|
| 84 |
stream_kiro_to_openai,
|
| 85 |
collect_stream_response,
|
| 86 |
)
|
| 87 |
|
| 88 |
# Exceptions
|
| 89 |
+
from kiro.exceptions import (
|
| 90 |
validation_exception_handler,
|
| 91 |
sanitize_validation_errors,
|
| 92 |
)
|
kiro/auth.py
CHANGED
|
@@ -38,14 +38,14 @@ from typing import Optional
|
|
| 38 |
import httpx
|
| 39 |
from loguru import logger
|
| 40 |
|
| 41 |
-
from
|
| 42 |
TOKEN_REFRESH_THRESHOLD,
|
| 43 |
get_kiro_refresh_url,
|
| 44 |
get_kiro_api_host,
|
| 45 |
get_kiro_q_host,
|
| 46 |
get_aws_sso_oidc_url,
|
| 47 |
)
|
| 48 |
-
from
|
| 49 |
|
| 50 |
|
| 51 |
class AuthType(Enum):
|
|
|
|
| 38 |
import httpx
|
| 39 |
from loguru import logger
|
| 40 |
|
| 41 |
+
from kiro.config import (
|
| 42 |
TOKEN_REFRESH_THRESHOLD,
|
| 43 |
get_kiro_refresh_url,
|
| 44 |
get_kiro_api_host,
|
| 45 |
get_kiro_q_host,
|
| 46 |
get_aws_sso_oidc_url,
|
| 47 |
)
|
| 48 |
+
from kiro.utils import get_machine_fingerprint
|
| 49 |
|
| 50 |
|
| 51 |
class AuthType(Enum):
|
kiro/cache.py
CHANGED
|
@@ -30,7 +30,7 @@ from typing import Any, Dict, List, Optional
|
|
| 30 |
|
| 31 |
from loguru import logger
|
| 32 |
|
| 33 |
-
from
|
| 34 |
|
| 35 |
|
| 36 |
class ModelInfoCache:
|
|
|
|
| 30 |
|
| 31 |
from loguru import logger
|
| 32 |
|
| 33 |
+
from kiro.config import MODEL_CACHE_TTL, DEFAULT_MAX_INPUT_TOKENS
|
| 34 |
|
| 35 |
|
| 36 |
class ModelInfoCache:
|
kiro/converters.py
CHANGED
|
@@ -32,13 +32,13 @@ from typing import Any, Dict, List, Optional, Tuple
|
|
| 32 |
|
| 33 |
from loguru import logger
|
| 34 |
|
| 35 |
-
from
|
| 36 |
get_internal_model_id,
|
| 37 |
TOOL_DESCRIPTION_MAX_LENGTH,
|
| 38 |
FAKE_REASONING_ENABLED,
|
| 39 |
FAKE_REASONING_MAX_TOKENS,
|
| 40 |
)
|
| 41 |
-
from
|
| 42 |
|
| 43 |
|
| 44 |
def extract_text_content(content: Any) -> str:
|
|
@@ -408,7 +408,7 @@ def process_tools_with_long_descriptions(
|
|
| 408 |
|
| 409 |
# Create copy of tool with reference description
|
| 410 |
# Use Tool model to create new copy
|
| 411 |
-
from
|
| 412 |
|
| 413 |
reference_description = f"[Full documentation in system prompt under '## Tool: {tool_name}']"
|
| 414 |
|
|
|
|
| 32 |
|
| 33 |
from loguru import logger
|
| 34 |
|
| 35 |
+
from kiro.config import (
|
| 36 |
get_internal_model_id,
|
| 37 |
TOOL_DESCRIPTION_MAX_LENGTH,
|
| 38 |
FAKE_REASONING_ENABLED,
|
| 39 |
FAKE_REASONING_MAX_TOKENS,
|
| 40 |
)
|
| 41 |
+
from kiro.models import ChatMessage, ChatCompletionRequest, Tool
|
| 42 |
|
| 43 |
|
| 44 |
def extract_text_content(content: Any) -> str:
|
|
|
|
| 408 |
|
| 409 |
# Create copy of tool with reference description
|
| 410 |
# Use Tool model to create new copy
|
| 411 |
+
from kiro.models import ToolFunction
|
| 412 |
|
| 413 |
reference_description = f"[Full documentation in system prompt under '## Tool: {tool_name}']"
|
| 414 |
|
kiro/debug_logger.py
CHANGED
|
@@ -39,7 +39,7 @@ from pathlib import Path
|
|
| 39 |
from typing import Optional
|
| 40 |
from loguru import logger
|
| 41 |
|
| 42 |
-
from
|
| 43 |
|
| 44 |
|
| 45 |
class DebugLogger:
|
|
|
|
| 39 |
from typing import Optional
|
| 40 |
from loguru import logger
|
| 41 |
|
| 42 |
+
from kiro.config import DEBUG_MODE, DEBUG_DIR
|
| 43 |
|
| 44 |
|
| 45 |
class DebugLogger:
|
kiro/http_client.py
CHANGED
|
@@ -34,9 +34,9 @@ import httpx
|
|
| 34 |
from fastapi import HTTPException
|
| 35 |
from loguru import logger
|
| 36 |
|
| 37 |
-
from
|
| 38 |
-
from
|
| 39 |
-
from
|
| 40 |
|
| 41 |
|
| 42 |
class KiroHttpClient:
|
|
|
|
| 34 |
from fastapi import HTTPException
|
| 35 |
from loguru import logger
|
| 36 |
|
| 37 |
+
from kiro.config import MAX_RETRIES, BASE_RETRY_DELAY, FIRST_TOKEN_MAX_RETRIES, STREAMING_READ_TIMEOUT
|
| 38 |
+
from kiro.auth import KiroAuthManager
|
| 39 |
+
from kiro.utils import get_kiro_headers
|
| 40 |
|
| 41 |
|
| 42 |
class KiroHttpClient:
|
kiro/parsers.py
CHANGED
|
@@ -33,7 +33,7 @@ from typing import Any, Dict, List, Optional
|
|
| 33 |
|
| 34 |
from loguru import logger
|
| 35 |
|
| 36 |
-
from
|
| 37 |
|
| 38 |
|
| 39 |
def find_matching_brace(text: str, start_pos: int) -> int:
|
|
|
|
| 33 |
|
| 34 |
from loguru import logger
|
| 35 |
|
| 36 |
+
from kiro.utils import generate_tool_call_id
|
| 37 |
|
| 38 |
|
| 39 |
def find_matching_brace(text: str, start_pos: int) -> int:
|
kiro/routes.py
CHANGED
|
@@ -35,26 +35,26 @@ from fastapi.responses import JSONResponse, StreamingResponse
|
|
| 35 |
from fastapi.security import APIKeyHeader
|
| 36 |
from loguru import logger
|
| 37 |
|
| 38 |
-
from
|
| 39 |
PROXY_API_KEY,
|
| 40 |
AVAILABLE_MODELS,
|
| 41 |
APP_VERSION,
|
| 42 |
)
|
| 43 |
-
from
|
| 44 |
OpenAIModel,
|
| 45 |
ModelList,
|
| 46 |
ChatCompletionRequest,
|
| 47 |
)
|
| 48 |
-
from
|
| 49 |
-
from
|
| 50 |
-
from
|
| 51 |
-
from
|
| 52 |
-
from
|
| 53 |
-
from
|
| 54 |
|
| 55 |
# Import debug_logger
|
| 56 |
try:
|
| 57 |
-
from
|
| 58 |
except ImportError:
|
| 59 |
debug_logger = None
|
| 60 |
|
|
|
|
| 35 |
from fastapi.security import APIKeyHeader
|
| 36 |
from loguru import logger
|
| 37 |
|
| 38 |
+
from kiro.config import (
|
| 39 |
PROXY_API_KEY,
|
| 40 |
AVAILABLE_MODELS,
|
| 41 |
APP_VERSION,
|
| 42 |
)
|
| 43 |
+
from kiro.models import (
|
| 44 |
OpenAIModel,
|
| 45 |
ModelList,
|
| 46 |
ChatCompletionRequest,
|
| 47 |
)
|
| 48 |
+
from kiro.auth import KiroAuthManager, AuthType
|
| 49 |
+
from kiro.cache import ModelInfoCache
|
| 50 |
+
from kiro.converters import build_kiro_payload
|
| 51 |
+
from kiro.streaming import stream_kiro_to_openai, collect_stream_response, stream_with_first_token_retry
|
| 52 |
+
from kiro.http_client import KiroHttpClient
|
| 53 |
+
from kiro.utils import get_kiro_headers, generate_conversation_id
|
| 54 |
|
| 55 |
# Import debug_logger
|
| 56 |
try:
|
| 57 |
+
from kiro.debug_logger import debug_logger
|
| 58 |
except ImportError:
|
| 59 |
debug_logger = None
|
| 60 |
|
kiro/streaming.py
CHANGED
|
@@ -35,24 +35,24 @@ import httpx
|
|
| 35 |
from fastapi import HTTPException
|
| 36 |
from loguru import logger
|
| 37 |
|
| 38 |
-
from
|
| 39 |
-
from
|
| 40 |
-
from
|
| 41 |
FIRST_TOKEN_TIMEOUT,
|
| 42 |
FIRST_TOKEN_MAX_RETRIES,
|
| 43 |
FAKE_REASONING_ENABLED,
|
| 44 |
FAKE_REASONING_HANDLING,
|
| 45 |
)
|
| 46 |
-
from
|
| 47 |
-
from
|
| 48 |
|
| 49 |
if TYPE_CHECKING:
|
| 50 |
-
from
|
| 51 |
-
from
|
| 52 |
|
| 53 |
# Import debug_logger for logging
|
| 54 |
try:
|
| 55 |
-
from
|
| 56 |
except ImportError:
|
| 57 |
debug_logger = None
|
| 58 |
|
|
|
|
| 35 |
from fastapi import HTTPException
|
| 36 |
from loguru import logger
|
| 37 |
|
| 38 |
+
from kiro.parsers import AwsEventStreamParser, parse_bracket_tool_calls, deduplicate_tool_calls
|
| 39 |
+
from kiro.utils import generate_completion_id
|
| 40 |
+
from kiro.config import (
|
| 41 |
FIRST_TOKEN_TIMEOUT,
|
| 42 |
FIRST_TOKEN_MAX_RETRIES,
|
| 43 |
FAKE_REASONING_ENABLED,
|
| 44 |
FAKE_REASONING_HANDLING,
|
| 45 |
)
|
| 46 |
+
from kiro.tokenizer import count_tokens, count_message_tokens, count_tools_tokens
|
| 47 |
+
from kiro.thinking_parser import ThinkingParser
|
| 48 |
|
| 49 |
if TYPE_CHECKING:
|
| 50 |
+
from kiro.auth import KiroAuthManager
|
| 51 |
+
from kiro.cache import ModelInfoCache
|
| 52 |
|
| 53 |
# Import debug_logger for logging
|
| 54 |
try:
|
| 55 |
+
from kiro.debug_logger import debug_logger
|
| 56 |
except ImportError:
|
| 57 |
debug_logger = None
|
| 58 |
|
kiro/thinking_parser.py
CHANGED
|
@@ -37,7 +37,7 @@ from dataclasses import dataclass, field
|
|
| 37 |
|
| 38 |
from loguru import logger
|
| 39 |
|
| 40 |
-
from
|
| 41 |
FAKE_REASONING_HANDLING,
|
| 42 |
FAKE_REASONING_OPEN_TAGS,
|
| 43 |
FAKE_REASONING_INITIAL_BUFFER_SIZE,
|
|
|
|
| 37 |
|
| 38 |
from loguru import logger
|
| 39 |
|
| 40 |
+
from kiro.config import (
|
| 41 |
FAKE_REASONING_HANDLING,
|
| 42 |
FAKE_REASONING_OPEN_TAGS,
|
| 43 |
FAKE_REASONING_INITIAL_BUFFER_SIZE,
|
kiro/utils.py
CHANGED
|
@@ -31,7 +31,7 @@ from typing import TYPE_CHECKING
|
|
| 31 |
from loguru import logger
|
| 32 |
|
| 33 |
if TYPE_CHECKING:
|
| 34 |
-
from
|
| 35 |
|
| 36 |
|
| 37 |
def get_machine_fingerprint() -> str:
|
|
|
|
| 31 |
from loguru import logger
|
| 32 |
|
| 33 |
if TYPE_CHECKING:
|
| 34 |
+
from kiro.auth import KiroAuthManager
|
| 35 |
|
| 36 |
|
| 37 |
def get_machine_fingerprint() -> str:
|
main.py
CHANGED
|
@@ -50,7 +50,7 @@ from fastapi.exceptions import RequestValidationError
|
|
| 50 |
from fastapi.middleware.cors import CORSMiddleware
|
| 51 |
from loguru import logger
|
| 52 |
|
| 53 |
-
from
|
| 54 |
APP_TITLE,
|
| 55 |
APP_DESCRIPTION,
|
| 56 |
APP_VERSION,
|
|
@@ -68,10 +68,10 @@ from kiro_gateway.config import (
|
|
| 68 |
_warn_deprecated_debug_setting,
|
| 69 |
_warn_timeout_configuration,
|
| 70 |
)
|
| 71 |
-
from
|
| 72 |
-
from
|
| 73 |
-
from
|
| 74 |
-
from
|
| 75 |
|
| 76 |
|
| 77 |
# --- Loguru Configuration ---
|
|
|
|
| 50 |
from fastapi.middleware.cors import CORSMiddleware
|
| 51 |
from loguru import logger
|
| 52 |
|
| 53 |
+
from kiro.config import (
|
| 54 |
APP_TITLE,
|
| 55 |
APP_DESCRIPTION,
|
| 56 |
APP_VERSION,
|
|
|
|
| 68 |
_warn_deprecated_debug_setting,
|
| 69 |
_warn_timeout_configuration,
|
| 70 |
)
|
| 71 |
+
from kiro.auth import KiroAuthManager
|
| 72 |
+
from kiro.cache import ModelInfoCache
|
| 73 |
+
from kiro.routes import router
|
| 74 |
+
from kiro.exceptions import validation_exception_handler
|
| 75 |
|
| 76 |
|
| 77 |
# --- Loguru Configuration ---
|
tests/README.md
CHANGED
|
@@ -2037,7 +2037,7 @@ To check code coverage:
|
|
| 2037 |
pip install pytest-cov
|
| 2038 |
|
| 2039 |
# Run with coverage report
|
| 2040 |
-
pytest --cov=
|
| 2041 |
|
| 2042 |
# View report
|
| 2043 |
open htmlcov/index.html # macOS/Linux
|
|
|
|
| 2037 |
pip install pytest-cov
|
| 2038 |
|
| 2039 |
# Run with coverage report
|
| 2040 |
+
pytest --cov=kiro --cov-report=html
|
| 2041 |
|
| 2042 |
# View report
|
| 2043 |
open htmlcov/index.html # macOS/Linux
|
tests/conftest.py
CHANGED
|
@@ -355,10 +355,10 @@ def block_all_network_calls():
|
|
| 355 |
|
| 356 |
# Patch AsyncClient in modules where it's used
|
| 357 |
patchers = [
|
| 358 |
-
patch('
|
| 359 |
-
patch('
|
| 360 |
-
patch('
|
| 361 |
-
patch('
|
| 362 |
]
|
| 363 |
|
| 364 |
# Start patchers
|
|
@@ -428,7 +428,7 @@ def mock_auth_manager():
|
|
| 428 |
"""
|
| 429 |
Creates a mocked KiroAuthManager for tests.
|
| 430 |
"""
|
| 431 |
-
from
|
| 432 |
|
| 433 |
manager = KiroAuthManager(
|
| 434 |
refresh_token="test_refresh_token",
|
|
@@ -450,7 +450,7 @@ def expired_auth_manager():
|
|
| 450 |
"""
|
| 451 |
Creates a KiroAuthManager with an expired token.
|
| 452 |
"""
|
| 453 |
-
from
|
| 454 |
|
| 455 |
manager = KiroAuthManager(
|
| 456 |
refresh_token="test_refresh_token",
|
|
@@ -509,7 +509,7 @@ def empty_model_cache():
|
|
| 509 |
"""
|
| 510 |
Creates an empty ModelInfoCache.
|
| 511 |
"""
|
| 512 |
-
from
|
| 513 |
return ModelInfoCache()
|
| 514 |
|
| 515 |
|
|
@@ -518,7 +518,7 @@ async def populated_model_cache(mock_kiro_models_response):
|
|
| 518 |
"""
|
| 519 |
Creates a ModelInfoCache with pre-populated data.
|
| 520 |
"""
|
| 521 |
-
from
|
| 522 |
|
| 523 |
cache = ModelInfoCache()
|
| 524 |
await cache.update(mock_kiro_models_response["models"])
|
|
@@ -547,7 +547,7 @@ def mock_datetime():
|
|
| 547 |
"""
|
| 548 |
fixed_time = datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc)
|
| 549 |
|
| 550 |
-
with patch('
|
| 551 |
mock_dt.now.return_value = fixed_time
|
| 552 |
mock_dt.fromisoformat = datetime.fromisoformat
|
| 553 |
mock_dt.fromtimestamp = datetime.fromtimestamp
|
|
@@ -750,7 +750,7 @@ def aws_event_parser():
|
|
| 750 |
"""
|
| 751 |
Creates an AwsEventStreamParser instance for tests.
|
| 752 |
"""
|
| 753 |
-
from
|
| 754 |
return AwsEventStreamParser()
|
| 755 |
|
| 756 |
|
|
|
|
| 355 |
|
| 356 |
# Patch AsyncClient in modules where it's used
|
| 357 |
patchers = [
|
| 358 |
+
patch('kiro.auth.httpx.AsyncClient', return_value=mock_async_client),
|
| 359 |
+
patch('kiro.http_client.httpx.AsyncClient', return_value=mock_async_client),
|
| 360 |
+
patch('kiro.routes.httpx.AsyncClient', return_value=mock_async_client),
|
| 361 |
+
patch('kiro.streaming.httpx.AsyncClient', return_value=mock_async_client),
|
| 362 |
]
|
| 363 |
|
| 364 |
# Start patchers
|
|
|
|
| 428 |
"""
|
| 429 |
Creates a mocked KiroAuthManager for tests.
|
| 430 |
"""
|
| 431 |
+
from kiro.auth import KiroAuthManager
|
| 432 |
|
| 433 |
manager = KiroAuthManager(
|
| 434 |
refresh_token="test_refresh_token",
|
|
|
|
| 450 |
"""
|
| 451 |
Creates a KiroAuthManager with an expired token.
|
| 452 |
"""
|
| 453 |
+
from kiro.auth import KiroAuthManager
|
| 454 |
|
| 455 |
manager = KiroAuthManager(
|
| 456 |
refresh_token="test_refresh_token",
|
|
|
|
| 509 |
"""
|
| 510 |
Creates an empty ModelInfoCache.
|
| 511 |
"""
|
| 512 |
+
from kiro.cache import ModelInfoCache
|
| 513 |
return ModelInfoCache()
|
| 514 |
|
| 515 |
|
|
|
|
| 518 |
"""
|
| 519 |
Creates a ModelInfoCache with pre-populated data.
|
| 520 |
"""
|
| 521 |
+
from kiro.cache import ModelInfoCache
|
| 522 |
|
| 523 |
cache = ModelInfoCache()
|
| 524 |
await cache.update(mock_kiro_models_response["models"])
|
|
|
|
| 547 |
"""
|
| 548 |
fixed_time = datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc)
|
| 549 |
|
| 550 |
+
with patch('kiro.auth.datetime') as mock_dt:
|
| 551 |
mock_dt.now.return_value = fixed_time
|
| 552 |
mock_dt.fromisoformat = datetime.fromisoformat
|
| 553 |
mock_dt.fromtimestamp = datetime.fromtimestamp
|
|
|
|
| 750 |
"""
|
| 751 |
Creates an AwsEventStreamParser instance for tests.
|
| 752 |
"""
|
| 753 |
+
from kiro.parsers import AwsEventStreamParser
|
| 754 |
return AwsEventStreamParser()
|
| 755 |
|
| 756 |
|
tests/integration/test_full_flow.py
CHANGED
|
@@ -13,7 +13,7 @@ from datetime import datetime, timezone, timedelta
|
|
| 13 |
from fastapi.testclient import TestClient
|
| 14 |
import httpx
|
| 15 |
|
| 16 |
-
from
|
| 17 |
|
| 18 |
|
| 19 |
class TestFullChatCompletionFlow:
|
|
@@ -329,7 +329,7 @@ class TestStreamingFlagHandling:
|
|
| 329 |
mock_response.aclose = AsyncMock()
|
| 330 |
|
| 331 |
# Мокируем request_with_retry чтобы вернуть наш мок response
|
| 332 |
-
with patch('
|
| 333 |
mock_client_instance = AsyncMock()
|
| 334 |
mock_client_instance.request_with_retry = AsyncMock(return_value=mock_response)
|
| 335 |
mock_client_instance.client = AsyncMock()
|
|
|
|
| 13 |
from fastapi.testclient import TestClient
|
| 14 |
import httpx
|
| 15 |
|
| 16 |
+
from kiro.config import PROXY_API_KEY, AVAILABLE_MODELS
|
| 17 |
|
| 18 |
|
| 19 |
class TestFullChatCompletionFlow:
|
|
|
|
| 329 |
mock_response.aclose = AsyncMock()
|
| 330 |
|
| 331 |
# Мокируем request_with_retry чтобы вернуть наш мок response
|
| 332 |
+
with patch('kiro.routes.KiroHttpClient') as MockHttpClient:
|
| 333 |
mock_client_instance = AsyncMock()
|
| 334 |
mock_client_instance.request_with_retry = AsyncMock(return_value=mock_response)
|
| 335 |
mock_client_instance.client = AsyncMock()
|
tests/unit/test_auth_manager.py
CHANGED
|
@@ -11,8 +11,8 @@ from datetime import datetime, timezone, timedelta
|
|
| 11 |
from unittest.mock import AsyncMock, Mock, patch
|
| 12 |
import httpx
|
| 13 |
|
| 14 |
-
from
|
| 15 |
-
from
|
| 16 |
|
| 17 |
|
| 18 |
class TestKiroAuthManagerInitialization:
|
|
@@ -204,7 +204,7 @@ class TestKiroAuthManagerTokenRefresh:
|
|
| 204 |
mock_response.json = Mock(return_value=mock_kiro_token_response())
|
| 205 |
mock_response.raise_for_status = Mock()
|
| 206 |
|
| 207 |
-
with patch('
|
| 208 |
mock_client = AsyncMock()
|
| 209 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 210 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
@@ -239,7 +239,7 @@ class TestKiroAuthManagerTokenRefresh:
|
|
| 239 |
mock_response.json = Mock(return_value=mock_kiro_token_response())
|
| 240 |
mock_response.raise_for_status = Mock()
|
| 241 |
|
| 242 |
-
with patch('
|
| 243 |
mock_client = AsyncMock()
|
| 244 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 245 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
@@ -268,7 +268,7 @@ class TestKiroAuthManagerTokenRefresh:
|
|
| 268 |
mock_response.json = Mock(return_value={"expiresIn": 3600}) # No accessToken!
|
| 269 |
mock_response.raise_for_status = Mock()
|
| 270 |
|
| 271 |
-
with patch('
|
| 272 |
mock_client = AsyncMock()
|
| 273 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 274 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
@@ -320,7 +320,7 @@ class TestKiroAuthManagerGetAccessToken:
|
|
| 320 |
mock_response.json = Mock(return_value=mock_kiro_token_response())
|
| 321 |
mock_response.raise_for_status = Mock()
|
| 322 |
|
| 323 |
-
with patch('
|
| 324 |
mock_client = AsyncMock()
|
| 325 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 326 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
@@ -350,7 +350,7 @@ class TestKiroAuthManagerGetAccessToken:
|
|
| 350 |
manager._expires_at = datetime.now(timezone.utc) + timedelta(hours=1)
|
| 351 |
|
| 352 |
print("Setup: Mocking httpx to track calls...")
|
| 353 |
-
with patch('
|
| 354 |
mock_client = AsyncMock()
|
| 355 |
mock_client.post = AsyncMock()
|
| 356 |
mock_client_class.return_value = mock_client
|
|
@@ -420,7 +420,7 @@ class TestKiroAuthManagerForceRefresh:
|
|
| 420 |
mock_response.json = Mock(return_value=mock_kiro_token_response())
|
| 421 |
mock_response.raise_for_status = Mock()
|
| 422 |
|
| 423 |
-
with patch('
|
| 424 |
mock_client = AsyncMock()
|
| 425 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 426 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
@@ -848,7 +848,7 @@ class TestKiroAuthManagerAwsSsoOidcRefresh:
|
|
| 848 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response())
|
| 849 |
mock_response.raise_for_status = Mock()
|
| 850 |
|
| 851 |
-
with patch('
|
| 852 |
mock_client = AsyncMock()
|
| 853 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 854 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
@@ -947,7 +947,7 @@ class TestKiroAuthManagerAwsSsoOidcRefresh:
|
|
| 947 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response())
|
| 948 |
mock_response.raise_for_status = Mock()
|
| 949 |
|
| 950 |
-
with patch('
|
| 951 |
mock_client = AsyncMock()
|
| 952 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 953 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
@@ -982,7 +982,7 @@ class TestKiroAuthManagerAwsSsoOidcRefresh:
|
|
| 982 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response())
|
| 983 |
mock_response.raise_for_status = Mock()
|
| 984 |
|
| 985 |
-
with patch('
|
| 986 |
mock_client = AsyncMock()
|
| 987 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 988 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
@@ -1016,7 +1016,7 @@ class TestKiroAuthManagerAwsSsoOidcRefresh:
|
|
| 1016 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response())
|
| 1017 |
mock_response.raise_for_status = Mock()
|
| 1018 |
|
| 1019 |
-
with patch('
|
| 1020 |
mock_client = AsyncMock()
|
| 1021 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 1022 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
@@ -1050,7 +1050,7 @@ class TestKiroAuthManagerAwsSsoOidcRefresh:
|
|
| 1050 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response())
|
| 1051 |
mock_response.raise_for_status = Mock()
|
| 1052 |
|
| 1053 |
-
with patch('
|
| 1054 |
mock_client = AsyncMock()
|
| 1055 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 1056 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
@@ -1084,7 +1084,7 @@ class TestKiroAuthManagerAwsSsoOidcRefresh:
|
|
| 1084 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response(expires_in=7200))
|
| 1085 |
mock_response.raise_for_status = Mock()
|
| 1086 |
|
| 1087 |
-
with patch('
|
| 1088 |
mock_client = AsyncMock()
|
| 1089 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 1090 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
@@ -1123,7 +1123,7 @@ class TestKiroAuthManagerAwsSsoOidcRefresh:
|
|
| 1123 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response())
|
| 1124 |
mock_response.raise_for_status = Mock()
|
| 1125 |
|
| 1126 |
-
with patch('
|
| 1127 |
mock_client = AsyncMock()
|
| 1128 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 1129 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
@@ -1164,7 +1164,7 @@ class TestKiroAuthManagerAwsSsoOidcRefresh:
|
|
| 1164 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response())
|
| 1165 |
mock_response.raise_for_status = Mock()
|
| 1166 |
|
| 1167 |
-
with patch('
|
| 1168 |
mock_client = AsyncMock()
|
| 1169 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 1170 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
@@ -1340,7 +1340,7 @@ class TestKiroAuthManagerSsoRegionSeparation:
|
|
| 1340 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response())
|
| 1341 |
mock_response.raise_for_status = Mock()
|
| 1342 |
|
| 1343 |
-
with patch('
|
| 1344 |
mock_client = AsyncMock()
|
| 1345 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 1346 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
@@ -1380,7 +1380,7 @@ class TestKiroAuthManagerSsoRegionSeparation:
|
|
| 1380 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response())
|
| 1381 |
mock_response.raise_for_status = Mock()
|
| 1382 |
|
| 1383 |
-
with patch('
|
| 1384 |
mock_client = AsyncMock()
|
| 1385 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 1386 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
@@ -1439,7 +1439,7 @@ class TestKiroAuthManagerSsoRegionSeparation:
|
|
| 1439 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response())
|
| 1440 |
mock_response.raise_for_status = Mock()
|
| 1441 |
|
| 1442 |
-
with patch('
|
| 1443 |
mock_client = AsyncMock()
|
| 1444 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 1445 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
@@ -1568,7 +1568,7 @@ class TestKiroAuthManagerSsoRegionSeparation:
|
|
| 1568 |
return mock_error_response
|
| 1569 |
return mock_success_response
|
| 1570 |
|
| 1571 |
-
with patch('
|
| 1572 |
mock_client = AsyncMock()
|
| 1573 |
mock_client.post = mock_post
|
| 1574 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
@@ -1619,7 +1619,7 @@ class TestKiroAuthManagerSsoRegionSeparation:
|
|
| 1619 |
)
|
| 1620 |
)
|
| 1621 |
|
| 1622 |
-
with patch('
|
| 1623 |
mock_client = AsyncMock()
|
| 1624 |
mock_client.post = AsyncMock(return_value=mock_error_response)
|
| 1625 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
@@ -1667,7 +1667,7 @@ class TestKiroAuthManagerSsoRegionSeparation:
|
|
| 1667 |
)
|
| 1668 |
)
|
| 1669 |
|
| 1670 |
-
with patch('
|
| 1671 |
mock_client = AsyncMock()
|
| 1672 |
mock_client.post = AsyncMock(return_value=mock_error_response)
|
| 1673 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
|
|
| 11 |
from unittest.mock import AsyncMock, Mock, patch
|
| 12 |
import httpx
|
| 13 |
|
| 14 |
+
from kiro.auth import KiroAuthManager, AuthType
|
| 15 |
+
from kiro.config import TOKEN_REFRESH_THRESHOLD, get_aws_sso_oidc_url
|
| 16 |
|
| 17 |
|
| 18 |
class TestKiroAuthManagerInitialization:
|
|
|
|
| 204 |
mock_response.json = Mock(return_value=mock_kiro_token_response())
|
| 205 |
mock_response.raise_for_status = Mock()
|
| 206 |
|
| 207 |
+
with patch('kiro.auth.httpx.AsyncClient') as mock_client_class:
|
| 208 |
mock_client = AsyncMock()
|
| 209 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 210 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
|
|
| 239 |
mock_response.json = Mock(return_value=mock_kiro_token_response())
|
| 240 |
mock_response.raise_for_status = Mock()
|
| 241 |
|
| 242 |
+
with patch('kiro.auth.httpx.AsyncClient') as mock_client_class:
|
| 243 |
mock_client = AsyncMock()
|
| 244 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 245 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
|
|
| 268 |
mock_response.json = Mock(return_value={"expiresIn": 3600}) # No accessToken!
|
| 269 |
mock_response.raise_for_status = Mock()
|
| 270 |
|
| 271 |
+
with patch('kiro.auth.httpx.AsyncClient') as mock_client_class:
|
| 272 |
mock_client = AsyncMock()
|
| 273 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 274 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
|
|
| 320 |
mock_response.json = Mock(return_value=mock_kiro_token_response())
|
| 321 |
mock_response.raise_for_status = Mock()
|
| 322 |
|
| 323 |
+
with patch('kiro.auth.httpx.AsyncClient') as mock_client_class:
|
| 324 |
mock_client = AsyncMock()
|
| 325 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 326 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
|
|
| 350 |
manager._expires_at = datetime.now(timezone.utc) + timedelta(hours=1)
|
| 351 |
|
| 352 |
print("Setup: Mocking httpx to track calls...")
|
| 353 |
+
with patch('kiro.auth.httpx.AsyncClient') as mock_client_class:
|
| 354 |
mock_client = AsyncMock()
|
| 355 |
mock_client.post = AsyncMock()
|
| 356 |
mock_client_class.return_value = mock_client
|
|
|
|
| 420 |
mock_response.json = Mock(return_value=mock_kiro_token_response())
|
| 421 |
mock_response.raise_for_status = Mock()
|
| 422 |
|
| 423 |
+
with patch('kiro.auth.httpx.AsyncClient') as mock_client_class:
|
| 424 |
mock_client = AsyncMock()
|
| 425 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 426 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
|
|
| 848 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response())
|
| 849 |
mock_response.raise_for_status = Mock()
|
| 850 |
|
| 851 |
+
with patch('kiro.auth.httpx.AsyncClient') as mock_client_class:
|
| 852 |
mock_client = AsyncMock()
|
| 853 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 854 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
|
|
| 947 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response())
|
| 948 |
mock_response.raise_for_status = Mock()
|
| 949 |
|
| 950 |
+
with patch('kiro.auth.httpx.AsyncClient') as mock_client_class:
|
| 951 |
mock_client = AsyncMock()
|
| 952 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 953 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
|
|
| 982 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response())
|
| 983 |
mock_response.raise_for_status = Mock()
|
| 984 |
|
| 985 |
+
with patch('kiro.auth.httpx.AsyncClient') as mock_client_class:
|
| 986 |
mock_client = AsyncMock()
|
| 987 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 988 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
|
|
| 1016 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response())
|
| 1017 |
mock_response.raise_for_status = Mock()
|
| 1018 |
|
| 1019 |
+
with patch('kiro.auth.httpx.AsyncClient') as mock_client_class:
|
| 1020 |
mock_client = AsyncMock()
|
| 1021 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 1022 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
|
|
| 1050 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response())
|
| 1051 |
mock_response.raise_for_status = Mock()
|
| 1052 |
|
| 1053 |
+
with patch('kiro.auth.httpx.AsyncClient') as mock_client_class:
|
| 1054 |
mock_client = AsyncMock()
|
| 1055 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 1056 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
|
|
| 1084 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response(expires_in=7200))
|
| 1085 |
mock_response.raise_for_status = Mock()
|
| 1086 |
|
| 1087 |
+
with patch('kiro.auth.httpx.AsyncClient') as mock_client_class:
|
| 1088 |
mock_client = AsyncMock()
|
| 1089 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 1090 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
|
|
| 1123 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response())
|
| 1124 |
mock_response.raise_for_status = Mock()
|
| 1125 |
|
| 1126 |
+
with patch('kiro.auth.httpx.AsyncClient') as mock_client_class:
|
| 1127 |
mock_client = AsyncMock()
|
| 1128 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 1129 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
|
|
| 1164 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response())
|
| 1165 |
mock_response.raise_for_status = Mock()
|
| 1166 |
|
| 1167 |
+
with patch('kiro.auth.httpx.AsyncClient') as mock_client_class:
|
| 1168 |
mock_client = AsyncMock()
|
| 1169 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 1170 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
|
|
| 1340 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response())
|
| 1341 |
mock_response.raise_for_status = Mock()
|
| 1342 |
|
| 1343 |
+
with patch('kiro.auth.httpx.AsyncClient') as mock_client_class:
|
| 1344 |
mock_client = AsyncMock()
|
| 1345 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 1346 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
|
|
| 1380 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response())
|
| 1381 |
mock_response.raise_for_status = Mock()
|
| 1382 |
|
| 1383 |
+
with patch('kiro.auth.httpx.AsyncClient') as mock_client_class:
|
| 1384 |
mock_client = AsyncMock()
|
| 1385 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 1386 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
|
|
| 1439 |
mock_response.json = Mock(return_value=mock_aws_sso_oidc_token_response())
|
| 1440 |
mock_response.raise_for_status = Mock()
|
| 1441 |
|
| 1442 |
+
with patch('kiro.auth.httpx.AsyncClient') as mock_client_class:
|
| 1443 |
mock_client = AsyncMock()
|
| 1444 |
mock_client.post = AsyncMock(return_value=mock_response)
|
| 1445 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
|
|
| 1568 |
return mock_error_response
|
| 1569 |
return mock_success_response
|
| 1570 |
|
| 1571 |
+
with patch('kiro.auth.httpx.AsyncClient') as mock_client_class:
|
| 1572 |
mock_client = AsyncMock()
|
| 1573 |
mock_client.post = mock_post
|
| 1574 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
|
|
| 1619 |
)
|
| 1620 |
)
|
| 1621 |
|
| 1622 |
+
with patch('kiro.auth.httpx.AsyncClient') as mock_client_class:
|
| 1623 |
mock_client = AsyncMock()
|
| 1624 |
mock_client.post = AsyncMock(return_value=mock_error_response)
|
| 1625 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
|
|
| 1667 |
)
|
| 1668 |
)
|
| 1669 |
|
| 1670 |
+
with patch('kiro.auth.httpx.AsyncClient') as mock_client_class:
|
| 1671 |
mock_client = AsyncMock()
|
| 1672 |
mock_client.post = AsyncMock(return_value=mock_error_response)
|
| 1673 |
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
tests/unit/test_cache.py
CHANGED
|
@@ -9,8 +9,8 @@ import asyncio
|
|
| 9 |
import time
|
| 10 |
import pytest
|
| 11 |
|
| 12 |
-
from
|
| 13 |
-
from
|
| 14 |
|
| 15 |
|
| 16 |
class TestModelInfoCacheInitialization:
|
|
|
|
| 9 |
import time
|
| 10 |
import pytest
|
| 11 |
|
| 12 |
+
from kiro.cache import ModelInfoCache
|
| 13 |
+
from kiro.config import DEFAULT_MAX_INPUT_TOKENS
|
| 14 |
|
| 15 |
|
| 16 |
class TestModelInfoCacheInitialization:
|
tests/unit/test_config.py
CHANGED
|
@@ -36,7 +36,7 @@ class TestLogLevelConfig:
|
|
| 36 |
with patch.object(os, 'getenv', side_effect=mock_getenv):
|
| 37 |
# Reload config module with mocked getenv
|
| 38 |
import importlib
|
| 39 |
-
import
|
| 40 |
importlib.reload(config_module)
|
| 41 |
|
| 42 |
print(f"LOG_LEVEL: {config_module.LOG_LEVEL}")
|
|
@@ -45,7 +45,7 @@ class TestLogLevelConfig:
|
|
| 45 |
|
| 46 |
# Restore module with real values
|
| 47 |
import importlib
|
| 48 |
-
import
|
| 49 |
importlib.reload(config_module)
|
| 50 |
|
| 51 |
def test_log_level_from_environment(self):
|
|
@@ -57,7 +57,7 @@ class TestLogLevelConfig:
|
|
| 57 |
|
| 58 |
with patch.dict(os.environ, {"LOG_LEVEL": "DEBUG"}):
|
| 59 |
import importlib
|
| 60 |
-
import
|
| 61 |
importlib.reload(config_module)
|
| 62 |
|
| 63 |
print(f"LOG_LEVEL: {config_module.LOG_LEVEL}")
|
|
@@ -73,7 +73,7 @@ class TestLogLevelConfig:
|
|
| 73 |
|
| 74 |
with patch.dict(os.environ, {"LOG_LEVEL": "warning"}):
|
| 75 |
import importlib
|
| 76 |
-
import
|
| 77 |
importlib.reload(config_module)
|
| 78 |
|
| 79 |
print(f"LOG_LEVEL: {config_module.LOG_LEVEL}")
|
|
@@ -89,7 +89,7 @@ class TestLogLevelConfig:
|
|
| 89 |
|
| 90 |
with patch.dict(os.environ, {"LOG_LEVEL": "TRACE"}):
|
| 91 |
import importlib
|
| 92 |
-
import
|
| 93 |
importlib.reload(config_module)
|
| 94 |
|
| 95 |
print(f"LOG_LEVEL: {config_module.LOG_LEVEL}")
|
|
@@ -104,7 +104,7 @@ class TestLogLevelConfig:
|
|
| 104 |
|
| 105 |
with patch.dict(os.environ, {"LOG_LEVEL": "ERROR"}):
|
| 106 |
import importlib
|
| 107 |
-
import
|
| 108 |
importlib.reload(config_module)
|
| 109 |
|
| 110 |
print(f"LOG_LEVEL: {config_module.LOG_LEVEL}")
|
|
@@ -119,7 +119,7 @@ class TestLogLevelConfig:
|
|
| 119 |
|
| 120 |
with patch.dict(os.environ, {"LOG_LEVEL": "CRITICAL"}):
|
| 121 |
import importlib
|
| 122 |
-
import
|
| 123 |
importlib.reload(config_module)
|
| 124 |
|
| 125 |
print(f"LOG_LEVEL: {config_module.LOG_LEVEL}")
|
|
@@ -141,7 +141,7 @@ class TestToolDescriptionMaxLengthConfig:
|
|
| 141 |
del os.environ["TOOL_DESCRIPTION_MAX_LENGTH"]
|
| 142 |
|
| 143 |
import importlib
|
| 144 |
-
import
|
| 145 |
importlib.reload(config_module)
|
| 146 |
|
| 147 |
print(f"TOOL_DESCRIPTION_MAX_LENGTH: {config_module.TOOL_DESCRIPTION_MAX_LENGTH}")
|
|
@@ -156,7 +156,7 @@ class TestToolDescriptionMaxLengthConfig:
|
|
| 156 |
|
| 157 |
with patch.dict(os.environ, {"TOOL_DESCRIPTION_MAX_LENGTH": "5000"}):
|
| 158 |
import importlib
|
| 159 |
-
import
|
| 160 |
importlib.reload(config_module)
|
| 161 |
|
| 162 |
print(f"TOOL_DESCRIPTION_MAX_LENGTH: {config_module.TOOL_DESCRIPTION_MAX_LENGTH}")
|
|
@@ -171,7 +171,7 @@ class TestToolDescriptionMaxLengthConfig:
|
|
| 171 |
|
| 172 |
with patch.dict(os.environ, {"TOOL_DESCRIPTION_MAX_LENGTH": "0"}):
|
| 173 |
import importlib
|
| 174 |
-
import
|
| 175 |
importlib.reload(config_module)
|
| 176 |
|
| 177 |
print(f"TOOL_DESCRIPTION_MAX_LENGTH: {config_module.TOOL_DESCRIPTION_MAX_LENGTH}")
|
|
@@ -193,7 +193,7 @@ class TestTimeoutConfigurationWarning:
|
|
| 193 |
"STREAMING_READ_TIMEOUT": "300"
|
| 194 |
}):
|
| 195 |
import importlib
|
| 196 |
-
import
|
| 197 |
importlib.reload(config_module)
|
| 198 |
|
| 199 |
# Call the warning function
|
|
@@ -218,7 +218,7 @@ class TestTimeoutConfigurationWarning:
|
|
| 218 |
"STREAMING_READ_TIMEOUT": "300"
|
| 219 |
}):
|
| 220 |
import importlib
|
| 221 |
-
import
|
| 222 |
importlib.reload(config_module)
|
| 223 |
|
| 224 |
# Call the warning function
|
|
@@ -242,7 +242,7 @@ class TestTimeoutConfigurationWarning:
|
|
| 242 |
"STREAMING_READ_TIMEOUT": "300"
|
| 243 |
}):
|
| 244 |
import importlib
|
| 245 |
-
import
|
| 246 |
importlib.reload(config_module)
|
| 247 |
|
| 248 |
# Call the warning function
|
|
@@ -269,7 +269,7 @@ class TestTimeoutConfigurationWarning:
|
|
| 269 |
"STREAMING_READ_TIMEOUT": "300"
|
| 270 |
}):
|
| 271 |
import importlib
|
| 272 |
-
import
|
| 273 |
importlib.reload(config_module)
|
| 274 |
|
| 275 |
# Call the warning function
|
|
@@ -292,7 +292,7 @@ class TestAwsSsoOidcUrlConfig:
|
|
| 292 |
"""
|
| 293 |
print("Setup: Importing config module...")
|
| 294 |
import importlib
|
| 295 |
-
import
|
| 296 |
importlib.reload(config_module)
|
| 297 |
|
| 298 |
print("Verification: AWS_SSO_OIDC_URL_TEMPLATE exists...")
|
|
@@ -309,7 +309,7 @@ class TestAwsSsoOidcUrlConfig:
|
|
| 309 |
Purpose: Ensure the function formats URL correctly.
|
| 310 |
"""
|
| 311 |
print("Setup: Importing get_aws_sso_oidc_url...")
|
| 312 |
-
from
|
| 313 |
|
| 314 |
print("Action: Calling get_aws_sso_oidc_url('us-east-1')...")
|
| 315 |
url = get_aws_sso_oidc_url("us-east-1")
|
|
@@ -325,7 +325,7 @@ class TestAwsSsoOidcUrlConfig:
|
|
| 325 |
Purpose: Ensure the function works with various AWS regions.
|
| 326 |
"""
|
| 327 |
print("Setup: Importing get_aws_sso_oidc_url...")
|
| 328 |
-
from
|
| 329 |
|
| 330 |
test_cases = [
|
| 331 |
("us-east-1", "https://oidc.us-east-1.amazonaws.com/token"),
|
|
@@ -356,7 +356,7 @@ class TestServerHostConfig:
|
|
| 356 |
del os.environ["SERVER_HOST"]
|
| 357 |
|
| 358 |
import importlib
|
| 359 |
-
import
|
| 360 |
importlib.reload(config_module)
|
| 361 |
|
| 362 |
print(f"SERVER_HOST: {config_module.SERVER_HOST}")
|
|
@@ -374,7 +374,7 @@ class TestServerHostConfig:
|
|
| 374 |
|
| 375 |
with patch.dict(os.environ, {"SERVER_HOST": "127.0.0.1"}):
|
| 376 |
import importlib
|
| 377 |
-
import
|
| 378 |
importlib.reload(config_module)
|
| 379 |
|
| 380 |
print(f"SERVER_HOST: {config_module.SERVER_HOST}")
|
|
@@ -390,7 +390,7 @@ class TestServerHostConfig:
|
|
| 390 |
|
| 391 |
with patch.dict(os.environ, {"SERVER_HOST": "192.168.1.100"}):
|
| 392 |
import importlib
|
| 393 |
-
import
|
| 394 |
importlib.reload(config_module)
|
| 395 |
|
| 396 |
print(f"SERVER_HOST: {config_module.SERVER_HOST}")
|
|
@@ -412,7 +412,7 @@ class TestServerPortConfig:
|
|
| 412 |
del os.environ["SERVER_PORT"]
|
| 413 |
|
| 414 |
import importlib
|
| 415 |
-
import
|
| 416 |
importlib.reload(config_module)
|
| 417 |
|
| 418 |
print(f"SERVER_PORT: {config_module.SERVER_PORT}")
|
|
@@ -430,7 +430,7 @@ class TestServerPortConfig:
|
|
| 430 |
|
| 431 |
with patch.dict(os.environ, {"SERVER_PORT": "9000"}):
|
| 432 |
import importlib
|
| 433 |
-
import
|
| 434 |
importlib.reload(config_module)
|
| 435 |
|
| 436 |
print(f"SERVER_PORT: {config_module.SERVER_PORT}")
|
|
@@ -446,7 +446,7 @@ class TestServerPortConfig:
|
|
| 446 |
|
| 447 |
with patch.dict(os.environ, {"SERVER_PORT": "3000"}):
|
| 448 |
import importlib
|
| 449 |
-
import
|
| 450 |
importlib.reload(config_module)
|
| 451 |
|
| 452 |
print(f"SERVER_PORT: {config_module.SERVER_PORT}")
|
|
@@ -461,7 +461,7 @@ class TestServerPortConfig:
|
|
| 461 |
|
| 462 |
with patch.dict(os.environ, {"SERVER_PORT": "8080"}):
|
| 463 |
import importlib
|
| 464 |
-
import
|
| 465 |
importlib.reload(config_module)
|
| 466 |
|
| 467 |
print(f"SERVER_PORT: {config_module.SERVER_PORT}")
|
|
@@ -480,7 +480,7 @@ class TestKiroCliDbFileConfig:
|
|
| 480 |
"""
|
| 481 |
print("Setup: Importing config module...")
|
| 482 |
import importlib
|
| 483 |
-
import
|
| 484 |
importlib.reload(config_module)
|
| 485 |
|
| 486 |
print("Verification: KIRO_CLI_DB_FILE exists...")
|
|
@@ -499,7 +499,7 @@ class TestKiroCliDbFileConfig:
|
|
| 499 |
|
| 500 |
with patch.dict(os.environ, {"KIRO_CLI_DB_FILE": "~/.local/share/kiro-cli/data.sqlite3"}):
|
| 501 |
import importlib
|
| 502 |
-
import
|
| 503 |
importlib.reload(config_module)
|
| 504 |
|
| 505 |
print(f"KIRO_CLI_DB_FILE: {config_module.KIRO_CLI_DB_FILE}")
|
|
|
|
| 36 |
with patch.object(os, 'getenv', side_effect=mock_getenv):
|
| 37 |
# Reload config module with mocked getenv
|
| 38 |
import importlib
|
| 39 |
+
import kiro.config as config_module
|
| 40 |
importlib.reload(config_module)
|
| 41 |
|
| 42 |
print(f"LOG_LEVEL: {config_module.LOG_LEVEL}")
|
|
|
|
| 45 |
|
| 46 |
# Restore module with real values
|
| 47 |
import importlib
|
| 48 |
+
import kiro.config as config_module
|
| 49 |
importlib.reload(config_module)
|
| 50 |
|
| 51 |
def test_log_level_from_environment(self):
|
|
|
|
| 57 |
|
| 58 |
with patch.dict(os.environ, {"LOG_LEVEL": "DEBUG"}):
|
| 59 |
import importlib
|
| 60 |
+
import kiro.config as config_module
|
| 61 |
importlib.reload(config_module)
|
| 62 |
|
| 63 |
print(f"LOG_LEVEL: {config_module.LOG_LEVEL}")
|
|
|
|
| 73 |
|
| 74 |
with patch.dict(os.environ, {"LOG_LEVEL": "warning"}):
|
| 75 |
import importlib
|
| 76 |
+
import kiro.config as config_module
|
| 77 |
importlib.reload(config_module)
|
| 78 |
|
| 79 |
print(f"LOG_LEVEL: {config_module.LOG_LEVEL}")
|
|
|
|
| 89 |
|
| 90 |
with patch.dict(os.environ, {"LOG_LEVEL": "TRACE"}):
|
| 91 |
import importlib
|
| 92 |
+
import kiro.config as config_module
|
| 93 |
importlib.reload(config_module)
|
| 94 |
|
| 95 |
print(f"LOG_LEVEL: {config_module.LOG_LEVEL}")
|
|
|
|
| 104 |
|
| 105 |
with patch.dict(os.environ, {"LOG_LEVEL": "ERROR"}):
|
| 106 |
import importlib
|
| 107 |
+
import kiro.config as config_module
|
| 108 |
importlib.reload(config_module)
|
| 109 |
|
| 110 |
print(f"LOG_LEVEL: {config_module.LOG_LEVEL}")
|
|
|
|
| 119 |
|
| 120 |
with patch.dict(os.environ, {"LOG_LEVEL": "CRITICAL"}):
|
| 121 |
import importlib
|
| 122 |
+
import kiro.config as config_module
|
| 123 |
importlib.reload(config_module)
|
| 124 |
|
| 125 |
print(f"LOG_LEVEL: {config_module.LOG_LEVEL}")
|
|
|
|
| 141 |
del os.environ["TOOL_DESCRIPTION_MAX_LENGTH"]
|
| 142 |
|
| 143 |
import importlib
|
| 144 |
+
import kiro.config as config_module
|
| 145 |
importlib.reload(config_module)
|
| 146 |
|
| 147 |
print(f"TOOL_DESCRIPTION_MAX_LENGTH: {config_module.TOOL_DESCRIPTION_MAX_LENGTH}")
|
|
|
|
| 156 |
|
| 157 |
with patch.dict(os.environ, {"TOOL_DESCRIPTION_MAX_LENGTH": "5000"}):
|
| 158 |
import importlib
|
| 159 |
+
import kiro.config as config_module
|
| 160 |
importlib.reload(config_module)
|
| 161 |
|
| 162 |
print(f"TOOL_DESCRIPTION_MAX_LENGTH: {config_module.TOOL_DESCRIPTION_MAX_LENGTH}")
|
|
|
|
| 171 |
|
| 172 |
with patch.dict(os.environ, {"TOOL_DESCRIPTION_MAX_LENGTH": "0"}):
|
| 173 |
import importlib
|
| 174 |
+
import kiro.config as config_module
|
| 175 |
importlib.reload(config_module)
|
| 176 |
|
| 177 |
print(f"TOOL_DESCRIPTION_MAX_LENGTH: {config_module.TOOL_DESCRIPTION_MAX_LENGTH}")
|
|
|
|
| 193 |
"STREAMING_READ_TIMEOUT": "300"
|
| 194 |
}):
|
| 195 |
import importlib
|
| 196 |
+
import kiro.config as config_module
|
| 197 |
importlib.reload(config_module)
|
| 198 |
|
| 199 |
# Call the warning function
|
|
|
|
| 218 |
"STREAMING_READ_TIMEOUT": "300"
|
| 219 |
}):
|
| 220 |
import importlib
|
| 221 |
+
import kiro.config as config_module
|
| 222 |
importlib.reload(config_module)
|
| 223 |
|
| 224 |
# Call the warning function
|
|
|
|
| 242 |
"STREAMING_READ_TIMEOUT": "300"
|
| 243 |
}):
|
| 244 |
import importlib
|
| 245 |
+
import kiro.config as config_module
|
| 246 |
importlib.reload(config_module)
|
| 247 |
|
| 248 |
# Call the warning function
|
|
|
|
| 269 |
"STREAMING_READ_TIMEOUT": "300"
|
| 270 |
}):
|
| 271 |
import importlib
|
| 272 |
+
import kiro.config as config_module
|
| 273 |
importlib.reload(config_module)
|
| 274 |
|
| 275 |
# Call the warning function
|
|
|
|
| 292 |
"""
|
| 293 |
print("Setup: Importing config module...")
|
| 294 |
import importlib
|
| 295 |
+
import kiro.config as config_module
|
| 296 |
importlib.reload(config_module)
|
| 297 |
|
| 298 |
print("Verification: AWS_SSO_OIDC_URL_TEMPLATE exists...")
|
|
|
|
| 309 |
Purpose: Ensure the function formats URL correctly.
|
| 310 |
"""
|
| 311 |
print("Setup: Importing get_aws_sso_oidc_url...")
|
| 312 |
+
from kiro.config import get_aws_sso_oidc_url
|
| 313 |
|
| 314 |
print("Action: Calling get_aws_sso_oidc_url('us-east-1')...")
|
| 315 |
url = get_aws_sso_oidc_url("us-east-1")
|
|
|
|
| 325 |
Purpose: Ensure the function works with various AWS regions.
|
| 326 |
"""
|
| 327 |
print("Setup: Importing get_aws_sso_oidc_url...")
|
| 328 |
+
from kiro.config import get_aws_sso_oidc_url
|
| 329 |
|
| 330 |
test_cases = [
|
| 331 |
("us-east-1", "https://oidc.us-east-1.amazonaws.com/token"),
|
|
|
|
| 356 |
del os.environ["SERVER_HOST"]
|
| 357 |
|
| 358 |
import importlib
|
| 359 |
+
import kiro.config as config_module
|
| 360 |
importlib.reload(config_module)
|
| 361 |
|
| 362 |
print(f"SERVER_HOST: {config_module.SERVER_HOST}")
|
|
|
|
| 374 |
|
| 375 |
with patch.dict(os.environ, {"SERVER_HOST": "127.0.0.1"}):
|
| 376 |
import importlib
|
| 377 |
+
import kiro.config as config_module
|
| 378 |
importlib.reload(config_module)
|
| 379 |
|
| 380 |
print(f"SERVER_HOST: {config_module.SERVER_HOST}")
|
|
|
|
| 390 |
|
| 391 |
with patch.dict(os.environ, {"SERVER_HOST": "192.168.1.100"}):
|
| 392 |
import importlib
|
| 393 |
+
import kiro.config as config_module
|
| 394 |
importlib.reload(config_module)
|
| 395 |
|
| 396 |
print(f"SERVER_HOST: {config_module.SERVER_HOST}")
|
|
|
|
| 412 |
del os.environ["SERVER_PORT"]
|
| 413 |
|
| 414 |
import importlib
|
| 415 |
+
import kiro.config as config_module
|
| 416 |
importlib.reload(config_module)
|
| 417 |
|
| 418 |
print(f"SERVER_PORT: {config_module.SERVER_PORT}")
|
|
|
|
| 430 |
|
| 431 |
with patch.dict(os.environ, {"SERVER_PORT": "9000"}):
|
| 432 |
import importlib
|
| 433 |
+
import kiro.config as config_module
|
| 434 |
importlib.reload(config_module)
|
| 435 |
|
| 436 |
print(f"SERVER_PORT: {config_module.SERVER_PORT}")
|
|
|
|
| 446 |
|
| 447 |
with patch.dict(os.environ, {"SERVER_PORT": "3000"}):
|
| 448 |
import importlib
|
| 449 |
+
import kiro.config as config_module
|
| 450 |
importlib.reload(config_module)
|
| 451 |
|
| 452 |
print(f"SERVER_PORT: {config_module.SERVER_PORT}")
|
|
|
|
| 461 |
|
| 462 |
with patch.dict(os.environ, {"SERVER_PORT": "8080"}):
|
| 463 |
import importlib
|
| 464 |
+
import kiro.config as config_module
|
| 465 |
importlib.reload(config_module)
|
| 466 |
|
| 467 |
print(f"SERVER_PORT: {config_module.SERVER_PORT}")
|
|
|
|
| 480 |
"""
|
| 481 |
print("Setup: Importing config module...")
|
| 482 |
import importlib
|
| 483 |
+
import kiro.config as config_module
|
| 484 |
importlib.reload(config_module)
|
| 485 |
|
| 486 |
print("Verification: KIRO_CLI_DB_FILE exists...")
|
|
|
|
| 499 |
|
| 500 |
with patch.dict(os.environ, {"KIRO_CLI_DB_FILE": "~/.local/share/kiro-cli/data.sqlite3"}):
|
| 501 |
import importlib
|
| 502 |
+
import kiro.config as config_module
|
| 503 |
importlib.reload(config_module)
|
| 504 |
|
| 505 |
print(f"KIRO_CLI_DB_FILE: {config_module.KIRO_CLI_DB_FILE}")
|
tests/unit/test_converters.py
CHANGED
|
@@ -9,7 +9,7 @@ import pytest
|
|
| 9 |
|
| 10 |
from unittest.mock import patch
|
| 11 |
|
| 12 |
-
from
|
| 13 |
extract_text_content,
|
| 14 |
merge_adjacent_messages,
|
| 15 |
build_kiro_history,
|
|
@@ -21,7 +21,7 @@ from kiro_gateway.converters import (
|
|
| 21 |
_build_user_input_context,
|
| 22 |
_sanitize_json_schema
|
| 23 |
)
|
| 24 |
-
from
|
| 25 |
|
| 26 |
|
| 27 |
class TestExtractTextContent:
|
|
@@ -751,7 +751,7 @@ class TestProcessToolsWithLongDescriptions:
|
|
| 751 |
)]
|
| 752 |
|
| 753 |
print("Action: Processing tools...")
|
| 754 |
-
with patch('
|
| 755 |
processed, doc = process_tools_with_long_descriptions(tools)
|
| 756 |
|
| 757 |
print(f"Comparing description: Expected 'Get weather for a location', Got '{processed[0].function.description}'")
|
|
@@ -776,7 +776,7 @@ class TestProcessToolsWithLongDescriptions:
|
|
| 776 |
)]
|
| 777 |
|
| 778 |
print("Action: Processing tools with limit 10000...")
|
| 779 |
-
with patch('
|
| 780 |
processed, doc = process_tools_with_long_descriptions(tools)
|
| 781 |
|
| 782 |
print(f"Checking reference in description...")
|
|
@@ -816,7 +816,7 @@ class TestProcessToolsWithLongDescriptions:
|
|
| 816 |
]
|
| 817 |
|
| 818 |
print("Action: Processing tools...")
|
| 819 |
-
with patch('
|
| 820 |
processed, doc = process_tools_with_long_descriptions(tools)
|
| 821 |
|
| 822 |
print(f"Checking tools count: Expected 2, Got {len(processed)}")
|
|
@@ -854,7 +854,7 @@ class TestProcessToolsWithLongDescriptions:
|
|
| 854 |
)]
|
| 855 |
|
| 856 |
print("Action: Processing tools...")
|
| 857 |
-
with patch('
|
| 858 |
processed, doc = process_tools_with_long_descriptions(tools)
|
| 859 |
|
| 860 |
print(f"Checking parameters preservation...")
|
|
@@ -877,7 +877,7 @@ class TestProcessToolsWithLongDescriptions:
|
|
| 877 |
)]
|
| 878 |
|
| 879 |
print("Action: Processing tools with limit 0...")
|
| 880 |
-
with patch('
|
| 881 |
processed, doc = process_tools_with_long_descriptions(tools)
|
| 882 |
|
| 883 |
print(f"Checking that description is unchanged...")
|
|
@@ -901,7 +901,7 @@ class TestProcessToolsWithLongDescriptions:
|
|
| 901 |
)]
|
| 902 |
|
| 903 |
print("Action: Processing tools...")
|
| 904 |
-
with patch('
|
| 905 |
processed, doc = process_tools_with_long_descriptions(tools)
|
| 906 |
|
| 907 |
print(f"Checking that tool is unchanged...")
|
|
@@ -922,7 +922,7 @@ class TestProcessToolsWithLongDescriptions:
|
|
| 922 |
]
|
| 923 |
|
| 924 |
print("Action: Processing tools...")
|
| 925 |
-
with patch('
|
| 926 |
processed, doc = process_tools_with_long_descriptions(tools)
|
| 927 |
|
| 928 |
print(f"Checking all three tools...")
|
|
@@ -951,7 +951,7 @@ class TestProcessToolsWithLongDescriptions:
|
|
| 951 |
)]
|
| 952 |
|
| 953 |
print("Action: Processing tools...")
|
| 954 |
-
with patch('
|
| 955 |
processed, doc = process_tools_with_long_descriptions(tools)
|
| 956 |
|
| 957 |
print(f"Checking that empty description remains empty...")
|
|
@@ -974,7 +974,7 @@ class TestProcessToolsWithLongDescriptions:
|
|
| 974 |
)]
|
| 975 |
|
| 976 |
print("Action: Processing tools...")
|
| 977 |
-
with patch('
|
| 978 |
processed, doc = process_tools_with_long_descriptions(tools)
|
| 979 |
|
| 980 |
print(f"Checking that None description is handled correctly...")
|
|
@@ -1497,7 +1497,7 @@ class TestInjectThinkingTags:
|
|
| 1497 |
content = "Hello, world!"
|
| 1498 |
|
| 1499 |
print("Action: Inject thinking tags with FAKE_REASONING_ENABLED=False...")
|
| 1500 |
-
with patch('
|
| 1501 |
result = inject_thinking_tags(content)
|
| 1502 |
|
| 1503 |
print(f"Comparing result: Expected 'Hello, world!', Got '{result}'")
|
|
@@ -1512,8 +1512,8 @@ class TestInjectThinkingTags:
|
|
| 1512 |
content = "What is 2+2?"
|
| 1513 |
|
| 1514 |
print("Action: Inject thinking tags with FAKE_REASONING_ENABLED=True...")
|
| 1515 |
-
with patch('
|
| 1516 |
-
with patch('
|
| 1517 |
result = inject_thinking_tags(content)
|
| 1518 |
|
| 1519 |
print(f"Result: {result[:200]}...")
|
|
@@ -1535,8 +1535,8 @@ class TestInjectThinkingTags:
|
|
| 1535 |
content = "Analyze this code"
|
| 1536 |
|
| 1537 |
print("Action: Inject thinking tags...")
|
| 1538 |
-
with patch('
|
| 1539 |
-
with patch('
|
| 1540 |
result = inject_thinking_tags(content)
|
| 1541 |
|
| 1542 |
print(f"Result length: {len(result)} chars")
|
|
@@ -1553,8 +1553,8 @@ class TestInjectThinkingTags:
|
|
| 1553 |
content = "Test"
|
| 1554 |
|
| 1555 |
print("Action: Inject thinking tags...")
|
| 1556 |
-
with patch('
|
| 1557 |
-
with patch('
|
| 1558 |
result = inject_thinking_tags(content)
|
| 1559 |
|
| 1560 |
print("Checking for English directive...")
|
|
@@ -1569,8 +1569,8 @@ class TestInjectThinkingTags:
|
|
| 1569 |
content = "Test"
|
| 1570 |
|
| 1571 |
print("Action: Inject thinking tags...")
|
| 1572 |
-
with patch('
|
| 1573 |
-
with patch('
|
| 1574 |
result = inject_thinking_tags(content)
|
| 1575 |
|
| 1576 |
print("Checking for systematic approach keywords...")
|
|
@@ -1585,8 +1585,8 @@ class TestInjectThinkingTags:
|
|
| 1585 |
content = "Test"
|
| 1586 |
|
| 1587 |
print("Action: Inject thinking tags...")
|
| 1588 |
-
with patch('
|
| 1589 |
-
with patch('
|
| 1590 |
result = inject_thinking_tags(content)
|
| 1591 |
|
| 1592 |
print("Checking for understanding step...")
|
|
@@ -1601,8 +1601,8 @@ class TestInjectThinkingTags:
|
|
| 1601 |
content = "Test"
|
| 1602 |
|
| 1603 |
print("Action: Inject thinking tags...")
|
| 1604 |
-
with patch('
|
| 1605 |
-
with patch('
|
| 1606 |
result = inject_thinking_tags(content)
|
| 1607 |
|
| 1608 |
print("Checking for alternatives consideration...")
|
|
@@ -1617,8 +1617,8 @@ class TestInjectThinkingTags:
|
|
| 1617 |
content = "Test"
|
| 1618 |
|
| 1619 |
print("Action: Inject thinking tags...")
|
| 1620 |
-
with patch('
|
| 1621 |
-
with patch('
|
| 1622 |
result = inject_thinking_tags(content)
|
| 1623 |
|
| 1624 |
print("Checking for edge cases consideration...")
|
|
@@ -1633,8 +1633,8 @@ class TestInjectThinkingTags:
|
|
| 1633 |
content = "Test"
|
| 1634 |
|
| 1635 |
print("Action: Inject thinking tags...")
|
| 1636 |
-
with patch('
|
| 1637 |
-
with patch('
|
| 1638 |
result = inject_thinking_tags(content)
|
| 1639 |
|
| 1640 |
print("Checking for verification step...")
|
|
@@ -1649,8 +1649,8 @@ class TestInjectThinkingTags:
|
|
| 1649 |
content = "Test"
|
| 1650 |
|
| 1651 |
print("Action: Inject thinking tags...")
|
| 1652 |
-
with patch('
|
| 1653 |
-
with patch('
|
| 1654 |
result = inject_thinking_tags(content)
|
| 1655 |
|
| 1656 |
print("Checking for assumptions challenge...")
|
|
@@ -1665,8 +1665,8 @@ class TestInjectThinkingTags:
|
|
| 1665 |
content = "Test"
|
| 1666 |
|
| 1667 |
print("Action: Inject thinking tags...")
|
| 1668 |
-
with patch('
|
| 1669 |
-
with patch('
|
| 1670 |
result = inject_thinking_tags(content)
|
| 1671 |
|
| 1672 |
print("Checking for quality over speed emphasis...")
|
|
@@ -1681,8 +1681,8 @@ class TestInjectThinkingTags:
|
|
| 1681 |
content = "Test"
|
| 1682 |
|
| 1683 |
print("Action: Inject thinking tags with FAKE_REASONING_MAX_TOKENS=16000...")
|
| 1684 |
-
with patch('
|
| 1685 |
-
with patch('
|
| 1686 |
result = inject_thinking_tags(content)
|
| 1687 |
|
| 1688 |
print(f"Result: {result[:300]}...")
|
|
@@ -1698,8 +1698,8 @@ class TestInjectThinkingTags:
|
|
| 1698 |
content = ""
|
| 1699 |
|
| 1700 |
print("Action: Inject thinking tags...")
|
| 1701 |
-
with patch('
|
| 1702 |
-
with patch('
|
| 1703 |
result = inject_thinking_tags(content)
|
| 1704 |
|
| 1705 |
print(f"Result length: {len(result)} chars")
|
|
@@ -1716,8 +1716,8 @@ class TestInjectThinkingTags:
|
|
| 1716 |
content = "Line 1\nLine 2\nLine 3"
|
| 1717 |
|
| 1718 |
print("Action: Inject thinking tags...")
|
| 1719 |
-
with patch('
|
| 1720 |
-
with patch('
|
| 1721 |
result = inject_thinking_tags(content)
|
| 1722 |
|
| 1723 |
print("Checking that multiline content is preserved...")
|
|
@@ -1732,8 +1732,8 @@ class TestInjectThinkingTags:
|
|
| 1732 |
content = "Check this <code>example</code> and {json: 'value'}"
|
| 1733 |
|
| 1734 |
print("Action: Inject thinking tags...")
|
| 1735 |
-
with patch('
|
| 1736 |
-
with patch('
|
| 1737 |
result = inject_thinking_tags(content)
|
| 1738 |
|
| 1739 |
print("Checking that special characters are preserved...")
|
|
@@ -1749,8 +1749,8 @@ class TestInjectThinkingTags:
|
|
| 1749 |
content = "USER_CONTENT_HERE"
|
| 1750 |
|
| 1751 |
print("Action: Inject thinking tags...")
|
| 1752 |
-
with patch('
|
| 1753 |
-
with patch('
|
| 1754 |
result = inject_thinking_tags(content)
|
| 1755 |
|
| 1756 |
print("Checking tag order...")
|
|
@@ -1982,7 +1982,7 @@ class TestBuildKiroPayload:
|
|
| 1982 |
)
|
| 1983 |
|
| 1984 |
print("Action: Building payload (with fake reasoning disabled)...")
|
| 1985 |
-
with patch('
|
| 1986 |
result = build_kiro_payload(request, "conv-123", "")
|
| 1987 |
|
| 1988 |
print(f"Result: {result}")
|
|
@@ -2032,7 +2032,7 @@ class TestBuildKiroPayload:
|
|
| 2032 |
)
|
| 2033 |
|
| 2034 |
print("Action: Building payload...")
|
| 2035 |
-
with patch('
|
| 2036 |
result = build_kiro_payload(request, "conv-123", "")
|
| 2037 |
|
| 2038 |
print(f"Checking that system prompt contains tool documentation...")
|
|
@@ -2074,8 +2074,8 @@ class TestBuildKiroPayload:
|
|
| 2074 |
)
|
| 2075 |
|
| 2076 |
print("Action: Building payload with FAKE_REASONING_ENABLED=True...")
|
| 2077 |
-
with patch('
|
| 2078 |
-
with patch('
|
| 2079 |
result = build_kiro_payload(request, "conv-123", "")
|
| 2080 |
|
| 2081 |
current_msg = result["conversationState"]["currentMessage"]["userInputMessage"]
|
|
@@ -2101,8 +2101,8 @@ class TestBuildKiroPayload:
|
|
| 2101 |
)
|
| 2102 |
|
| 2103 |
print("Action: Building payload with FAKE_REASONING_ENABLED=True...")
|
| 2104 |
-
with patch('
|
| 2105 |
-
with patch('
|
| 2106 |
result = build_kiro_payload(request, "conv-123", "")
|
| 2107 |
|
| 2108 |
current_msg = result["conversationState"]["currentMessage"]["userInputMessage"]
|
|
|
|
| 9 |
|
| 10 |
from unittest.mock import patch
|
| 11 |
|
| 12 |
+
from kiro.converters import (
|
| 13 |
extract_text_content,
|
| 14 |
merge_adjacent_messages,
|
| 15 |
build_kiro_history,
|
|
|
|
| 21 |
_build_user_input_context,
|
| 22 |
_sanitize_json_schema
|
| 23 |
)
|
| 24 |
+
from kiro.models import ChatMessage, ChatCompletionRequest, Tool, ToolFunction
|
| 25 |
|
| 26 |
|
| 27 |
class TestExtractTextContent:
|
|
|
|
| 751 |
)]
|
| 752 |
|
| 753 |
print("Action: Processing tools...")
|
| 754 |
+
with patch('kiro.converters.TOOL_DESCRIPTION_MAX_LENGTH', 10000):
|
| 755 |
processed, doc = process_tools_with_long_descriptions(tools)
|
| 756 |
|
| 757 |
print(f"Comparing description: Expected 'Get weather for a location', Got '{processed[0].function.description}'")
|
|
|
|
| 776 |
)]
|
| 777 |
|
| 778 |
print("Action: Processing tools with limit 10000...")
|
| 779 |
+
with patch('kiro.converters.TOOL_DESCRIPTION_MAX_LENGTH', 10000):
|
| 780 |
processed, doc = process_tools_with_long_descriptions(tools)
|
| 781 |
|
| 782 |
print(f"Checking reference in description...")
|
|
|
|
| 816 |
]
|
| 817 |
|
| 818 |
print("Action: Processing tools...")
|
| 819 |
+
with patch('kiro.converters.TOOL_DESCRIPTION_MAX_LENGTH', 10000):
|
| 820 |
processed, doc = process_tools_with_long_descriptions(tools)
|
| 821 |
|
| 822 |
print(f"Checking tools count: Expected 2, Got {len(processed)}")
|
|
|
|
| 854 |
)]
|
| 855 |
|
| 856 |
print("Action: Processing tools...")
|
| 857 |
+
with patch('kiro.converters.TOOL_DESCRIPTION_MAX_LENGTH', 10000):
|
| 858 |
processed, doc = process_tools_with_long_descriptions(tools)
|
| 859 |
|
| 860 |
print(f"Checking parameters preservation...")
|
|
|
|
| 877 |
)]
|
| 878 |
|
| 879 |
print("Action: Processing tools with limit 0...")
|
| 880 |
+
with patch('kiro.converters.TOOL_DESCRIPTION_MAX_LENGTH', 0):
|
| 881 |
processed, doc = process_tools_with_long_descriptions(tools)
|
| 882 |
|
| 883 |
print(f"Checking that description is unchanged...")
|
|
|
|
| 901 |
)]
|
| 902 |
|
| 903 |
print("Action: Processing tools...")
|
| 904 |
+
with patch('kiro.converters.TOOL_DESCRIPTION_MAX_LENGTH', 10000):
|
| 905 |
processed, doc = process_tools_with_long_descriptions(tools)
|
| 906 |
|
| 907 |
print(f"Checking that tool is unchanged...")
|
|
|
|
| 922 |
]
|
| 923 |
|
| 924 |
print("Action: Processing tools...")
|
| 925 |
+
with patch('kiro.converters.TOOL_DESCRIPTION_MAX_LENGTH', 10000):
|
| 926 |
processed, doc = process_tools_with_long_descriptions(tools)
|
| 927 |
|
| 928 |
print(f"Checking all three tools...")
|
|
|
|
| 951 |
)]
|
| 952 |
|
| 953 |
print("Action: Processing tools...")
|
| 954 |
+
with patch('kiro.converters.TOOL_DESCRIPTION_MAX_LENGTH', 10000):
|
| 955 |
processed, doc = process_tools_with_long_descriptions(tools)
|
| 956 |
|
| 957 |
print(f"Checking that empty description remains empty...")
|
|
|
|
| 974 |
)]
|
| 975 |
|
| 976 |
print("Action: Processing tools...")
|
| 977 |
+
with patch('kiro.converters.TOOL_DESCRIPTION_MAX_LENGTH', 10000):
|
| 978 |
processed, doc = process_tools_with_long_descriptions(tools)
|
| 979 |
|
| 980 |
print(f"Checking that None description is handled correctly...")
|
|
|
|
| 1497 |
content = "Hello, world!"
|
| 1498 |
|
| 1499 |
print("Action: Inject thinking tags with FAKE_REASONING_ENABLED=False...")
|
| 1500 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', False):
|
| 1501 |
result = inject_thinking_tags(content)
|
| 1502 |
|
| 1503 |
print(f"Comparing result: Expected 'Hello, world!', Got '{result}'")
|
|
|
|
| 1512 |
content = "What is 2+2?"
|
| 1513 |
|
| 1514 |
print("Action: Inject thinking tags with FAKE_REASONING_ENABLED=True...")
|
| 1515 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', True):
|
| 1516 |
+
with patch('kiro.converters.FAKE_REASONING_MAX_TOKENS', 4000):
|
| 1517 |
result = inject_thinking_tags(content)
|
| 1518 |
|
| 1519 |
print(f"Result: {result[:200]}...")
|
|
|
|
| 1535 |
content = "Analyze this code"
|
| 1536 |
|
| 1537 |
print("Action: Inject thinking tags...")
|
| 1538 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', True):
|
| 1539 |
+
with patch('kiro.converters.FAKE_REASONING_MAX_TOKENS', 8000):
|
| 1540 |
result = inject_thinking_tags(content)
|
| 1541 |
|
| 1542 |
print(f"Result length: {len(result)} chars")
|
|
|
|
| 1553 |
content = "Test"
|
| 1554 |
|
| 1555 |
print("Action: Inject thinking tags...")
|
| 1556 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', True):
|
| 1557 |
+
with patch('kiro.converters.FAKE_REASONING_MAX_TOKENS', 4000):
|
| 1558 |
result = inject_thinking_tags(content)
|
| 1559 |
|
| 1560 |
print("Checking for English directive...")
|
|
|
|
| 1569 |
content = "Test"
|
| 1570 |
|
| 1571 |
print("Action: Inject thinking tags...")
|
| 1572 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', True):
|
| 1573 |
+
with patch('kiro.converters.FAKE_REASONING_MAX_TOKENS', 4000):
|
| 1574 |
result = inject_thinking_tags(content)
|
| 1575 |
|
| 1576 |
print("Checking for systematic approach keywords...")
|
|
|
|
| 1585 |
content = "Test"
|
| 1586 |
|
| 1587 |
print("Action: Inject thinking tags...")
|
| 1588 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', True):
|
| 1589 |
+
with patch('kiro.converters.FAKE_REASONING_MAX_TOKENS', 4000):
|
| 1590 |
result = inject_thinking_tags(content)
|
| 1591 |
|
| 1592 |
print("Checking for understanding step...")
|
|
|
|
| 1601 |
content = "Test"
|
| 1602 |
|
| 1603 |
print("Action: Inject thinking tags...")
|
| 1604 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', True):
|
| 1605 |
+
with patch('kiro.converters.FAKE_REASONING_MAX_TOKENS', 4000):
|
| 1606 |
result = inject_thinking_tags(content)
|
| 1607 |
|
| 1608 |
print("Checking for alternatives consideration...")
|
|
|
|
| 1617 |
content = "Test"
|
| 1618 |
|
| 1619 |
print("Action: Inject thinking tags...")
|
| 1620 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', True):
|
| 1621 |
+
with patch('kiro.converters.FAKE_REASONING_MAX_TOKENS', 4000):
|
| 1622 |
result = inject_thinking_tags(content)
|
| 1623 |
|
| 1624 |
print("Checking for edge cases consideration...")
|
|
|
|
| 1633 |
content = "Test"
|
| 1634 |
|
| 1635 |
print("Action: Inject thinking tags...")
|
| 1636 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', True):
|
| 1637 |
+
with patch('kiro.converters.FAKE_REASONING_MAX_TOKENS', 4000):
|
| 1638 |
result = inject_thinking_tags(content)
|
| 1639 |
|
| 1640 |
print("Checking for verification step...")
|
|
|
|
| 1649 |
content = "Test"
|
| 1650 |
|
| 1651 |
print("Action: Inject thinking tags...")
|
| 1652 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', True):
|
| 1653 |
+
with patch('kiro.converters.FAKE_REASONING_MAX_TOKENS', 4000):
|
| 1654 |
result = inject_thinking_tags(content)
|
| 1655 |
|
| 1656 |
print("Checking for assumptions challenge...")
|
|
|
|
| 1665 |
content = "Test"
|
| 1666 |
|
| 1667 |
print("Action: Inject thinking tags...")
|
| 1668 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', True):
|
| 1669 |
+
with patch('kiro.converters.FAKE_REASONING_MAX_TOKENS', 4000):
|
| 1670 |
result = inject_thinking_tags(content)
|
| 1671 |
|
| 1672 |
print("Checking for quality over speed emphasis...")
|
|
|
|
| 1681 |
content = "Test"
|
| 1682 |
|
| 1683 |
print("Action: Inject thinking tags with FAKE_REASONING_MAX_TOKENS=16000...")
|
| 1684 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', True):
|
| 1685 |
+
with patch('kiro.converters.FAKE_REASONING_MAX_TOKENS', 16000):
|
| 1686 |
result = inject_thinking_tags(content)
|
| 1687 |
|
| 1688 |
print(f"Result: {result[:300]}...")
|
|
|
|
| 1698 |
content = ""
|
| 1699 |
|
| 1700 |
print("Action: Inject thinking tags...")
|
| 1701 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', True):
|
| 1702 |
+
with patch('kiro.converters.FAKE_REASONING_MAX_TOKENS', 4000):
|
| 1703 |
result = inject_thinking_tags(content)
|
| 1704 |
|
| 1705 |
print(f"Result length: {len(result)} chars")
|
|
|
|
| 1716 |
content = "Line 1\nLine 2\nLine 3"
|
| 1717 |
|
| 1718 |
print("Action: Inject thinking tags...")
|
| 1719 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', True):
|
| 1720 |
+
with patch('kiro.converters.FAKE_REASONING_MAX_TOKENS', 4000):
|
| 1721 |
result = inject_thinking_tags(content)
|
| 1722 |
|
| 1723 |
print("Checking that multiline content is preserved...")
|
|
|
|
| 1732 |
content = "Check this <code>example</code> and {json: 'value'}"
|
| 1733 |
|
| 1734 |
print("Action: Inject thinking tags...")
|
| 1735 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', True):
|
| 1736 |
+
with patch('kiro.converters.FAKE_REASONING_MAX_TOKENS', 4000):
|
| 1737 |
result = inject_thinking_tags(content)
|
| 1738 |
|
| 1739 |
print("Checking that special characters are preserved...")
|
|
|
|
| 1749 |
content = "USER_CONTENT_HERE"
|
| 1750 |
|
| 1751 |
print("Action: Inject thinking tags...")
|
| 1752 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', True):
|
| 1753 |
+
with patch('kiro.converters.FAKE_REASONING_MAX_TOKENS', 4000):
|
| 1754 |
result = inject_thinking_tags(content)
|
| 1755 |
|
| 1756 |
print("Checking tag order...")
|
|
|
|
| 1982 |
)
|
| 1983 |
|
| 1984 |
print("Action: Building payload (with fake reasoning disabled)...")
|
| 1985 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', False):
|
| 1986 |
result = build_kiro_payload(request, "conv-123", "")
|
| 1987 |
|
| 1988 |
print(f"Result: {result}")
|
|
|
|
| 2032 |
)
|
| 2033 |
|
| 2034 |
print("Action: Building payload...")
|
| 2035 |
+
with patch('kiro.converters.TOOL_DESCRIPTION_MAX_LENGTH', 10000):
|
| 2036 |
result = build_kiro_payload(request, "conv-123", "")
|
| 2037 |
|
| 2038 |
print(f"Checking that system prompt contains tool documentation...")
|
|
|
|
| 2074 |
)
|
| 2075 |
|
| 2076 |
print("Action: Building payload with FAKE_REASONING_ENABLED=True...")
|
| 2077 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', True):
|
| 2078 |
+
with patch('kiro.converters.FAKE_REASONING_MAX_TOKENS', 4000):
|
| 2079 |
result = build_kiro_payload(request, "conv-123", "")
|
| 2080 |
|
| 2081 |
current_msg = result["conversationState"]["currentMessage"]["userInputMessage"]
|
|
|
|
| 2101 |
)
|
| 2102 |
|
| 2103 |
print("Action: Building payload with FAKE_REASONING_ENABLED=True...")
|
| 2104 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', True):
|
| 2105 |
+
with patch('kiro.converters.FAKE_REASONING_MAX_TOKENS', 4000):
|
| 2106 |
result = build_kiro_payload(request, "conv-123", "")
|
| 2107 |
|
| 2108 |
current_msg = result["conversationState"]["currentMessage"]["userInputMessage"]
|
tests/unit/test_debug_logger.py
CHANGED
|
@@ -20,10 +20,10 @@ class TestDebugLoggerModeOff:
|
|
| 20 |
Цель: Убедиться, что в режиме off директория не создаётся.
|
| 21 |
"""
|
| 22 |
print("Настройка: Режим off...")
|
| 23 |
-
with patch('
|
| 24 |
-
with patch('
|
| 25 |
# Пересоздаём экземпляр с новыми настройками
|
| 26 |
-
from
|
| 27 |
logger = DebugLogger.__new__(DebugLogger)
|
| 28 |
logger._initialized = False
|
| 29 |
logger.__init__()
|
|
@@ -41,8 +41,8 @@ class TestDebugLoggerModeOff:
|
|
| 41 |
Цель: Убедиться, что данные не записываются.
|
| 42 |
"""
|
| 43 |
print("Настройка: Режим off...")
|
| 44 |
-
with patch('
|
| 45 |
-
from
|
| 46 |
logger = DebugLogger.__new__(DebugLogger)
|
| 47 |
logger._initialized = False
|
| 48 |
logger.__init__()
|
|
@@ -69,8 +69,8 @@ class TestDebugLoggerModeAll:
|
|
| 69 |
old_file = debug_dir / "old_file.txt"
|
| 70 |
old_file.write_text("old content")
|
| 71 |
|
| 72 |
-
with patch('
|
| 73 |
-
from
|
| 74 |
logger = DebugLogger.__new__(DebugLogger)
|
| 75 |
logger._initialized = False
|
| 76 |
logger.__init__()
|
|
@@ -93,8 +93,8 @@ class TestDebugLoggerModeAll:
|
|
| 93 |
debug_dir = tmp_path / "debug_logs"
|
| 94 |
debug_dir.mkdir()
|
| 95 |
|
| 96 |
-
with patch('
|
| 97 |
-
from
|
| 98 |
logger = DebugLogger.__new__(DebugLogger)
|
| 99 |
logger._initialized = False
|
| 100 |
logger.__init__()
|
|
@@ -121,8 +121,8 @@ class TestDebugLoggerModeAll:
|
|
| 121 |
debug_dir = tmp_path / "debug_logs"
|
| 122 |
debug_dir.mkdir()
|
| 123 |
|
| 124 |
-
with patch('
|
| 125 |
-
from
|
| 126 |
logger = DebugLogger.__new__(DebugLogger)
|
| 127 |
logger._initialized = False
|
| 128 |
logger.__init__()
|
|
@@ -145,8 +145,8 @@ class TestDebugLoggerModeAll:
|
|
| 145 |
debug_dir = tmp_path / "debug_logs"
|
| 146 |
debug_dir.mkdir()
|
| 147 |
|
| 148 |
-
with patch('
|
| 149 |
-
from
|
| 150 |
logger = DebugLogger.__new__(DebugLogger)
|
| 151 |
logger._initialized = False
|
| 152 |
logger.__init__()
|
|
@@ -173,8 +173,8 @@ class TestDebugLoggerModeErrors:
|
|
| 173 |
print("Настройка: Режим errors...")
|
| 174 |
debug_dir = tmp_path / "debug_logs"
|
| 175 |
|
| 176 |
-
with patch('
|
| 177 |
-
from
|
| 178 |
logger = DebugLogger.__new__(DebugLogger)
|
| 179 |
logger._initialized = False
|
| 180 |
logger.__init__()
|
|
@@ -198,8 +198,8 @@ class TestDebugLoggerModeErrors:
|
|
| 198 |
print("Настройка: Режим errors, заполняем буферы...")
|
| 199 |
debug_dir = tmp_path / "debug_logs"
|
| 200 |
|
| 201 |
-
with patch('
|
| 202 |
-
from
|
| 203 |
logger = DebugLogger.__new__(DebugLogger)
|
| 204 |
logger._initialized = False
|
| 205 |
logger.__init__()
|
|
@@ -234,8 +234,8 @@ class TestDebugLoggerModeErrors:
|
|
| 234 |
print("Настройка: Режим errors...")
|
| 235 |
debug_dir = tmp_path / "debug_logs"
|
| 236 |
|
| 237 |
-
with patch('
|
| 238 |
-
from
|
| 239 |
logger = DebugLogger.__new__(DebugLogger)
|
| 240 |
logger._initialized = False
|
| 241 |
logger.__init__()
|
|
@@ -260,8 +260,8 @@ class TestDebugLoggerModeErrors:
|
|
| 260 |
print("Настройка: Режим errors, заполняем буферы...")
|
| 261 |
debug_dir = tmp_path / "debug_logs"
|
| 262 |
|
| 263 |
-
with patch('
|
| 264 |
-
from
|
| 265 |
logger = DebugLogger.__new__(DebugLogger)
|
| 266 |
logger._initialized = False
|
| 267 |
logger.__init__()
|
|
@@ -288,8 +288,8 @@ class TestDebugLoggerModeErrors:
|
|
| 288 |
print("Настройка: Режим all...")
|
| 289 |
debug_dir = tmp_path / "debug_logs"
|
| 290 |
|
| 291 |
-
with patch('
|
| 292 |
-
from
|
| 293 |
logger = DebugLogger.__new__(DebugLogger)
|
| 294 |
logger._initialized = False
|
| 295 |
logger.__init__()
|
|
@@ -318,8 +318,8 @@ class TestDebugLoggerLogErrorInfo:
|
|
| 318 |
print("Настройка: Режим all...")
|
| 319 |
debug_dir = tmp_path / "debug_logs"
|
| 320 |
|
| 321 |
-
with patch('
|
| 322 |
-
from
|
| 323 |
logger = DebugLogger.__new__(DebugLogger)
|
| 324 |
logger._initialized = False
|
| 325 |
logger.__init__()
|
|
@@ -345,8 +345,8 @@ class TestDebugLoggerLogErrorInfo:
|
|
| 345 |
print("Настройка: Режим errors...")
|
| 346 |
debug_dir = tmp_path / "debug_logs"
|
| 347 |
|
| 348 |
-
with patch('
|
| 349 |
-
from
|
| 350 |
logger = DebugLogger.__new__(DebugLogger)
|
| 351 |
logger._initialized = False
|
| 352 |
logger.__init__()
|
|
@@ -367,8 +367,8 @@ class TestDebugLoggerLogErrorInfo:
|
|
| 367 |
print("Настройка: Режим off...")
|
| 368 |
debug_dir = tmp_path / "debug_logs"
|
| 369 |
|
| 370 |
-
with patch('
|
| 371 |
-
from
|
| 372 |
logger = DebugLogger.__new__(DebugLogger)
|
| 373 |
logger._initialized = False
|
| 374 |
logger.__init__()
|
|
@@ -390,8 +390,8 @@ class TestDebugLoggerHelperMethods:
|
|
| 390 |
Цель: Убедиться, что режим errors считается включённым.
|
| 391 |
"""
|
| 392 |
print("Настройка: Режим errors...")
|
| 393 |
-
with patch('
|
| 394 |
-
from
|
| 395 |
logger = DebugLogger.__new__(DebugLogger)
|
| 396 |
logger._initialized = False
|
| 397 |
logger.__init__()
|
|
@@ -405,8 +405,8 @@ class TestDebugLoggerHelperMethods:
|
|
| 405 |
Цель: Убедиться, что режим all считается включённым.
|
| 406 |
"""
|
| 407 |
print("Настройка: Режим all...")
|
| 408 |
-
with patch('
|
| 409 |
-
from
|
| 410 |
logger = DebugLogger.__new__(DebugLogger)
|
| 411 |
logger._initialized = False
|
| 412 |
logger.__init__()
|
|
@@ -420,8 +420,8 @@ class TestDebugLoggerHelperMethods:
|
|
| 420 |
Цель: Убедиться, что режим off считается выключенным.
|
| 421 |
"""
|
| 422 |
print("Настройка: Режим off...")
|
| 423 |
-
with patch('
|
| 424 |
-
from
|
| 425 |
logger = DebugLogger.__new__(DebugLogger)
|
| 426 |
logger._initialized = False
|
| 427 |
logger.__init__()
|
|
@@ -435,8 +435,8 @@ class TestDebugLoggerHelperMethods:
|
|
| 435 |
Цель: Убедиться, что режим all пишет сразу.
|
| 436 |
"""
|
| 437 |
print("Настройка: Режим all...")
|
| 438 |
-
with patch('
|
| 439 |
-
from
|
| 440 |
logger = DebugLogger.__new__(DebugLogger)
|
| 441 |
logger._initialized = False
|
| 442 |
logger.__init__()
|
|
@@ -450,8 +450,8 @@ class TestDebugLoggerHelperMethods:
|
|
| 450 |
Цель: Убедиться, что режим errors буферизует.
|
| 451 |
"""
|
| 452 |
print("Настройка: Режим errors...")
|
| 453 |
-
with patch('
|
| 454 |
-
from
|
| 455 |
logger = DebugLogger.__new__(DebugLogger)
|
| 456 |
logger._initialized = False
|
| 457 |
logger.__init__()
|
|
@@ -472,8 +472,8 @@ class TestDebugLoggerJsonHandling:
|
|
| 472 |
debug_dir = tmp_path / "debug_logs"
|
| 473 |
debug_dir.mkdir()
|
| 474 |
|
| 475 |
-
with patch('
|
| 476 |
-
from
|
| 477 |
logger = DebugLogger.__new__(DebugLogger)
|
| 478 |
logger._initialized = False
|
| 479 |
logger.__init__()
|
|
@@ -496,8 +496,8 @@ class TestDebugLoggerJsonHandling:
|
|
| 496 |
debug_dir = tmp_path / "debug_logs"
|
| 497 |
debug_dir.mkdir()
|
| 498 |
|
| 499 |
-
with patch('
|
| 500 |
-
from
|
| 501 |
logger = DebugLogger.__new__(DebugLogger)
|
| 502 |
logger._initialized = False
|
| 503 |
logger.__init__()
|
|
@@ -523,8 +523,8 @@ class TestDebugLoggerAppLogsCapture:
|
|
| 523 |
print("Настройка: Режим all...")
|
| 524 |
debug_dir = tmp_path / "debug_logs"
|
| 525 |
|
| 526 |
-
with patch('
|
| 527 |
-
from
|
| 528 |
dbg_logger = DebugLogger.__new__(DebugLogger)
|
| 529 |
dbg_logger._initialized = False
|
| 530 |
dbg_logger.__init__()
|
|
@@ -547,8 +547,8 @@ class TestDebugLoggerAppLogsCapture:
|
|
| 547 |
print("Настройка: Режим errors...")
|
| 548 |
debug_dir = tmp_path / "debug_logs"
|
| 549 |
|
| 550 |
-
with patch('
|
| 551 |
-
from
|
| 552 |
from loguru import logger as loguru_logger
|
| 553 |
|
| 554 |
dbg_logger = DebugLogger.__new__(DebugLogger)
|
|
@@ -585,8 +585,8 @@ class TestDebugLoggerAppLogsCapture:
|
|
| 585 |
debug_dir = tmp_path / "debug_logs"
|
| 586 |
debug_dir.mkdir()
|
| 587 |
|
| 588 |
-
with patch('
|
| 589 |
-
from
|
| 590 |
|
| 591 |
dbg_logger = DebugLogger.__new__(DebugLogger)
|
| 592 |
dbg_logger._initialized = False
|
|
@@ -618,8 +618,8 @@ class TestDebugLoggerAppLogsCapture:
|
|
| 618 |
print("Настройка: Режим errors...")
|
| 619 |
debug_dir = tmp_path / "debug_logs"
|
| 620 |
|
| 621 |
-
with patch('
|
| 622 |
-
from
|
| 623 |
|
| 624 |
dbg_logger = DebugLogger.__new__(DebugLogger)
|
| 625 |
dbg_logger._initialized = False
|
|
@@ -644,8 +644,8 @@ class TestDebugLoggerAppLogsCapture:
|
|
| 644 |
Цель: Убедиться, что sink корректно удаляется.
|
| 645 |
"""
|
| 646 |
print("Настройка: Режим all...")
|
| 647 |
-
with patch('
|
| 648 |
-
from
|
| 649 |
|
| 650 |
dbg_logger = DebugLogger.__new__(DebugLogger)
|
| 651 |
dbg_logger._initialized = False
|
|
@@ -672,8 +672,8 @@ class TestDebugLoggerAppLogsCapture:
|
|
| 672 |
debug_dir = tmp_path / "debug_logs"
|
| 673 |
debug_dir.mkdir()
|
| 674 |
|
| 675 |
-
with patch('
|
| 676 |
-
from
|
| 677 |
|
| 678 |
dbg_logger = DebugLogger.__new__(DebugLogger)
|
| 679 |
dbg_logger._initialized = False
|
|
|
|
| 20 |
Цель: Убедиться, что в режиме off директория не создаётся.
|
| 21 |
"""
|
| 22 |
print("Настройка: Режим off...")
|
| 23 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'off'):
|
| 24 |
+
with patch('kiro.debug_logger.DEBUG_DIR', str(tmp_path / "debug_logs")):
|
| 25 |
# Пересоздаём экземпляр с новыми настройками
|
| 26 |
+
from kiro.debug_logger import DebugLogger
|
| 27 |
logger = DebugLogger.__new__(DebugLogger)
|
| 28 |
logger._initialized = False
|
| 29 |
logger.__init__()
|
|
|
|
| 41 |
Цель: Убедиться, что данные не записываются.
|
| 42 |
"""
|
| 43 |
print("Настройка: Режим off...")
|
| 44 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'off'):
|
| 45 |
+
from kiro.debug_logger import DebugLogger
|
| 46 |
logger = DebugLogger.__new__(DebugLogger)
|
| 47 |
logger._initialized = False
|
| 48 |
logger.__init__()
|
|
|
|
| 69 |
old_file = debug_dir / "old_file.txt"
|
| 70 |
old_file.write_text("old content")
|
| 71 |
|
| 72 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'all'):
|
| 73 |
+
from kiro.debug_logger import DebugLogger
|
| 74 |
logger = DebugLogger.__new__(DebugLogger)
|
| 75 |
logger._initialized = False
|
| 76 |
logger.__init__()
|
|
|
|
| 93 |
debug_dir = tmp_path / "debug_logs"
|
| 94 |
debug_dir.mkdir()
|
| 95 |
|
| 96 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'all'):
|
| 97 |
+
from kiro.debug_logger import DebugLogger
|
| 98 |
logger = DebugLogger.__new__(DebugLogger)
|
| 99 |
logger._initialized = False
|
| 100 |
logger.__init__()
|
|
|
|
| 121 |
debug_dir = tmp_path / "debug_logs"
|
| 122 |
debug_dir.mkdir()
|
| 123 |
|
| 124 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'all'):
|
| 125 |
+
from kiro.debug_logger import DebugLogger
|
| 126 |
logger = DebugLogger.__new__(DebugLogger)
|
| 127 |
logger._initialized = False
|
| 128 |
logger.__init__()
|
|
|
|
| 145 |
debug_dir = tmp_path / "debug_logs"
|
| 146 |
debug_dir.mkdir()
|
| 147 |
|
| 148 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'all'):
|
| 149 |
+
from kiro.debug_logger import DebugLogger
|
| 150 |
logger = DebugLogger.__new__(DebugLogger)
|
| 151 |
logger._initialized = False
|
| 152 |
logger.__init__()
|
|
|
|
| 173 |
print("Настройка: Режим errors...")
|
| 174 |
debug_dir = tmp_path / "debug_logs"
|
| 175 |
|
| 176 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'errors'):
|
| 177 |
+
from kiro.debug_logger import DebugLogger
|
| 178 |
logger = DebugLogger.__new__(DebugLogger)
|
| 179 |
logger._initialized = False
|
| 180 |
logger.__init__()
|
|
|
|
| 198 |
print("Настройка: Режим errors, заполняем буферы...")
|
| 199 |
debug_dir = tmp_path / "debug_logs"
|
| 200 |
|
| 201 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'errors'):
|
| 202 |
+
from kiro.debug_logger import DebugLogger
|
| 203 |
logger = DebugLogger.__new__(DebugLogger)
|
| 204 |
logger._initialized = False
|
| 205 |
logger.__init__()
|
|
|
|
| 234 |
print("Настройка: Режим errors...")
|
| 235 |
debug_dir = tmp_path / "debug_logs"
|
| 236 |
|
| 237 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'errors'):
|
| 238 |
+
from kiro.debug_logger import DebugLogger
|
| 239 |
logger = DebugLogger.__new__(DebugLogger)
|
| 240 |
logger._initialized = False
|
| 241 |
logger.__init__()
|
|
|
|
| 260 |
print("Настройка: Режим errors, заполняем буферы...")
|
| 261 |
debug_dir = tmp_path / "debug_logs"
|
| 262 |
|
| 263 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'errors'):
|
| 264 |
+
from kiro.debug_logger import DebugLogger
|
| 265 |
logger = DebugLogger.__new__(DebugLogger)
|
| 266 |
logger._initialized = False
|
| 267 |
logger.__init__()
|
|
|
|
| 288 |
print("Настройка: Режим all...")
|
| 289 |
debug_dir = tmp_path / "debug_logs"
|
| 290 |
|
| 291 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'all'):
|
| 292 |
+
from kiro.debug_logger import DebugLogger
|
| 293 |
logger = DebugLogger.__new__(DebugLogger)
|
| 294 |
logger._initialized = False
|
| 295 |
logger.__init__()
|
|
|
|
| 318 |
print("Настройка: Режим all...")
|
| 319 |
debug_dir = tmp_path / "debug_logs"
|
| 320 |
|
| 321 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'all'):
|
| 322 |
+
from kiro.debug_logger import DebugLogger
|
| 323 |
logger = DebugLogger.__new__(DebugLogger)
|
| 324 |
logger._initialized = False
|
| 325 |
logger.__init__()
|
|
|
|
| 345 |
print("Настройка: Режим errors...")
|
| 346 |
debug_dir = tmp_path / "debug_logs"
|
| 347 |
|
| 348 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'errors'):
|
| 349 |
+
from kiro.debug_logger import DebugLogger
|
| 350 |
logger = DebugLogger.__new__(DebugLogger)
|
| 351 |
logger._initialized = False
|
| 352 |
logger.__init__()
|
|
|
|
| 367 |
print("Настройка: Режим off...")
|
| 368 |
debug_dir = tmp_path / "debug_logs"
|
| 369 |
|
| 370 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'off'):
|
| 371 |
+
from kiro.debug_logger import DebugLogger
|
| 372 |
logger = DebugLogger.__new__(DebugLogger)
|
| 373 |
logger._initialized = False
|
| 374 |
logger.__init__()
|
|
|
|
| 390 |
Цель: Убедиться, что режим errors считается включённым.
|
| 391 |
"""
|
| 392 |
print("Настройка: Режим errors...")
|
| 393 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'errors'):
|
| 394 |
+
from kiro.debug_logger import DebugLogger
|
| 395 |
logger = DebugLogger.__new__(DebugLogger)
|
| 396 |
logger._initialized = False
|
| 397 |
logger.__init__()
|
|
|
|
| 405 |
Цель: Убедиться, что режим all считается включённым.
|
| 406 |
"""
|
| 407 |
print("Настройка: Режим all...")
|
| 408 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'all'):
|
| 409 |
+
from kiro.debug_logger import DebugLogger
|
| 410 |
logger = DebugLogger.__new__(DebugLogger)
|
| 411 |
logger._initialized = False
|
| 412 |
logger.__init__()
|
|
|
|
| 420 |
Цель: Убедиться, что режим off считается выключенным.
|
| 421 |
"""
|
| 422 |
print("Настройка: Режим off...")
|
| 423 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'off'):
|
| 424 |
+
from kiro.debug_logger import DebugLogger
|
| 425 |
logger = DebugLogger.__new__(DebugLogger)
|
| 426 |
logger._initialized = False
|
| 427 |
logger.__init__()
|
|
|
|
| 435 |
Цель: Убедиться, что режим all пишет сразу.
|
| 436 |
"""
|
| 437 |
print("Настройка: Режим all...")
|
| 438 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'all'):
|
| 439 |
+
from kiro.debug_logger import DebugLogger
|
| 440 |
logger = DebugLogger.__new__(DebugLogger)
|
| 441 |
logger._initialized = False
|
| 442 |
logger.__init__()
|
|
|
|
| 450 |
Цель: Убедиться, что режим errors буферизует.
|
| 451 |
"""
|
| 452 |
print("Настройка: Режим errors...")
|
| 453 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'errors'):
|
| 454 |
+
from kiro.debug_logger import DebugLogger
|
| 455 |
logger = DebugLogger.__new__(DebugLogger)
|
| 456 |
logger._initialized = False
|
| 457 |
logger.__init__()
|
|
|
|
| 472 |
debug_dir = tmp_path / "debug_logs"
|
| 473 |
debug_dir.mkdir()
|
| 474 |
|
| 475 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'all'):
|
| 476 |
+
from kiro.debug_logger import DebugLogger
|
| 477 |
logger = DebugLogger.__new__(DebugLogger)
|
| 478 |
logger._initialized = False
|
| 479 |
logger.__init__()
|
|
|
|
| 496 |
debug_dir = tmp_path / "debug_logs"
|
| 497 |
debug_dir.mkdir()
|
| 498 |
|
| 499 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'all'):
|
| 500 |
+
from kiro.debug_logger import DebugLogger
|
| 501 |
logger = DebugLogger.__new__(DebugLogger)
|
| 502 |
logger._initialized = False
|
| 503 |
logger.__init__()
|
|
|
|
| 523 |
print("Настройка: Режим all...")
|
| 524 |
debug_dir = tmp_path / "debug_logs"
|
| 525 |
|
| 526 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'all'):
|
| 527 |
+
from kiro.debug_logger import DebugLogger
|
| 528 |
dbg_logger = DebugLogger.__new__(DebugLogger)
|
| 529 |
dbg_logger._initialized = False
|
| 530 |
dbg_logger.__init__()
|
|
|
|
| 547 |
print("Настройка: Режим errors...")
|
| 548 |
debug_dir = tmp_path / "debug_logs"
|
| 549 |
|
| 550 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'errors'):
|
| 551 |
+
from kiro.debug_logger import DebugLogger
|
| 552 |
from loguru import logger as loguru_logger
|
| 553 |
|
| 554 |
dbg_logger = DebugLogger.__new__(DebugLogger)
|
|
|
|
| 585 |
debug_dir = tmp_path / "debug_logs"
|
| 586 |
debug_dir.mkdir()
|
| 587 |
|
| 588 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'all'):
|
| 589 |
+
from kiro.debug_logger import DebugLogger
|
| 590 |
|
| 591 |
dbg_logger = DebugLogger.__new__(DebugLogger)
|
| 592 |
dbg_logger._initialized = False
|
|
|
|
| 618 |
print("Настройка: Режим errors...")
|
| 619 |
debug_dir = tmp_path / "debug_logs"
|
| 620 |
|
| 621 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'errors'):
|
| 622 |
+
from kiro.debug_logger import DebugLogger
|
| 623 |
|
| 624 |
dbg_logger = DebugLogger.__new__(DebugLogger)
|
| 625 |
dbg_logger._initialized = False
|
|
|
|
| 644 |
Цель: Убедиться, что sink корректно удаляется.
|
| 645 |
"""
|
| 646 |
print("Настройка: Режим all...")
|
| 647 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'all'):
|
| 648 |
+
from kiro.debug_logger import DebugLogger
|
| 649 |
|
| 650 |
dbg_logger = DebugLogger.__new__(DebugLogger)
|
| 651 |
dbg_logger._initialized = False
|
|
|
|
| 672 |
debug_dir = tmp_path / "debug_logs"
|
| 673 |
debug_dir.mkdir()
|
| 674 |
|
| 675 |
+
with patch('kiro.debug_logger.DEBUG_MODE', 'all'):
|
| 676 |
+
from kiro.debug_logger import DebugLogger
|
| 677 |
|
| 678 |
dbg_logger = DebugLogger.__new__(DebugLogger)
|
| 679 |
dbg_logger._initialized = False
|
tests/unit/test_http_client.py
CHANGED
|
@@ -13,9 +13,9 @@ from datetime import datetime, timezone, timedelta
|
|
| 13 |
import httpx
|
| 14 |
from fastapi import HTTPException
|
| 15 |
|
| 16 |
-
from
|
| 17 |
-
from
|
| 18 |
-
from
|
| 19 |
|
| 20 |
|
| 21 |
@pytest.fixture
|
|
@@ -68,7 +68,7 @@ class TestKiroHttpClientGetClient:
|
|
| 68 |
http_client = KiroHttpClient(mock_auth_manager_for_http)
|
| 69 |
|
| 70 |
print("Action: Getting client...")
|
| 71 |
-
with patch('
|
| 72 |
mock_instance = AsyncMock()
|
| 73 |
mock_instance.is_closed = False
|
| 74 |
mock_async_client.return_value = mock_instance
|
|
@@ -112,7 +112,7 @@ class TestKiroHttpClientGetClient:
|
|
| 112 |
http_client.client = mock_closed
|
| 113 |
|
| 114 |
print("Action: Getting client...")
|
| 115 |
-
with patch('
|
| 116 |
mock_new = AsyncMock()
|
| 117 |
mock_new.is_closed = False
|
| 118 |
mock_async_client.return_value = mock_new
|
|
@@ -202,7 +202,7 @@ class TestKiroHttpClientRequestWithRetry:
|
|
| 202 |
|
| 203 |
print("Action: Executing request...")
|
| 204 |
with patch.object(http_client, '_get_client', return_value=mock_client):
|
| 205 |
-
with patch('
|
| 206 |
response = await http_client.request_with_retry(
|
| 207 |
"POST",
|
| 208 |
"https://api.example.com/test",
|
|
@@ -234,7 +234,7 @@ class TestKiroHttpClientRequestWithRetry:
|
|
| 234 |
|
| 235 |
print("Action: Executing request...")
|
| 236 |
with patch.object(http_client, '_get_client', return_value=mock_client):
|
| 237 |
-
with patch('
|
| 238 |
response = await http_client.request_with_retry(
|
| 239 |
"POST",
|
| 240 |
"https://api.example.com/test",
|
|
@@ -266,8 +266,8 @@ class TestKiroHttpClientRequestWithRetry:
|
|
| 266 |
|
| 267 |
print("Action: Executing request...")
|
| 268 |
with patch.object(http_client, '_get_client', return_value=mock_client):
|
| 269 |
-
with patch('
|
| 270 |
-
with patch('
|
| 271 |
response = await http_client.request_with_retry(
|
| 272 |
"POST",
|
| 273 |
"https://api.example.com/test",
|
|
@@ -299,8 +299,8 @@ class TestKiroHttpClientRequestWithRetry:
|
|
| 299 |
|
| 300 |
print("Action: Executing request...")
|
| 301 |
with patch.object(http_client, '_get_client', return_value=mock_client):
|
| 302 |
-
with patch('
|
| 303 |
-
with patch('
|
| 304 |
response = await http_client.request_with_retry(
|
| 305 |
"POST",
|
| 306 |
"https://api.example.com/test",
|
|
@@ -332,8 +332,8 @@ class TestKiroHttpClientRequestWithRetry:
|
|
| 332 |
|
| 333 |
print("Action: Executing request...")
|
| 334 |
with patch.object(http_client, '_get_client', return_value=mock_client):
|
| 335 |
-
with patch('
|
| 336 |
-
with patch('
|
| 337 |
response = await http_client.request_with_retry(
|
| 338 |
"POST",
|
| 339 |
"https://api.example.com/test",
|
|
@@ -365,8 +365,8 @@ class TestKiroHttpClientRequestWithRetry:
|
|
| 365 |
|
| 366 |
print("Action: Executing request...")
|
| 367 |
with patch.object(http_client, '_get_client', return_value=mock_client):
|
| 368 |
-
with patch('
|
| 369 |
-
with patch('
|
| 370 |
response = await http_client.request_with_retry(
|
| 371 |
"POST",
|
| 372 |
"https://api.example.com/test",
|
|
@@ -392,8 +392,8 @@ class TestKiroHttpClientRequestWithRetry:
|
|
| 392 |
|
| 393 |
print("Action: Executing request...")
|
| 394 |
with patch.object(http_client, '_get_client', return_value=mock_client):
|
| 395 |
-
with patch('
|
| 396 |
-
with patch('
|
| 397 |
with pytest.raises(HTTPException) as exc_info:
|
| 398 |
await http_client.request_with_retry(
|
| 399 |
"POST",
|
|
@@ -423,7 +423,7 @@ class TestKiroHttpClientRequestWithRetry:
|
|
| 423 |
|
| 424 |
print("Action: Executing request...")
|
| 425 |
with patch.object(http_client, '_get_client', return_value=mock_client):
|
| 426 |
-
with patch('
|
| 427 |
response = await http_client.request_with_retry(
|
| 428 |
"POST",
|
| 429 |
"https://api.example.com/test",
|
|
@@ -455,7 +455,7 @@ class TestKiroHttpClientRequestWithRetry:
|
|
| 455 |
|
| 456 |
print("Action: Executing streaming request...")
|
| 457 |
with patch.object(http_client, '_get_client', return_value=mock_client):
|
| 458 |
-
with patch('
|
| 459 |
response = await http_client.request_with_retry(
|
| 460 |
"POST",
|
| 461 |
"https://api.example.com/test",
|
|
@@ -542,8 +542,8 @@ class TestKiroHttpClientExponentialBackoff:
|
|
| 542 |
|
| 543 |
print("Action: Executing request with multiple retries...")
|
| 544 |
with patch.object(http_client, '_get_client', return_value=mock_client):
|
| 545 |
-
with patch('
|
| 546 |
-
with patch('
|
| 547 |
response = await http_client.request_with_retry(
|
| 548 |
"POST",
|
| 549 |
"https://api.example.com/test",
|
|
@@ -580,10 +580,10 @@ class TestKiroHttpClientStreamingTimeout:
|
|
| 580 |
mock_client.send = AsyncMock(return_value=mock_response)
|
| 581 |
|
| 582 |
print("Action: Executing streaming request...")
|
| 583 |
-
with patch('
|
| 584 |
mock_async_client.return_value = mock_client
|
| 585 |
|
| 586 |
-
with patch('
|
| 587 |
response = await http_client.request_with_retry(
|
| 588 |
"POST",
|
| 589 |
"https://api.example.com/test",
|
|
@@ -619,8 +619,8 @@ class TestKiroHttpClientStreamingTimeout:
|
|
| 619 |
mock_client.send = AsyncMock(side_effect=httpx.TimeoutException("Timeout"))
|
| 620 |
|
| 621 |
print("Action: Executing streaming request with timeouts...")
|
| 622 |
-
with patch('
|
| 623 |
-
with patch('
|
| 624 |
with pytest.raises(HTTPException) as exc_info:
|
| 625 |
await http_client.request_with_retry(
|
| 626 |
"POST",
|
|
@@ -666,9 +666,9 @@ class TestKiroHttpClientStreamingTimeout:
|
|
| 666 |
sleep_called = True
|
| 667 |
|
| 668 |
print("Action: Executing streaming request with one timeout...")
|
| 669 |
-
with patch('
|
| 670 |
-
with patch('
|
| 671 |
-
with patch('
|
| 672 |
response = await http_client.request_with_retry(
|
| 673 |
"POST",
|
| 674 |
"https://api.example.com/test",
|
|
@@ -697,10 +697,10 @@ class TestKiroHttpClientStreamingTimeout:
|
|
| 697 |
mock_client.request = AsyncMock(return_value=mock_response)
|
| 698 |
|
| 699 |
print("Action: Executing non-streaming request...")
|
| 700 |
-
with patch('
|
| 701 |
mock_async_client.return_value = mock_client
|
| 702 |
|
| 703 |
-
with patch('
|
| 704 |
response = await http_client.request_with_retry(
|
| 705 |
"POST",
|
| 706 |
"https://api.example.com/test",
|
|
@@ -742,9 +742,9 @@ class TestKiroHttpClientStreamingTimeout:
|
|
| 742 |
])
|
| 743 |
|
| 744 |
print("Action: Executing streaming request with ConnectTimeout...")
|
| 745 |
-
with patch('
|
| 746 |
-
with patch('
|
| 747 |
-
with patch('
|
| 748 |
response = await http_client.request_with_retry(
|
| 749 |
"POST",
|
| 750 |
"https://api.example.com/test",
|
|
@@ -781,9 +781,9 @@ class TestKiroHttpClientStreamingTimeout:
|
|
| 781 |
])
|
| 782 |
|
| 783 |
print("Action: Executing streaming request with ReadTimeout...")
|
| 784 |
-
with patch('
|
| 785 |
-
with patch('
|
| 786 |
-
with patch('
|
| 787 |
response = await http_client.request_with_retry(
|
| 788 |
"POST",
|
| 789 |
"https://api.example.com/test",
|
|
@@ -814,8 +814,8 @@ class TestKiroHttpClientStreamingTimeout:
|
|
| 814 |
mock_client.send = AsyncMock(side_effect=httpx.ReadTimeout("Timeout"))
|
| 815 |
|
| 816 |
print("Action: Executing streaming request with persistent timeouts...")
|
| 817 |
-
with patch('
|
| 818 |
-
with patch('
|
| 819 |
with pytest.raises(HTTPException) as exc_info:
|
| 820 |
await http_client.request_with_retry(
|
| 821 |
"POST",
|
|
@@ -845,9 +845,9 @@ class TestKiroHttpClientStreamingTimeout:
|
|
| 845 |
mock_client.request = AsyncMock(side_effect=httpx.TimeoutException("Timeout"))
|
| 846 |
|
| 847 |
print("Action: Executing non-streaming request with persistent timeouts...")
|
| 848 |
-
with patch('
|
| 849 |
-
with patch('
|
| 850 |
-
with patch('
|
| 851 |
with pytest.raises(HTTPException) as exc_info:
|
| 852 |
await http_client.request_with_retry(
|
| 853 |
"POST",
|
|
|
|
| 13 |
import httpx
|
| 14 |
from fastapi import HTTPException
|
| 15 |
|
| 16 |
+
from kiro.http_client import KiroHttpClient
|
| 17 |
+
from kiro.auth import KiroAuthManager
|
| 18 |
+
from kiro.config import MAX_RETRIES, BASE_RETRY_DELAY, FIRST_TOKEN_MAX_RETRIES, STREAMING_READ_TIMEOUT
|
| 19 |
|
| 20 |
|
| 21 |
@pytest.fixture
|
|
|
|
| 68 |
http_client = KiroHttpClient(mock_auth_manager_for_http)
|
| 69 |
|
| 70 |
print("Action: Getting client...")
|
| 71 |
+
with patch('kiro.http_client.httpx.AsyncClient') as mock_async_client:
|
| 72 |
mock_instance = AsyncMock()
|
| 73 |
mock_instance.is_closed = False
|
| 74 |
mock_async_client.return_value = mock_instance
|
|
|
|
| 112 |
http_client.client = mock_closed
|
| 113 |
|
| 114 |
print("Action: Getting client...")
|
| 115 |
+
with patch('kiro.http_client.httpx.AsyncClient') as mock_async_client:
|
| 116 |
mock_new = AsyncMock()
|
| 117 |
mock_new.is_closed = False
|
| 118 |
mock_async_client.return_value = mock_new
|
|
|
|
| 202 |
|
| 203 |
print("Action: Executing request...")
|
| 204 |
with patch.object(http_client, '_get_client', return_value=mock_client):
|
| 205 |
+
with patch('kiro.http_client.get_kiro_headers', return_value={}):
|
| 206 |
response = await http_client.request_with_retry(
|
| 207 |
"POST",
|
| 208 |
"https://api.example.com/test",
|
|
|
|
| 234 |
|
| 235 |
print("Action: Executing request...")
|
| 236 |
with patch.object(http_client, '_get_client', return_value=mock_client):
|
| 237 |
+
with patch('kiro.http_client.get_kiro_headers', return_value={}):
|
| 238 |
response = await http_client.request_with_retry(
|
| 239 |
"POST",
|
| 240 |
"https://api.example.com/test",
|
|
|
|
| 266 |
|
| 267 |
print("Action: Executing request...")
|
| 268 |
with patch.object(http_client, '_get_client', return_value=mock_client):
|
| 269 |
+
with patch('kiro.http_client.get_kiro_headers', return_value={}):
|
| 270 |
+
with patch('kiro.http_client.asyncio.sleep', new_callable=AsyncMock) as mock_sleep:
|
| 271 |
response = await http_client.request_with_retry(
|
| 272 |
"POST",
|
| 273 |
"https://api.example.com/test",
|
|
|
|
| 299 |
|
| 300 |
print("Action: Executing request...")
|
| 301 |
with patch.object(http_client, '_get_client', return_value=mock_client):
|
| 302 |
+
with patch('kiro.http_client.get_kiro_headers', return_value={}):
|
| 303 |
+
with patch('kiro.http_client.asyncio.sleep', new_callable=AsyncMock) as mock_sleep:
|
| 304 |
response = await http_client.request_with_retry(
|
| 305 |
"POST",
|
| 306 |
"https://api.example.com/test",
|
|
|
|
| 332 |
|
| 333 |
print("Action: Executing request...")
|
| 334 |
with patch.object(http_client, '_get_client', return_value=mock_client):
|
| 335 |
+
with patch('kiro.http_client.get_kiro_headers', return_value={}):
|
| 336 |
+
with patch('kiro.http_client.asyncio.sleep', new_callable=AsyncMock) as mock_sleep:
|
| 337 |
response = await http_client.request_with_retry(
|
| 338 |
"POST",
|
| 339 |
"https://api.example.com/test",
|
|
|
|
| 365 |
|
| 366 |
print("Action: Executing request...")
|
| 367 |
with patch.object(http_client, '_get_client', return_value=mock_client):
|
| 368 |
+
with patch('kiro.http_client.get_kiro_headers', return_value={}):
|
| 369 |
+
with patch('kiro.http_client.asyncio.sleep', new_callable=AsyncMock) as mock_sleep:
|
| 370 |
response = await http_client.request_with_retry(
|
| 371 |
"POST",
|
| 372 |
"https://api.example.com/test",
|
|
|
|
| 392 |
|
| 393 |
print("Action: Executing request...")
|
| 394 |
with patch.object(http_client, '_get_client', return_value=mock_client):
|
| 395 |
+
with patch('kiro.http_client.get_kiro_headers', return_value={}):
|
| 396 |
+
with patch('kiro.http_client.asyncio.sleep', new_callable=AsyncMock):
|
| 397 |
with pytest.raises(HTTPException) as exc_info:
|
| 398 |
await http_client.request_with_retry(
|
| 399 |
"POST",
|
|
|
|
| 423 |
|
| 424 |
print("Action: Executing request...")
|
| 425 |
with patch.object(http_client, '_get_client', return_value=mock_client):
|
| 426 |
+
with patch('kiro.http_client.get_kiro_headers', return_value={}):
|
| 427 |
response = await http_client.request_with_retry(
|
| 428 |
"POST",
|
| 429 |
"https://api.example.com/test",
|
|
|
|
| 455 |
|
| 456 |
print("Action: Executing streaming request...")
|
| 457 |
with patch.object(http_client, '_get_client', return_value=mock_client):
|
| 458 |
+
with patch('kiro.http_client.get_kiro_headers', return_value={}):
|
| 459 |
response = await http_client.request_with_retry(
|
| 460 |
"POST",
|
| 461 |
"https://api.example.com/test",
|
|
|
|
| 542 |
|
| 543 |
print("Action: Executing request with multiple retries...")
|
| 544 |
with patch.object(http_client, '_get_client', return_value=mock_client):
|
| 545 |
+
with patch('kiro.http_client.get_kiro_headers', return_value={}):
|
| 546 |
+
with patch('kiro.http_client.asyncio.sleep', side_effect=capture_sleep):
|
| 547 |
response = await http_client.request_with_retry(
|
| 548 |
"POST",
|
| 549 |
"https://api.example.com/test",
|
|
|
|
| 580 |
mock_client.send = AsyncMock(return_value=mock_response)
|
| 581 |
|
| 582 |
print("Action: Executing streaming request...")
|
| 583 |
+
with patch('kiro.http_client.httpx.AsyncClient') as mock_async_client:
|
| 584 |
mock_async_client.return_value = mock_client
|
| 585 |
|
| 586 |
+
with patch('kiro.http_client.get_kiro_headers', return_value={}):
|
| 587 |
response = await http_client.request_with_retry(
|
| 588 |
"POST",
|
| 589 |
"https://api.example.com/test",
|
|
|
|
| 619 |
mock_client.send = AsyncMock(side_effect=httpx.TimeoutException("Timeout"))
|
| 620 |
|
| 621 |
print("Action: Executing streaming request with timeouts...")
|
| 622 |
+
with patch('kiro.http_client.httpx.AsyncClient', return_value=mock_client):
|
| 623 |
+
with patch('kiro.http_client.get_kiro_headers', return_value={}):
|
| 624 |
with pytest.raises(HTTPException) as exc_info:
|
| 625 |
await http_client.request_with_retry(
|
| 626 |
"POST",
|
|
|
|
| 666 |
sleep_called = True
|
| 667 |
|
| 668 |
print("Action: Executing streaming request with one timeout...")
|
| 669 |
+
with patch('kiro.http_client.httpx.AsyncClient', return_value=mock_client):
|
| 670 |
+
with patch('kiro.http_client.get_kiro_headers', return_value={}):
|
| 671 |
+
with patch('kiro.http_client.asyncio.sleep', side_effect=capture_sleep):
|
| 672 |
response = await http_client.request_with_retry(
|
| 673 |
"POST",
|
| 674 |
"https://api.example.com/test",
|
|
|
|
| 697 |
mock_client.request = AsyncMock(return_value=mock_response)
|
| 698 |
|
| 699 |
print("Action: Executing non-streaming request...")
|
| 700 |
+
with patch('kiro.http_client.httpx.AsyncClient') as mock_async_client:
|
| 701 |
mock_async_client.return_value = mock_client
|
| 702 |
|
| 703 |
+
with patch('kiro.http_client.get_kiro_headers', return_value={}):
|
| 704 |
response = await http_client.request_with_retry(
|
| 705 |
"POST",
|
| 706 |
"https://api.example.com/test",
|
|
|
|
| 742 |
])
|
| 743 |
|
| 744 |
print("Action: Executing streaming request with ConnectTimeout...")
|
| 745 |
+
with patch('kiro.http_client.httpx.AsyncClient', return_value=mock_client):
|
| 746 |
+
with patch('kiro.http_client.get_kiro_headers', return_value={}):
|
| 747 |
+
with patch('kiro.http_client.logger') as mock_logger:
|
| 748 |
response = await http_client.request_with_retry(
|
| 749 |
"POST",
|
| 750 |
"https://api.example.com/test",
|
|
|
|
| 781 |
])
|
| 782 |
|
| 783 |
print("Action: Executing streaming request with ReadTimeout...")
|
| 784 |
+
with patch('kiro.http_client.httpx.AsyncClient', return_value=mock_client):
|
| 785 |
+
with patch('kiro.http_client.get_kiro_headers', return_value={}):
|
| 786 |
+
with patch('kiro.http_client.logger') as mock_logger:
|
| 787 |
response = await http_client.request_with_retry(
|
| 788 |
"POST",
|
| 789 |
"https://api.example.com/test",
|
|
|
|
| 814 |
mock_client.send = AsyncMock(side_effect=httpx.ReadTimeout("Timeout"))
|
| 815 |
|
| 816 |
print("Action: Executing streaming request with persistent timeouts...")
|
| 817 |
+
with patch('kiro.http_client.httpx.AsyncClient', return_value=mock_client):
|
| 818 |
+
with patch('kiro.http_client.get_kiro_headers', return_value={}):
|
| 819 |
with pytest.raises(HTTPException) as exc_info:
|
| 820 |
await http_client.request_with_retry(
|
| 821 |
"POST",
|
|
|
|
| 845 |
mock_client.request = AsyncMock(side_effect=httpx.TimeoutException("Timeout"))
|
| 846 |
|
| 847 |
print("Action: Executing non-streaming request with persistent timeouts...")
|
| 848 |
+
with patch('kiro.http_client.httpx.AsyncClient', return_value=mock_client):
|
| 849 |
+
with patch('kiro.http_client.get_kiro_headers', return_value={}):
|
| 850 |
+
with patch('kiro.http_client.asyncio.sleep', new_callable=AsyncMock):
|
| 851 |
with pytest.raises(HTTPException) as exc_info:
|
| 852 |
await http_client.request_with_retry(
|
| 853 |
"POST",
|
tests/unit/test_main_cli.py
CHANGED
|
@@ -395,7 +395,7 @@ class TestCliVersion:
|
|
| 395 |
"""
|
| 396 |
print("Setup: Importing parse_cli_args and APP_VERSION...")
|
| 397 |
from main import parse_cli_args
|
| 398 |
-
from
|
| 399 |
|
| 400 |
print("Action: Calling parse_cli_args with --version...")
|
| 401 |
with patch.object(sys, 'argv', ['main.py', '--version']):
|
|
|
|
| 395 |
"""
|
| 396 |
print("Setup: Importing parse_cli_args and APP_VERSION...")
|
| 397 |
from main import parse_cli_args
|
| 398 |
+
from kiro.config import APP_VERSION
|
| 399 |
|
| 400 |
print("Action: Calling parse_cli_args with --version...")
|
| 401 |
with patch.object(sys, 'argv', ['main.py', '--version']):
|
tests/unit/test_parsers.py
CHANGED
|
@@ -7,7 +7,7 @@ Unit-тесты для AwsEventStreamParser и вспомогательных ф
|
|
| 7 |
|
| 8 |
import pytest
|
| 9 |
|
| 10 |
-
from
|
| 11 |
AwsEventStreamParser,
|
| 12 |
find_matching_brace,
|
| 13 |
parse_bracket_tool_calls,
|
|
|
|
| 7 |
|
| 8 |
import pytest
|
| 9 |
|
| 10 |
+
from kiro.parsers import (
|
| 11 |
AwsEventStreamParser,
|
| 12 |
find_matching_brace,
|
| 13 |
parse_bracket_tool_calls,
|
tests/unit/test_routes.py
CHANGED
|
@@ -12,8 +12,8 @@ from datetime import datetime, timezone
|
|
| 12 |
from fastapi import HTTPException
|
| 13 |
from fastapi.testclient import TestClient
|
| 14 |
|
| 15 |
-
from
|
| 16 |
-
from
|
| 17 |
|
| 18 |
|
| 19 |
class TestVerifyApiKey:
|
|
|
|
| 12 |
from fastapi import HTTPException
|
| 13 |
from fastapi.testclient import TestClient
|
| 14 |
|
| 15 |
+
from kiro.routes import verify_api_key, router
|
| 16 |
+
from kiro.config import PROXY_API_KEY, APP_VERSION, AVAILABLE_MODELS
|
| 17 |
|
| 18 |
|
| 19 |
class TestVerifyApiKey:
|
tests/unit/test_streaming.py
CHANGED
|
@@ -10,7 +10,7 @@ import pytest
|
|
| 10 |
import json
|
| 11 |
from unittest.mock import AsyncMock, MagicMock, patch
|
| 12 |
|
| 13 |
-
from
|
| 14 |
stream_kiro_to_openai,
|
| 15 |
collect_stream_response
|
| 16 |
)
|
|
@@ -77,8 +77,8 @@ class TestStreamingToolCallsIndex:
|
|
| 77 |
print("Action: Collecting streaming chunks...")
|
| 78 |
chunks = []
|
| 79 |
|
| 80 |
-
with patch('
|
| 81 |
-
with patch('
|
| 82 |
async for chunk in stream_kiro_to_openai(
|
| 83 |
mock_http_client, mock_response, "test-model",
|
| 84 |
mock_model_cache, mock_auth_manager
|
|
@@ -137,8 +137,8 @@ class TestStreamingToolCallsIndex:
|
|
| 137 |
print("Action: Collecting streaming chunks...")
|
| 138 |
chunks = []
|
| 139 |
|
| 140 |
-
with patch('
|
| 141 |
-
with patch('
|
| 142 |
async for chunk in stream_kiro_to_openai(
|
| 143 |
mock_http_client, mock_response, "test-model",
|
| 144 |
mock_model_cache, mock_auth_manager
|
|
@@ -197,8 +197,8 @@ class TestStreamingToolCallsNoneProtection:
|
|
| 197 |
print("Action: Collecting streaming chunks...")
|
| 198 |
chunks = []
|
| 199 |
|
| 200 |
-
with patch('
|
| 201 |
-
with patch('
|
| 202 |
async for chunk in stream_kiro_to_openai(
|
| 203 |
mock_http_client, mock_response, "test-model",
|
| 204 |
mock_model_cache, mock_auth_manager
|
|
@@ -256,8 +256,8 @@ class TestStreamingToolCallsNoneProtection:
|
|
| 256 |
print("Action: Collecting streaming chunks...")
|
| 257 |
chunks = []
|
| 258 |
|
| 259 |
-
with patch('
|
| 260 |
-
with patch('
|
| 261 |
async for chunk in stream_kiro_to_openai(
|
| 262 |
mock_http_client, mock_response, "test-model",
|
| 263 |
mock_model_cache, mock_auth_manager
|
|
@@ -312,8 +312,8 @@ class TestStreamingToolCallsNoneProtection:
|
|
| 312 |
print("Action: Collecting streaming chunks...")
|
| 313 |
chunks = []
|
| 314 |
|
| 315 |
-
with patch('
|
| 316 |
-
with patch('
|
| 317 |
async for chunk in stream_kiro_to_openai(
|
| 318 |
mock_http_client, mock_response, "test-model",
|
| 319 |
mock_model_cache, mock_auth_manager
|
|
@@ -357,8 +357,8 @@ class TestCollectStreamResponseToolCalls:
|
|
| 357 |
|
| 358 |
print("Action: Collecting full response...")
|
| 359 |
|
| 360 |
-
with patch('
|
| 361 |
-
with patch('
|
| 362 |
result = await collect_stream_response(
|
| 363 |
mock_http_client, mock_response, "test-model",
|
| 364 |
mock_model_cache, mock_auth_manager
|
|
@@ -403,8 +403,8 @@ class TestCollectStreamResponseToolCalls:
|
|
| 403 |
|
| 404 |
print("Action: Collecting full response...")
|
| 405 |
|
| 406 |
-
with patch('
|
| 407 |
-
with patch('
|
| 408 |
result = await collect_stream_response(
|
| 409 |
mock_http_client, mock_response, "test-model",
|
| 410 |
mock_model_cache, mock_auth_manager
|
|
@@ -453,8 +453,8 @@ class TestCollectStreamResponseToolCalls:
|
|
| 453 |
|
| 454 |
print("Action: Collecting full response...")
|
| 455 |
|
| 456 |
-
with patch('
|
| 457 |
-
with patch('
|
| 458 |
result = await collect_stream_response(
|
| 459 |
mock_http_client, mock_response, "test-model",
|
| 460 |
mock_model_cache, mock_auth_manager
|
|
@@ -497,8 +497,8 @@ class TestStreamingErrorHandling:
|
|
| 497 |
chunks_received = []
|
| 498 |
generator_exit_caught = False
|
| 499 |
|
| 500 |
-
with patch('
|
| 501 |
-
with patch('
|
| 502 |
try:
|
| 503 |
async for chunk in stream_kiro_to_openai(
|
| 504 |
mock_http_client, mock_response, "test-model",
|
|
@@ -546,9 +546,9 @@ class TestStreamingErrorHandling:
|
|
| 546 |
|
| 547 |
print("Action: Running streaming with EmptyMessageError...")
|
| 548 |
|
| 549 |
-
with patch('
|
| 550 |
-
with patch('
|
| 551 |
-
with patch('
|
| 552 |
exception_raised = False
|
| 553 |
try:
|
| 554 |
async for chunk in stream_kiro_to_openai(
|
|
@@ -599,8 +599,8 @@ class TestStreamingErrorHandling:
|
|
| 599 |
|
| 600 |
print("Action: Running streaming with RuntimeError...")
|
| 601 |
|
| 602 |
-
with patch('
|
| 603 |
-
with patch('
|
| 604 |
with pytest.raises(RuntimeError) as exc_info:
|
| 605 |
async for chunk in stream_kiro_to_openai(
|
| 606 |
mock_http_client, mock_response, "test-model",
|
|
@@ -636,8 +636,8 @@ class TestStreamingErrorHandling:
|
|
| 636 |
|
| 637 |
print("Action: Running streaming with ValueError...")
|
| 638 |
|
| 639 |
-
with patch('
|
| 640 |
-
with patch('
|
| 641 |
try:
|
| 642 |
async for chunk in stream_kiro_to_openai(
|
| 643 |
mock_http_client, mock_response, "test-model",
|
|
@@ -675,8 +675,8 @@ class TestStreamingErrorHandling:
|
|
| 675 |
print("Action: Running successful streaming...")
|
| 676 |
chunks = []
|
| 677 |
|
| 678 |
-
with patch('
|
| 679 |
-
with patch('
|
| 680 |
async for chunk in stream_kiro_to_openai(
|
| 681 |
mock_http_client, mock_response, "test-model",
|
| 682 |
mock_model_cache, mock_auth_manager
|
|
@@ -712,8 +712,8 @@ class TestStreamingErrorHandling:
|
|
| 712 |
|
| 713 |
print("Action: Running streaming with error and error in aclose()...")
|
| 714 |
|
| 715 |
-
with patch('
|
| 716 |
-
with patch('
|
| 717 |
with pytest.raises(RuntimeError) as exc_info:
|
| 718 |
async for chunk in stream_kiro_to_openai(
|
| 719 |
mock_http_client, mock_response, "test-model",
|
|
@@ -737,7 +737,7 @@ class TestFirstTokenTimeoutError:
|
|
| 737 |
Goal: Ensure first token timeout is not handled as regular error.
|
| 738 |
"""
|
| 739 |
import asyncio
|
| 740 |
-
from
|
| 741 |
|
| 742 |
print("Setup: Mock response with timeout...")
|
| 743 |
|
|
@@ -757,7 +757,7 @@ class TestFirstTokenTimeoutError:
|
|
| 757 |
async def mock_wait_for_timeout(*args, **kwargs):
|
| 758 |
raise asyncio.TimeoutError()
|
| 759 |
|
| 760 |
-
with patch('
|
| 761 |
with pytest.raises(FirstTokenTimeoutError) as exc_info:
|
| 762 |
async for chunk in stream_kiro_to_openai_internal(
|
| 763 |
mock_http_client, mock_response, "test-model",
|
|
@@ -780,7 +780,7 @@ class TestFirstTokenTimeoutError:
|
|
| 780 |
Goal: Ensure consistent logging format for first token timeout.
|
| 781 |
"""
|
| 782 |
import asyncio
|
| 783 |
-
from
|
| 784 |
|
| 785 |
print("Setup: Mock response with timeout...")
|
| 786 |
|
|
@@ -798,8 +798,8 @@ class TestFirstTokenTimeoutError:
|
|
| 798 |
|
| 799 |
print("Action: Running streaming with timeout and checking logs...")
|
| 800 |
|
| 801 |
-
with patch('
|
| 802 |
-
with patch('
|
| 803 |
try:
|
| 804 |
async for chunk in stream_kiro_to_openai_internal(
|
| 805 |
mock_http_client, mock_response, "test-model",
|
|
@@ -825,7 +825,7 @@ class TestFirstTokenTimeoutError:
|
|
| 825 |
Goal: Ensure timeout value is visible in logs for debugging.
|
| 826 |
"""
|
| 827 |
import asyncio
|
| 828 |
-
from
|
| 829 |
|
| 830 |
print("Setup: Mock response with timeout...")
|
| 831 |
|
|
@@ -845,8 +845,8 @@ class TestFirstTokenTimeoutError:
|
|
| 845 |
|
| 846 |
print(f"Action: Running streaming with custom timeout={custom_timeout}...")
|
| 847 |
|
| 848 |
-
with patch('
|
| 849 |
-
with patch('
|
| 850 |
try:
|
| 851 |
async for chunk in stream_kiro_to_openai_internal(
|
| 852 |
mock_http_client, mock_response, "test-model",
|
|
@@ -871,7 +871,7 @@ class TestFirstTokenTimeoutError:
|
|
| 871 |
What it does: Verifies that successful first token receipt is logged.
|
| 872 |
Goal: Ensure debug log shows when first token is received.
|
| 873 |
"""
|
| 874 |
-
from
|
| 875 |
|
| 876 |
print("Setup: Mock response with successful first token...")
|
| 877 |
|
|
@@ -890,9 +890,9 @@ class TestFirstTokenTimeoutError:
|
|
| 890 |
|
| 891 |
print("Action: Running streaming and checking debug logs...")
|
| 892 |
|
| 893 |
-
with patch('
|
| 894 |
-
with patch('
|
| 895 |
-
with patch('
|
| 896 |
chunks = []
|
| 897 |
async for chunk in stream_kiro_to_openai_internal(
|
| 898 |
mock_http_client, mock_response, "test-model",
|
|
@@ -921,7 +921,7 @@ class TestStreamWithFirstTokenRetry:
|
|
| 921 |
Goal: Ensure retry logic works for first token timeout.
|
| 922 |
"""
|
| 923 |
import asyncio
|
| 924 |
-
from
|
| 925 |
|
| 926 |
print("Setup: Mock make_request that succeeds on second attempt...")
|
| 927 |
|
|
@@ -958,9 +958,9 @@ class TestStreamWithFirstTokenRetry:
|
|
| 958 |
|
| 959 |
print("Action: Running stream_with_first_token_retry...")
|
| 960 |
|
| 961 |
-
with patch('
|
| 962 |
-
with patch('
|
| 963 |
-
with patch('
|
| 964 |
chunks = []
|
| 965 |
async for chunk in stream_with_first_token_retry(
|
| 966 |
mock_make_request,
|
|
@@ -988,7 +988,7 @@ class TestStreamWithFirstTokenRetry:
|
|
| 988 |
"""
|
| 989 |
import asyncio
|
| 990 |
from fastapi import HTTPException
|
| 991 |
-
from
|
| 992 |
|
| 993 |
print("Setup: Mock make_request that always times out...")
|
| 994 |
|
|
@@ -1016,7 +1016,7 @@ class TestStreamWithFirstTokenRetry:
|
|
| 1016 |
|
| 1017 |
print(f"Action: Running stream_with_first_token_retry with max_retries={max_retries}...")
|
| 1018 |
|
| 1019 |
-
with patch('
|
| 1020 |
with pytest.raises(HTTPException) as exc_info:
|
| 1021 |
async for chunk in stream_with_first_token_retry(
|
| 1022 |
mock_make_request,
|
|
@@ -1047,7 +1047,7 @@ class TestStreamWithFirstTokenRetry:
|
|
| 1047 |
"""
|
| 1048 |
import asyncio
|
| 1049 |
from fastapi import HTTPException
|
| 1050 |
-
from
|
| 1051 |
|
| 1052 |
print("Setup: Mock make_request that always times out...")
|
| 1053 |
|
|
@@ -1068,8 +1068,8 @@ class TestStreamWithFirstTokenRetry:
|
|
| 1068 |
|
| 1069 |
print("Action: Running stream_with_first_token_retry and checking logs...")
|
| 1070 |
|
| 1071 |
-
with patch('
|
| 1072 |
-
with patch('
|
| 1073 |
try:
|
| 1074 |
async for chunk in stream_with_first_token_retry(
|
| 1075 |
mock_make_request,
|
|
|
|
| 10 |
import json
|
| 11 |
from unittest.mock import AsyncMock, MagicMock, patch
|
| 12 |
|
| 13 |
+
from kiro.streaming import (
|
| 14 |
stream_kiro_to_openai,
|
| 15 |
collect_stream_response
|
| 16 |
)
|
|
|
|
| 77 |
print("Action: Collecting streaming chunks...")
|
| 78 |
chunks = []
|
| 79 |
|
| 80 |
+
with patch('kiro.streaming.AwsEventStreamParser', return_value=mock_parser):
|
| 81 |
+
with patch('kiro.streaming.parse_bracket_tool_calls', return_value=[]):
|
| 82 |
async for chunk in stream_kiro_to_openai(
|
| 83 |
mock_http_client, mock_response, "test-model",
|
| 84 |
mock_model_cache, mock_auth_manager
|
|
|
|
| 137 |
print("Action: Collecting streaming chunks...")
|
| 138 |
chunks = []
|
| 139 |
|
| 140 |
+
with patch('kiro.streaming.AwsEventStreamParser', return_value=mock_parser):
|
| 141 |
+
with patch('kiro.streaming.parse_bracket_tool_calls', return_value=[]):
|
| 142 |
async for chunk in stream_kiro_to_openai(
|
| 143 |
mock_http_client, mock_response, "test-model",
|
| 144 |
mock_model_cache, mock_auth_manager
|
|
|
|
| 197 |
print("Action: Collecting streaming chunks...")
|
| 198 |
chunks = []
|
| 199 |
|
| 200 |
+
with patch('kiro.streaming.AwsEventStreamParser', return_value=mock_parser):
|
| 201 |
+
with patch('kiro.streaming.parse_bracket_tool_calls', return_value=[]):
|
| 202 |
async for chunk in stream_kiro_to_openai(
|
| 203 |
mock_http_client, mock_response, "test-model",
|
| 204 |
mock_model_cache, mock_auth_manager
|
|
|
|
| 256 |
print("Action: Collecting streaming chunks...")
|
| 257 |
chunks = []
|
| 258 |
|
| 259 |
+
with patch('kiro.streaming.AwsEventStreamParser', return_value=mock_parser):
|
| 260 |
+
with patch('kiro.streaming.parse_bracket_tool_calls', return_value=[]):
|
| 261 |
async for chunk in stream_kiro_to_openai(
|
| 262 |
mock_http_client, mock_response, "test-model",
|
| 263 |
mock_model_cache, mock_auth_manager
|
|
|
|
| 312 |
print("Action: Collecting streaming chunks...")
|
| 313 |
chunks = []
|
| 314 |
|
| 315 |
+
with patch('kiro.streaming.AwsEventStreamParser', return_value=mock_parser):
|
| 316 |
+
with patch('kiro.streaming.parse_bracket_tool_calls', return_value=[]):
|
| 317 |
async for chunk in stream_kiro_to_openai(
|
| 318 |
mock_http_client, mock_response, "test-model",
|
| 319 |
mock_model_cache, mock_auth_manager
|
|
|
|
| 357 |
|
| 358 |
print("Action: Collecting full response...")
|
| 359 |
|
| 360 |
+
with patch('kiro.streaming.AwsEventStreamParser', return_value=mock_parser):
|
| 361 |
+
with patch('kiro.streaming.parse_bracket_tool_calls', return_value=[]):
|
| 362 |
result = await collect_stream_response(
|
| 363 |
mock_http_client, mock_response, "test-model",
|
| 364 |
mock_model_cache, mock_auth_manager
|
|
|
|
| 403 |
|
| 404 |
print("Action: Collecting full response...")
|
| 405 |
|
| 406 |
+
with patch('kiro.streaming.AwsEventStreamParser', return_value=mock_parser):
|
| 407 |
+
with patch('kiro.streaming.parse_bracket_tool_calls', return_value=[]):
|
| 408 |
result = await collect_stream_response(
|
| 409 |
mock_http_client, mock_response, "test-model",
|
| 410 |
mock_model_cache, mock_auth_manager
|
|
|
|
| 453 |
|
| 454 |
print("Action: Collecting full response...")
|
| 455 |
|
| 456 |
+
with patch('kiro.streaming.AwsEventStreamParser', return_value=mock_parser):
|
| 457 |
+
with patch('kiro.streaming.parse_bracket_tool_calls', return_value=[]):
|
| 458 |
result = await collect_stream_response(
|
| 459 |
mock_http_client, mock_response, "test-model",
|
| 460 |
mock_model_cache, mock_auth_manager
|
|
|
|
| 497 |
chunks_received = []
|
| 498 |
generator_exit_caught = False
|
| 499 |
|
| 500 |
+
with patch('kiro.streaming.AwsEventStreamParser', return_value=mock_parser):
|
| 501 |
+
with patch('kiro.streaming.parse_bracket_tool_calls', return_value=[]):
|
| 502 |
try:
|
| 503 |
async for chunk in stream_kiro_to_openai(
|
| 504 |
mock_http_client, mock_response, "test-model",
|
|
|
|
| 546 |
|
| 547 |
print("Action: Running streaming with EmptyMessageError...")
|
| 548 |
|
| 549 |
+
with patch('kiro.streaming.AwsEventStreamParser', return_value=mock_parser):
|
| 550 |
+
with patch('kiro.streaming.parse_bracket_tool_calls', return_value=[]):
|
| 551 |
+
with patch('kiro.streaming.logger') as mock_logger:
|
| 552 |
exception_raised = False
|
| 553 |
try:
|
| 554 |
async for chunk in stream_kiro_to_openai(
|
|
|
|
| 599 |
|
| 600 |
print("Action: Running streaming with RuntimeError...")
|
| 601 |
|
| 602 |
+
with patch('kiro.streaming.AwsEventStreamParser', return_value=mock_parser):
|
| 603 |
+
with patch('kiro.streaming.parse_bracket_tool_calls', return_value=[]):
|
| 604 |
with pytest.raises(RuntimeError) as exc_info:
|
| 605 |
async for chunk in stream_kiro_to_openai(
|
| 606 |
mock_http_client, mock_response, "test-model",
|
|
|
|
| 636 |
|
| 637 |
print("Action: Running streaming with ValueError...")
|
| 638 |
|
| 639 |
+
with patch('kiro.streaming.AwsEventStreamParser', return_value=mock_parser):
|
| 640 |
+
with patch('kiro.streaming.parse_bracket_tool_calls', return_value=[]):
|
| 641 |
try:
|
| 642 |
async for chunk in stream_kiro_to_openai(
|
| 643 |
mock_http_client, mock_response, "test-model",
|
|
|
|
| 675 |
print("Action: Running successful streaming...")
|
| 676 |
chunks = []
|
| 677 |
|
| 678 |
+
with patch('kiro.streaming.AwsEventStreamParser', return_value=mock_parser):
|
| 679 |
+
with patch('kiro.streaming.parse_bracket_tool_calls', return_value=[]):
|
| 680 |
async for chunk in stream_kiro_to_openai(
|
| 681 |
mock_http_client, mock_response, "test-model",
|
| 682 |
mock_model_cache, mock_auth_manager
|
|
|
|
| 712 |
|
| 713 |
print("Action: Running streaming with error and error in aclose()...")
|
| 714 |
|
| 715 |
+
with patch('kiro.streaming.AwsEventStreamParser', return_value=mock_parser):
|
| 716 |
+
with patch('kiro.streaming.parse_bracket_tool_calls', return_value=[]):
|
| 717 |
with pytest.raises(RuntimeError) as exc_info:
|
| 718 |
async for chunk in stream_kiro_to_openai(
|
| 719 |
mock_http_client, mock_response, "test-model",
|
|
|
|
| 737 |
Goal: Ensure first token timeout is not handled as regular error.
|
| 738 |
"""
|
| 739 |
import asyncio
|
| 740 |
+
from kiro.streaming import FirstTokenTimeoutError, stream_kiro_to_openai_internal
|
| 741 |
|
| 742 |
print("Setup: Mock response with timeout...")
|
| 743 |
|
|
|
|
| 757 |
async def mock_wait_for_timeout(*args, **kwargs):
|
| 758 |
raise asyncio.TimeoutError()
|
| 759 |
|
| 760 |
+
with patch('kiro.streaming.asyncio.wait_for', side_effect=mock_wait_for_timeout):
|
| 761 |
with pytest.raises(FirstTokenTimeoutError) as exc_info:
|
| 762 |
async for chunk in stream_kiro_to_openai_internal(
|
| 763 |
mock_http_client, mock_response, "test-model",
|
|
|
|
| 780 |
Goal: Ensure consistent logging format for first token timeout.
|
| 781 |
"""
|
| 782 |
import asyncio
|
| 783 |
+
from kiro.streaming import FirstTokenTimeoutError, stream_kiro_to_openai_internal
|
| 784 |
|
| 785 |
print("Setup: Mock response with timeout...")
|
| 786 |
|
|
|
|
| 798 |
|
| 799 |
print("Action: Running streaming with timeout and checking logs...")
|
| 800 |
|
| 801 |
+
with patch('kiro.streaming.asyncio.wait_for', side_effect=mock_wait_for_timeout):
|
| 802 |
+
with patch('kiro.streaming.logger') as mock_logger:
|
| 803 |
try:
|
| 804 |
async for chunk in stream_kiro_to_openai_internal(
|
| 805 |
mock_http_client, mock_response, "test-model",
|
|
|
|
| 825 |
Goal: Ensure timeout value is visible in logs for debugging.
|
| 826 |
"""
|
| 827 |
import asyncio
|
| 828 |
+
from kiro.streaming import FirstTokenTimeoutError, stream_kiro_to_openai_internal
|
| 829 |
|
| 830 |
print("Setup: Mock response with timeout...")
|
| 831 |
|
|
|
|
| 845 |
|
| 846 |
print(f"Action: Running streaming with custom timeout={custom_timeout}...")
|
| 847 |
|
| 848 |
+
with patch('kiro.streaming.asyncio.wait_for', side_effect=mock_wait_for_timeout):
|
| 849 |
+
with patch('kiro.streaming.logger') as mock_logger:
|
| 850 |
try:
|
| 851 |
async for chunk in stream_kiro_to_openai_internal(
|
| 852 |
mock_http_client, mock_response, "test-model",
|
|
|
|
| 871 |
What it does: Verifies that successful first token receipt is logged.
|
| 872 |
Goal: Ensure debug log shows when first token is received.
|
| 873 |
"""
|
| 874 |
+
from kiro.streaming import stream_kiro_to_openai_internal
|
| 875 |
|
| 876 |
print("Setup: Mock response with successful first token...")
|
| 877 |
|
|
|
|
| 890 |
|
| 891 |
print("Action: Running streaming and checking debug logs...")
|
| 892 |
|
| 893 |
+
with patch('kiro.streaming.AwsEventStreamParser', return_value=mock_parser):
|
| 894 |
+
with patch('kiro.streaming.parse_bracket_tool_calls', return_value=[]):
|
| 895 |
+
with patch('kiro.streaming.logger') as mock_logger:
|
| 896 |
chunks = []
|
| 897 |
async for chunk in stream_kiro_to_openai_internal(
|
| 898 |
mock_http_client, mock_response, "test-model",
|
|
|
|
| 921 |
Goal: Ensure retry logic works for first token timeout.
|
| 922 |
"""
|
| 923 |
import asyncio
|
| 924 |
+
from kiro.streaming import stream_with_first_token_retry, FirstTokenTimeoutError
|
| 925 |
|
| 926 |
print("Setup: Mock make_request that succeeds on second attempt...")
|
| 927 |
|
|
|
|
| 958 |
|
| 959 |
print("Action: Running stream_with_first_token_retry...")
|
| 960 |
|
| 961 |
+
with patch('kiro.streaming.AwsEventStreamParser', return_value=mock_parser):
|
| 962 |
+
with patch('kiro.streaming.parse_bracket_tool_calls', return_value=[]):
|
| 963 |
+
with patch('kiro.streaming.asyncio.wait_for', side_effect=mock_wait_for_with_retry):
|
| 964 |
chunks = []
|
| 965 |
async for chunk in stream_with_first_token_retry(
|
| 966 |
mock_make_request,
|
|
|
|
| 988 |
"""
|
| 989 |
import asyncio
|
| 990 |
from fastapi import HTTPException
|
| 991 |
+
from kiro.streaming import stream_with_first_token_retry
|
| 992 |
|
| 993 |
print("Setup: Mock make_request that always times out...")
|
| 994 |
|
|
|
|
| 1016 |
|
| 1017 |
print(f"Action: Running stream_with_first_token_retry with max_retries={max_retries}...")
|
| 1018 |
|
| 1019 |
+
with patch('kiro.streaming.asyncio.wait_for', side_effect=mock_wait_for_always_timeout):
|
| 1020 |
with pytest.raises(HTTPException) as exc_info:
|
| 1021 |
async for chunk in stream_with_first_token_retry(
|
| 1022 |
mock_make_request,
|
|
|
|
| 1047 |
"""
|
| 1048 |
import asyncio
|
| 1049 |
from fastapi import HTTPException
|
| 1050 |
+
from kiro.streaming import stream_with_first_token_retry
|
| 1051 |
|
| 1052 |
print("Setup: Mock make_request that always times out...")
|
| 1053 |
|
|
|
|
| 1068 |
|
| 1069 |
print("Action: Running stream_with_first_token_retry and checking logs...")
|
| 1070 |
|
| 1071 |
+
with patch('kiro.streaming.asyncio.wait_for', side_effect=mock_wait_for_always_timeout):
|
| 1072 |
+
with patch('kiro.streaming.logger') as mock_logger:
|
| 1073 |
try:
|
| 1074 |
async for chunk in stream_with_first_token_retry(
|
| 1075 |
mock_make_request,
|
tests/unit/test_thinking_parser.py
CHANGED
|
@@ -14,7 +14,7 @@ Tests cover:
|
|
| 14 |
import pytest
|
| 15 |
from unittest.mock import patch
|
| 16 |
|
| 17 |
-
from
|
| 18 |
ThinkingParser,
|
| 19 |
ThinkingParseResult,
|
| 20 |
ParserState,
|
|
@@ -900,7 +900,7 @@ class TestThinkingParserConfigIntegration:
|
|
| 900 |
Purpose: Ensure config integration works.
|
| 901 |
"""
|
| 902 |
print("Testing config handling mode...")
|
| 903 |
-
with patch('
|
| 904 |
parser = ThinkingParser()
|
| 905 |
|
| 906 |
print(f"Handling mode: {parser.handling_mode}")
|
|
@@ -913,7 +913,7 @@ class TestThinkingParserConfigIntegration:
|
|
| 913 |
"""
|
| 914 |
print("Testing config open tags...")
|
| 915 |
custom_tags = ["<custom>"]
|
| 916 |
-
with patch('
|
| 917 |
parser = ThinkingParser()
|
| 918 |
|
| 919 |
print(f"Open tags: {parser.open_tags}")
|
|
@@ -929,7 +929,7 @@ class TestThinkingParserConfigIntegration:
|
|
| 929 |
TestThinkingParserInitialization.test_custom_initial_buffer_size.
|
| 930 |
"""
|
| 931 |
print("Testing default initial buffer size from config...")
|
| 932 |
-
from
|
| 933 |
|
| 934 |
parser = ThinkingParser()
|
| 935 |
|
|
@@ -947,10 +947,10 @@ class TestInjectThinkingTags:
|
|
| 947 |
Purpose: Ensure tags are added to content.
|
| 948 |
"""
|
| 949 |
print("Testing tag injection when enabled...")
|
| 950 |
-
from
|
| 951 |
|
| 952 |
-
with patch('
|
| 953 |
-
with patch('
|
| 954 |
result = inject_thinking_tags("Hello")
|
| 955 |
|
| 956 |
print(f"Result: '{result}'")
|
|
@@ -964,9 +964,9 @@ class TestInjectThinkingTags:
|
|
| 964 |
Purpose: Ensure tags are not added when disabled.
|
| 965 |
"""
|
| 966 |
print("Testing no tag injection when disabled...")
|
| 967 |
-
from
|
| 968 |
|
| 969 |
-
with patch('
|
| 970 |
result = inject_thinking_tags("Hello")
|
| 971 |
|
| 972 |
print(f"Result: '{result}'")
|
|
@@ -979,12 +979,12 @@ class TestInjectThinkingTags:
|
|
| 979 |
Purpose: Ensure content is not modified.
|
| 980 |
"""
|
| 981 |
print("Testing content preservation...")
|
| 982 |
-
from
|
| 983 |
|
| 984 |
original = "This is my original content with special chars: <>&"
|
| 985 |
|
| 986 |
-
with patch('
|
| 987 |
-
with patch('
|
| 988 |
result = inject_thinking_tags(original)
|
| 989 |
|
| 990 |
print(f"Result ends with original: {result.endswith(original)}")
|
|
|
|
| 14 |
import pytest
|
| 15 |
from unittest.mock import patch
|
| 16 |
|
| 17 |
+
from kiro.thinking_parser import (
|
| 18 |
ThinkingParser,
|
| 19 |
ThinkingParseResult,
|
| 20 |
ParserState,
|
|
|
|
| 900 |
Purpose: Ensure config integration works.
|
| 901 |
"""
|
| 902 |
print("Testing config handling mode...")
|
| 903 |
+
with patch('kiro.thinking_parser.FAKE_REASONING_HANDLING', 'remove'):
|
| 904 |
parser = ThinkingParser()
|
| 905 |
|
| 906 |
print(f"Handling mode: {parser.handling_mode}")
|
|
|
|
| 913 |
"""
|
| 914 |
print("Testing config open tags...")
|
| 915 |
custom_tags = ["<custom>"]
|
| 916 |
+
with patch('kiro.thinking_parser.FAKE_REASONING_OPEN_TAGS', custom_tags):
|
| 917 |
parser = ThinkingParser()
|
| 918 |
|
| 919 |
print(f"Open tags: {parser.open_tags}")
|
|
|
|
| 929 |
TestThinkingParserInitialization.test_custom_initial_buffer_size.
|
| 930 |
"""
|
| 931 |
print("Testing default initial buffer size from config...")
|
| 932 |
+
from kiro.config import FAKE_REASONING_INITIAL_BUFFER_SIZE
|
| 933 |
|
| 934 |
parser = ThinkingParser()
|
| 935 |
|
|
|
|
| 947 |
Purpose: Ensure tags are added to content.
|
| 948 |
"""
|
| 949 |
print("Testing tag injection when enabled...")
|
| 950 |
+
from kiro.converters import inject_thinking_tags
|
| 951 |
|
| 952 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', True):
|
| 953 |
+
with patch('kiro.converters.FAKE_REASONING_MAX_TOKENS', 4000):
|
| 954 |
result = inject_thinking_tags("Hello")
|
| 955 |
|
| 956 |
print(f"Result: '{result}'")
|
|
|
|
| 964 |
Purpose: Ensure tags are not added when disabled.
|
| 965 |
"""
|
| 966 |
print("Testing no tag injection when disabled...")
|
| 967 |
+
from kiro.converters import inject_thinking_tags
|
| 968 |
|
| 969 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', False):
|
| 970 |
result = inject_thinking_tags("Hello")
|
| 971 |
|
| 972 |
print(f"Result: '{result}'")
|
|
|
|
| 979 |
Purpose: Ensure content is not modified.
|
| 980 |
"""
|
| 981 |
print("Testing content preservation...")
|
| 982 |
+
from kiro.converters import inject_thinking_tags
|
| 983 |
|
| 984 |
original = "This is my original content with special chars: <>&"
|
| 985 |
|
| 986 |
+
with patch('kiro.converters.FAKE_REASONING_ENABLED', True):
|
| 987 |
+
with patch('kiro.converters.FAKE_REASONING_MAX_TOKENS', 4000):
|
| 988 |
result = inject_thinking_tags(original)
|
| 989 |
|
| 990 |
print(f"Result ends with original: {result.endswith(original)}")
|
tests/unit/test_tokenizer.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
|
| 3 |
"""
|
| 4 |
-
Unit-тесты для модуля токенизатора (
|
| 5 |
|
| 6 |
Проверяет:
|
| 7 |
- Подсчёт токенов в тексте (count_tokens)
|
|
@@ -15,7 +15,7 @@ Unit-тесты для модуля токенизатора (kiro_gateway/token
|
|
| 15 |
import pytest
|
| 16 |
from unittest.mock import patch, MagicMock
|
| 17 |
|
| 18 |
-
from
|
| 19 |
count_tokens,
|
| 20 |
count_message_tokens,
|
| 21 |
count_tools_tokens,
|
|
@@ -163,7 +163,7 @@ class TestCountTokensFallback:
|
|
| 163 |
print("Тест: Fallback без tiktoken...")
|
| 164 |
|
| 165 |
# Мокируем _get_encoding чтобы вернуть None
|
| 166 |
-
with patch('
|
| 167 |
result = count_tokens("Hello world test")
|
| 168 |
print(f"Результат: {result}")
|
| 169 |
|
|
@@ -180,7 +180,7 @@ class TestCountTokensFallback:
|
|
| 180 |
"""
|
| 181 |
print("Тест: Fallback без коррекции...")
|
| 182 |
|
| 183 |
-
with patch('
|
| 184 |
result = count_tokens("Test", apply_claude_correction=False)
|
| 185 |
print(f"Результат: {result}")
|
| 186 |
|
|
@@ -724,7 +724,7 @@ class TestGetEncoding:
|
|
| 724 |
print("Тест: tiktoken доступен...")
|
| 725 |
|
| 726 |
# Сбрасываем глобальную переменную для чистого теста
|
| 727 |
-
import
|
| 728 |
original_encoding = tokenizer_module._encoding
|
| 729 |
tokenizer_module._encoding = None
|
| 730 |
|
|
@@ -762,7 +762,7 @@ class TestGetEncoding:
|
|
| 762 |
"""
|
| 763 |
print("Тест: ImportError...")
|
| 764 |
|
| 765 |
-
import
|
| 766 |
original_encoding = tokenizer_module._encoding
|
| 767 |
tokenizer_module._encoding = None
|
| 768 |
|
|
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
|
| 3 |
"""
|
| 4 |
+
Unit-тесты для модуля токенизатора (kiro/tokenizer.py).
|
| 5 |
|
| 6 |
Проверяет:
|
| 7 |
- Подсчёт токенов в тексте (count_tokens)
|
|
|
|
| 15 |
import pytest
|
| 16 |
from unittest.mock import patch, MagicMock
|
| 17 |
|
| 18 |
+
from kiro.tokenizer import (
|
| 19 |
count_tokens,
|
| 20 |
count_message_tokens,
|
| 21 |
count_tools_tokens,
|
|
|
|
| 163 |
print("Тест: Fallback без tiktoken...")
|
| 164 |
|
| 165 |
# Мокируем _get_encoding чтобы вернуть None
|
| 166 |
+
with patch('kiro.tokenizer._get_encoding', return_value=None):
|
| 167 |
result = count_tokens("Hello world test")
|
| 168 |
print(f"Результат: {result}")
|
| 169 |
|
|
|
|
| 180 |
"""
|
| 181 |
print("Тест: Fallback без коррекции...")
|
| 182 |
|
| 183 |
+
with patch('kiro.tokenizer._get_encoding', return_value=None):
|
| 184 |
result = count_tokens("Test", apply_claude_correction=False)
|
| 185 |
print(f"Результат: {result}")
|
| 186 |
|
|
|
|
| 724 |
print("Тест: tiktoken доступен...")
|
| 725 |
|
| 726 |
# Сбрасываем глобальную переменную для чистого теста
|
| 727 |
+
import kiro.tokenizer as tokenizer_module
|
| 728 |
original_encoding = tokenizer_module._encoding
|
| 729 |
tokenizer_module._encoding = None
|
| 730 |
|
|
|
|
| 762 |
"""
|
| 763 |
print("Тест: ImportError...")
|
| 764 |
|
| 765 |
+
import kiro.tokenizer as tokenizer_module
|
| 766 |
original_encoding = tokenizer_module._encoding
|
| 767 |
tokenizer_module._encoding = None
|
| 768 |
|