solver-sdk
Version:
SDK for WorkAI API - AI-powered code analysis with WorkCoins billing system
1,262 lines (1,012 loc) • 41.8 kB
Markdown
# Code Solver SDK
AI-powered code analysis SDK with WorkCoins billing system. 100% typed, production ready.
## Install
```bash
npm install solver-sdk@10.1.0
# или
npm install solver-sdk@latest
```
## Quick Start
```typescript
import { CodeSolverSDK } from 'solver-sdk';
const sdk = await CodeSolverSDK.create({
baseURL: 'https://api.example.com',
getAuthToken: async () => getToken()
});
// Projects
const projects = await sdk.projects.getAllProjects();
await sdk.projects.createProject('MyProject');
// Chat with Thinking
const response = await sdk.chat.chat([
{ role: 'user', content: 'explain this code' }
], { thinking: { type: 'enabled', budget_tokens: 10000 } });
// Search
const results = await sdk.search.searchCode({
projectId,
query: 'authentication function'
});
// User & Updates
const profile = await sdk.user.getProfile();
const update = await sdk.updates.checkForUpdates({...});
```
## API Overview
| Feature | Methods | Status |
|---------|---------|--------|
| **Projects** | 19 | ✅ Full CRUD + state |
| **Delta Chunking** | 7 | ✅ Encrypted sync |
| **Chat** | 13 | ✅ Streaming + Extended Thinking |
| **Search** | 4 | ✅ Vector-based |
| **Tools** | 5 | ✅ AI tool schemas |
| **Models** | 5 | ✅ Provider info |
| **Updates** | 5 | ✅ Auto-check + changelog |
| **User** | 3 | ✅ Profile + Limits |
| **Credits** | 3 | ✅ Balance + Status + Estimates |
| **Auth** | 2 | ✅ Token revocation + logout |
**Total: 66 methods** | [Full API Reference](#api-reference)
## Quick Reference
<details>
<summary><b>Projects (19)</b> - управление проектами и индексация</summary>
```typescript
getAllProjects() | getProject(id) | createProject(name) | deleteProject(id)
findOrCreateProject(name) | getReadyProjects() | getProjectState(id)
getProjectStatus(id) | getIndexingStatus(id) | cancelIndexing(id)
clearIndexingError(id) | getRecoveryStatus(id) | resumeSync(id)
cancelRecovery(id) | initializeDeltaSync(id) | resetIndexing(id)
restartIndexing(id) | getFilePathMapping(id)
```
</details>
<details>
<summary><b>Chat (13)</b> - AI диалоги и streaming</summary>
```typescript
chat(messages, options?) | chatCompletion(messages, options?)
chatWithRegionFailover(messages, options?) | streamChat(messages, options?)
streamPrompt(prompt, options?) | sendContinuation(requestId, messages)
resumeChat(requestId, messages, partialText, options?)
sendPromptWithRegionFailover(prompt, options?) | checkAvailability()
cancelRequest(requestId) | getStreamsStats() | cleanupStaleStreams()
cancelUserStreams(reason?)
```
</details>
<details>
<summary><b>Delta Chunking (7)</b> - синхронизация файлов</summary>
```typescript
initSync(projectId, request) | uploadChunkBatch(projectId, chunks)
uploadChunksWithRetry(projectId, chunks, options?)
finalizeSync(projectId) | getSyncStatus(projectId)
cancelSync(projectId) | cleanupFiles(projectId, activeFiles)
```
</details>
<details>
<summary><b>Search (4)</b> - поиск кода</summary>
```typescript
searchCode(projectId, params) | searchFunctions(projectId, params)
semanticSearch(projectId, params) | getFunctionStats(projectId)
```
</details>
<details>
<summary><b>Tools (5)</b> - AI инструменты</summary>
```typescript
getSchemas() | findToolByName(name) | getToolsStats()
validateToolSchema(tool) | createToolSchema(name, desc, props, required?)
```
</details>
<details>
<summary><b>Models (5)</b> - AI модели</summary>
```typescript
getAllModels() | getModels() | getAvailableModels()
getProviderModels(providerId) | getModelInfo(modelId)
```
</details>
<details>
<summary><b>Updates (5)</b> - обновления SDK</summary>
```typescript
checkForUpdates(options) | getChangelog(version, locale)
sendStats(event) | getLatestVersion(channel?) | checkAvailability()
```
</details>
<details>
<summary><b>Credits (3)</b> - WorkCoins баланс</summary>
```typescript
getBalance() | getStatus() | estimate(tokens, model?, operationType?)
```
</details>
<details>
<summary><b>User (3)</b> - профиль и лимиты</summary>
```typescript
getProfile() | getLimitStatus() | checkAvailability()
```
</details>
<details>
<summary><b>Auth (2)</b> - аутентификация</summary>
```typescript
revokeToken(token) | logout()
```
</details>
## Auth
```typescript
// Local dev
const sdk = await CodeSolverSDK.create({
baseURL: 'http://localhost:3000',
getAuthToken: async () => 'your-token'
});
// Production with token refresh + error handling
import { AuthenticationError } from 'solver-sdk';
const sdk = await CodeSolverSDK.create({
baseURL: 'https://api.example.com',
getAuthToken: async () => {
if (isTokenExpired()) {
await refreshToken();
}
return getToken();
}
});
// All methods throw AuthenticationError on 401
try {
await sdk.user.getProfile();
} catch (error) {
if (error instanceof AuthenticationError) {
await refreshToken();
// retry
}
}
```
## WebSocket (Real-time) v10.1.0
```typescript
// Конфигурация (рекомендуется для production)
const sdk = await CodeSolverSDK.create({
baseURL: 'https://api.workai.su',
webSocket: {
enabled: true,
disableAutoReconnect: true, // Используйте свой reconnect координатор
logLevel: 'warn', // 'silent' | 'error' | 'warn' | 'info' | 'debug'
maxRetries: 0,
connectionTimeout: 15000,
}
});
// Connect
await sdk.connectWebSocket();
// Subscribe (ВАЖНО: вызвать в течение 30 секунд после connect!)
sdk.projectSync?.subscribeToProject(projectId);
// События
sdk.projectSync?.on('connected', () => console.log('✅ Connected'));
sdk.projectSync?.on('disconnected', ({ reason }) => console.log('🔌 Disconnected:', reason));
sdk.projectSync?.on('project-sync-joined', (e) => console.log('📋 Subscribed:', e.projectId));
sdk.projectSync?.on('disconnect-idle', (e) => console.log('⏰ Idle timeout:', e.idleTime));
sdk.projectSync?.on('reconnect_exhausted', (e) => console.log('💀 Retries exhausted:', e.attempts));
// Прогресс синхронизации
sdk.projectSync?.on('sync-progress', (data) => {
console.log(`Progress: ${data.processedFiles}/${data.totalFiles} (${data.progress}%)`);
});
sdk.projectSync?.on('sync-completed', (data) => {
console.log(`Done: ${data.statistics?.totalFiles} files`);
});
// Ошибки (с кодами: IP_CONNECTIONS_EXCEEDED, MAX_CONNECTIONS_EXCEEDED, etc.)
sdk.projectSync?.on('error', (e) => {
if (e.code === 'IP_CONNECTIONS_EXCEEDED') {
// Cooldown 60 секунд перед повторной попыткой
}
});
```
### WebSocket Events (v10.1.0)
| Event | Описание |
|-------|----------|
| `connected` | Подключение установлено |
| `disconnected` | Отключение (+ reason) |
| `project-sync-joined` | Подписка на проект подтверждена |
| `project-sync-left` | Отписка подтверждена |
| `sync-status-update` | Изменение статуса |
| `sync-progress` | Прогресс (processedFiles/totalFiles) |
| `sync-completed` | Завершение синхронизации |
| `error` | Ошибки (IP_CONNECTIONS_EXCEEDED, etc.) |
| `disconnect-idle` | Отключение по idle timeout (30s без subscribe) |
| `reconnect_exhausted` | Исчерпаны попытки reconnect |
## Error Handling
All SDK methods throw typed exceptions. All errors have: `statusCode`, `errorType`, `message`, `requestId`, `timestamp`.
### Available Error Types
```typescript
import {
AuthenticationError, // 401 - token expired/invalid
ForbiddenError, // 403 - access denied
ValidationError, // 422 - invalid data
NotFoundError, // 404 - resource not found
ConflictError, // 409 - data conflict
BadRequestError, // 400 - bad request
InternalServerError, // 500 - server error
ServiceUnavailableError,// 503 - service down
GatewayTimeoutError, // 504 - timeout
LimitExceededError, // 403 - WorkCoins limit exceeded
RateLimitError, // 429 - too many requests
NetworkError, // 0 - network failure
TimeoutError, // 408 - request timeout
DatabaseError // 400 - database error
} from 'solver-sdk';
```
### Common Patterns
```typescript
import { AuthenticationError, LimitExceededError, RateLimitError } from 'solver-sdk';
try {
await sdk.chat.streamChat([{ role: 'user', content: 'Hello' }]);
} catch (error) {
// Auth errors - refresh token and retry
if (error instanceof AuthenticationError) {
await refreshToken();
return retry();
}
// WorkCoins limit - show upgrade prompt
if (error instanceof LimitExceededError) {
console.log(`WorkCoins exhausted: ${error.message}`);
if (error.action) {
console.log(`Recommendation: ${error.action.suggestion}`);
console.log(`Action URL: ${error.action.url}`);
}
}
// Rate limit - wait and retry
if (error instanceof RateLimitError) {
await new Promise(r => setTimeout(r, error.getRetryDelayMs()));
return retry();
}
}
```
### Proactive Limit Checking
```typescript
// Check WorkCoins before expensive operations
const status = await sdk.credits.getStatus();
if (!status.canMakeRequest) {
console.error('No WorkCoins available');
return;
}
if (status.status === 'warning') {
console.warn(`Low on WorkCoins: ${status.message}`);
}
// Make request
await sdk.chat.streamChat([...]);
```
### WorkCoins (Credits) API
```typescript
// Get balance
const balance = await sdk.credits.getBalance();
console.log(`${balance.creditsRemaining} / ${balance.creditsLimit} WorkCoins`);
// Get status with recommendations
const status = await sdk.credits.getStatus();
console.log(`Status: ${status.status}`); // 'ok' | 'warning' | 'critical' | 'depleted'
// Estimate cost
const estimate = await sdk.credits.estimate(50000, 'haiku', 'code_generation');
console.log(`Estimated: ${estimate.estimatedCredits} WorkCoins`);
```
## Streaming Tool Use (Создание/Редактирование файлов)
SDK передаёт **только официальные события Anthropic API** через callback `onEvent`. Когда AI создаёт или редактирует файлы, параметры инструментов стримятся **посимвольно** через официальное событие `input_json_delta`.
> ✅ **Рекомендация Anthropic:** Используйте стандартное событие `content_block_delta` с типом `input_json_delta` для real-time визуализации прогресса. Это обеспечивает совместимость с обновлениями API и гарантирует стабильность работы.
### Real-time отображение прогресса через официальный API
```typescript
import { PartialJsonAccumulator, safeParsePartialJson } from '@code-solver/sdk';
// 🛡️ СПОСОБ 1: Используем PartialJsonAccumulator (рекомендуется)
const toolAccumulators = new Map<number, PartialJsonAccumulator>();
for await (const chunk of sdk.chat.streamChat(messages, {
projectId: 'project-id',
tools: ['read_file', 'search_replace'],
onEvent: (type, data) => {
// 🎯 Стриминг параметров инструмента
if (type === 'content_block_delta' && data.delta?.type === 'input_json_delta') {
const index = data.index;
// Инициализируем accumulator для блока
if (!toolAccumulators.has(index)) {
toolAccumulators.set(index, new PartialJsonAccumulator(console));
}
const accumulator = toolAccumulators.get(index)!;
accumulator.add(data.delta.partial_json);
// Пытаемся распарсить (с автоматическим восстановлением)
const parsedInput = accumulator.tryParse();
if (parsedInput) {
// 💡 Показываем прогресс с безопасно распарсенными данными
console.log(`📝 ${parsedInput.operation || 'Обработка'}: ${parsedInput.file_path || '...'}`);
updateProgress({
file: parsedInput.file_path,
operation: parsedInput.operation,
bytesReceived: accumulator.getAccumulated().length
});
}
}
// ✅ Инструмент готов к выполнению (полный JSON получен)
if (type === 'content_block_stop') {
const index = data.index;
const accumulator = toolAccumulators.get(index);
if (accumulator) {
const toolInput = accumulator.tryParse();
if (toolInput) {
console.log('🔧 Инструмент готов:', toolInput);
} else {
console.error('❌ Не удалось распарсить JSON инструмента');
}
toolAccumulators.delete(index); // Очищаем
}
}
}
})) {
// Обрабатываем остальные события...
}
```
**🛡️ СПОСОБ 2: Ручная работа с safeParsePartialJson**
```typescript
const toolInputs = new Map<number, string>();
for await (const chunk of sdk.chat.streamChat(messages, {
onEvent: (type, data) => {
if (type === 'content_block_delta' && data.delta?.type === 'input_json_delta') {
const index = data.index;
const accumulated = (toolInputs.get(index) || '') + data.delta.partial_json;
toolInputs.set(index, accumulated);
// Безопасный парсинг с автоматическим восстановлением
const parsedInput = safeParsePartialJson(accumulated, console);
if (parsedInput?.file_path) {
console.log(`📝 ${parsedInput.operation}: ${parsedInput.file_path}`);
}
}
if (type === 'content_block_stop') {
const fullJson = toolInputs.get(data.index);
if (fullJson) {
const toolInput = safeParsePartialJson(fullJson, console);
console.log('🔧 Инструмент готов:', toolInput);
toolInputs.delete(data.index);
}
}
}
}));
```
### Официальные события Anthropic API
| Событие | Данные | Описание |
|---------|--------|----------|
| `message_start` | `{ message: {...} }` | Начало ответа AI |
| `content_block_start` | `{ index, content_block }` | Начало блока (text/thinking/tool_use) |
| **`content_block_delta`** | `{ index, delta }` | **Стриминг содержимого** |
| `content_block_stop` | `{ index }` | Завершение блока |
| `message_delta` | `{ delta }` | Метаданные ответа |
| `message_stop` | `{}` | Конец ответа |
### Типы delta для `content_block_delta`
```typescript
// Текст ответа AI
delta: { type: 'text_delta', text: 'Создаю функцию...' }
// Мышление AI (Extended Thinking)
delta: { type: 'thinking_delta', thinking: 'Анализирую требования...' }
// 🎯 Параметры инструмента (посимвольно!)
delta: { type: 'input_json_delta', partial_json: '{"file_path": "src/app' }
```
## Защита от сетевых задержек при стриминге
### Проблема
При нестабильной сети чанки JSON могут приходить с задержками и рывками,
что приводит к ошибкам парсинга на клиенте. Например:
```
❌ Ошибка: Unterminated string in JSON at position 96
{"pattern": "Dashboard", "explanation": "Ищу компоненты dashboard}
↑ обрыв
```
### Решение
SDK предоставляет утилиты для безопасной работы с partial JSON:
**1. PartialJsonAccumulator** - накапливает чанки и безопасно парсит:
- Автоматически восстанавливает незакрытые кавычки
- Балансирует скобки {} и []
- Возвращает null если JSON еще неполный
**2. safeParsePartialJson** - парсит с автоматическим восстановлением:
- Пытается исправить типичные проблемы чанкинга
- Логирует процесс восстановления (опционально)
- Безопасно возвращает null при неудаче
### Серверная защита
Сервер использует несколько слоев защиты:
- **Умный чанкинг** - режет JSON только на безопасных границах (после `,`, `}`, `]`)
- **Event batching** - сглаживает отправку событий (16мс интервал, ~60fps)
- **Валидация tool_use** - проверяет JSON перед отправкой клиенту
- **Метрики качества** - отслеживает принудительные разрывы (должно быть 0)
### Рекомендации для клиентов
✅ **DO:**
- Всегда используйте `PartialJsonAccumulator` для накопления чанков
- Обрабатывайте `content_block_stop` для финального парсинга
- Логируйте ошибки парсинга для диагностики
❌ **DON'T:**
- Не парсите partial JSON напрямую через `JSON.parse()`
- Не игнорируйте ошибки парсинга - они указывают на проблемы
- Не ожидайте что каждый чанк содержит валидный JSON
### Пример использования
```typescript
import { PartialJsonAccumulator } from '@code-solver/sdk';
const accumulator = new PartialJsonAccumulator(console);
// При получении чанка
if (event.type === 'content_block_delta' && event.delta?.type === 'input_json_delta') {
accumulator.add(event.delta.partial_json);
// Пробуем распарсить для UI превью (может быть null)
const preview = accumulator.tryParse();
if (preview?.file_path) {
updateProgressUI(preview.file_path);
}
}
// При завершении блока
if (event.type === 'content_block_stop') {
const finalInput = accumulator.tryParse();
if (finalInput) {
console.log('✅ Tool готов:', finalInput);
} else {
console.error('❌ Не удалось распарсить tool input');
}
accumulator.reset();
}
```
### Пример UI компонента
```typescript
function ToolProgressIndicator() {
const [currentTool, setCurrentTool] = useState<{
file?: string;
operation?: string;
progress: number;
} | null>(null);
// В onEvent callback
if (type === 'content_block_delta' && data.delta?.type === 'input_json_delta') {
// Извлекаем инфо из partial JSON
const fileMatch = accumulated.match(/"file_path"\s*:\s*"([^"]*)"/);
const opMatch = accumulated.match(/"operation"\s*:\s*"([^"]*)"/);
setCurrentTool({
file: fileMatch?.[1],
operation: opMatch?.[1] || 'processing',
progress: accumulated.length
});
}
return currentTool && (
<div className="tool-progress">
<span className="operation">{currentTool.operation}</span>
<span className="file">{currentTool.file}</span>
<div className="progress-bar" style={{ width: `${currentTool.progress / 10}%` }} />
</div>
);
}
```
## Fine-grained Tool Streaming
SDK автоматически использует fine-grained tool streaming для всех запросов с tools. Это значительно снижает latency при генерации больших tool inputs:
- **Без fine-grained**: задержки 15+ секунд, мелкие чанки
- **С fine-grained**: задержки ~3 секунды, крупные чанки
### Обработка Invalid JSON
При использовании fine-grained streaming возможны incomplete JSON responses (особенно при достижении max_tokens). SDK автоматически:
1. Обнаруживает invalid JSON в tool responses
2. Оборачивает в валидный объект `{"INVALID_JSON": "..."}`
3. Отправляет retry запрос с увеличенным max_tokens
Метрики доступны в DEBUG режиме логирования.
## TODO Tracking
SDK автоматически передает события прогресса выполнения задач от Claude. Когда AI работает над сложной задачей (рефакторинг, добавление фичи), Claude создает TODO список для отслеживания прогресса.
### Пример использования
```typescript
import { ChatApi } from '@vscursor/solver-sdk';
const api = new ChatApi(baseURL);
for await (const chunk of api.streamChat(messages, { thinking: true })) {
if (chunk.type === 'todo_update') {
const event = chunk as any;
// Отображаем прогресс-бар
console.log(`📋 Прогресс: ${event.progress?.completed}/${event.progress?.total}`);
if (event.progress?.current) {
console.log(`🔧 Текущая задача: ${event.progress.current}`);
}
// Отображаем все задачи
event.todos.forEach((todo: any) => {
const icon = todo.status === 'completed' ? '✅' :
todo.status === 'in_progress' ? '🔧' : '⏳';
const text = todo.status === 'in_progress' && todo.activeForm
? `${todo.content} (${todo.activeForm})`
: todo.content;
console.log(`${icon} ${text}`);
});
}
}
```
### Формат TODO события
```typescript
interface TodoUpdateEvent {
type: 'todo_update';
operation: 'write' | 'read';
todos: Array<{
id: string; // Уникальный ID (например: "todo-1234567890-abc")
content: string; // Описание задачи
status: 'pending' | 'in_progress' | 'completed' | 'cancelled';
activeForm?: string; // Дополнительная информация для in_progress
}>;
tool_use_id: string; // ID tool_use от Claude
progress: {
completed: number; // Количество завершенных задач
total: number; // Общее количество задач
current: string; // Описание текущей задачи
};
}
```
**Пример:**
```typescript
{
type: 'todo_update',
operation: 'write',
todos: [
{
id: 'todo-1763533466409-uxptasdq3',
content: 'Analyze codebase',
status: 'completed',
},
{
id: 'todo-1763533466409-r67wnbk83',
content: 'Implement feature',
status: 'in_progress',
activeForm: 'Creating authentication logic...'
},
{
id: 'todo-1763533466409-2se2juf10',
content: 'Add tests',
status: 'pending'
}
],
tool_use_id: 'toolu_01ABC123',
progress: {
completed: 1,
total: 3,
current: 'Implement feature'
}
}
```
### ⚠️ Важно: Backend автоматически обрабатывает manage_todo_list
Backend не требует явной отправки `tool_result` для `manage_todo_list` от клиента.
Событие `todo_update` отправляется для UI widget, но `tool_result` создается автоматически на стороне backend.
**Клиент должен только обрабатывать `todo_update` события для обновления UI:**
```typescript
if (chunk.type === 'todo_update') {
// ✅ Обновить TODO widget в UI
updateTodoWidget(chunk.todos, chunk.progress);
// ✅ Показать прогресс-бар
updateProgressBar(chunk.progress.completed, chunk.progress.total);
// ✅ Показать текущую задачу
if (chunk.progress.current) {
showCurrentTask(chunk.progress.current);
}
// ❌ НЕ нужно отправлять tool_result обратно!
// Backend создает tool_result автоматически
}
```
**Почему так работает:**
`manage_todo_list` - это информационный CLIENT tool, который:
- Не блокирует выполнение задач
- Используется только для UI tracking
- Автоматически помечается как успешный на backend
- Не требует реального выполнения на клиенте
### Статусы TODO
Backend поддерживает оба формата статусов для совместимости:
| Backend Status | Cursor Format | Описание |
|---------------|---------------|----------|
| `pending` | `not-started` | Задача еще не начата |
| `in_progress` | `in-progress` | Задача выполняется прямо сейчас |
| `completed` | `completed` | Задача завершена |
| `cancelled` | `cancelled` | Задача отменена |
**Примечание:** Backend автоматически нормализует `not-started` → `pending` и `in-progress` → `in_progress` для внутренней обработки.
### Интеграция в UI
**React компонент с использованием progress:**
```typescript
interface TodoProgressViewProps {
todos: Array<{
id: string;
content: string;
status: string;
activeForm?: string;
}>;
progress: {
completed: number;
total: number;
current: string;
};
}
function TodoProgressView({ todos, progress }: TodoProgressViewProps) {
const progressPercent = Math.round((progress.completed / progress.total) * 100);
return (
<div className="todo-progress">
{/* Прогресс-бар */}
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${progressPercent}%` }}
aria-valuenow={progressPercent}
aria-valuemin={0}
aria-valuemax={100}
/>
</div>
{/* Текстовый прогресс */}
<div className="progress-text">
{progress.completed}/{progress.total} tasks completed
</div>
{/* Текущая задача (если есть) */}
{progress.current && (
<div className="current-task">
🔧 <strong>Working on:</strong> {progress.current}
</div>
)}
{/* Список задач */}
<ul className="todo-list">
{todos.map(todo => {
const icon = getIcon(todo.status);
const text = todo.status === 'in_progress' && todo.activeForm
? `${todo.content} (${todo.activeForm})`
: todo.content;
return (
<li key={todo.id} className={`todo-item ${todo.status}`}>
<span className="todo-icon">{icon}</span>
<span className="todo-text">{text}</span>
</li>
);
})}
</ul>
</div>
);
}
function getIcon(status: string): string {
switch (status) {
case 'completed': return '✅';
case 'in_progress': return '🔧';
case 'cancelled': return '❌';
case 'pending':
default: return '⏳';
}
}
```
## Types
```typescript
import {
ChatMessage,
ChatOptions,
Project,
ProjectState,
SearchResult,
UserProfile,
LimitStatus,
LimitExceededError,
RateLimitError
} from 'solver-sdk';
```
## Debug
```typescript
const sdk = await CodeSolverSDK.create({
debug: 'verbose', // silent | error | warn | info | debug | verbose
webSocket: { debug: true }
});
```
## Delta Chunking (Indexing)
```typescript
// ✅ Main indexing method
await sdk.deltaManager.syncEncryptedChunks(projectId, chunks, rootHash, {
batchSize: 50,
onProgress: (current, total) => console.log(`${current}/${total}`)
});
// Check sync status
const state = await sdk.projects.getProjectState(projectId, clientRootHash);
if (state.syncRequired) {
// Needs sync
}
// Cancel ongoing sync
await sdk.deltaManager.cancelSync(projectId);
```
### File Invalidation
```typescript
// Инвалидировать файл при изменении
const result = await sdk.deltaManager.invalidateFile(projectId, {
filePath: 'src/app.tsx',
reason: 'file_changed'
});
console.log(`Invalidated ${result.chunksInvalidated} chunks`);
console.log(`Cache invalidated: ${result.cacheInvalidated}`);
// Отправить новые chunks после инвалидации
await sdk.deltaManager.syncEncryptedChunks(projectId, newChunks, rootHash);
```
## Session Recovery
```typescript
// Always check on project open
const recovery = await sdk.projects.getRecoveryStatus(projectId);
if (recovery.needsRecovery) {
const { processedFiles, totalFiles, percentage } = recovery.progress;
// Show dialog: "Continue (67%) — 480/642 files" or "Start Fresh"
await sdk.projects.resumeSync(projectId); // Continue
// OR
await sdk.projects.cancelRecovery(projectId); // Start over
}
```
## Key Types
### ProjectState
```typescript
interface ProjectState {
projectId: string;
merkleRootHash: string | null;
totalFiles: number; // For UI display
indexingStatus: 'pending' | 'in-progress' | 'complete' | 'failed';
syncRequired?: boolean; // Compare with client hash
}
```
### SyncProgressEvent
```typescript
interface SyncProgressEvent {
projectId: string;
progress: number; // 0-100
processedFiles: number; // 450
totalFiles: number; // 722
stage: 'receiving_chunks' | 'processing_chunks' | 'creating_embeddings' | 'finalizing';
}
```
### RecoveryInfo
```typescript
interface RecoveryInfo {
needsRecovery: boolean;
progress: {
processedFiles: number;
totalFiles: number;
percentage: number; // 0-100
};
canResume: boolean;
}
```
### CreditsStatus
```typescript
interface CreditsStatus {
status: 'ok' | 'warning' | 'critical' | 'depleted';
balance: CreditsBalance;
canMakeRequest: boolean;
message: string;
action?: CreditsAction;
}
```
### CreditsBalance
```typescript
interface CreditsBalance {
creditsLimit: number; // Monthly credits limit
creditsUsed: number; // Total credits used
creditsRemaining: number; // Credits remaining
percentUsed: number; // Usage percentage (0-100)
bonusCredits?: number; // Bonus credits available
bonusCreditsUsed?: number; // Bonus credits used
bonusCreditsRemaining?: number; // Bonus credits remaining
autoPurchaseEnabled: boolean; // Is auto-purchase enabled
purchasedCredits?: number; // Credits purchased this month via auto-purchase
resetDate?: Date; // Monthly reset date
}
```
### CreditsAction
```typescript
interface CreditsAction {
type: 'info' | 'upgrade' | 'enable_auto_purchase' | 'buy_credits';
suggestion: string; // User-facing recommendation
url: string; // Action URL (e.g. '/dashboard#billing')
options?: string[]; // Available options ['enable_auto_purchase', 'buy_credits', 'upgrade']
}
```
## Troubleshooting
### "0 files" after indexing
```typescript
const state = await sdk.projects.getProjectState(projectId);
console.log(state.totalFiles); // Should be > 0
// If 0: update backend to latest version
```
### No WebSocket updates
```typescript
// 1. Check connection
console.log(sdk.isWebSocketConnected); // should be true
// 2. Verify subscription
sdk.projectSync.subscribeToProject(projectId);
// 3. Ensure OAuth token (production)
const sdk = await CodeSolverSDK.create({
getAuthToken: () => authManager.getAccessToken()
});
// 4. Fallback to HTTP polling
const status = await sdk.projects.getIndexingStatus(projectId);
```
### Interrupted sync
```typescript
const recovery = await sdk.projects.getRecoveryStatus(projectId);
if (recovery.needsRecovery) {
// Option 1: Resume
await sdk.projects.resumeSync(projectId);
// Option 2: Cancel and restart
await sdk.projects.cancelRecovery(projectId);
}
```
## 🔍 Web Search Tool
SDK поддерживает Anthropic Web Search Tool - server-side инструмент для поиска в интернете.
### Конфигурация
Web search настраивается через environment variables на сервере:
```bash
# Включить web search
WEB_SEARCH_ENABLED=true
# Максимальное количество searches за один запрос
WEB_SEARCH_MAX_USES=5
# Whitelist доменов (optional)
WEB_SEARCH_ALLOWED_DOMAINS=wikipedia.org,stackoverflow.com,github.com
# Blacklist доменов (optional)
WEB_SEARCH_BLOCKED_DOMAINS=spam.com,malicious-site.org
```
### Content Block Types для Server-Side Tools
```typescript
type ServerSideContentBlock =
| 'server_tool_use' // Server-side tool использование
| 'web_search_tool_result' // Результаты поиска
| 'web_fetch_tool_result' // Результаты web fetch
| 'code_execution_tool_result' // Результаты code execution
| 'text_editor_code_execution_tool_result'; // Результаты text editor
```
### Stop Reason: pause_turn
Когда модель использует server-side tool, вы получите `stop_reason: 'pause_turn'`:
```typescript
// В streaming событии message_delta
{
type: 'message_delta',
delta: {
stop_reason: 'pause_turn' // Server-side tool execution
}
}
// После выполнения придут результаты
{
type: 'content_block_start',
content_block: {
type: 'web_search_tool_result',
content: [
{
type: 'web_search_result',
url: 'https://example.com',
title: 'Example Page',
encrypted_content: '...',
page_age: '2024-01-15'
}
]
}
}
```
### Usage Tracking
Web search запросы отслеживаются в `usage.server_tool_use`:
```typescript
interface Usage {
input_tokens: number;
output_tokens: number;
cache_read_input_tokens?: number;
cache_creation_input_tokens?: number;
server_tool_use?: {
web_search_requests?: number; // Количество searches
code_execution_requests?: number; // Количество code executions
};
}
```
### Pricing
**Official Anthropic Pricing:**
- **$10 за 1000 web searches**
- **$0.01 за один search**
Web search конвертируется в WorkCoins credits автоматически на основе текущего exchange rate.
## API Reference
### Projects (`sdk.projects`)
```typescript
getAllProjects(): Promise<Project[]>
getProjects(): Promise<Project[]>
getProject(projectId: string): Promise<Project>
createProject(name: string, data?: any, options?: ProjectOptions): Promise<Project>
findOrCreateProject(name: string): Promise<Project>
deleteProject(projectId: string): Promise<void>
getReadyProjects(): Promise<Project[]>
getProjectState(projectId: string, clientRootHash?: string): Promise<ProjectState>
getProjectStatus(projectId: string): Promise<any>
getIndexingStatus(projectId: string): Promise<any>
cancelIndexing(projectId: string): Promise<boolean>
clearIndexingError(projectId: string): Promise<boolean>
getRecoveryStatus(projectId: string): Promise<RecoveryInfo>
resumeSync(projectId: string, options?: any): Promise<any>
cancelRecovery(projectId: string): Promise<void>
initializeDeltaSync(projectId: string, params: any): Promise<any>
resetIndexing(projectId: string): Promise<any>
restartIndexing(projectId: string): Promise<any>
getFilePathMapping(projectId: string): Promise<any>
```
### Delta Manager (`sdk.deltaManager`)
```typescript
initSync(projectId: string, request: SyncInitRequest): Promise<any>
uploadChunkBatch(projectId: string, chunks: any[]): Promise<any>
uploadChunksWithRetry(projectId: string, chunks: any[], options?: any): Promise<any>
finalizeSync(projectId: string): Promise<SyncResult>
getSyncStatus(projectId: string): Promise<SyncStatus>
cancelSync(projectId: string): Promise<{ success: boolean; message?: string }>
cleanupFiles(projectId: string, activeFiles: string[]): Promise<any>
```
### Chat (`sdk.chat`)
```typescript
chat(messages: ChatMessage[], options?: ChatOptions): Promise<ChatResponse>
chatCompletion(messages: ChatMessage[], options?: ChatOptions): Promise<ChatResponse>
chatWithRegionFailover(messages: ChatMessage[], options?: ChatOptions): Promise<ChatResponse>
streamChat(messages: ChatMessage[], options?: ChatStreamOptions): AsyncGenerator<ChatStreamChunk>
streamPrompt(prompt: string, options?: ChatStreamOptions): AsyncGenerator<ChatStreamChunk>
sendContinuation(requestId: string, messages: ChatMessage[]): AsyncGenerator<ChatStreamChunk>
resumeChat(requestId: string, messages: ChatMessage[], partialText: string, options?: ChatStreamOptions): AsyncGenerator<ChatStreamChunk>
sendPromptWithRegionFailover(prompt: string, options?: ChatOptions): Promise<string>
checkAvailability(): Promise<boolean>
cancelRequest(requestId: string): Promise<void>
getStreamsStats(): Promise<any>
cleanupStaleStreams(timeoutMs?: number): Promise<any>
cancelUserStreams(reason?: string): Promise<number>
```
### Search (`sdk.search`)
```typescript
searchCode(projectIdOrParams: string | SearchCodeParams, params?: SearchCodeParams): Promise<SearchResult[]>
searchFunctions(projectIdOrParams: string | SearchFunctionsParams, params?: SearchFunctionsParams): Promise<FunctionSearchResult>
semanticSearch(projectId: string, params: SearchCodeParams): Promise<SearchResult[]>
getFunctionStats(projectId: string): Promise<{ stats: { totalFunctions: number } }>
```
### Tools (`sdk.tools`)
```typescript
getSchemas(): Promise<ToolsResponse>
findToolByName(name: string): Promise<ToolSchema | null>
getToolsStats(): Promise<{ total: number; categories: Record<string, number>; mostUsed?: string[] }>
validateToolSchema(tool: ToolSchema): boolean
createToolSchema(name: string, description: string, properties: any, required?: string[]): ToolSchema
```
### User (`sdk.user`)
```typescript
getProfile(): Promise<UserProfile>
getLimitStatus(): Promise<LimitStatus>
checkAvailability(): Promise<boolean>
```
### Credits (`sdk.credits`)
```typescript
getBalance(): Promise<CreditsBalance>
getStatus(): Promise<CreditsStatus>
estimate(tokens: number, model?: ModelType, operationType?: OperationType): Promise<CreditsEstimate>
```
### Auth (`sdk.auth`)
```typescript
revokeToken(token: string): Promise<{ ok: boolean }>
logout(): Promise<{ ok: boolean }>
```
### Models (`sdk.models`)
```typescript
getAllModels(): Promise<any[]>
getModels(): Promise<any[]>
getAvailableModels(): Promise<any[]>
getProviderModels(providerId: string): Promise<ProviderModels>
getModelInfo(modelId: string): Promise<any>
```
### Updates (`sdk.updates`)
```typescript
checkForUpdates(options: UpdateCheckOptions): Promise<UpdateResponse>
getChangelog(version: string, locale: string): Promise<string>
sendStats(event: UpdateStatsEvent): Promise<void>
getLatestVersion(channel?: string): Promise<LatestVersionInfo>
checkAvailability(): Promise<boolean>
```
### WebSocket (`sdk.projectSync`) v10.1.0
```typescript
// Connection
connectWebSocket(): Promise<void>
disconnectWebSocket(): void
isWebSocketConnected: boolean
// Subscription
projectSync.subscribeToProject(projectId: string): void
projectSync.unsubscribeFromProject(projectId: string): void
// Events (v10.1.0 - полный список)
projectSync.on('connected', callback): void
projectSync.on('disconnected', callback): void
projectSync.on('project-sync-joined', callback): void // NEW
projectSync.on('project-sync-left', callback): void // NEW
projectSync.on('sync-status-update', callback): void
projectSync.on('sync-progress', callback): void
projectSync.on('sync-completed', callback): void
projectSync.on('error', callback): void
projectSync.on('disconnect-idle', callback): void // NEW
projectSync.on('reconnect_exhausted', callback): void // NEW
projectSync.off(event: string, callback): void
```
### WebSocket Options (v10.1.0)
```typescript
webSocket: {
enabled?: boolean; // default: true
connectionTimeout?: number; // default: 10000 (ms)
maxRetries?: number; // default: 3
retryDelay?: number; // default: 2000 (ms)
debug?: boolean; // default: false
disableAutoReconnect?: boolean; // NEW: полностью отключить SDK reconnect
logLevel?: 'silent' | 'error' | 'warn' | 'info' | 'debug'; // NEW: контроль логов
}
```
### Utility
```typescript
checkHealth(): Promise<boolean>
version: string
baseURL: string
```
## Documentation
All methods have full JSDoc comments with examples. Check your IDE autocomplete for detailed reference.
**For full API documentation:** See [API Reference](#api-reference) section above.