
Research
Supply Chain Attack on Axios Pulls Malicious Dependency from npm
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.
solver-sdk
Advanced tools
SDK for WorkAI API - AI-powered code analysis with WorkCoins billing system
AI-powered code analysis SDK with WorkCoins billing system. 100% typed, production ready.
npm install solver-sdk@10.1.0
# или
npm install solver-sdk@latest
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({...});
| 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
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)
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?)
initSync(projectId, request) | uploadChunkBatch(projectId, chunks)
uploadChunksWithRetry(projectId, chunks, options?)
finalizeSync(projectId) | getSyncStatus(projectId)
cancelSync(projectId) | cleanupFiles(projectId, activeFiles)
searchCode(projectId, params) | searchFunctions(projectId, params)
semanticSearch(projectId, params) | getFunctionStats(projectId)
getSchemas() | findToolByName(name) | getToolsStats()
validateToolSchema(tool) | createToolSchema(name, desc, props, required?)
getAllModels() | getModels() | getAvailableModels()
getProviderModels(providerId) | getModelInfo(modelId)
checkForUpdates(options) | getChangelog(version, locale)
sendStats(event) | getLatestVersion(channel?) | checkAvailability()
getBalance() | getStatus() | estimate(tokens, model?, operationType?)
getProfile() | getLimitStatus() | checkAvailability()
revokeToken(token) | logout()
// 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
}
}
// Конфигурация (рекомендуется для 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 секунд перед повторной попыткой
}
});
| 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 |
All SDK methods throw typed exceptions. All errors have: statusCode, errorType, message, requestId, timestamp.
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';
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();
}
}
// 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([...]);
// 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`);
SDK передаёт только официальные события Anthropic API через callback onEvent. Когда AI создаёт или редактирует файлы, параметры инструментов стримятся посимвольно через официальное событие input_json_delta.
✅ Рекомендация Anthropic: Используйте стандартное событие
content_block_deltaс типомinput_json_deltaдля real-time визуализации прогресса. Это обеспечивает совместимость с обновлениями API и гарантирует стабильность работы.
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
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);
}
}
}
}));
| Событие | Данные | Описание |
|---|---|---|
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 | {} | Конец ответа |
content_block_delta// 🧠 Мышление AI (Extended Thinking)
delta: { type: 'thinking_delta', thinking: 'Анализирую требования...' }
// 📝 Текст ответа AI
delta: { type: 'text_delta', text: 'Создаю функцию...' }
// 🔧 Параметры инструмента (постепенно)
delta: { type: 'input_json_delta', partial_json: '{"file_path": "src/app' }
Обработка всех типов:
const accumulators = { thinking: '', text: '' };
sdk.chat.streamChat(messages, {
thinking: { type: 'enabled', budget_tokens: 10000 },
onEvent: (type, data) => {
if (type === 'content_block_delta') {
const { delta, index } = data;
// 🧠 Thinking - показываем прогресс "AI думает..."
if (delta.type === 'thinking_delta') {
accumulators.thinking += delta.thinking;
updateThinkingUI(accumulators.thinking); // Серый блок в UI
}
// 📝 Text - печатаем ответ в реальном времени
else if (delta.type === 'text_delta') {
accumulators.text += delta.text;
updateResponseUI(accumulators.text); // Обычный текст
}
// 🔧 Tool Input - см. "Streaming Tool Use" выше
else if (delta.type === 'input_json_delta') {
// Используйте PartialJsonAccumulator (детали выше)
}
}
}
});
При нестабильной сети чанки JSON могут приходить с задержками и рывками, что приводит к ошибкам парсинга на клиенте. Например:
❌ Ошибка: Unterminated string in JSON at position 96
{"pattern": "Dashboard", "explanation": "Ищу компоненты dashboard}
↑ обрыв
SDK предоставляет утилиты для безопасной работы с partial JSON:
1. PartialJsonAccumulator - накапливает чанки и безопасно парсит:
2. safeParsePartialJson - парсит с автоматическим восстановлением:
Сервер использует несколько слоев защиты:
,, }, ])✅ DO:
PartialJsonAccumulator для накопления чанковcontent_block_stop для финального парсинга❌ DON'T:
JSON.parse()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();
}
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>
);
}
SDK автоматически использует fine-grained tool streaming для всех запросов с tools. Это значительно снижает latency при генерации больших tool inputs:
При использовании fine-grained streaming возможны incomplete JSON responses (особенно при достижении max_tokens). SDK автоматически:
{"INVALID_JSON": "..."}Метрики доступны в DEBUG режиме логирования.
SDK автоматически передает события прогресса выполнения задач от Claude. Когда AI работает над сложной задачей (рефакторинг, добавление фичи), Claude создает TODO список для отслеживания прогресса.
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}`);
});
}
}
interface TodoUpdateEvent {
type: 'todo_update';
operation: 'write' | 'read';
todos: Array<{
id: string; // Уникальный ID
content: string; // Описание задачи
status: 'not-started' | 'in-progress' | 'completed';
activeForm?: string; // Дополнительная информация для in-progress
}>;
tool_use_id: string; // ID tool_use от Claude
progress: {
completed: number; // Количество завершенных задач
total: number; // Общее количество задач
current: string; // Описание текущей задачи
};
}
Пример:
{
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: 'not-started'
}
],
tool_use_id: 'toolu_01ABC123',
progress: {
completed: 1,
total: 3,
current: 'Implement feature'
}
}
Backend не требует явной отправки tool_result для manage_todo_list от клиента.
Событие todo_update отправляется для UI widget, но tool_result создается автоматически на стороне backend.
Клиент должен только обрабатывать todo_update события для обновления UI:
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, который:
Backend поддерживает оба формата статусов для совместимости:
| Backend Status | Cursor Format | Описание |
|---|---|---|
| Backend (Claude) | Frontend (VS Code) | Описание |
| --- | --- | --- |
not-started | not-started | Задача еще не начата |
in-progress | in-progress | Задача выполняется прямо сейчас |
completed | completed | Задача завершена |
Примечание: Статусы используют kebab-case формат (in-progress, not-started) для совместимости с VS Code API.
React компонент с использованием progress:
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 'not-started':
default: return '⏳';
}
}
import {
ChatMessage,
ChatOptions,
Project,
ProjectState,
SearchResult,
UserProfile,
LimitStatus,
LimitExceededError,
RateLimitError
} from 'solver-sdk';
const sdk = await CodeSolverSDK.create({
debug: 'verbose', // silent | error | warn | info | debug | verbose
webSocket: { debug: true }
});
// ✅ 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);
// Инвалидировать файл при изменении
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);
// 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
}
interface ProjectState {
projectId: string;
merkleRootHash: string | null;
totalFiles: number; // For UI display
indexingStatus: 'pending' | 'in-progress' | 'complete' | 'failed';
syncRequired?: boolean; // Compare with client hash
}
interface SyncProgressEvent {
projectId: string;
progress: number; // 0-100
processedFiles: number; // 450
totalFiles: number; // 722
stage: 'receiving_chunks' | 'processing_chunks' | 'creating_embeddings' | 'finalizing';
}
interface RecoveryInfo {
needsRecovery: boolean;
progress: {
processedFiles: number;
totalFiles: number;
percentage: number; // 0-100
};
canResume: boolean;
}
interface CreditsStatus {
status: 'ok' | 'warning' | 'critical' | 'depleted';
balance: CreditsBalance;
canMakeRequest: boolean;
message: string;
action?: CreditsAction;
}
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
}
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']
}
const state = await sdk.projects.getProjectState(projectId);
console.log(state.totalFiles); // Should be > 0
// If 0: update backend to latest version
// 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);
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);
}
SDK поддерживает Anthropic Web Search Tool - server-side инструмент для поиска в интернете.
Web search настраивается через environment variables на сервере:
# Включить 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
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
Когда модель использует server-side tool, вы получите stop_reason: 'pause_turn':
// В 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'
}
]
}
}
Web search запросы отслеживаются в usage.server_tool_use:
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
};
}
Official Anthropic Pricing:
Web search конвертируется в WorkCoins credits автоматически на основе текущего exchange rate.
sdk.projects)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>
sdk.deltaManager)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>
sdk.chat)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>
sdk.search)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 } }>
sdk.tools)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
sdk.user)getProfile(): Promise<UserProfile>
getLimitStatus(): Promise<LimitStatus>
checkAvailability(): Promise<boolean>
sdk.credits)getBalance(): Promise<CreditsBalance>
getStatus(): Promise<CreditsStatus>
estimate(tokens: number, model?: ModelType, operationType?: OperationType): Promise<CreditsEstimate>
sdk.auth)revokeToken(token: string): Promise<{ ok: boolean }>
logout(): Promise<{ ok: boolean }>
sdk.models)getAllModels(): Promise<any[]>
getModels(): Promise<any[]>
getAvailableModels(): Promise<any[]>
getProviderModels(providerId: string): Promise<ProviderModels>
getModelInfo(modelId: string): Promise<any>
sdk.updates)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>
sdk.projectSync) v10.1.0// 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: {
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: контроль логов
}
checkHealth(): Promise<boolean>
version: string
baseURL: string
All methods have full JSDoc comments with examples. Check your IDE autocomplete for detailed reference.
For full API documentation: See API Reference section above.
FAQs
SDK for WorkAI API - AI-powered code analysis with WorkCoins billing system
The npm package solver-sdk receives a total of 15 weekly downloads. As such, solver-sdk popularity was classified as not popular.
We found that solver-sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.

Security News
TeamPCP is partnering with ransomware group Vect to turn open source supply chain attacks on tools like Trivy and LiteLLM into large-scale ransomware operations.