惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
CERT Recently Published Vulnerability Notes
D
Docker
GbyAI
GbyAI
博客园 - 三生石上(FineUI控件)
博客园_首页
H
Help Net Security
T
The Exploit Database - CXSecurity.com
Microsoft Security Blog
Microsoft Security Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
P
Privacy & Cybersecurity Law Blog
AWS News Blog
AWS News Blog
Spread Privacy
Spread Privacy
F
Full Disclosure
Blog — PlanetScale
Blog — PlanetScale
Y
Y Combinator Blog
V
Vulnerabilities – Threatpost
P
Proofpoint News Feed
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
aimingoo的专栏
aimingoo的专栏
Simon Willison's Weblog
Simon Willison's Weblog
MyScale Blog
MyScale Blog
P
Palo Alto Networks Blog
S
Security Affairs
T
Tailwind CSS Blog
T
Tor Project blog
W
WeLiveSecurity
G
GRAHAM CLULEY
Know Your Adversary
Know Your Adversary
The Hacker News
The Hacker News
腾讯CDC
M
MIT News - Artificial intelligence
D
DataBreaches.Net
量子位
Martin Fowler
Martin Fowler
T
Threat Research - Cisco Blogs
博客园 - 【当耐特】
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
云风的 BLOG
云风的 BLOG
C
Cisco Blogs
I
InfoQ
Engineering at Meta
Engineering at Meta
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
C
CXSECURITY Database RSS Feed - CXSecurity.com
O
OpenAI News
美团技术团队
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
Troy Hunt's Blog
Microsoft Azure Blog
Microsoft Azure Blog
L
LangChain Blog
A
About on SuperTechFans

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Как я написал свой Claude Code на DeepSeek за вечер
grand_inquis · 2026-05-14 · via Все публикации подряд на Хабре

# Зачем это всё
Claude Code — терминальный AI-ассистент к которому захотелось прикрутить Дипсик, но есть маленькая Проблема - он привязан к API Anthropic.

Естественно захотелось запилить свой велосипед с черным CMD и командами обеспечивающие ключевые концепции: tool use, permissions, memory, compaction, subagents — но с нуля, на чистом Node.js.Результат — deepseek-agent: ~2000 строк кода, 4 зависимости (openai, fast-glob, dotenv, @modelcontextprotocol/sdk), никаких фреймворков.Казалось бы, не столько сложно запилить своего агента, но есть нюансы.И так поехали

С ходу пилим такую структуру:`` index.js — точка входа, REPL src/agent.js — agent loop src/config.js — .agent/settings.json src/memory.js — AGENT.md → system prompt src/permissions.js — alwaysAllow / neverAllow / [y/N] src/hooks.js — PreToolUse / PostToolUse события src/compactor.js — автосжатие контекста через LLM src/mcp.js — подключение MCP-серверов src/thinking.js — deepseek-reasoner (--think) src/worktree.js — git worktree изоляция src/output.js — JSON-режим для CI src/ui.js — ANSI-цвета src/tools/ — 9 инструментов ` Ключевое решение: каждый инструмент — это объект с фиксированной структурой:`js { name: "read_file", description: "Read the contents of a text file.", parameters: { /* JSON Schema / }, isReadOnly: true, // false = нужно разрешение async execute(args) { return "результат строкой" } } `

Добавить инструмент = написать объект и вставить его в массив TOOLS. Маршрутизация, JSON Schema для API, хуки, разрешения — всё подхватывается автоматически. но чего-то не хватает, давай добавим сессии, команды, документацию еще три коммита.Вынес систему команд (/clear, /compact, /diff, /review, ...) в отдельный commands.js. Добавил session.js — сессии с чекпоинтами. Переписал README. Здесь появилась важная абстракция: *команды и инструменты — разные вещи**. Команды (/clear, /rewind) — для пользователя. Инструменты (read_file, bash) — для модели. Команды могут вызывать agentLoop(), но не наоборот. Зарегистрировал глобальную команду agent через npm link и поле bin в package.json. Теперь вместо npm start — просто agent из любой директории. Казалось бы все хорошо, но конечно же нет (а как ты хотел) Еще пол дня на полировку проекта. Каждый коммит — конкретная проблема, вроде мелочи, а сильно портят картину.--- ## Архитектура### Agent Loop — сердце агентаВсё строится вокруг одного цикла в agent.js:` agentLoop(userMessage) ├─ pushMessage({ role: "user", content: userMessage }) └─ while(true) ├─ compactIfNeeded() — сжать контекст если > 80% лимита ├─ chat.completions.create({ stream: true }) │ ├─ собрать fullContent (текст ответа) │ └─ собрать toolCalls (вызовы инструментов) ├─ finish_reason === "stop" → return └─ finish_reason === "tool_calls" └─ для каждого вызова: ├─ PreToolUse hook ├─ checkPermission() ├─ tool.execute(args) ├─ PostToolUse hook └─ pushMessage({ role: "tool", result }) ` Модель сама решает, какой инструмент вызвать. Агент выполняет вызов и возвращает результат обратно в контекст. Цикл крутится, пока модель не ответит stop.DeepSeek API совместим с OpenAI — используется пакет openai с кастомным baseURL:`js const client = new OpenAI({ baseURL: "https://api.deepseek.com", apiKey: process.env.DEEPSEEK_API_KEY }) ` ### Стриминг: собираем tool_calls из дельтПри стриминге tool_calls приходят по частям. Имя функции и аргументы дробятся на чанки:`js for await (const chunk of stream) { if (delta?.tool_calls) { for (const tc of delta.tool_calls) { if (!toolCalls[tc.index]) { toolCalls[tc.index] = { id: "", type: "function", function: { name: "", arguments: "" } } } if (tc.id) toolCalls[tc.index].id += tc.id if (tc.function?.name) toolCalls[tc.index].function.name += tc.function.name if (tc.function?.arguments) toolCalls[tc.index].function.arguments += tc.function.arguments } } } ` Ключевой момент: tc.index определяет, к какому tool_call относится дельта. Без этого нельзя корректно обработать параллельные вызовы.--- ## Инструменты: 9 штук, каждый — один файл### read_file — чтение с определением кодировкиНе просто fs.readFile. Определяем кодировку по BOM:`js if (buf[0] === 0xEF && buf[1] === 0xBB && buf[2] === 0xBF) { return buf.slice(3).toString("utf-8") // UTF-8 BOM } if (buf[0] === 0xFF && buf[1] === 0xFE) { return buf.slice(2).toString("utf16le") // UTF-16 LE } ` Бинарные файлы блокируются по расширению — без этого модель радостно пытается «прочитать» .png и .exe, тратя токены на мусор.### bash — песочницаБлокируем опасные паттерны по умолчанию:`js const SANDBOX_BLOCKED = [ /\bcurl\b/, /\bwget\b/, // сеть /\brm\s+-rf\s+\//, // деструктивные операции /\bsudo\b/, /\bsu\b/ // привилегии ] ` На Windows переключаем кодовую страницу в UTF-8 перед каждой командой:`js const cmd = process.platform === "win32" ? chcp 65001 >nul 2>&1 & ${command} : command ` Вывод обрезается до 8000 символов — без этого один cat на большой файл съест весь контекст.### edit_file — точная замена строкВместо line-based diff — exact string replacement. Модель передаёт old_string и new_string. Если строка встречается больше одного раза — ошибка:`js const count = original.split(old_string).length - 1 if (count > 1) { return Error: old_string found ${count} times — make it more specific } ` Красивый diff с ANSI-подсветкой — удалённые строки на тёмно-красном фоне, добавленные на зелёном, с 3 строками контекста.### web_search — DuckDuckGo без API ключаПарсим HTML DuckDuckGo напрямую — никакого API ключа не нужно:`js const url = https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)} const html = await fetch(url).then(r => r.text()) // Извлекаем результаты регуляркой const resultRegex = /<a[^>]+class="result__a"[^>]*href="([^"]+)"[^>]*>([^<]+)<\/a>... ` ### task — субагентыРекурсивный вызов agentLoop() — субагент получает свой контекст и работает независимо:`js // Параллельно const results = await Promise.all( parallel.map(desc => agentLoop(desc)) ) // Фоново const entry = { done: false, result: null } entry.promise = agentLoop(description).then(result => { entry.done = true entry.result = result }) ` Для инициализации используется инъекция: initTaskTool(agentLoop). Это решает проблему циклической зависимости — task.js не импортирует agent.js.### todo — задачи с зависимостямиВнутрисессионный трекер задач. Поддерживает blockedBy — задача не может перейти в in_progress, пока зависимости не завершены:`js if (status === "in_progress" && isBlocked(todo)) { const blocking = todo.blockedBy.filter( depId => getTodo(depId)?.status !== "done" ) return Cannot start #${id} — blocked by: ${blocking.join(", ")} } ` --- ## Система разрешенийТри уровня: 1. alwaysAllow — выполняется без вопросов (read_file, glob, grep) 2. neverAllow — заблокировано навсегда 3. Интерактивный запрос — для всего остальногоДля файловых операций — запрос на уровне директории:` ┌ [?] write_file → src/utils.js └ [y] один раз [d] запомнить папку "src" [N] отклонить: ` Нажал d — папка сохраняется в .agent/settings.json. Следующий раз не спросит.Для bash — запрос на уровне инструмента:` ┌ [?] bash: {"command":"npm test"} └ [y] один раз [a] запомнить для проекта [N] отклонить: ` Нажал a — bash добавляется в alwaysAllow в конфиге.--- ## Компактор: бесконечный контекст через суммаризациюПроблема: у DeepSeek контекстное окно ограничено. После 10–15 ходов контекст переполняется.Решение: перед каждым запросом к API проверяем размер контекста. Если > 80% лимита — суммаризируем всю историю через ту же модель:`js if (!force && tokens < contextLimit 0.8) return messages // Оставляем system prompt, суммаризируем остальное const summaryResponse = await client.chat.completions.create({ model: getModel(), messages: [ { role: "system", content: "Summarize the conversation..." }, { role: "user", content: rest.map(m => [${m.role}]: ${m.content}).join("\n") } ] }) return [system, { role: "user", content: [Summary]:\n${summary} }, { role: "assistant", content: "Understood." }] ` Оценка токенов — грубая, но работает: ~3 символа = 1 токен. Base64-изображения считаются по длине строки.--- ## MCP — подключай чужие инструментыModel Context Protocol — стандарт от Anthropic для подключения внешних инструментов. Конфиг в .agent/settings.json:`json "mcpServers": { "fs": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path"] } } ` При старте агент подключается к серверу через stdio, получает список инструментов и регистрирует их с префиксом mcp__<server>__<tool>:`js const transport = new StdioClientTransport({ command: cfg.command, args: cfg.args ?? [] }) const client = new Client({ name: "deepseek-agent", version: "0.1.0" }) await client.connect(transport) const { tools: serverTools } = await client.listTools() ` Инструменты MCP проходят через ту же систему разрешений.--- ## Память: AGENT.mdТри уровня памяти, все загружаются в system prompt:| Файл | Назначение | |---|---| | ~/.agent/AGENT.md | Глобальные инструкции (стиль, предпочтения) | | .agent/AGENT.md | Инструкции для проекта (архитектура, стек) | | AGENT.md | Инструкции в корне репо | Это аналог CLAUDE.md в Claude Code. Модель видит эти инструкции в каждом диалоге.--- ## Хуки: интеграция со своими скриптами.agent/hooks.json позволяет запускать shell-команды на события агента:`json { "PreToolUse": [{ "command": "cat >> agent.log" }], "PostToolUse": [], "Stop": [] } ` PreToolUse с ненулевым exit code блокирует выполнение инструмента. Payload приходит через stdin как JSON — можно фильтровать по имени инструмента, аргументам.--- ## Слеш-команды: 17 штукВсё управление — через /-команды в чате:- /clear — сбросить контекст - /compact — принудительно сжать контекст - /context — прогресс-бар заполненности контекста - /btw <вопрос> — вопрос без добавления в историю - /rewind — откат к чекпоинту (автоматически создаются каждый ход) - /review — отправить git diff на ревью - /security-review — анализ безопасности - /simplify — три параллельных агента: DRY, качество, производительность - /batch <задача> — агент декомпозирует задачу и выполняет параллельно - /loop 5m <промпт> — периодический запуск (аналог cron) - /resume — восстановить предыдущую сессию - /export — сохранить диалог в файл/simplify — пример мощи субагентов. Три агента запускаются параллельно через Promise.all, каждый анализирует изменённые файлы под своим углом:`js const tasks = [ "Review for code reuse opportunities and DRY violations...", "Review for code quality: naming, complexity, readability...", "Review for performance and efficiency issues..." ] await taskTool.execute({ parallel: tasks }) ` --- ## Проблемы, которые пришлось решать### Прожорливость по токенамКоммит fa04583: модель читала файлы целиком и вставляла их в контекст. Решение — обрезка результатов инструментов:`js const CONTEXT_LIMIT = 12000 const toolContent = full.length > CONTEXT_LIMIT ? full.slice(0, CONTEXT_LIMIT) + \n[... truncated, ${full.length - CONTEXT_LIMIT} chars omitted] : full ` Вывод bash тоже ограничен: 8000 символов.### Бинарные файлыКоммит 56116a1: модель лезла в .exe, .png, .zip без спроса. Добавил блокировку бинарных расширений в read_file и исключения бинарников в grep.### Windows: кодировкаКоммит a5193ac: на Windows stdout по умолчанию использует cp1251. Русский текст превращался в кракозябры. Решение: chcp 65001 перед каждой командой bash и BOM-детекция в read_file.### Файлы валятся в консольКоммит 499a9ff: когда модель читала файл, его содержимое выводилось в терминал целиком. Добавил formatToolResult() — для read_file выводит только «42 строки, 1200 символов», а не весь файл.### Разрешение на папкуКоммит 028d4e4: при первой записи в файл агент спрашивал разрешение. При второй — снова. Добавил механизм approvedDirs — одобряешь папку, и все файлы в ней пишутся без вопросов.### Персистентность сессийКоммит 86e21e0: при закрытии терминала вся история терялась. Добавил автосохранение в .agent/session.json при выходе и /resume для восстановления.--- ## Что под капотом: зависимостиВсего 4 пакета:| Пакет | Зачем | |---|---| | openai | Клиент DeepSeek API (совместим с OpenAI) | | fast-glob | Поиск файлов по паттернам в glob и grep | | dotenv | Загрузка .env | | @modelcontextprotocol/sdk | Клиент MCP | Никаких chalk, inquirer, commander, yargs. ANSI-цвета — 6 строк. CLI-парсинг — process.argv.slice(2). readline — встроенный node:readline.--- ## Переключение моделейБлагодаря OpenAI-совместимому API, агент работает не только с DeepSeek:` DeepSeek baseURL: https://api.deepseek.com model: deepseek-chat OpenAI без baseURL model: gpt-4o Ollama baseURL: http://localhost:11434/v1 model: qwen2.5-coder Groq baseURL: https://api.groq.com/openai/v1 model: llama-3.3-70b-versatile ` Меняешь baseURL в коде и model в конфиге — готово.--- ## Режим extended thinkingФлаг --think переключает на deepseek-reasoner. Эта модель возвращает reasoning_content отдельно от ответа — внутренний chain-of-thought:`js export function printReasoning(chunk) { const delta = chunk.choices[0]?.delta if (delta?.reasoning_content) { process.stdout.write(c.dim(delta.reasoning_content)) return true } return false } ` В терминале reasoning выводится приглушённым цветом между маркерами [thinking]/[/thinking].--- ## JSON-режим для CI`bash agent --output-format=json "что делает index.js?" ` Каждое событие — отдельная JSON-строка:`json { "type": "text", "text": "фрагмент ответа" } { "type": "tool_call", "tool": "bash", "args": { "command": "ls" } } { "type": "tool_result", "tool": "bash", "result": "file1.js" } ` Обычный вывод подавляется — функция print() ничего не делает в JSON-режиме:`js export function print(text) { if (_format !== "json") process.stdout.write(text) } ` --- ## Итоги*Что получилось:** - ~2000 строк JavaScript (ES modules) - 27 коммитов за неделю - 4 зависимости, ноль фреймворков - 9 инструментов + MCP для расширения - Система разрешений с персистентностью - Автокомпакция контекста - Субагенты (синхронные, параллельные, фоновые) - 17 слеш-команд - Хуки, память, сессии, git worktree - Работает на Windows, Linux, macOS**Что я вынес:** 1. OpenAI SDK — универсальный клиент. DeepSeek, Groq, Ollama — все говорят на одном протоколе. Один пакет покрывает всех.2. Tool use — это просто. JSON Schema описывает параметры, модель сама решает когда вызывать. Не нужно парсить текст, искать команды в ответе — API всё делает.3. Стриминг tool_calls — единственная сложность. Дельты приходят по частям, нужно склеивать по индексу. Но когда разберёшься — это 15 строк кода.4. Контекст — главный ресурс. 80% багов были про «модель съела слишком много токенов». Обрезка результатов, блокировка бинарников, компактор — всё ради экономии контекста.5. Минимализм работает. Без фреймворков проще понимать, что происходит. ANSI-цвета за 6 строк вместо chalk. process.argv` вместо yargs. readline вместо inquirer.Весь код — [на GitHub](https://github.com/skydeex/deepseekAgent).

В следующей статье я напишу как я запилил оптимизатор расхода токенов для ai кодовых агентов