Редактор кода
Редактор функций -- это специализированная среда разработки, встроенная в платформу. Она состоит из двух панелей: слева -- редактор кода, справа -- панель тестирования.
Интерфейс редактора
Верхняя панель
В верхней панели расположены:
- Кнопка назад -- возврат к списку функций
- Имя функции -- редактируемое поле (кликните, чтобы изменить)
- Категория -- автодополняемое поле; можно выбрать существующую категорию или ввести новую
- Вкладки «Код» и «Параметры» -- переключение между редактором кода и JSON Schema параметров
- Таймаут (сек) -- максимальное время выполнения (от 1 до 60 секунд, по умолчанию 10)
- Кнопка «Сохранить» -- сохраняет все изменения
- Cmd+S (Mac) / Ctrl+S (Windows) -- сохранить функцию
- Cmd+Enter / Ctrl+Enter -- отправить AI-промпт для генерации
AI-генерация
Под верхней панелью находится поле для AI-генерации. Опишите, что должна делать функция, и нажмите Генерировать. AI сгенерирует:
- Код функции (на вкладке «Код»)
- Имя и описание функции
- JSON Schema параметров (на вкладке «Параметры»)
Если вы находитесь на вкладке Параметры, AI сгенерирует только JSON Schema на основе текущего кода функции и вашего описания.
Примеры промптов:
- «Получить лид из Bitrix24 по ID»
- «Добавить фильтр по статусу и дате»
- «Отправить сообщение в Telegram через Bot API»
Нажмите кнопку Стоп, чтобы отменить генерацию.
Форматирование кода
Кнопка с иконкой кисти (справа от кнопки «Генерировать») автоматически форматирует код или JSON с помощью Prettier.
Структура кода функции
Каждая функция оборачивается в асинхронную функцию execute со следующей сигнатурой:
async function execute(context, axios, moment, console) {
// Ваш код здесь
return result;
}
Вам не нужно писать обертку вручную -- редактор добавляет ее автоматически.
При сохранении платформа снимает обертку и хранит только тело функции. context, axios, moment, console -- это глобальные объекты песочницы, а не аргументы, которые кто-то передает при вызове.
Отсюда следствие: переименовывать их нельзя. Если написать async function execute(ctx, http), то в теле не будет ни ctx, ни http -- глобалы по-прежнему называются context и axios, а код упадет с ctx is not defined.
Не стоит и добавлять свои аргументы в сигнатуру -- они ничем не будут заполнены.
Если обертку не удается разобрать -- например, после нее остался посторонний код, -- функция не сохранится, и вы получите ошибку с описанием проблемы. Так сделано намеренно: раньше такой код сохранялся вместе с оберткой, и функция затем возвращала null со статусом «успех», не давая никакой подсказки о причине.
Доступные объекты
context -- контекст выполнения
| Свойство | Описание |
|---|---|
context.input | Входные параметры функции (из предыдущей ноды, аргументов инструмента или тестовой панели) |
context.lastOutput | Результат предыдущей ноды в рабочем процессе |
context.state | Все переменные рабочего процесса |
context.nodeOutputs | Результаты всех выполненных нод, доступные по ID ноды |
context.user_id | ID текущего пользователя |
context.project_id | ID текущего проекта |
context.organization_id | ID организации |
context.thread_id | ID текущего потока (сессии) |
context.execution_id | ID текущего выполнения |
context.integrations | Учетные данные интеграций организации (см. ниже) |
Все перечисленные поля присутствуют всегда, на любом пути вызова. Там, где значения нет, приходит null или пустой объект -- но не undefined. Это позволяет читать поля без проверок.
Различия между путями вызова
Набор ключей одинаков, но наполнение зависит от того, откуда вызвана функция:
| Поле | Нода рабочего процесса | Инструмент текстового агента | Обработчик ошибки | Тест из редактора | Инструмент голосового агента |
|---|---|---|---|---|---|
input | вход ноды | аргументы вызова | контекст ошибки | данные из панели | аргументы вызова |
project_id, organization_id | да | да | да | да | project_id да, organization_id нет |
user_id, thread_id | да | да | да | нет | да |
state, lastOutput, nodeOutputs | да | пусто | пусто | пусто | пусто |
memory | да | да | да | да | пусто |
memoryOps (запись) | да | да | да | да | не применяется |
integrations | да | да | да | да | пусто |
meta | да | да | да | да | пусто |
state, lastOutput и nodeOutputs пусты вне рабочего процесса потому, что вне графа им нечего содержать: у агента нет предыдущих нод.
У функции, вызванной голосовым агентом, память недоступна: context.memory всегда пустая, а операции из context.memoryOps не применяются. Интеграции тоже недоступны -- голосовая сессия не привязана к организации. Если функция должна работать и в голосовом сценарии, не полагайтесь на память и интеграции.
context.memory -- чтение памяти
Доступ к данным памяти по областям видимости. Подробнее см. в разделе Память агента:
const userName = context.memory.user["name"];
const projectSettings = context.memory.project["settings"];
const threadHistory = context.memory.thread["history"];
const orgConfig = context.memory.organization["config"];
context.memoryOps -- запись в память
Для записи и удаления данных из памяти используйте массив операций:
// Записать значение
context.memoryOps.push({
action: "set",
scope: "project", // user, project, thread, organization
key: "myKey",
value: "myValue"
});
context.set('memoryOps', context.memoryOps);
// Удалить значение
context.memoryOps.push({
action: "delete",
scope: "project",
key: "myKey"
});
context.set('memoryOps', context.memoryOps);
После добавления операций в context.memoryOps необходимо вызвать context.set('memoryOps', context.memoryOps), чтобы изменения были применены. Без этого вызова платформа не увидит операции и молча ничего не запишет.
Второй рабочий вариант -- вернуть операции в составе результата: return { memoryOps: context.memoryOps, ... }. Достаточно любого из двух способов.
context.integrations -- учетные данные интеграций
Активные интеграции организации, доступные по имени провайдера. Позволяют обращаться к внешним системам, не вписывая токены в код функции:
const token = context.integrations.bitrix24?.webhook_url;
if (!token) {
return { error: "Интеграция Bitrix24 не настроена" };
}
Набор полей внутри каждого провайдера определяется тем, как настроена интеграция. Проверяйте наличие интеграции перед использованием: если она не настроена, отключена или недоступна, соответствующего ключа просто не будет.
Значения интеграций -- это секреты. Не выводите их в console.log и не возвращайте из функции: логи выполнения и результат видны в интерфейсе платформы.
context.meta -- метаданные выполнения
| Свойство | Описание |
|---|---|
context.meta.startedAt | Время начала выполнения |
context.meta.currentNode | ID текущей ноды |
context.meta.executionCount | Количество выполнений |
axios -- HTTP-клиент
Встроенный HTTP-клиент для выполнения запросов к внешним API:
// GET-запрос
const response = await axios.get(url, {
headers: { 'Authorization': 'Bearer token' }
});
return response.data;
// POST-запрос
const response = await axios.post(url, {
key: 'value'
});
return response.data;
Доступны get, post, put, patch, delete, head, options, request, вызов axios(config) как функции и создание отдельного клиента через axios.create(). Все они попадают в трейс выполнения: каждый запрос виден в логах вместе с URL, методом, телом и маршрутом.
// Отдельный клиент с общими настройками
const api = axios.create({ baseURL: 'https://api.example.com' });
const response = await api.get('/clients');
moment -- работа с датами
Библиотека Moment.js для работы с датами и временем:
const now = moment();
const formatted = now.format('YYYY-MM-DD HH:mm');
const tomorrow = moment().add(1, 'day');
request -- устаревший HTTP-клиент
В песочнице также доступна библиотека request@2.88. Она оставлена для совместимости со старыми функциями; для нового кода используйте axios.
Запросы через request не попадают в трейс выполнения и всегда идут напрямую, минуя прокси, -- настройка маршрута на них не действует. См. Сетевые запросы и прокси.
console -- логирование
Стандартный объект для вывода логов:
console.log("Отладочное сообщение");
Доступны console.log, console.info, console.warn и console.error. Логи отображаются в панели результатов при тестировании и сохраняются в трейсе выполнения.
Сетевые запросы и прокси
Некоторые внешние API недоступны с исходящего адреса платформы. Для таких случаев запрос можно направить через прокси -- тот же, что используют остальные сервисы платформы.
Маршрут задается в коде функции, для каждого запроса отдельно, ключом useProxy:
// Через прокси
const response = await axios.get(url, { useProxy: true });
// Напрямую, даже если по умолчанию включен прокси
const response = await axios.get(url, { useProxy: false });
// Для запросов с телом -- третьим аргументом
const response = await axios.post(url, data, { useProxy: true });
Если useProxy не указан, действует режим по умолчанию, заданный на уровне сервиса. Обычно это прямые запросы.
В логах выполнения у каждого запроса виден фактический маршрут -- via proxy или via direct. По нему можно проверить, что настройка сработала.
Что важно знать
Прокси не настроен, а запрошен явно. Если написать useProxy: true там, где прокси недоступен, запрос завершится ошибкой Proxy requested, but proxy is not configured for this service. Молчаливого перехода на прямой запрос не происходит специально: иначе вы бы получили успешный ответ, не заметив, что трафик ушел не тем маршрутом.
Редиректы через прокси не отслеживаются. Если запрос с useProxy: true получает ответ 301 или 302, функция завершится ошибкой Request failed with status code 302 с пояснением. Причина техническая: переход по редиректу ушел бы напрямую, минуя прокси.
Лучшее решение -- указать конечный URL. Если он заранее неизвестен, обработайте ответ 3xx сами: отключите проверку статуса и перейдите по location вторым запросом, тоже через прокси.
const first = await axios.get(url, { useProxy: true, validateStatus: null });
const response = first.status >= 300 && first.status < 400
? await axios.get(first.headers.location, { useProxy: true, validateStatus: null })
: first;
return response.data;
Если задать свой maxRedirects больше нуля, axios снова начнет переходить по редиректам самостоятельно -- и эти переходы пойдут напрямую, минуя прокси, хотя в логах у запроса останется пометка via proxy.
useProxy работает только для отдельного запроса. В axios.create({ useProxy: true }) этот ключ не действует -- указывайте его в самом запросе:
const api = axios.create({ baseURL: 'https://api.example.com' });
const response = await api.get('/clients', { useProxy: true }); // так
Маршрут контролируется только для axios. Запросы через request всегда идут напрямую.
Ограничения песочницы
Код функции выполняется в изолированной песочнице, где доступны стандартные возможности JavaScript (Promise, JSON, Math, Date, классы, деструктуризация, ?., ??) и перечисленные выше объекты. Остального окружения Node.js там нет:
| Недоступно | Что использовать вместо |
|---|---|
fetch | axios |
require, import | Только встроенные axios, moment, request |
setTimeout, setInterval | Пауз в коде сделать нельзя (см. ниже) |
process, Buffer, файловая система, сеть напрямую | -- |
setTimeout в песочнице нет, поэтому привычная пауза не сработает:
await new Promise(r => setTimeout(r, 1000)); // ОШИБКА: setTimeout is not defined
Если внешний API требует ожидания или повторной попытки, реализуйте это на стороне рабочего процесса -- отдельными нодами, -- а не внутри функции.
Таймаут
Таймаут задается в верхней панели редактора, от 1 до 60 секунд, по умолчанию 10. Он действует на всех путях вызова одинаково: и когда функция стоит нодой рабочего процесса, и когда ее вызывает текстовый или голосовой агент, и при запуске из панели тестирования.
По истечении таймаута выполнение прерывается, а функция возвращает ошибку -- частичный результат не сохраняется.
Автодополнение
Редактор поддерживает автодополнение. Начните вводить context, axios или moment, и появятся подсказки с описанием доступных свойств и методов.
Вкладка «Параметры»
На вкладке Параметры вы описываете входные параметры функции в формате JSON Schema. Это описание используется:
- Агентом для понимания, какие аргументы передать при вызове функции как инструмента
- Панелью тестирования для автогенерации примера входных данных
Пример JSON Schema:
{
"type": "object",
"properties": {
"phone": {
"type": "string",
"description": "Номер телефона клиента"
},
"name": {
"type": "string",
"description": "Имя клиента"
},
"amount": {
"type": "number",
"description": "Сумма заказа"
}
},
"required": ["phone"]
}
Перейдите на вкладку «Параметры», введите описание в поле AI-генерации и нажмите «Генерировать». AI проанализирует код функции и создаст JSON Schema автоматически. После этого можно протестировать функцию с автогенерированными примерами.
Возвращаемое значение
Функция должна вернуть результат через return. Результат будет доступен как lastOutput для следующей ноды в рабочем процессе или как ответ инструмента для агента.
async function execute(context, axios, moment, console) {
const { phone } = context.input;
const response = await axios.get(`https://api.example.com/clients?phone=${phone}`);
return {
client_id: response.data.id,
name: response.data.name,
status: "found"
};
}
Если функция не возвращает значение явно, результат будет undefined.