@yugenlab/vaayu 0.1.3 → 0.1.4

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.
@@ -0,0 +1,147 @@
1
+ import "./chunk-IGKYKEKT.js";
2
+
3
+ // apps/gateway/dist/agent/agentic-tool-loop.js
4
+ async function runAgenticToolLoop(params) {
5
+ const { initialContent, initialModel, contextMessages, tools, getToolRegistration, isToolAllowed, reChat, maxIterations, signal, logger, sessionId } = params;
6
+ let currentToolCalls = params.toolCalls;
7
+ let allToolResults = [];
8
+ const accumulatedMessages = [...contextMessages];
9
+ let finalContent = initialContent;
10
+ let finalModel = initialModel;
11
+ let iteration = 0;
12
+ while (currentToolCalls.length > 0 && iteration < maxIterations) {
13
+ iteration++;
14
+ if (signal?.aborted) {
15
+ logger.info("agentic_loop_aborted", { sessionId, iteration });
16
+ break;
17
+ }
18
+ accumulatedMessages.push({
19
+ role: "assistant",
20
+ content: finalContent,
21
+ toolCalls: currentToolCalls
22
+ });
23
+ const iterationResults = [];
24
+ for (const call of currentToolCalls) {
25
+ const callId = call.id ?? `call_${call.name}_${iteration}`;
26
+ if (!isToolAllowed(call.name)) {
27
+ const errorMsg = `Tool "${call.name}" is not allowed by current policy.`;
28
+ logger.warn("agentic_tool_blocked", { sessionId, tool: call.name, iteration });
29
+ iterationResults.push({
30
+ name: call.name,
31
+ input: call.input,
32
+ output: { error: errorMsg },
33
+ ok: false,
34
+ callId
35
+ });
36
+ accumulatedMessages.push({
37
+ role: "tool",
38
+ content: JSON.stringify({ error: errorMsg }),
39
+ name: call.name,
40
+ toolCallId: callId
41
+ });
42
+ continue;
43
+ }
44
+ const registration = getToolRegistration(call.name);
45
+ if (!registration) {
46
+ const errorMsg = `Tool "${call.name}" not found in registry.`;
47
+ logger.warn("agentic_tool_not_found", { sessionId, tool: call.name, iteration });
48
+ iterationResults.push({
49
+ name: call.name,
50
+ input: call.input,
51
+ output: { error: errorMsg },
52
+ ok: false,
53
+ callId
54
+ });
55
+ accumulatedMessages.push({
56
+ role: "tool",
57
+ content: JSON.stringify({ error: errorMsg }),
58
+ name: call.name,
59
+ toolCallId: callId
60
+ });
61
+ continue;
62
+ }
63
+ try {
64
+ const ctx = {
65
+ sessionId,
66
+ signal
67
+ };
68
+ const result = await registration.handler(call.input, ctx);
69
+ const output = result.ok ? result.output : result.error;
70
+ iterationResults.push({
71
+ name: call.name,
72
+ input: call.input,
73
+ output,
74
+ ok: result.ok,
75
+ callId
76
+ });
77
+ accumulatedMessages.push({
78
+ role: "tool",
79
+ content: JSON.stringify(result.ok ? result.output : { error: result.error }),
80
+ name: call.name,
81
+ toolCallId: callId
82
+ });
83
+ logger.info("agentic_tool_executed", {
84
+ sessionId,
85
+ tool: call.name,
86
+ ok: result.ok,
87
+ iteration
88
+ });
89
+ } catch (error) {
90
+ const errorMsg = error instanceof Error ? error.message : String(error);
91
+ logger.warn("agentic_tool_error", {
92
+ sessionId,
93
+ tool: call.name,
94
+ error: errorMsg,
95
+ iteration
96
+ });
97
+ iterationResults.push({
98
+ name: call.name,
99
+ input: call.input,
100
+ output: { error: errorMsg },
101
+ ok: false,
102
+ callId
103
+ });
104
+ accumulatedMessages.push({
105
+ role: "tool",
106
+ content: JSON.stringify({ error: errorMsg }),
107
+ name: call.name,
108
+ toolCallId: callId
109
+ });
110
+ }
111
+ }
112
+ allToolResults = [...allToolResults, ...iterationResults];
113
+ try {
114
+ const response = await reChat(accumulatedMessages, tools);
115
+ finalContent = response.content;
116
+ finalModel = response.model;
117
+ currentToolCalls = response.toolCalls ?? [];
118
+ } catch (error) {
119
+ logger.warn("agentic_rechat_failed", {
120
+ sessionId,
121
+ iteration,
122
+ error: error instanceof Error ? error.message : String(error)
123
+ });
124
+ if (!finalContent && allToolResults.length > 0) {
125
+ finalContent = allToolResults.map((r) => `${r.name}: ${r.ok ? JSON.stringify(r.output) : `Error: ${JSON.stringify(r.output)}`}`).join("\n");
126
+ }
127
+ break;
128
+ }
129
+ }
130
+ if (iteration >= maxIterations && currentToolCalls.length > 0) {
131
+ logger.warn("agentic_loop_max_iterations", {
132
+ sessionId,
133
+ maxIterations,
134
+ remainingToolCalls: currentToolCalls.length
135
+ });
136
+ }
137
+ return {
138
+ finalContent,
139
+ toolResults: allToolResults,
140
+ iterations: iteration,
141
+ finalModel
142
+ };
143
+ }
144
+ export {
145
+ runAgenticToolLoop
146
+ };
147
+ //# sourceMappingURL=agentic-tool-loop-2FZK72JO.js.map
package/gateway.js CHANGED
@@ -14387,10 +14387,33 @@ var OpenAICompatibleProvider = class {
14387
14387
  }
14388
14388
  const payload = {
14389
14389
  model: request.model,
14390
- messages: request.messages.map((message) => ({
14391
- role: message.role,
14392
- content: message.content
14393
- })),
14390
+ messages: request.messages.map((message) => {
14391
+ if (message.role === "assistant" && message.toolCalls?.length) {
14392
+ return {
14393
+ role: "assistant",
14394
+ content: message.content || null,
14395
+ tool_calls: message.toolCalls.map((call) => ({
14396
+ id: call.id ?? `call_${call.name}`,
14397
+ type: "function",
14398
+ function: {
14399
+ name: call.name,
14400
+ arguments: JSON.stringify(call.input)
14401
+ }
14402
+ }))
14403
+ };
14404
+ }
14405
+ if (message.role === "tool") {
14406
+ return {
14407
+ role: "tool",
14408
+ tool_call_id: message.toolCallId ?? message.name ?? "unknown",
14409
+ content: message.content
14410
+ };
14411
+ }
14412
+ return {
14413
+ role: message.role,
14414
+ content: message.content
14415
+ };
14416
+ }),
14394
14417
  temperature: request.temperature,
14395
14418
  top_p: request.topP,
14396
14419
  max_tokens: request.maxTokens,
@@ -14508,10 +14531,49 @@ function splitSystem(messages) {
14508
14531
  };
14509
14532
  }
14510
14533
  function toAnthropicMessages(messages) {
14511
- return messages.filter((message) => message.role !== "tool").map((message) => ({
14512
- role: message.role === "assistant" ? "assistant" : "user",
14513
- content: [{ type: "text", text: message.content }]
14514
- }));
14534
+ const result = [];
14535
+ for (let i = 0; i < messages.length; i++) {
14536
+ const message = messages[i];
14537
+ if (message.role === "system") continue;
14538
+ if (message.role === "tool") {
14539
+ const toolResults = [];
14540
+ let j = i;
14541
+ while (j < messages.length) {
14542
+ const tm = messages[j];
14543
+ if (tm.role !== "tool") break;
14544
+ toolResults.push({
14545
+ type: "tool_result",
14546
+ tool_use_id: tm.toolCallId ?? tm.name ?? "unknown",
14547
+ content: tm.content
14548
+ });
14549
+ j++;
14550
+ }
14551
+ result.push({ role: "user", content: toolResults });
14552
+ i = j - 1;
14553
+ continue;
14554
+ }
14555
+ if (message.role === "assistant" && message.toolCalls?.length) {
14556
+ const content = [];
14557
+ if (message.content) {
14558
+ content.push({ type: "text", text: message.content });
14559
+ }
14560
+ for (const call of message.toolCalls) {
14561
+ content.push({
14562
+ type: "tool_use",
14563
+ id: call.id ?? `call_${call.name}`,
14564
+ name: call.name,
14565
+ input: call.input
14566
+ });
14567
+ }
14568
+ result.push({ role: "assistant", content });
14569
+ continue;
14570
+ }
14571
+ result.push({
14572
+ role: message.role === "assistant" ? "assistant" : "user",
14573
+ content: [{ type: "text", text: message.content }]
14574
+ });
14575
+ }
14576
+ return result;
14515
14577
  }
14516
14578
  function buildUrl2(config) {
14517
14579
  if (config.path) {
@@ -29723,7 +29785,7 @@ function buildForcedIntentPlan(params) {
29723
29785
  return null;
29724
29786
  }
29725
29787
  async function handleToolPlanning(params) {
29726
- const { session, message, runId, signal, profile, locale, contextMessages, combinedSystem, resolvedProviderId, resolvedModel, budgetFallbackTarget, routingDecision, routingResult, isPureGreeting: isPureGreeting2, isPureAck: isPureAck2, maybeAppendSmritiMemory, config, logger, storage, toolRegistry, toolPolicy, isToolAllowed: isToolAllowed2, interpretRules: interpretRules2, interpretNlu, planToolInvocation: planToolInvocation2, looksLikeWeatherAsk: looksLikeWeatherAsk2, cleanLocationInput: cleanLocationInput2, isTemporalLocation: isTemporalLocation2, getToolApprovalRequirement: getToolApprovalRequirement2, createToolApprovalRequest, skillSynthEngine, skillSynthEnabled, getProvider, chitraguptaBridge } = params;
29788
+ const { session, message, runId, signal, plannerMode = "default", profile, locale, contextMessages, combinedSystem, resolvedProviderId, resolvedModel, budgetFallbackTarget, routingDecision, routingResult, isPureGreeting: isPureGreeting2, isPureAck: isPureAck2, maybeAppendSmritiMemory, config, logger, storage, toolRegistry, toolPolicy, isToolAllowed: isToolAllowed2, interpretRules: interpretRules2, interpretNlu, planToolInvocation: planToolInvocation2, looksLikeWeatherAsk: looksLikeWeatherAsk2, cleanLocationInput: cleanLocationInput2, isTemporalLocation: isTemporalLocation2, getToolApprovalRequirement: getToolApprovalRequirement2, createToolApprovalRequest, skillSynthEngine, skillSynthEnabled, getProvider, chitraguptaBridge } = params;
29727
29789
  if (!message.text)
29728
29790
  return null;
29729
29791
  const normalized = normalizeIncomingText(message.text);
@@ -29937,6 +29999,10 @@ async function handleToolPlanning(params) {
29937
29999
  let plannerProvider = null;
29938
30000
  let plannerResult = null;
29939
30001
  if (!plan) {
30002
+ if (plannerMode === "deterministic_only") {
30003
+ await maybeRecordActiveToolDiscovery();
30004
+ return null;
30005
+ }
29940
30006
  const plannerRole = config.routing.roles?.planner;
29941
30007
  plannerProviderId = plannerRole?.providerId ?? resolvedProviderId;
29942
30008
  plannerModel = plannerRole?.model ?? resolvedModel;
@@ -30982,6 +31048,8 @@ async function runChatWithFallback(params) {
30982
31048
  messages: prunedMessages ?? baseMessages,
30983
31049
  maxTokens: config.routing.budgets?.maxTokensPerRequest,
30984
31050
  metadata: requestMeta,
31051
+ tools: params.tools,
31052
+ toolChoice: params.toolChoice,
30985
31053
  signal: attemptSignal
30986
31054
  });
30987
31055
  response = await chatWithPolicy({
@@ -31057,7 +31125,8 @@ async function runChatWithFallback(params) {
31057
31125
  reason: "no_healthy_provider",
31058
31126
  retryable: true,
31059
31127
  responseModel: "provider.failure"
31060
- }
31128
+ },
31129
+ friendlyMessage: "I'm switching routes right now and can still handle quick tasks like weather, reminders, and notes. Please retry this request in a moment."
31061
31130
  };
31062
31131
  }
31063
31132
  return {
@@ -31068,7 +31137,8 @@ async function runChatWithFallback(params) {
31068
31137
  reason: "attempts_exhausted",
31069
31138
  retryable: true,
31070
31139
  responseModel: "provider.failure"
31071
- }
31140
+ },
31141
+ friendlyMessage: "I couldn't complete that with the current model route. I'm rebalancing providers automatically - please retry this request now."
31072
31142
  };
31073
31143
  }
31074
31144
  return {
@@ -33238,7 +33308,8 @@ async function executeAgentRun(params) {
33238
33308
  taskType: margaDecision.taskType,
33239
33309
  complexity: margaDecision.complexity
33240
33310
  });
33241
- const skipContent = "Hey! What can I help you with?";
33311
+ const skipKind = margaDecision.taskType === "heartbeat" ? "ack" : "greeting";
33312
+ const skipContent = deps.buildSmalltalkReply(runtime.profile, skipKind, payload.message.channel, locale);
33242
33313
  const skipModel = `marga:${margaDecision.taskType}`;
33243
33314
  await appendAssistantEvent(storage, session.id, skipContent, {
33244
33315
  providerId: "marga",
@@ -33302,6 +33373,11 @@ async function executeAgentRun(params) {
33302
33373
  routingResult.fallbackUsed = false;
33303
33374
  return buildEarlyResult(session, cacheModel, cached2.responseContent, routingDecision, routingResult);
33304
33375
  }
33376
+ const agenticTools = runtime.toolRegistry.list().filter((t2) => deps.isToolAllowed(toolPolicy, t2.name)).filter((t2) => !t2.name.startsWith("memory.")).map((t2) => ({
33377
+ name: t2.name,
33378
+ description: t2.description,
33379
+ inputSchema: t2.inputSchema
33380
+ }));
33305
33381
  const chatResult = await runChatWithFallback({
33306
33382
  sessionId: session.id,
33307
33383
  messageText: payload.message.text,
@@ -33338,10 +33414,63 @@ async function executeAgentRun(params) {
33338
33414
  providerHealth: runtime.providerHealth,
33339
33415
  lastAnthropicCallAt,
33340
33416
  logger,
33341
- signal: runSignal
33417
+ signal: runSignal,
33418
+ tools: agenticTools.length > 0 ? agenticTools : void 0
33342
33419
  });
33343
33420
  if (!chatResult.ok) {
33344
33421
  const escalation = chatResult.escalation;
33422
+ if (escalation.reason !== "request_aborted" && actionability.kind !== "smalltalk") {
33423
+ const degradedToolResult = await handleToolPlanning({
33424
+ session,
33425
+ message,
33426
+ runId,
33427
+ signal: runSignal,
33428
+ plannerMode: "deterministic_only",
33429
+ profile,
33430
+ locale: locale ?? "en",
33431
+ contextMessages: context.messages,
33432
+ combinedSystem,
33433
+ resolvedProviderId,
33434
+ resolvedModel,
33435
+ budgetFallbackTarget,
33436
+ routingDecision,
33437
+ routingResult,
33438
+ isPureGreeting: deps.isPureGreeting,
33439
+ isPureAck: deps.isPureAck,
33440
+ maybeAppendSmritiMemory: maybeAppendSmritiMemorySafe,
33441
+ config,
33442
+ storage,
33443
+ logger,
33444
+ providerHealth: runtime.providerHealth,
33445
+ getProfile: () => runtime.profile,
33446
+ toolRegistry: runtime.toolRegistry,
33447
+ toolPolicy,
33448
+ isToolAllowed: deps.isToolAllowed,
33449
+ interpretRules: deps.interpretRules,
33450
+ interpretNlu: deps.interpretNlu,
33451
+ planToolInvocation: deps.planToolInvocation,
33452
+ looksLikeWeatherAsk: deps.looksLikeWeatherAsk,
33453
+ cleanLocationInput: deps.cleanLocationInput,
33454
+ isTemporalLocation: deps.isTemporalLocation,
33455
+ getToolApprovalRequirement: deps.getToolApprovalRequirement,
33456
+ createToolApprovalRequest: deps.createToolApprovalRequest,
33457
+ guardToolExecution: deps.guardToolExecution,
33458
+ updateWeatherDefaults: deps.updateWeatherDefaults,
33459
+ formatWeatherResponse: deps.formatWeatherResponse,
33460
+ formatWeatherForecastResponse: deps.formatWeatherForecastResponse,
33461
+ renderToolOutput: deps.renderToolOutput,
33462
+ renderToolFailure: deps.renderToolFailure,
33463
+ stripModelThinking: deps.stripModelThinking,
33464
+ getProvider,
33465
+ skillSynthEngine: deps.skillSynthEngine,
33466
+ skillSynthEnabled: deps.skillSynthEnabled,
33467
+ chitraguptaBridge: runtime.chitraguptaBridge
33468
+ });
33469
+ if (degradedToolResult) {
33470
+ routingResult.escalation = escalation;
33471
+ return degradedToolResult;
33472
+ }
33473
+ }
33345
33474
  const smalltalkFallback = resolveSmalltalkProviderFallback({
33346
33475
  actionability,
33347
33476
  escalation,
@@ -33370,7 +33499,7 @@ async function executeAgentRun(params) {
33370
33499
  routingResult.fallbackUsed = false;
33371
33500
  return buildEarlyResult(session, smalltalkFallback.model, smalltalkFallback.content, routingDecision, routingResult);
33372
33501
  }
33373
- const content = escalation.policy === "degraded_tools_only" && chatResult.friendlyMessage ? chatResult.friendlyMessage : formatProviderError2(chatResult.error ?? new Error("Provider failed"));
33502
+ const content = chatResult.friendlyMessage ? chatResult.friendlyMessage : formatProviderError2(chatResult.error ?? new Error("Provider failed"));
33374
33503
  const errorModel = escalation.responseModel;
33375
33504
  routingResult.escalation = escalation;
33376
33505
  await appendAssistantEvent(storage, session.id, content, {
@@ -33392,6 +33521,43 @@ async function executeAgentRun(params) {
33392
33521
  return buildEarlyResult(session, errorModel, content, routingDecision, routingResult);
33393
33522
  }
33394
33523
  const { response, provider, model, fallbackUsed } = chatResult;
33524
+ let finalResponse = response;
33525
+ if (response.toolCalls?.length && agenticTools.length > 0) {
33526
+ const { runAgenticToolLoop } = await import("./chunks/agentic-tool-loop-2FZK72JO.js");
33527
+ const agenticResult = await runAgenticToolLoop({
33528
+ toolCalls: response.toolCalls,
33529
+ initialContent: response.content,
33530
+ initialModel: model,
33531
+ contextMessages: [...context.messages, { role: "user", content: payload.message.text }],
33532
+ tools: agenticTools,
33533
+ getToolRegistration: (name) => runtime.toolRegistry.get(name),
33534
+ isToolAllowed: (name) => deps.isToolAllowed(toolPolicy, name),
33535
+ reChat: async (msgs, tools) => {
33536
+ return provider.chat({
33537
+ model,
33538
+ messages: msgs,
33539
+ tools,
33540
+ maxTokens: config.routing.budgets?.maxTokensPerRequest,
33541
+ signal: runSignal
33542
+ });
33543
+ },
33544
+ maxIterations: config.routing.agenticMaxIterations ?? 5,
33545
+ signal: runSignal,
33546
+ logger,
33547
+ sessionId: session.id
33548
+ });
33549
+ finalResponse = {
33550
+ ...response,
33551
+ content: agenticResult.finalContent,
33552
+ model: agenticResult.finalModel,
33553
+ toolCalls: void 0
33554
+ };
33555
+ logger.info("agentic_loop_complete", {
33556
+ sessionId: session.id,
33557
+ iterations: agenticResult.iterations,
33558
+ toolsExecuted: agenticResult.toolResults.length
33559
+ });
33560
+ }
33395
33561
  if (runSignal.aborted) {
33396
33562
  throw new Error("Request aborted");
33397
33563
  }
@@ -33399,7 +33565,7 @@ async function executeAgentRun(params) {
33399
33565
  sessionId: session.id,
33400
33566
  category: semanticCacheCategory,
33401
33567
  query: routingText,
33402
- responseContent: response.content,
33568
+ responseContent: finalResponse.content,
33403
33569
  selected: { providerId: provider.id, model },
33404
33570
  ttlMs: semanticCacheRule.ttlMs
33405
33571
  });
@@ -33424,7 +33590,7 @@ async function executeAgentRun(params) {
33424
33590
  config,
33425
33591
  provider,
33426
33592
  model,
33427
- response,
33593
+ response: finalResponse,
33428
33594
  fallbackUsed,
33429
33595
  routingDecision,
33430
33596
  routingResult,
@@ -38453,6 +38619,7 @@ var routingSchema = external_exports.object({
38453
38619
  chatTimeoutMs: external_exports.number().int().min(1e3).max(12e4).default(15e3),
38454
38620
  maxAttempts: external_exports.number().int().min(1).max(10).default(2),
38455
38621
  plannerMaxAttempts: external_exports.number().int().min(1).max(10).default(1),
38622
+ agenticMaxIterations: external_exports.number().int().min(1).max(20).default(5),
38456
38623
  strategy: external_exports.enum(["first", "hash"]).default("hash"),
38457
38624
  minChars: external_exports.number().int().min(10).max(5e3).default(280),
38458
38625
  minWords: external_exports.number().int().min(5).max(500).default(60),
@@ -41004,6 +41171,9 @@ function formatProviderError(error) {
41004
41171
  return "I hit an error while handling that.";
41005
41172
  const raw = error instanceof Error ? error.message : String(error);
41006
41173
  const sanitized = sanitizeProviderError(raw);
41174
+ if (/^(i['’]m|please try)/i.test(sanitized)) {
41175
+ return sanitized;
41176
+ }
41007
41177
  return `I hit an error while handling that: ${sanitized}`;
41008
41178
  }
41009
41179
  function stripAnsi(input) {
@@ -41047,10 +41217,12 @@ function sanitizeProviderError(message) {
41047
41217
  if (/\b(timeout|timed out|etimedout|econnrefused|econnreset|fetch failed|network)\b/i.test(cleaned)) {
41048
41218
  return `${providerLabel} is temporarily unreachable.`;
41049
41219
  }
41220
+ if (/\b(no healthy provider|all providers on cooldown|all candidates on cooldown)\b/i.test(cleaned) || /\bprovider\s+[a-z0-9._-]+\s+unavailable\b/i.test(cleaned)) {
41221
+ return "I'm switching routes automatically right now. Please retry your request in a moment.";
41222
+ }
41050
41223
  const cooling = cleaned.match(/\bcooling down\s*\(([^)]+)\)/i);
41051
41224
  if (cooling) {
41052
- const reason = cooling[1]?.trim() || "temporary_error";
41053
- return `${providerLabel} is cooling down (${reason}). You can run /provider reset all or wait briefly.`;
41225
+ return "I'm switching routes automatically right now. Please retry your request in a moment.";
41054
41226
  }
41055
41227
  if (providerId) {
41056
41228
  return `${providerLabel} failed.`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yugenlab/vaayu",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "AI gateway — multi-provider, multi-channel personal AI assistant with memory",
5
5
  "type": "module",
6
6
  "author": "Srinivas Pendela",