nodal-agents 0.4.0 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -14
- package/cli.js +16 -1
- package/migrations/0030_agent_fallback_llm_keys.sql +6 -0
- package/migrations/0031_llm_key_capabilities.sql +7 -0
- package/migrations/0032_drop_llm_key_model_caps.sql +9 -0
- package/migrations/0033_agent_fallback_chain.sql +21 -0
- package/migrations/meta/_journal.json +28 -0
- package/package.json +2 -1
- package/runner.js +602 -94
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/app-path-routes-manifest.json +1 -1
- package/web/.next/build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/server/app/(dashboard)/agents/[id]/edit/page.js +3 -3
- package/web/.next/server/app/(dashboard)/agents/[id]/edit/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/agents/[id]/edit/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page.js +1 -1
- package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/agents/page.js +2 -2
- package/web/.next/server/app/(dashboard)/agents/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/agents/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/approvals/page.js +2 -2
- package/web/.next/server/app/(dashboard)/approvals/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/approvals/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/automations/page.js +2 -2
- package/web/.next/server/app/(dashboard)/automations/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/automations/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/billing/page.js +1 -1
- package/web/.next/server/app/(dashboard)/billing/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/billing/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/chat/page.js +2 -2
- package/web/.next/server/app/(dashboard)/chat/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/chat/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/connectors/page.js +1 -1
- package/web/.next/server/app/(dashboard)/connectors/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/connectors/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/credentials/page.js +1 -1
- package/web/.next/server/app/(dashboard)/credentials/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/credentials/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/jobs/[id]/page.js +2 -2
- package/web/.next/server/app/(dashboard)/jobs/[id]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/jobs/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/jobs/page.js +2 -2
- package/web/.next/server/app/(dashboard)/jobs/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/jobs/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/llm-providers/page.js +2 -2
- package/web/.next/server/app/(dashboard)/llm-providers/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/llm-providers/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/logs/page.js +1 -1
- package/web/.next/server/app/(dashboard)/logs/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/logs/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/mcp/page.js +2 -2
- package/web/.next/server/app/(dashboard)/mcp/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/mcp/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/memories/page.js +2 -2
- package/web/.next/server/app/(dashboard)/memories/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/memories/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/page.js +2 -2
- package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/settings/page.js +2 -2
- package/web/.next/server/app/(dashboard)/settings/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/settings/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/skills/[id]/edit/page.js +1 -1
- package/web/.next/server/app/(dashboard)/skills/[id]/edit/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/skills/[id]/edit/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/skills/new/page.js +2 -2
- package/web/.next/server/app/(dashboard)/skills/new/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/skills/new/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/skills/page.js +2 -2
- package/web/.next/server/app/(dashboard)/skills/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_global-error.html +1 -1
- package/web/.next/server/app/_global-error.rsc +2 -2
- package/web/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
- package/web/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_index.segment.rsc +2 -2
- package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/server/app/_not-found.rsc +3 -3
- package/web/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/server/app/api/oauth/[provider]/callback/route.js +1 -1
- package/web/.next/server/app/api/oauth/[provider]/start/route.js +1 -1
- package/web/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/onboarding.html +1 -1
- package/web/.next/server/app/onboarding.rsc +3 -3
- package/web/.next/server/app/onboarding.segments/_full.segment.rsc +3 -3
- package/web/.next/server/app/onboarding.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/onboarding.segments/_index.segment.rsc +3 -3
- package/web/.next/server/app/onboarding.segments/_tree.segment.rsc +2 -2
- package/web/.next/server/app/onboarding.segments/onboarding/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/onboarding.segments/onboarding.segment.rsc +1 -1
- package/web/.next/server/app-paths-manifest.json +1 -1
- package/web/.next/server/chunks/3233.js +1 -0
- package/web/.next/server/chunks/4574.js +1 -1
- package/web/.next/server/chunks/4808.js +1 -0
- package/web/.next/server/chunks/{3057.js → 7466.js} +1 -1
- package/web/.next/server/chunks/7741.js +3 -3
- package/web/.next/server/chunks/8052.js +1 -0
- package/web/.next/server/chunks/8766.js +1 -0
- package/web/.next/server/chunks/8782.js +1 -0
- package/web/.next/server/chunks/9084.js +1 -0
- package/web/.next/server/chunks/{7557.js → 9606.js} +2 -2
- package/web/.next/server/middleware-build-manifest.js +1 -1
- package/web/.next/server/pages/404.html +1 -1
- package/web/.next/server/pages/500.html +1 -1
- package/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/static/chunks/{9060-df7c0c4c6fa27737.js → 2575-e660568bd1a9bcb6.js} +2 -2
- package/web/.next/static/chunks/3233-e6efb7fb1fa24591.js +1 -0
- package/web/.next/static/chunks/5436-c1006a40e59853ed.js +1 -0
- package/web/.next/static/chunks/7025-7afa82fda10bddc4.js +62 -0
- package/web/.next/static/chunks/{5801-e411029984b17b8b.js → 8396-f3502b9af3172006.js} +1 -1
- package/web/.next/static/chunks/{8503-ced632da5c3fce79.js → 9098-2bfef80a73c706b3.js} +1 -1
- package/web/.next/static/chunks/9123-5c5ad180c831baa4.js +1 -0
- package/web/.next/static/chunks/{6679-7c76034b83edeb06.js → 9582-fbf7c8d9b2a39101.js} +1 -1
- package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-de6c8fc7cb73a3de.js +2 -0
- package/web/.next/static/chunks/app/(dashboard)/agents/[id]/telegram/page-6d4161f1e0b19885.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/agents/page-50005050a3304bee.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/approvals/page-7f4314908d1024f6.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/automations/page-7693601b49363371.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/chat/page-839128f211f63728.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/connectors/page-a6a1d8f0a33d2faf.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/page-4fc570c6c1e39edb.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/jobs/page-cf861b235dc54ced.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/layout-769de8a52528194a.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-99eab754716f9071.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/mcp/page-082442b4f9ac0f91.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/memories/page-e201633b4bbbdf73.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/page-a42d880f7036e866.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/settings/page-d85cac3728506241.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/page-ecaf3520da303237.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/skills/new/page-cdbc2aada2be0bfc.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/skills/page-234553540bef945b.js +1 -0
- package/web/.next/static/css/78ead23854ab041e.css +3 -0
- package/web/.next/server/chunks/1511.js +0 -1
- package/web/.next/server/chunks/2103.js +0 -1
- package/web/.next/server/chunks/211.js +0 -1
- package/web/.next/server/chunks/8178.js +0 -1
- package/web/.next/server/chunks/9201.js +0 -1
- package/web/.next/server/chunks/9824.js +0 -1
- package/web/.next/static/chunks/1165-ec573be2aa63710b.js +0 -1
- package/web/.next/static/chunks/2569-6b5e0af9c1f584a4.js +0 -1
- package/web/.next/static/chunks/6522-3f865de55adb618d.js +0 -1
- package/web/.next/static/chunks/921-f437093debcddbb3.js +0 -1
- package/web/.next/static/chunks/9421-d522a48618c4fe37.js +0 -62
- package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-d3724fbf38b71806.js +0 -2
- package/web/.next/static/chunks/app/(dashboard)/agents/[id]/telegram/page-e6b35d5f361044a9.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/agents/page-b58294bf588f4581.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/approvals/page-b9e504918d043b6d.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/automations/page-4807e81e2af3030e.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/chat/page-2c8f9571a443f250.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/connectors/page-72ccb0e3a5ed6f2d.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/page-40172a14d0b1368f.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/jobs/page-d4a3a16745e02fd1.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/layout-4d5634ba460464d7.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-90fb785e2ab32759.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/mcp/page-426478332dfe8313.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/memories/page-aa46f5f7efbfa262.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/page-fc49d7ed8e472118.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/settings/page-1cc10beb46234c7d.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/page-0b61f21847f4c7a0.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/skills/new/page-9de96e643c361732.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/skills/page-4566512d74e54bfe.js +0 -1
- package/web/.next/static/css/0a81480f93d3ab37.css +0 -3
- /package/web/.next/static/{9FXcaPSw8KYgjKzjKLpT2 → n6jP_zB4kqJScKY_T2ciu}/_buildManifest.js +0 -0
- /package/web/.next/static/{9FXcaPSw8KYgjKzjKLpT2 → n6jP_zB4kqJScKY_T2ciu}/_ssgManifest.js +0 -0
package/runner.js
CHANGED
|
@@ -1102,6 +1102,155 @@ var init_connector_catalog = __esm({
|
|
|
1102
1102
|
}
|
|
1103
1103
|
});
|
|
1104
1104
|
|
|
1105
|
+
// ../../packages/shared/src/model-catalog.ts
|
|
1106
|
+
function findModelCatalogEntry(provider, modelId) {
|
|
1107
|
+
return MODEL_CATALOG[provider]?.find((e) => e.modelId === modelId);
|
|
1108
|
+
}
|
|
1109
|
+
var MODEL_CATALOG;
|
|
1110
|
+
var init_model_catalog = __esm({
|
|
1111
|
+
"../../packages/shared/src/model-catalog.ts"() {
|
|
1112
|
+
"use strict";
|
|
1113
|
+
MODEL_CATALOG = {
|
|
1114
|
+
anthropic: [
|
|
1115
|
+
{
|
|
1116
|
+
modelId: "claude-opus-4-8",
|
|
1117
|
+
label: "Claude Opus 4.8",
|
|
1118
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1119
|
+
},
|
|
1120
|
+
{
|
|
1121
|
+
modelId: "claude-sonnet-4-6",
|
|
1122
|
+
label: "Claude Sonnet 4.6",
|
|
1123
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1124
|
+
},
|
|
1125
|
+
{
|
|
1126
|
+
modelId: "claude-haiku-4-5-20251001",
|
|
1127
|
+
label: "Claude Haiku 4.5",
|
|
1128
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1129
|
+
}
|
|
1130
|
+
],
|
|
1131
|
+
openai: [
|
|
1132
|
+
{ modelId: "gpt-5", label: "GPT-5", capabilities: { tools: true, forcedToolChoice: true } },
|
|
1133
|
+
{
|
|
1134
|
+
modelId: "gpt-5-mini",
|
|
1135
|
+
label: "GPT-5 mini",
|
|
1136
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1137
|
+
}
|
|
1138
|
+
],
|
|
1139
|
+
google: [
|
|
1140
|
+
{
|
|
1141
|
+
modelId: "gemini-2.0-flash",
|
|
1142
|
+
label: "Gemini 2.0 Flash",
|
|
1143
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1144
|
+
},
|
|
1145
|
+
{
|
|
1146
|
+
modelId: "gemini-2.5-pro",
|
|
1147
|
+
label: "Gemini 2.5 Pro",
|
|
1148
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1149
|
+
}
|
|
1150
|
+
],
|
|
1151
|
+
groq: [
|
|
1152
|
+
{
|
|
1153
|
+
modelId: "llama-3.3-70b-versatile",
|
|
1154
|
+
label: "Llama 3.3 70B",
|
|
1155
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1156
|
+
}
|
|
1157
|
+
],
|
|
1158
|
+
mistral: [
|
|
1159
|
+
{
|
|
1160
|
+
modelId: "mistral-large-latest",
|
|
1161
|
+
label: "Mistral Large",
|
|
1162
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1163
|
+
}
|
|
1164
|
+
],
|
|
1165
|
+
// OpenRouter models are namespaced by sub-vendor (anthropic/, deepseek/, …).
|
|
1166
|
+
// The UI groups them by that vendor (see modelGroupLabel). Tested + working
|
|
1167
|
+
// routes. `forcedToolChoice` is per-model: most accept tool_choice:'required';
|
|
1168
|
+
// MiniMax M3 does not (some of its OpenRouter endpoints reject the forced
|
|
1169
|
+
// value), so it runs on 'auto' + the runtime floor.
|
|
1170
|
+
openrouter: [
|
|
1171
|
+
// Anthropic
|
|
1172
|
+
{
|
|
1173
|
+
modelId: "anthropic/claude-haiku-4.5",
|
|
1174
|
+
label: "Claude Haiku 4.5",
|
|
1175
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1176
|
+
},
|
|
1177
|
+
{
|
|
1178
|
+
modelId: "anthropic/claude-opus-4.7",
|
|
1179
|
+
label: "Claude Opus 4.7",
|
|
1180
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1181
|
+
},
|
|
1182
|
+
{
|
|
1183
|
+
modelId: "anthropic/claude-opus-4.7-fast",
|
|
1184
|
+
label: "Claude Opus 4.7 (fast)",
|
|
1185
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1186
|
+
},
|
|
1187
|
+
{
|
|
1188
|
+
modelId: "anthropic/claude-opus-4.8",
|
|
1189
|
+
label: "Claude Opus 4.8",
|
|
1190
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1191
|
+
},
|
|
1192
|
+
{
|
|
1193
|
+
modelId: "anthropic/claude-opus-4.8-fast",
|
|
1194
|
+
label: "Claude Opus 4.8 (fast)",
|
|
1195
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1196
|
+
},
|
|
1197
|
+
{
|
|
1198
|
+
modelId: "anthropic/claude-sonnet-4.6",
|
|
1199
|
+
label: "Claude Sonnet 4.6",
|
|
1200
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1201
|
+
},
|
|
1202
|
+
// DeepSeek
|
|
1203
|
+
{
|
|
1204
|
+
modelId: "deepseek/deepseek-v3.2",
|
|
1205
|
+
label: "DeepSeek V3.2",
|
|
1206
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1207
|
+
},
|
|
1208
|
+
{
|
|
1209
|
+
modelId: "deepseek/deepseek-v4-flash",
|
|
1210
|
+
label: "DeepSeek V4 Flash",
|
|
1211
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1212
|
+
},
|
|
1213
|
+
{
|
|
1214
|
+
modelId: "deepseek/deepseek-v4-pro",
|
|
1215
|
+
label: "DeepSeek V4 Pro",
|
|
1216
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1217
|
+
},
|
|
1218
|
+
// Google
|
|
1219
|
+
{
|
|
1220
|
+
modelId: "google/gemini-3.1-flash-lite-preview",
|
|
1221
|
+
label: "Gemini 3.1 Flash Lite (preview)",
|
|
1222
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1223
|
+
},
|
|
1224
|
+
{
|
|
1225
|
+
modelId: "google/gemini-3.1-pro-preview",
|
|
1226
|
+
label: "Gemini 3.1 Pro (preview)",
|
|
1227
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1228
|
+
},
|
|
1229
|
+
{
|
|
1230
|
+
modelId: "google/gemini-3.5-flash",
|
|
1231
|
+
label: "Gemini 3.5 Flash",
|
|
1232
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1233
|
+
},
|
|
1234
|
+
{
|
|
1235
|
+
modelId: "google/gemma-4-31b-it",
|
|
1236
|
+
label: "Gemma 4 31B-IT",
|
|
1237
|
+
capabilities: { tools: true, forcedToolChoice: true }
|
|
1238
|
+
},
|
|
1239
|
+
// MiniMax
|
|
1240
|
+
{
|
|
1241
|
+
modelId: "minimax/minimax-m3",
|
|
1242
|
+
label: "MiniMax M3",
|
|
1243
|
+
// A reasoning model. Some of its OpenRouter endpoints reject a FORCED
|
|
1244
|
+
// tool_choice ('required') → we send 'auto' (forcedToolChoice:false).
|
|
1245
|
+
// reasoning:true makes the provider return reasoning_details so the runner
|
|
1246
|
+
// can round-trip them across tool-call turns.
|
|
1247
|
+
capabilities: { tools: true, forcedToolChoice: false, reasoning: true }
|
|
1248
|
+
}
|
|
1249
|
+
]
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1105
1254
|
// ../../packages/shared/src/index.ts
|
|
1106
1255
|
var init_src = __esm({
|
|
1107
1256
|
"../../packages/shared/src/index.ts"() {
|
|
@@ -1127,6 +1276,7 @@ var init_src = __esm({
|
|
|
1127
1276
|
init_providers();
|
|
1128
1277
|
init_root_agent();
|
|
1129
1278
|
init_connector_catalog();
|
|
1279
|
+
init_model_catalog();
|
|
1130
1280
|
}
|
|
1131
1281
|
});
|
|
1132
1282
|
|
|
@@ -1262,7 +1412,6 @@ var init_llm_keys = __esm({
|
|
|
1262
1412
|
apiKeyLast4: text3("api_key_last4").notNull().default(""),
|
|
1263
1413
|
baseUrl: text3("base_url"),
|
|
1264
1414
|
nickname: text3("nickname"),
|
|
1265
|
-
defaultModel: text3("default_model"),
|
|
1266
1415
|
isActive: boolean2("is_active").notNull().default(true),
|
|
1267
1416
|
createdAt: timestamp3("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
1268
1417
|
updatedAt: timestamp3("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
@@ -1281,6 +1430,7 @@ import {
|
|
|
1281
1430
|
integer,
|
|
1282
1431
|
bigint,
|
|
1283
1432
|
timestamp as timestamp4,
|
|
1433
|
+
jsonb as jsonb2,
|
|
1284
1434
|
index as index3,
|
|
1285
1435
|
check as check2
|
|
1286
1436
|
} from "drizzle-orm/pg-core";
|
|
@@ -1301,6 +1451,14 @@ var init_agents = __esm({
|
|
|
1301
1451
|
personality: text4("personality").notNull(),
|
|
1302
1452
|
model: text4("model").default("claude-sonnet-4-6-20260217"),
|
|
1303
1453
|
llmKeyId: uuid4("llm_key_id").references(() => entityLlmKeys.id, { onDelete: "set null" }),
|
|
1454
|
+
// Ordered LLM-key fallback chain (Guard 2). When the primary key
|
|
1455
|
+
// (llmKeyId) exhausts retries / times out / hits quota mid-job, the runner
|
|
1456
|
+
// fails over to these in order; all-down fails loud (`all_providers_failed`).
|
|
1457
|
+
// Empty = no failover (default). Each link is a (keyId, model) pair so a
|
|
1458
|
+
// fallback runs on a CHOSEN model (empty model ⇒ that provider's catalog
|
|
1459
|
+
// default). FK integrity is enforced in the app layer; a deleted key is
|
|
1460
|
+
// skipped at resolution time.
|
|
1461
|
+
fallbackChain: jsonb2("fallback_chain").$type().default(sql2`'[]'::jsonb`),
|
|
1304
1462
|
active: boolean3("active").default(true),
|
|
1305
1463
|
isDefault: boolean3("is_default").default(false),
|
|
1306
1464
|
role: text4("role").default("agent"),
|
|
@@ -1380,7 +1538,7 @@ var init_agents = __esm({
|
|
|
1380
1538
|
});
|
|
1381
1539
|
|
|
1382
1540
|
// ../../packages/db/src/schema/jobs.ts
|
|
1383
|
-
import { pgTable as pgTable5, text as text5, uuid as uuid5, integer as integer2, jsonb as
|
|
1541
|
+
import { pgTable as pgTable5, text as text5, uuid as uuid5, integer as integer2, jsonb as jsonb3, timestamp as timestamp5, index as index4, check as check3 } from "drizzle-orm/pg-core";
|
|
1384
1542
|
import { sql as sql3 } from "drizzle-orm";
|
|
1385
1543
|
var agentJobs;
|
|
1386
1544
|
var init_jobs = __esm({
|
|
@@ -1400,7 +1558,7 @@ var init_jobs = __esm({
|
|
|
1400
1558
|
originalTask: text5("original_task"),
|
|
1401
1559
|
chatId: text5("chat_id"),
|
|
1402
1560
|
systemPrompt: text5("system_prompt"),
|
|
1403
|
-
messages:
|
|
1561
|
+
messages: jsonb3("messages").default(sql3`'[]'::jsonb`),
|
|
1404
1562
|
toolsUsed: text5("tools_used").array().default(sql3`'{}'::text[]`),
|
|
1405
1563
|
turn: integer2("turn").default(0),
|
|
1406
1564
|
result: text5("result"),
|
|
@@ -1428,7 +1586,7 @@ var init_jobs = __esm({
|
|
|
1428
1586
|
* different specialist (live regression: job `7767a3c1`, 2026-05-19).
|
|
1429
1587
|
*/
|
|
1430
1588
|
lastFailedDelegationSlug: text5("last_failed_delegation_slug"),
|
|
1431
|
-
pendingDelegation:
|
|
1589
|
+
pendingDelegation: jsonb3("pending_delegation"),
|
|
1432
1590
|
completedAt: timestamp5("completed_at", { withTimezone: true }),
|
|
1433
1591
|
createdAt: timestamp5("created_at", { withTimezone: true }).defaultNow(),
|
|
1434
1592
|
updatedAt: timestamp5("updated_at", { withTimezone: true }).defaultNow()
|
|
@@ -1463,7 +1621,7 @@ import {
|
|
|
1463
1621
|
text as text6,
|
|
1464
1622
|
uuid as uuid6,
|
|
1465
1623
|
integer as integer3,
|
|
1466
|
-
jsonb as
|
|
1624
|
+
jsonb as jsonb4,
|
|
1467
1625
|
timestamp as timestamp6,
|
|
1468
1626
|
numeric,
|
|
1469
1627
|
index as index5,
|
|
@@ -1499,7 +1657,7 @@ var init_tasks = __esm({
|
|
|
1499
1657
|
outputTokens: integer3("output_tokens").default(0),
|
|
1500
1658
|
costUsd: numeric("cost_usd", { precision: 10, scale: 6 }).default("0"),
|
|
1501
1659
|
dependsOn: uuid6("depends_on").array().default(sql4`'{}'::uuid[]`),
|
|
1502
|
-
context:
|
|
1660
|
+
context: jsonb4("context").default(sql4`'{}'::jsonb`),
|
|
1503
1661
|
rootJobId: uuid6("root_job_id"),
|
|
1504
1662
|
lockedAt: timestamp6("locked_at", { withTimezone: true }),
|
|
1505
1663
|
lockedBy: text6("locked_by"),
|
|
@@ -1607,7 +1765,7 @@ var init_connectors = __esm({
|
|
|
1607
1765
|
});
|
|
1608
1766
|
|
|
1609
1767
|
// ../../packages/db/src/schema/tool_calls.ts
|
|
1610
|
-
import { pgTable as pgTable9, text as text9, uuid as uuid9, integer as integer4, jsonb as
|
|
1768
|
+
import { pgTable as pgTable9, text as text9, uuid as uuid9, integer as integer4, jsonb as jsonb5, timestamp as timestamp9, index as index8 } from "drizzle-orm/pg-core";
|
|
1611
1769
|
import { sql as sql7 } from "drizzle-orm";
|
|
1612
1770
|
var toolCalls;
|
|
1613
1771
|
var init_tool_calls = __esm({
|
|
@@ -1622,7 +1780,7 @@ var init_tool_calls = __esm({
|
|
|
1622
1780
|
entityId: uuid9("entity_id").references(() => entities.id, { onDelete: "cascade" }),
|
|
1623
1781
|
jobId: uuid9("job_id").references(() => agentJobs.id, { onDelete: "cascade" }),
|
|
1624
1782
|
toolName: text9("tool_name").notNull(),
|
|
1625
|
-
toolInput:
|
|
1783
|
+
toolInput: jsonb5("tool_input"),
|
|
1626
1784
|
toolOutput: text9("tool_output"),
|
|
1627
1785
|
durationMs: integer4("duration_ms"),
|
|
1628
1786
|
turn: integer4("turn"),
|
|
@@ -1639,7 +1797,7 @@ var init_tool_calls = __esm({
|
|
|
1639
1797
|
});
|
|
1640
1798
|
|
|
1641
1799
|
// ../../packages/db/src/schema/approvals.ts
|
|
1642
|
-
import { pgTable as pgTable10, text as text10, uuid as uuid10, jsonb as
|
|
1800
|
+
import { pgTable as pgTable10, text as text10, uuid as uuid10, jsonb as jsonb6, timestamp as timestamp10, index as index9, check as check7 } from "drizzle-orm/pg-core";
|
|
1643
1801
|
import { sql as sql8 } from "drizzle-orm";
|
|
1644
1802
|
var approvalRequests, approvalRules;
|
|
1645
1803
|
var init_approvals = __esm({
|
|
@@ -1656,7 +1814,7 @@ var init_approvals = __esm({
|
|
|
1656
1814
|
jobId: uuid10("job_id").notNull().references(() => agentJobs.id, { onDelete: "cascade" }),
|
|
1657
1815
|
agentId: uuid10("agent_id").references(() => agents.id, { onDelete: "cascade" }),
|
|
1658
1816
|
toolName: text10("tool_name").notNull(),
|
|
1659
|
-
toolInput:
|
|
1817
|
+
toolInput: jsonb6("tool_input").notNull(),
|
|
1660
1818
|
status: text10("status").default("pending"),
|
|
1661
1819
|
requestedAt: timestamp10("requested_at", { withTimezone: true }).defaultNow(),
|
|
1662
1820
|
resolvedAt: timestamp10("resolved_at", { withTimezone: true }),
|
|
@@ -1691,7 +1849,7 @@ var init_approvals = __esm({
|
|
|
1691
1849
|
agentId: uuid10("agent_id").references(() => agents.id, { onDelete: "cascade" }),
|
|
1692
1850
|
toolName: text10("tool_name").notNull(),
|
|
1693
1851
|
action: text10("action").notNull(),
|
|
1694
|
-
conditionJson:
|
|
1852
|
+
conditionJson: jsonb6("condition_json").default(sql8`'{}'::jsonb`),
|
|
1695
1853
|
createdAt: timestamp10("created_at", { withTimezone: true }).defaultNow(),
|
|
1696
1854
|
updatedAt: timestamp10("updated_at", { withTimezone: true }).defaultNow()
|
|
1697
1855
|
},
|
|
@@ -1819,7 +1977,7 @@ import {
|
|
|
1819
1977
|
uuid as uuid13,
|
|
1820
1978
|
boolean as boolean7,
|
|
1821
1979
|
integer as integer7,
|
|
1822
|
-
jsonb as
|
|
1980
|
+
jsonb as jsonb7,
|
|
1823
1981
|
timestamp as timestamp13,
|
|
1824
1982
|
index as index12
|
|
1825
1983
|
} from "drizzle-orm/pg-core";
|
|
@@ -1843,8 +2001,8 @@ var init_skills = __esm({
|
|
|
1843
2001
|
description: text13("description"),
|
|
1844
2002
|
defaultContent: text13("default_content"),
|
|
1845
2003
|
contentOverridden: boolean7("content_overridden").default(false),
|
|
1846
|
-
requiredConfig:
|
|
1847
|
-
operations:
|
|
2004
|
+
requiredConfig: jsonb7("required_config").default(sql10`'[]'::jsonb`),
|
|
2005
|
+
operations: jsonb7("operations").default(sql10`'[]'::jsonb`),
|
|
1848
2006
|
requiredBuiltins: text13("required_builtins").array().notNull().default(sql10`'{}'::text[]`),
|
|
1849
2007
|
createdAt: timestamp13("created_at", { withTimezone: true }).defaultNow(),
|
|
1850
2008
|
updatedAt: timestamp13("updated_at", { withTimezone: true }).defaultNow()
|
|
@@ -1889,7 +2047,7 @@ var init_skills = __esm({
|
|
|
1889
2047
|
entityId: uuid13("entity_id").notNull().references(() => entities.id, { onDelete: "cascade" }),
|
|
1890
2048
|
agentId: uuid13("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
|
|
1891
2049
|
skillId: uuid13("skill_id").notNull().references(() => agentSkills.id, { onDelete: "cascade" }),
|
|
1892
|
-
approvalOverrides:
|
|
2050
|
+
approvalOverrides: jsonb7("approval_overrides").default(sql10`'{}'::jsonb`),
|
|
1893
2051
|
useCustomInstructions: boolean7("use_custom_instructions").notNull().default(false),
|
|
1894
2052
|
enabledOperations: text13("enabled_operations").array(),
|
|
1895
2053
|
createdAt: timestamp13("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
@@ -1999,7 +2157,7 @@ import {
|
|
|
1999
2157
|
text as text16,
|
|
2000
2158
|
uuid as uuid16,
|
|
2001
2159
|
boolean as boolean10,
|
|
2002
|
-
jsonb as
|
|
2160
|
+
jsonb as jsonb8,
|
|
2003
2161
|
timestamp as timestamp16,
|
|
2004
2162
|
index as index15,
|
|
2005
2163
|
uniqueIndex as uniqueIndex2,
|
|
@@ -2023,7 +2181,7 @@ var init_mcp = __esm({
|
|
|
2023
2181
|
url: text16("url"),
|
|
2024
2182
|
command: text16("command"),
|
|
2025
2183
|
args: text16("args").array().default(sql13`'{}'::text[]`),
|
|
2026
|
-
envVars:
|
|
2184
|
+
envVars: jsonb8("env_vars").default(sql13`'{}'::jsonb`),
|
|
2027
2185
|
// Encrypted (enc:v1: blob) credential for HTTP MCP servers — same pattern
|
|
2028
2186
|
// as connectors.api_key. NULL for servers that need no auth.
|
|
2029
2187
|
apiKey: text16("api_key"),
|
|
@@ -2036,7 +2194,7 @@ var init_mcp = __esm({
|
|
|
2036
2194
|
// The literal header name or query param name (e.g. 'x-api-key', 'api_key').
|
|
2037
2195
|
authParamName: text16("auth_param_name"),
|
|
2038
2196
|
active: boolean10("active").default(true),
|
|
2039
|
-
availableTools:
|
|
2197
|
+
availableTools: jsonb8("available_tools"),
|
|
2040
2198
|
createdAt: timestamp16("created_at", { withTimezone: true }).defaultNow(),
|
|
2041
2199
|
updatedAt: timestamp16("updated_at", { withTimezone: true }).defaultNow()
|
|
2042
2200
|
},
|
|
@@ -2058,7 +2216,7 @@ var init_mcp = __esm({
|
|
|
2058
2216
|
entityId: uuid16("entity_id").notNull().references(() => entities.id, { onDelete: "cascade" }),
|
|
2059
2217
|
agentId: uuid16("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
|
|
2060
2218
|
mcpServerId: uuid16("mcp_server_id").notNull().references(() => mcpServers.id, { onDelete: "cascade" }),
|
|
2061
|
-
enabledTools:
|
|
2219
|
+
enabledTools: jsonb8("enabled_tools"),
|
|
2062
2220
|
createdAt: timestamp16("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
2063
2221
|
updatedAt: timestamp16("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
2064
2222
|
},
|
|
@@ -2077,7 +2235,7 @@ var init_mcp = __esm({
|
|
|
2077
2235
|
entityId: uuid16("entity_id").notNull().references(() => entities.id, { onDelete: "cascade" }),
|
|
2078
2236
|
slug: text16("slug").notNull(),
|
|
2079
2237
|
active: boolean10("active").notNull().default(true),
|
|
2080
|
-
toolConfig:
|
|
2238
|
+
toolConfig: jsonb8("tool_config").notNull().default(sql13`'{}'::jsonb`),
|
|
2081
2239
|
createdAt: timestamp16("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
2082
2240
|
updatedAt: timestamp16("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
2083
2241
|
},
|
|
@@ -2093,7 +2251,7 @@ import {
|
|
|
2093
2251
|
uuid as uuid17,
|
|
2094
2252
|
boolean as boolean11,
|
|
2095
2253
|
integer as integer9,
|
|
2096
|
-
jsonb as
|
|
2254
|
+
jsonb as jsonb9,
|
|
2097
2255
|
timestamp as timestamp17,
|
|
2098
2256
|
index as index16,
|
|
2099
2257
|
check as check12
|
|
@@ -2111,7 +2269,7 @@ var init_misc = __esm({
|
|
|
2111
2269
|
id: uuid17("id").primaryKey().defaultRandom(),
|
|
2112
2270
|
entityId: uuid17("entity_id").notNull().references(() => entities.id, { onDelete: "cascade" }),
|
|
2113
2271
|
agentId: uuid17("agent_id").references(() => agents.id, { onDelete: "cascade" }),
|
|
2114
|
-
messages:
|
|
2272
|
+
messages: jsonb9("messages").notNull().default(sql14`'[]'::jsonb`),
|
|
2115
2273
|
status: text17("status").notNull().default("active"),
|
|
2116
2274
|
turnCount: integer9("turn_count").notNull().default(0),
|
|
2117
2275
|
createdAt: timestamp17("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
@@ -2131,7 +2289,7 @@ var init_misc = __esm({
|
|
|
2131
2289
|
slug: text17("slug").notNull(),
|
|
2132
2290
|
description: text17("description"),
|
|
2133
2291
|
pluginType: text17("plugin_type").notNull(),
|
|
2134
|
-
config:
|
|
2292
|
+
config: jsonb9("config").default(sql14`'{}'::jsonb`),
|
|
2135
2293
|
active: boolean11("active").default(true),
|
|
2136
2294
|
hook: text17("hook").notNull(),
|
|
2137
2295
|
webhookUrl: text17("webhook_url"),
|
|
@@ -10307,6 +10465,21 @@ Caused by: ${underlyingCause.stack}`;
|
|
|
10307
10465
|
}
|
|
10308
10466
|
}
|
|
10309
10467
|
};
|
|
10468
|
+
var AllProvidersFailedError = class extends Error {
|
|
10469
|
+
code = "all_providers_failed";
|
|
10470
|
+
underlyingCause;
|
|
10471
|
+
constructor(providerCount, underlyingCause) {
|
|
10472
|
+
super(
|
|
10473
|
+
`All ${providerCount} LLM providers failed; last: ${formatCauseSummary(underlyingCause)}`
|
|
10474
|
+
);
|
|
10475
|
+
this.name = "AllProvidersFailedError";
|
|
10476
|
+
this.underlyingCause = underlyingCause;
|
|
10477
|
+
if (underlyingCause instanceof Error) {
|
|
10478
|
+
this.stack = `${this.stack}
|
|
10479
|
+
Caused by: ${underlyingCause.stack}`;
|
|
10480
|
+
}
|
|
10481
|
+
}
|
|
10482
|
+
};
|
|
10310
10483
|
function formatCauseSummary(cause) {
|
|
10311
10484
|
if (cause instanceof Error) {
|
|
10312
10485
|
const name = cause.name || "Error";
|
|
@@ -10587,6 +10760,48 @@ function sleep(ms) {
|
|
|
10587
10760
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10588
10761
|
}
|
|
10589
10762
|
|
|
10763
|
+
// ../../packages/llm/src/tool-choice-floor.ts
|
|
10764
|
+
function isUnsupportedToolChoiceError(err) {
|
|
10765
|
+
let cur = err;
|
|
10766
|
+
for (let depth = 0; depth < 5 && cur; depth++) {
|
|
10767
|
+
if (!(cur instanceof Error)) return false;
|
|
10768
|
+
const parts = [cur.message ?? ""];
|
|
10769
|
+
const body = cur.responseBody;
|
|
10770
|
+
if (typeof body === "string") parts.push(body);
|
|
10771
|
+
const data = cur.data;
|
|
10772
|
+
if (data !== void 0 && data !== null) {
|
|
10773
|
+
try {
|
|
10774
|
+
parts.push(JSON.stringify(data));
|
|
10775
|
+
} catch {
|
|
10776
|
+
}
|
|
10777
|
+
}
|
|
10778
|
+
const text22 = parts.join(" ").toLowerCase();
|
|
10779
|
+
if (text22.includes("tool_choice") && (text22.includes("no endpoints") || text22.includes("not supported") || text22.includes("does not support") || text22.includes("unsupported") || text22.includes("invalid value"))) {
|
|
10780
|
+
return true;
|
|
10781
|
+
}
|
|
10782
|
+
const inner = cur.cause;
|
|
10783
|
+
if (inner === cur) return false;
|
|
10784
|
+
cur = inner;
|
|
10785
|
+
}
|
|
10786
|
+
return false;
|
|
10787
|
+
}
|
|
10788
|
+
async function generateWithToolChoiceFloor(run, toolChoice, label) {
|
|
10789
|
+
try {
|
|
10790
|
+
return await run();
|
|
10791
|
+
} catch (err) {
|
|
10792
|
+
const wasForced = toolChoice !== void 0 && toolChoice !== "auto";
|
|
10793
|
+
if (wasForced && isUnsupportedToolChoiceError(err)) {
|
|
10794
|
+
console.warn(
|
|
10795
|
+
`[tool_choice_relaxed] ${label}: provider rejected tool_choice=${JSON.stringify(
|
|
10796
|
+
toolChoice
|
|
10797
|
+
)} \u2014 retrying with 'auto'`
|
|
10798
|
+
);
|
|
10799
|
+
return run("auto");
|
|
10800
|
+
}
|
|
10801
|
+
throw err;
|
|
10802
|
+
}
|
|
10803
|
+
}
|
|
10804
|
+
|
|
10590
10805
|
// ../../packages/llm/src/providers/anthropic.ts
|
|
10591
10806
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
10592
10807
|
function buildAnthropicModel(config) {
|
|
@@ -10743,7 +10958,8 @@ function buildGroqModel(config) {
|
|
|
10743
10958
|
}
|
|
10744
10959
|
|
|
10745
10960
|
// ../../packages/llm/src/providers/openrouter.ts
|
|
10746
|
-
|
|
10961
|
+
init_src();
|
|
10962
|
+
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
10747
10963
|
import { wrapLanguageModel } from "ai";
|
|
10748
10964
|
|
|
10749
10965
|
// ../../packages/llm/src/providers/parsers.ts
|
|
@@ -10971,15 +11187,21 @@ function buildOpenRouterModel(config) {
|
|
|
10971
11187
|
throw new ProviderConfigError("openrouter provider requires an apiKey");
|
|
10972
11188
|
}
|
|
10973
11189
|
const baseURL = config.baseURL ?? PROVIDER_PRESETS.openrouter.defaultBaseURL;
|
|
10974
|
-
const provider =
|
|
10975
|
-
name: "openrouter",
|
|
10976
|
-
baseURL,
|
|
11190
|
+
const provider = createOpenRouter({
|
|
10977
11191
|
apiKey: config.apiKey,
|
|
11192
|
+
baseURL,
|
|
11193
|
+
// 'strict' is the documented mode for the first-party OpenRouter API
|
|
11194
|
+
// ('compatible' is for 3rd-party proxies). We hit openrouter.ai directly.
|
|
11195
|
+
compatibility: "strict",
|
|
10978
11196
|
// Normalise non-spec responses (e.g. DeepSeek V4 returning function.arguments
|
|
10979
|
-
// as an object instead of a JSON string) before
|
|
11197
|
+
// as an object instead of a JSON string) before the SDK's Zod schema sees them.
|
|
10980
11198
|
fetch: createTolerantFetch()
|
|
10981
11199
|
});
|
|
10982
|
-
const
|
|
11200
|
+
const isReasoning = findModelCatalogEntry("openrouter", config.model)?.capabilities.reasoning;
|
|
11201
|
+
const base = provider.chat(
|
|
11202
|
+
config.model,
|
|
11203
|
+
isReasoning ? { extraBody: { reasoning: { enabled: true } } } : void 0
|
|
11204
|
+
);
|
|
10983
11205
|
const middleware = middlewareForFamily(detectAgenticFamily(config.model));
|
|
10984
11206
|
if (middleware) {
|
|
10985
11207
|
return wrapLanguageModel({ model: base, middleware });
|
|
@@ -11068,21 +11290,27 @@ function createLlmClient(config) {
|
|
|
11068
11290
|
};
|
|
11069
11291
|
const clientGenerateText = async (args) => {
|
|
11070
11292
|
validateIfMessages(args);
|
|
11071
|
-
|
|
11072
|
-
|
|
11073
|
-
|
|
11074
|
-
|
|
11075
|
-
|
|
11076
|
-
|
|
11077
|
-
|
|
11078
|
-
|
|
11079
|
-
|
|
11080
|
-
|
|
11081
|
-
|
|
11082
|
-
|
|
11083
|
-
|
|
11293
|
+
const toolChoice = args.toolChoice;
|
|
11294
|
+
return generateWithToolChoiceFloor(
|
|
11295
|
+
(override) => withRetry(
|
|
11296
|
+
() => callWithTimeout(
|
|
11297
|
+
() => generateText({
|
|
11298
|
+
...args,
|
|
11299
|
+
model,
|
|
11300
|
+
...override ? { toolChoice: override } : {},
|
|
11301
|
+
// AI SDK native timeout via AbortSignal.timeout(). Survives
|
|
11302
|
+
// middleware wrapping unlike a passed-in abortSignal which their
|
|
11303
|
+
// internal retry can swallow.
|
|
11304
|
+
timeout: LLM_TIMEOUT_MS,
|
|
11305
|
+
// Disable AI SDK internal retry — we own retries via withRetry to
|
|
11306
|
+
// preserve typed error handling (Quota/MessageStructure/LLMTimeout).
|
|
11307
|
+
maxRetries: 0
|
|
11308
|
+
})
|
|
11309
|
+
),
|
|
11310
|
+
retryOpts
|
|
11084
11311
|
),
|
|
11085
|
-
|
|
11312
|
+
toolChoice,
|
|
11313
|
+
`${config.provider}/${config.model}`
|
|
11086
11314
|
);
|
|
11087
11315
|
};
|
|
11088
11316
|
const clientStreamText = (args) => {
|
|
@@ -11112,6 +11340,66 @@ function createLlmClient(config) {
|
|
|
11112
11340
|
};
|
|
11113
11341
|
}
|
|
11114
11342
|
|
|
11343
|
+
// ../../packages/llm/src/failover.ts
|
|
11344
|
+
function isFailoverWorthy(err) {
|
|
11345
|
+
return err instanceof RetryExhaustedError || err instanceof LLMTimeoutError || err instanceof QuotaExhaustedError;
|
|
11346
|
+
}
|
|
11347
|
+
function errLabel(err) {
|
|
11348
|
+
return err instanceof Error ? err.name : String(err);
|
|
11349
|
+
}
|
|
11350
|
+
function createFailoverFromClients(clients) {
|
|
11351
|
+
if (clients.length === 0) {
|
|
11352
|
+
throw new ProviderConfigError("failover: at least one client is required");
|
|
11353
|
+
}
|
|
11354
|
+
if (clients.length === 1) return clients[0];
|
|
11355
|
+
let activeIndex = 0;
|
|
11356
|
+
async function runWithFailover(op, label) {
|
|
11357
|
+
let lastErr;
|
|
11358
|
+
for (let i = activeIndex; i < clients.length; i++) {
|
|
11359
|
+
try {
|
|
11360
|
+
const result = await op(clients[i]);
|
|
11361
|
+
activeIndex = i;
|
|
11362
|
+
return result;
|
|
11363
|
+
} catch (err) {
|
|
11364
|
+
lastErr = err;
|
|
11365
|
+
if (!isFailoverWorthy(err)) throw err;
|
|
11366
|
+
const next = i + 1;
|
|
11367
|
+
if (next < clients.length) {
|
|
11368
|
+
console.warn(
|
|
11369
|
+
`[llm-failover] ${label}: ${clients[i].config.provider}/${clients[i].config.model} failed (${errLabel(err)}) \u2014 failing over to ${clients[next].config.provider}/${clients[next].config.model}`
|
|
11370
|
+
);
|
|
11371
|
+
}
|
|
11372
|
+
}
|
|
11373
|
+
}
|
|
11374
|
+
throw new AllProvidersFailedError(clients.length, lastErr);
|
|
11375
|
+
}
|
|
11376
|
+
const primary = clients[0];
|
|
11377
|
+
return {
|
|
11378
|
+
// Surface the primary's identity/capabilities; the chain is homogeneous in
|
|
11379
|
+
// the capability that matters here (tool use). Failover is for outages, not
|
|
11380
|
+
// capability switching.
|
|
11381
|
+
config: primary.config,
|
|
11382
|
+
capabilities: primary.capabilities,
|
|
11383
|
+
generateText: ((args) => runWithFailover(
|
|
11384
|
+
(c) => c.generateText(args),
|
|
11385
|
+
"generateText"
|
|
11386
|
+
)),
|
|
11387
|
+
// Streaming keeps single-provider semantics (the runner loop uses
|
|
11388
|
+
// generateText). Delegate to the currently-active provider.
|
|
11389
|
+
streamText: ((args) => clients[activeIndex].streamText(args)),
|
|
11390
|
+
generateObject: ((args) => runWithFailover(
|
|
11391
|
+
(c) => c.generateObject(args),
|
|
11392
|
+
"generateObject"
|
|
11393
|
+
))
|
|
11394
|
+
};
|
|
11395
|
+
}
|
|
11396
|
+
function createFailoverLlmClient(configs) {
|
|
11397
|
+
if (configs.length === 0) {
|
|
11398
|
+
throw new ProviderConfigError("failover: at least one provider config is required");
|
|
11399
|
+
}
|
|
11400
|
+
return createFailoverFromClients(configs.map((c) => createLlmClient(c)));
|
|
11401
|
+
}
|
|
11402
|
+
|
|
11115
11403
|
// ../../packages/llm/src/embeddings.ts
|
|
11116
11404
|
import { embed } from "ai";
|
|
11117
11405
|
import { createOllama as createOllama2 } from "ollama-ai-provider-v2";
|
|
@@ -11344,7 +11632,10 @@ async function _writeToolCall(ctx, toolName, input, output, durationMs) {
|
|
|
11344
11632
|
|
|
11345
11633
|
// ../../packages/tools/src/tool-choice.ts
|
|
11346
11634
|
function computeToolChoice(cfg) {
|
|
11347
|
-
const { isOrchestrator, turn, hasAdapterTools } = cfg;
|
|
11635
|
+
const { isOrchestrator, turn, hasAdapterTools, modelSupportsForcedToolChoice = true } = cfg;
|
|
11636
|
+
if (!modelSupportsForcedToolChoice) {
|
|
11637
|
+
return "auto";
|
|
11638
|
+
}
|
|
11348
11639
|
if (hasAdapterTools && !isOrchestrator) {
|
|
11349
11640
|
return "required";
|
|
11350
11641
|
}
|
|
@@ -26212,6 +26503,67 @@ async function createMcpTools(opts) {
|
|
|
26212
26503
|
return { tools, close: conn.close };
|
|
26213
26504
|
}
|
|
26214
26505
|
|
|
26506
|
+
// src/job/resolve-llm.ts
|
|
26507
|
+
init_src();
|
|
26508
|
+
async function resolveAgentLlmClient(db, agent, onSkip) {
|
|
26509
|
+
if (!agent.llmKeyId) return { ok: false, reason: "agent_no_llm_configured" };
|
|
26510
|
+
const seen = /* @__PURE__ */ new Set();
|
|
26511
|
+
const requested = [];
|
|
26512
|
+
for (const link of [
|
|
26513
|
+
{ keyId: agent.llmKeyId, model: agent.model },
|
|
26514
|
+
...agent.fallbackChain ?? []
|
|
26515
|
+
]) {
|
|
26516
|
+
if (typeof link.keyId === "string" && link.keyId.length > 0 && !seen.has(link.keyId)) {
|
|
26517
|
+
seen.add(link.keyId);
|
|
26518
|
+
requested.push({ keyId: link.keyId, model: link.model ?? "" });
|
|
26519
|
+
}
|
|
26520
|
+
}
|
|
26521
|
+
const ids = requested.map((r) => r.keyId);
|
|
26522
|
+
const rows = await db.select().from(entityLlmKeys).where(inArray3(entityLlmKeys.id, ids));
|
|
26523
|
+
const byId = new Map(rows.map((r) => [r.id, r]));
|
|
26524
|
+
try {
|
|
26525
|
+
const configs = [];
|
|
26526
|
+
for (const { keyId, model: requestedModel } of requested) {
|
|
26527
|
+
const row = byId.get(keyId);
|
|
26528
|
+
if (!row || !row.isActive) {
|
|
26529
|
+
onSkip?.({ keyId, reason: "missing_or_inactive" });
|
|
26530
|
+
continue;
|
|
26531
|
+
}
|
|
26532
|
+
const model = requestedModel.length > 0 ? requestedModel : MODEL_CATALOG[row.provider]?.[0]?.modelId ?? "";
|
|
26533
|
+
if (!model) {
|
|
26534
|
+
onSkip?.({ keyId, reason: "no_catalog_model" });
|
|
26535
|
+
continue;
|
|
26536
|
+
}
|
|
26537
|
+
const plaintextKey = row.apiKey ? decrypt(row.apiKey) : "";
|
|
26538
|
+
configs.push({
|
|
26539
|
+
provider: row.provider,
|
|
26540
|
+
model,
|
|
26541
|
+
apiKey: plaintextKey || void 0,
|
|
26542
|
+
baseURL: row.baseUrl ?? void 0
|
|
26543
|
+
});
|
|
26544
|
+
}
|
|
26545
|
+
if (configs.length === 0) return { ok: false, reason: "agent_no_llm_configured" };
|
|
26546
|
+
const effectivePrimary = configs[0];
|
|
26547
|
+
const client = configs.length > 1 ? createFailoverLlmClient(configs) : createLlmClient(effectivePrimary);
|
|
26548
|
+
return {
|
|
26549
|
+
ok: true,
|
|
26550
|
+
client,
|
|
26551
|
+
primaryProvider: effectivePrimary.provider,
|
|
26552
|
+
chainLength: configs.length,
|
|
26553
|
+
// Capability comes from the model CATALOG (provider, model of the
|
|
26554
|
+
// effective primary), not a stored column. Unknown/custom models default
|
|
26555
|
+
// to true; the runtime tool_choice floor backstops a wrong guess.
|
|
26556
|
+
primarySupportsForcedToolChoice: findModelCatalogEntry(effectivePrimary.provider, effectivePrimary.model)?.capabilities.forcedToolChoice ?? true
|
|
26557
|
+
};
|
|
26558
|
+
} catch (err) {
|
|
26559
|
+
return {
|
|
26560
|
+
ok: false,
|
|
26561
|
+
reason: "llm_key_invalid",
|
|
26562
|
+
detail: err instanceof Error ? err.message.slice(0, 200) : "llm_key_invalid"
|
|
26563
|
+
};
|
|
26564
|
+
}
|
|
26565
|
+
}
|
|
26566
|
+
|
|
26215
26567
|
// ../../packages/orchestration/src/errors.ts
|
|
26216
26568
|
var DelegationPendingError = class extends Error {
|
|
26217
26569
|
constructor(childJobId, childSlug) {
|
|
@@ -26273,7 +26625,16 @@ var DEFAULT_LIMITS = {
|
|
|
26273
26625
|
maxDelegationDepth: 3,
|
|
26274
26626
|
maxTurns: 50,
|
|
26275
26627
|
// matches Hermes Agent's per-subagent iteration budget; cumulative cap across resumes
|
|
26276
|
-
maxConsecutiveDeliveryTurns: 3
|
|
26628
|
+
maxConsecutiveDeliveryTurns: 3,
|
|
26629
|
+
// 1.5M total tokens: a loud backstop well above any legitimate single job
|
|
26630
|
+
// (typical jobs sit in the tens of thousands) yet below the ~2.4M-token
|
|
26631
|
+
// runaway that motivated it. Override per-deployment via MAX_TOTAL_TOKENS_PER_JOB.
|
|
26632
|
+
maxTotalTokensPerJob: 15e5,
|
|
26633
|
+
// 12 identical (toolName+input+output) turns in a row before declaring the job
|
|
26634
|
+
// stuck. Deliberately conservative: a real poll completes (output changes) long
|
|
26635
|
+
// before 12 identical reads, so this only catches genuinely degenerate loops —
|
|
26636
|
+
// and maxTurns (50) is the ultimate backstop above it.
|
|
26637
|
+
maxNoProgressRepeats: 12
|
|
26277
26638
|
};
|
|
26278
26639
|
var ChainCounters = class _ChainCounters {
|
|
26279
26640
|
constructor(limits = DEFAULT_LIMITS) {
|
|
@@ -26971,7 +27332,7 @@ function buildJobContextBlock(ctx) {
|
|
|
26971
27332
|
if (ctx.telegramChatId) lines.push(`- telegram_chat_id: ${ctx.telegramChatId}`);
|
|
26972
27333
|
if (ctx.surface === "chat") {
|
|
26973
27334
|
lines.push(
|
|
26974
|
-
'- surface: in-app dashboard chat \u2014 you are talking directly with the user; reply in plain text. For conversation or recalling facts, just reply (your durable facts are loaded below). For ANY action \u2014 using a connector or skill, delegating to your team, sending/fetching/creating/publishing, or (as the workspace ROOT) creating agents, skills, MCP servers, connectors or automations \u2014 call `run_task` with a clear, self-contained instruction:
|
|
27335
|
+
'- surface: in-app dashboard chat \u2014 you are talking directly with the user; reply in plain text. For conversation or recalling facts, just reply (your durable facts are loaded below). For ANY action \u2014 using a connector or skill, delegating to your team, sending/fetching/creating/publishing, or (as the workspace ROOT) creating agents, skills, MCP servers, connectors or automations \u2014 you MUST call the `run_task` tool with a clear, self-contained instruction. CRITICAL: writing in text that you will do something (e.g. "Je lance X\u2026") does NOT start anything \u2014 ONLY an actual `run_task` tool call performs the action. If you intend to act, the `run_task` tool call is mandatory; a text-only reply about an action accomplishes nothing. It runs as a tracked job with your FULL toolset. `run_task` is your gateway to everything you can do \u2014 NEVER tell the user you cannot do something that an action could accomplish; escalate it via `run_task` instead. You may add a one-line acknowledgment in your own voice alongside the call, but the `run_task` call is what actually does the work. Do not call any other named tool on this surface.'
|
|
26975
27336
|
);
|
|
26976
27337
|
}
|
|
26977
27338
|
if (ctx.notifyOnSuccess) {
|
|
@@ -27312,9 +27673,21 @@ function truncateForContext(value) {
|
|
|
27312
27673
|
|
|
27313
27674
|
[... truncated: ${dropped} chars dropped (total ${value.length}) ...]`;
|
|
27314
27675
|
}
|
|
27676
|
+
function stableStringify(value) {
|
|
27677
|
+
return JSON.stringify(value, (_key, val) => {
|
|
27678
|
+
if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
27679
|
+
return Object.keys(val).sort().reduce((acc, k) => {
|
|
27680
|
+
acc[k] = val[k];
|
|
27681
|
+
return acc;
|
|
27682
|
+
}, {});
|
|
27683
|
+
}
|
|
27684
|
+
return val;
|
|
27685
|
+
});
|
|
27686
|
+
}
|
|
27315
27687
|
async function executeJob(jobId, deps, _runnerEnv) {
|
|
27316
27688
|
const { db, registry } = deps;
|
|
27317
27689
|
let llmClient;
|
|
27690
|
+
let modelSupportsForcedToolChoice = true;
|
|
27318
27691
|
const startedAt = Date.now();
|
|
27319
27692
|
const trace = (event, data) => {
|
|
27320
27693
|
console.error(`[exec ${jobId}] ${event}`, data ? JSON.stringify(data) : "");
|
|
@@ -27378,28 +27751,27 @@ async function executeJob(jobId, deps, _runnerEnv) {
|
|
|
27378
27751
|
return { status: "failed", error: "agent_no_llm_configured" };
|
|
27379
27752
|
}
|
|
27380
27753
|
{
|
|
27381
|
-
const
|
|
27382
|
-
|
|
27383
|
-
|
|
27384
|
-
|
|
27385
|
-
|
|
27386
|
-
|
|
27387
|
-
|
|
27388
|
-
|
|
27389
|
-
|
|
27390
|
-
|
|
27391
|
-
|
|
27392
|
-
|
|
27393
|
-
}
|
|
27394
|
-
|
|
27395
|
-
|
|
27396
|
-
|
|
27397
|
-
|
|
27398
|
-
|
|
27399
|
-
|
|
27400
|
-
|
|
27401
|
-
|
|
27402
|
-
}
|
|
27754
|
+
const resolved = await resolveAgentLlmClient(
|
|
27755
|
+
db,
|
|
27756
|
+
{
|
|
27757
|
+
llmKeyId: agentRow.llmKeyId,
|
|
27758
|
+
fallbackChain: agentRow.fallbackChain ?? null,
|
|
27759
|
+
model: agent.model
|
|
27760
|
+
},
|
|
27761
|
+
(info) => trace("fallback_key_skipped", info)
|
|
27762
|
+
);
|
|
27763
|
+
if (!resolved.ok) {
|
|
27764
|
+
const code = resolved.reason === "agent_no_llm_configured" ? "agent_no_llm_configured" : `llm_key_invalid:${resolved.detail}`;
|
|
27765
|
+
await failJob(db, jobId, code, runStats());
|
|
27766
|
+
return { status: "failed", error: code };
|
|
27767
|
+
}
|
|
27768
|
+
llmClient = resolved.client;
|
|
27769
|
+
modelSupportsForcedToolChoice = resolved.primarySupportsForcedToolChoice;
|
|
27770
|
+
trace("llm_client_from_key", {
|
|
27771
|
+
provider: resolved.primaryProvider,
|
|
27772
|
+
chainLength: resolved.chainLength,
|
|
27773
|
+
forcedToolChoice: modelSupportsForcedToolChoice
|
|
27774
|
+
});
|
|
27403
27775
|
}
|
|
27404
27776
|
const childRows = await db.select({ id: agents.id, slug: agents.slug, role: agents.role }).from(agentAssignments).innerJoin(agents, eq4(agentAssignments.subAgentId, agents.id)).where(and3(eq4(agentAssignments.orchestratorId, agentRow.id), eq4(agents.active, true)));
|
|
27405
27777
|
const children = childRows.map((r) => ({
|
|
@@ -27734,6 +28106,20 @@ async function executeJob(jobId, deps, _runnerEnv) {
|
|
|
27734
28106
|
await setJobStatus(db, jobId, "awaiting_approval");
|
|
27735
28107
|
return { status: "awaiting_approval" };
|
|
27736
28108
|
};
|
|
28109
|
+
const maxTotalTokensPerJob = (() => {
|
|
28110
|
+
const raw = process.env["MAX_TOTAL_TOKENS_PER_JOB"];
|
|
28111
|
+
const n = raw ? Number(raw) : NaN;
|
|
28112
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_LIMITS.maxTotalTokensPerJob;
|
|
28113
|
+
})();
|
|
28114
|
+
const recentTurnSignatures = [];
|
|
28115
|
+
const maxNoProgressRepeats = (() => {
|
|
28116
|
+
const raw = process.env["MAX_NO_PROGRESS_REPEATS"];
|
|
28117
|
+
const n = raw ? Number(raw) : NaN;
|
|
28118
|
+
return Number.isFinite(n) && n >= 2 ? Math.floor(n) : DEFAULT_LIMITS.maxNoProgressRepeats;
|
|
28119
|
+
})();
|
|
28120
|
+
const unresolvedToolFailures = /* @__PURE__ */ new Set();
|
|
28121
|
+
const MAX_UNRESOLVED_FAILURE_NUDGES = 2;
|
|
28122
|
+
let unresolvedFailureNudges = 0;
|
|
27737
28123
|
try {
|
|
27738
28124
|
while (true) {
|
|
27739
28125
|
turn += 1;
|
|
@@ -27749,7 +28135,12 @@ async function executeJob(jobId, deps, _runnerEnv) {
|
|
|
27749
28135
|
return { status: "failed", error: "turn_limit_exceeded" };
|
|
27750
28136
|
}
|
|
27751
28137
|
validateMessageStructure(messages);
|
|
27752
|
-
const toolChoice = computeToolChoice({
|
|
28138
|
+
const toolChoice = computeToolChoice({
|
|
28139
|
+
isOrchestrator,
|
|
28140
|
+
turn,
|
|
28141
|
+
hasAdapterTools,
|
|
28142
|
+
modelSupportsForcedToolChoice
|
|
28143
|
+
});
|
|
27753
28144
|
const aiSdkTools = {};
|
|
27754
28145
|
for (const [name, toolDef] of toolMap) {
|
|
27755
28146
|
const description = authoringToolsSuffix && (name === "create_skill" || name === "update_skill") ? toolDef.description + authoringToolsSuffix : toolDef.description;
|
|
@@ -27767,6 +28158,11 @@ async function executeJob(jobId, deps, _runnerEnv) {
|
|
|
27767
28158
|
const completionT = Number(usage?.outputTokens ?? 0);
|
|
27768
28159
|
inputTokens += Number.isFinite(promptT) ? promptT : 0;
|
|
27769
28160
|
outputTokens += Number.isFinite(completionT) ? completionT : 0;
|
|
28161
|
+
if (inputTokens + outputTokens > maxTotalTokensPerJob) {
|
|
28162
|
+
trace("token_budget_exceeded", { turn, inputTokens, outputTokens, maxTotalTokensPerJob });
|
|
28163
|
+
await failJob(db, jobId, "token_budget_exceeded", runStats());
|
|
28164
|
+
return { status: "failed", error: "token_budget_exceeded" };
|
|
28165
|
+
}
|
|
27770
28166
|
const rawToolCalls = response.toolCalls ?? [];
|
|
27771
28167
|
trace("llm_call_done", {
|
|
27772
28168
|
turn,
|
|
@@ -27781,14 +28177,18 @@ async function executeJob(jobId, deps, _runnerEnv) {
|
|
|
27781
28177
|
await failJob(db, jobId, "delivery_spam_guard", runStats());
|
|
27782
28178
|
return { status: "failed", error: "delivery_spam_guard" };
|
|
27783
28179
|
}
|
|
28180
|
+
const reasoningParts = response.reasoning ?? [];
|
|
27784
28181
|
const assistantMsg = {
|
|
27785
28182
|
role: "assistant",
|
|
27786
|
-
content: rawToolCalls.length > 0 ?
|
|
27787
|
-
|
|
27788
|
-
|
|
27789
|
-
|
|
27790
|
-
|
|
27791
|
-
|
|
28183
|
+
content: rawToolCalls.length > 0 ? [
|
|
28184
|
+
...reasoningParts,
|
|
28185
|
+
...rawToolCalls.map((tc) => ({
|
|
28186
|
+
type: "tool-call",
|
|
28187
|
+
toolCallId: tc.toolCallId,
|
|
28188
|
+
toolName: tc.toolName,
|
|
28189
|
+
input: tc.input
|
|
28190
|
+
}))
|
|
28191
|
+
] : reasoningParts.length > 0 ? [...reasoningParts, { type: "text", text: response.text || "" }] : response.text || ""
|
|
27792
28192
|
};
|
|
27793
28193
|
messages = [...messages, assistantMsg];
|
|
27794
28194
|
const returnResultCall = rawToolCalls.find((tc) => tc.toolName === "return_result");
|
|
@@ -28011,6 +28411,11 @@ async function executeJob(jobId, deps, _runnerEnv) {
|
|
|
28011
28411
|
if (toolResult.outcome === "success" && DELIVERY_TOOL_NAMES.has(call.name)) {
|
|
28012
28412
|
telegramDelivered = true;
|
|
28013
28413
|
}
|
|
28414
|
+
if (toolResult.outcome === "success") {
|
|
28415
|
+
unresolvedToolFailures.delete(call.name);
|
|
28416
|
+
} else {
|
|
28417
|
+
unresolvedToolFailures.add(call.name);
|
|
28418
|
+
}
|
|
28014
28419
|
toolResultBlocks.push({
|
|
28015
28420
|
type: "tool-result",
|
|
28016
28421
|
toolCallId: call.id,
|
|
@@ -28059,6 +28464,38 @@ async function executeJob(jobId, deps, _runnerEnv) {
|
|
|
28059
28464
|
}
|
|
28060
28465
|
if (returnResultCall) {
|
|
28061
28466
|
trace("return_result_branch", { turn });
|
|
28467
|
+
const rrStatus = returnResultCall.input?.status;
|
|
28468
|
+
if (rrStatus === "success" && unresolvedToolFailures.size > 0) {
|
|
28469
|
+
const stuck = [...unresolvedToolFailures];
|
|
28470
|
+
if (unresolvedFailureNudges < MAX_UNRESOLVED_FAILURE_NUDGES) {
|
|
28471
|
+
unresolvedFailureNudges += 1;
|
|
28472
|
+
trace("unresolved_tool_failure_nudge", {
|
|
28473
|
+
turn,
|
|
28474
|
+
attempt: unresolvedFailureNudges,
|
|
28475
|
+
stuck
|
|
28476
|
+
});
|
|
28477
|
+
toolResultBlocks.push({
|
|
28478
|
+
type: "tool-result",
|
|
28479
|
+
toolCallId: returnResultCall.toolCallId,
|
|
28480
|
+
toolName: "return_result",
|
|
28481
|
+
output: toResultOutput({
|
|
28482
|
+
error: "deferred: tu signales success mais ces actions ont \xE9chou\xE9 sans \xEAtre corrig\xE9es (" + stuck.join(", ") + "). R\xE9essaie l'action jusqu'\xE0 r\xE9ussite, ou appelle return_result avec status='blocked'."
|
|
28483
|
+
})
|
|
28484
|
+
});
|
|
28485
|
+
messages = [...messages, { role: "tool", content: toolResultBlocks }];
|
|
28486
|
+
messages = [
|
|
28487
|
+
...messages,
|
|
28488
|
+
{
|
|
28489
|
+
role: "user",
|
|
28490
|
+
content: "[syst\xE8me] Ne d\xE9clare pas un succ\xE8s qui n'a pas eu lieu. Une action a \xE9chou\xE9 et n'a pas \xE9t\xE9 corrig\xE9e. Corrige-la, ou termine honn\xEAtement avec status='blocked'."
|
|
28491
|
+
}
|
|
28492
|
+
];
|
|
28493
|
+
continue;
|
|
28494
|
+
}
|
|
28495
|
+
trace("unresolved_tool_failure", { turn, stuck });
|
|
28496
|
+
await failJob(db, jobId, "unresolved_tool_failure", runStats());
|
|
28497
|
+
return { status: "failed", error: "unresolved_tool_failure" };
|
|
28498
|
+
}
|
|
28062
28499
|
const taskRows = await db.select({ id: agentTasks.id }).from(agentTasks).where(eq4(agentTasks.rootJobId, jobId));
|
|
28063
28500
|
if (requiresToolDelivery && !telegramDelivered && taskRows.length === 0) {
|
|
28064
28501
|
if (telegramRedeliveryNudges < MAX_TELEGRAM_REDELIVERY_NUDGES) {
|
|
@@ -28118,6 +28555,23 @@ async function executeJob(jobId, deps, _runnerEnv) {
|
|
|
28118
28555
|
if (toolResultBlocks.length > 0) {
|
|
28119
28556
|
messages = [...messages, { role: "tool", content: toolResultBlocks }];
|
|
28120
28557
|
}
|
|
28558
|
+
const turnSignature = toolResultBlocks.map((b) => {
|
|
28559
|
+
const call = callsToProcess.find((c) => c.id === b.toolCallId);
|
|
28560
|
+
const input = call ? stableStringify(call.input) : "";
|
|
28561
|
+
const output = b.output.type === "text" ? b.output.value : stableStringify(b.output.value ?? null);
|
|
28562
|
+
return `${b.toolName}\0${input}\0${output}`;
|
|
28563
|
+
}).sort().join("\n");
|
|
28564
|
+
if (turnSignature !== "") {
|
|
28565
|
+
recentTurnSignatures.push(turnSignature);
|
|
28566
|
+
if (recentTurnSignatures.length > maxNoProgressRepeats) {
|
|
28567
|
+
recentTurnSignatures.shift();
|
|
28568
|
+
}
|
|
28569
|
+
if (recentTurnSignatures.length === maxNoProgressRepeats && recentTurnSignatures.every((s) => s === turnSignature)) {
|
|
28570
|
+
trace("no_progress_detected", { turn, repeats: recentTurnSignatures.length });
|
|
28571
|
+
await failJob(db, jobId, "no_progress_detected", runStats());
|
|
28572
|
+
return { status: "failed", error: "no_progress_detected" };
|
|
28573
|
+
}
|
|
28574
|
+
}
|
|
28121
28575
|
await saveCheckpoint(db, jobId, {
|
|
28122
28576
|
messages,
|
|
28123
28577
|
turn,
|
|
@@ -28148,6 +28602,10 @@ async function executeJob(jobId, deps, _runnerEnv) {
|
|
|
28148
28602
|
await failJob(db, jobId, "quota_exhausted", runStats());
|
|
28149
28603
|
return { status: "failed", error: "quota_exhausted" };
|
|
28150
28604
|
}
|
|
28605
|
+
if (err instanceof AllProvidersFailedError) {
|
|
28606
|
+
await failJob(db, jobId, err.code, runStats());
|
|
28607
|
+
return { status: "failed", error: err.code };
|
|
28608
|
+
}
|
|
28151
28609
|
if (err instanceof MessageStructureError) {
|
|
28152
28610
|
await failJob(db, jobId, `message_structure_invalid:${err.code}`, runStats());
|
|
28153
28611
|
return { status: "failed", error: `message_structure_invalid:${err.code}` };
|
|
@@ -28773,6 +29231,7 @@ var CHAT_TOOLS = {
|
|
|
28773
29231
|
inputSchema: z89.object({ instruction: z89.string().min(1).max(4e3) })
|
|
28774
29232
|
}
|
|
28775
29233
|
};
|
|
29234
|
+
var ESCALATION_RECHECK = "Re-read your previous reply. If it committed to performing an action \u2014 running, launching, sending, fetching, creating, configuring, delegating, or any task or tool use \u2014 then your text ALONE did nothing: call the run_task tool NOW with a clear, self-contained instruction. If your reply was pure conversation, a question, or simply recalling a fact, do not call any tool \u2014 the conversation is complete.";
|
|
28776
29235
|
async function runChatTurn(opts) {
|
|
28777
29236
|
const { deps, entityId, agentId, conversationId, message } = opts;
|
|
28778
29237
|
const db = deps.db;
|
|
@@ -28786,20 +29245,18 @@ async function runChatTurn(opts) {
|
|
|
28786
29245
|
const title = message.trim().slice(0, TITLE_MAX) + (message.trim().length > TITLE_MAX ? "\u2026" : "");
|
|
28787
29246
|
await db.update(conversations).set({ title }).where(eq4(conversations.id, conversationId));
|
|
28788
29247
|
}
|
|
28789
|
-
const
|
|
28790
|
-
|
|
28791
|
-
|
|
28792
|
-
|
|
28793
|
-
|
|
28794
|
-
|
|
28795
|
-
|
|
28796
|
-
|
|
28797
|
-
|
|
28798
|
-
|
|
28799
|
-
});
|
|
28800
|
-
} catch {
|
|
28801
|
-
return { ok: false, error: "llm_key_invalid" };
|
|
29248
|
+
const resolved = await resolveAgentLlmClient(db, {
|
|
29249
|
+
llmKeyId: agentRow.llmKeyId,
|
|
29250
|
+
fallbackChain: agentRow.fallbackChain ?? null,
|
|
29251
|
+
model: agentRow.model ?? DEFAULT_MODEL
|
|
29252
|
+
});
|
|
29253
|
+
if (!resolved.ok) {
|
|
29254
|
+
return {
|
|
29255
|
+
ok: false,
|
|
29256
|
+
error: resolved.reason === "agent_no_llm_configured" ? "agent_no_llm_configured" : "llm_key_invalid"
|
|
29257
|
+
};
|
|
28802
29258
|
}
|
|
29259
|
+
const llmClient = resolved.client;
|
|
28803
29260
|
const agent = {
|
|
28804
29261
|
id: agentRow.id,
|
|
28805
29262
|
name: agentRow.name,
|
|
@@ -28816,8 +29273,43 @@ async function runChatTurn(opts) {
|
|
|
28816
29273
|
origin: "dashboard",
|
|
28817
29274
|
surface: "chat"
|
|
28818
29275
|
});
|
|
28819
|
-
const rows = await db.select({
|
|
28820
|
-
|
|
29276
|
+
const rows = await db.select({
|
|
29277
|
+
role: chatMessages.role,
|
|
29278
|
+
content: chatMessages.content,
|
|
29279
|
+
jobId: chatMessages.jobId,
|
|
29280
|
+
jobTask: agentJobs.task
|
|
29281
|
+
}).from(chatMessages).leftJoin(agentJobs, eq4(chatMessages.jobId, agentJobs.id)).where(eq4(chatMessages.conversationId, conversationId)).orderBy(desc(chatMessages.createdAt)).limit(HISTORY_LIMIT);
|
|
29282
|
+
const messages = [];
|
|
29283
|
+
for (const r of rows.reverse()) {
|
|
29284
|
+
if (r.role === "assistant" && r.jobId) {
|
|
29285
|
+
const toolCallId = `hist-${r.jobId}`;
|
|
29286
|
+
messages.push({
|
|
29287
|
+
role: "assistant",
|
|
29288
|
+
content: [
|
|
29289
|
+
...r.content ? [{ type: "text", text: r.content }] : [],
|
|
29290
|
+
{
|
|
29291
|
+
type: "tool-call",
|
|
29292
|
+
toolCallId,
|
|
29293
|
+
toolName: "run_task",
|
|
29294
|
+
input: { instruction: r.jobTask ?? "" }
|
|
29295
|
+
}
|
|
29296
|
+
]
|
|
29297
|
+
});
|
|
29298
|
+
messages.push({
|
|
29299
|
+
role: "tool",
|
|
29300
|
+
content: [
|
|
29301
|
+
{
|
|
29302
|
+
type: "tool-result",
|
|
29303
|
+
toolCallId,
|
|
29304
|
+
toolName: "run_task",
|
|
29305
|
+
output: { type: "text", value: "Task dispatched." }
|
|
29306
|
+
}
|
|
29307
|
+
]
|
|
29308
|
+
});
|
|
29309
|
+
} else {
|
|
29310
|
+
messages.push({ role: r.role, content: r.content });
|
|
29311
|
+
}
|
|
29312
|
+
}
|
|
28821
29313
|
let text22 = "";
|
|
28822
29314
|
let runTask;
|
|
28823
29315
|
try {
|
|
@@ -28828,7 +29320,23 @@ async function runChatTurn(opts) {
|
|
|
28828
29320
|
});
|
|
28829
29321
|
text22 = (response.text ?? "").trim();
|
|
28830
29322
|
runTask = (response.toolCalls ?? []).find((tc) => tc.toolName === "run_task");
|
|
28831
|
-
} catch {
|
|
29323
|
+
} catch (err) {
|
|
29324
|
+
console.warn(`[run-chat-turn] tools call failed (${agentRow.slug}):`, err.message);
|
|
29325
|
+
}
|
|
29326
|
+
if (!runTask && text22) {
|
|
29327
|
+
try {
|
|
29328
|
+
const recheck = await llmClient.generateText({
|
|
29329
|
+
system: systemPrompt,
|
|
29330
|
+
messages: [
|
|
29331
|
+
...messages,
|
|
29332
|
+
{ role: "assistant", content: text22 },
|
|
29333
|
+
{ role: "user", content: ESCALATION_RECHECK }
|
|
29334
|
+
],
|
|
29335
|
+
tools: CHAT_TOOLS
|
|
29336
|
+
});
|
|
29337
|
+
runTask = (recheck.toolCalls ?? []).find((tc) => tc.toolName === "run_task");
|
|
29338
|
+
} catch {
|
|
29339
|
+
}
|
|
28832
29340
|
}
|
|
28833
29341
|
if (runTask) {
|
|
28834
29342
|
const instruction = String(runTask.input?.instruction ?? "").trim() || message;
|
|
@@ -28915,7 +29423,6 @@ async function seedDefaultLlmKey(db, env2) {
|
|
|
28915
29423
|
apiKeyLast4: last4(plaintextKey),
|
|
28916
29424
|
baseUrl: env2.LLM_BASE_URL ?? null,
|
|
28917
29425
|
nickname: "Default (env)",
|
|
28918
|
-
defaultModel: env2.LLM_MODEL,
|
|
28919
29426
|
isActive: true
|
|
28920
29427
|
}).returning({ id: entityLlmKeys.id });
|
|
28921
29428
|
if (!newKey) return;
|
|
@@ -30710,7 +31217,7 @@ function createApp(deps, runnerEnv) {
|
|
|
30710
31217
|
throw err;
|
|
30711
31218
|
}
|
|
30712
31219
|
});
|
|
30713
|
-
|
|
31220
|
+
const bearerOrSession = async (c, next) => {
|
|
30714
31221
|
const auth2 = c.req.header("authorization") ?? "";
|
|
30715
31222
|
const bearer = auth2.startsWith("Bearer ") ? auth2.slice(7) : null;
|
|
30716
31223
|
if (bearer && runnerEnv.WORKER_SECRET && bearer === runnerEnv.WORKER_SECRET) {
|
|
@@ -30729,7 +31236,8 @@ function createApp(deps, runnerEnv) {
|
|
|
30729
31236
|
}
|
|
30730
31237
|
throw err;
|
|
30731
31238
|
}
|
|
30732
|
-
}
|
|
31239
|
+
};
|
|
31240
|
+
app.use("/api/approve", bearerOrSession);
|
|
30733
31241
|
app.get("/api/health", (c) => healthRoute(c, deps));
|
|
30734
31242
|
app.post("/api/agent", (c) => agentRoute(c, deps, runnerEnv));
|
|
30735
31243
|
app.post("/api/worker", (c) => workerRoute(c, deps, runnerEnv));
|