@open-mercato/ai-assistant 0.6.1-develop.3291.1.6fad645fd0 → 0.6.1
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +30 -4
- package/dist/frontend/components/AiChatButton.js +3 -2
- package/dist/frontend/components/AiChatButton.js.map +2 -2
- package/dist/modules/ai_assistant/__integration__/TC-AI-AGENT-LOOP-001-006.spec.js +364 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-AGENT-LOOP-001-006.spec.js.map +7 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.js +7 -7
- package/dist/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.js.map +2 -2
- package/dist/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.js +182 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.js +316 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js +8 -7
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/chat/route.js +43 -20
- package/dist/modules/ai_assistant/api/ai/chat/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/settings/route.js +4 -3
- package/dist/modules/ai_assistant/api/settings/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/usage/daily/route.js +111 -0
- package/dist/modules/ai_assistant/api/usage/daily/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/usage/sessions/[sessionId]/route.js +108 -0
- package/dist/modules/ai_assistant/api/usage/sessions/[sessionId]/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/usage/sessions/route.js +153 -0
- package/dist/modules/ai_assistant/api/usage/sessions/route.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js +335 -38
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js +2 -7
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js +44 -35
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/AiUsageStatsPageClient.js +282 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/AiUsageStatsPageClient.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/page.js +10 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/page.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/page.meta.js +25 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/page.meta.js.map +7 -0
- package/dist/modules/ai_assistant/cli.js +12 -0
- package/dist/modules/ai_assistant/cli.js.map +2 -2
- package/dist/modules/ai_assistant/components/AiAssistantSettingsPageClient.js.map +1 -1
- package/dist/modules/ai_assistant/data/entities.js +177 -1
- package/dist/modules/ai_assistant/data/entities.js.map +2 -2
- package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js +104 -2
- package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js.map +2 -2
- package/dist/modules/ai_assistant/data/repositories/AiTokenUsageRepository.js +168 -0
- package/dist/modules/ai_assistant/data/repositories/AiTokenUsageRepository.js.map +7 -0
- package/dist/modules/ai_assistant/events.js +8 -0
- package/dist/modules/ai_assistant/events.js.map +2 -2
- package/dist/modules/ai_assistant/i18n/de.json +74 -1
- package/dist/modules/ai_assistant/i18n/en.json +74 -1
- package/dist/modules/ai_assistant/i18n/es.json +75 -2
- package/dist/modules/ai_assistant/i18n/pl.json +74 -1
- package/dist/modules/ai_assistant/lib/agent-policy.js.map +2 -2
- package/dist/modules/ai_assistant/lib/agent-runtime.js +588 -23
- package/dist/modules/ai_assistant/lib/agent-runtime.js.map +3 -3
- package/dist/modules/ai_assistant/lib/agent-tools.js +6 -1
- package/dist/modules/ai_assistant/lib/agent-tools.js.map +2 -2
- package/dist/modules/ai_assistant/lib/ai-agent-definition.js.map +2 -2
- package/dist/modules/ai_assistant/lib/model-factory.js +63 -22
- package/dist/modules/ai_assistant/lib/model-factory.js.map +2 -2
- package/dist/modules/ai_assistant/lib/token-usage-recorder.js +78 -0
- package/dist/modules/ai_assistant/lib/token-usage-recorder.js.map +7 -0
- package/dist/modules/ai_assistant/lib/usage-serialization.js +33 -0
- package/dist/modules/ai_assistant/lib/usage-serialization.js.map +7 -0
- package/dist/modules/ai_assistant/migrations/Migration20260508160000_ai_agent_loop_overrides.js +25 -0
- package/dist/modules/ai_assistant/migrations/Migration20260508160000_ai_agent_loop_overrides.js.map +7 -0
- package/dist/modules/ai_assistant/migrations/Migration20260508170000_ai_token_usage.js +88 -0
- package/dist/modules/ai_assistant/migrations/Migration20260508170000_ai_token_usage.js.map +7 -0
- package/dist/modules/ai_assistant/setup.js +34 -0
- package/dist/modules/ai_assistant/setup.js.map +2 -2
- package/dist/modules/ai_assistant/workers/ai-token-usage-prune.js +114 -0
- package/dist/modules/ai_assistant/workers/ai-token-usage-prune.js.map +7 -0
- package/generated/entities/ai_agent_runtime_override/index.ts +7 -0
- package/generated/entities/ai_token_usage_daily/index.ts +16 -0
- package/generated/entities/ai_token_usage_event/index.ts +19 -0
- package/generated/entities.ids.generated.ts +2 -0
- package/generated/entity-fields-registry.ts +47 -1
- package/package.json +15 -7
- package/src/frontend/components/AiChatButton.tsx +3 -2
- package/src/modules/ai_assistant/__integration__/TC-AI-AGENT-LOOP-001-006.spec.ts +521 -0
- package/src/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.ts +8 -8
- package/src/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.ts +231 -0
- package/src/modules/ai_assistant/__tests__/events.test.ts +4 -3
- package/src/modules/ai_assistant/__tests__/settings-page-logic.test.ts +5 -5
- package/src/modules/ai_assistant/__tests__/token-usage-recorder.test.ts +109 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.ts +388 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/models/__tests__/route.test.ts +5 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/models/route.ts +8 -7
- package/src/modules/ai_assistant/api/ai/chat/__tests__/route.test.ts +102 -5
- package/src/modules/ai_assistant/api/ai/chat/route.ts +55 -18
- package/src/modules/ai_assistant/api/settings/route.ts +5 -3
- package/src/modules/ai_assistant/api/usage/daily/__tests__/route.test.ts +159 -0
- package/src/modules/ai_assistant/api/usage/daily/route.ts +126 -0
- package/src/modules/ai_assistant/api/usage/sessions/[sessionId]/__tests__/route.test.ts +143 -0
- package/src/modules/ai_assistant/api/usage/sessions/[sessionId]/route.ts +130 -0
- package/src/modules/ai_assistant/api/usage/sessions/__tests__/route.test.ts +123 -0
- package/src/modules/ai_assistant/api/usage/sessions/route.ts +184 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.tsx +372 -16
- package/src/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.tsx +1 -4
- package/src/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.tsx +26 -9
- package/src/modules/ai_assistant/backend/config/ai-assistant/usage/AiUsageStatsPageClient.tsx +469 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/usage/page.meta.ts +23 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/usage/page.tsx +12 -0
- package/src/modules/ai_assistant/cli.ts +18 -0
- package/src/modules/ai_assistant/components/AiAssistantSettingsPageClient.tsx +1 -1
- package/src/modules/ai_assistant/data/entities.ts +237 -0
- package/src/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.ts +135 -3
- package/src/modules/ai_assistant/data/repositories/AiTokenUsageRepository.ts +213 -0
- package/src/modules/ai_assistant/data/repositories/__tests__/AiAgentRuntimeOverrideRepository.test.ts +223 -0
- package/src/modules/ai_assistant/data/repositories/__tests__/AiTokenUsageRepository.test.ts +58 -0
- package/src/modules/ai_assistant/events.ts +8 -0
- package/src/modules/ai_assistant/i18n/de.json +74 -1
- package/src/modules/ai_assistant/i18n/en.json +74 -1
- package/src/modules/ai_assistant/i18n/es.json +75 -2
- package/src/modules/ai_assistant/i18n/pl.json +74 -1
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase0.test.ts +439 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase1.test.ts +243 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase2.test.ts +388 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase3.test.ts +359 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-phase4a.test.ts +2 -2
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime.test.ts +2 -1
- package/src/modules/ai_assistant/lib/__tests__/max-steps-budget.integration.test.ts +12 -13
- package/src/modules/ai_assistant/lib/__tests__/model-factory.test.ts +77 -14
- package/src/modules/ai_assistant/lib/agent-policy.ts +9 -0
- package/src/modules/ai_assistant/lib/agent-runtime.ts +1148 -43
- package/src/modules/ai_assistant/lib/agent-tools.ts +5 -1
- package/src/modules/ai_assistant/lib/ai-agent-definition.ts +289 -2
- package/src/modules/ai_assistant/lib/model-factory.ts +128 -43
- package/src/modules/ai_assistant/lib/token-usage-recorder.ts +122 -0
- package/src/modules/ai_assistant/lib/usage-serialization.ts +29 -0
- package/src/modules/ai_assistant/migrations/.snapshot-open-mercato.json +791 -0
- package/src/modules/ai_assistant/migrations/Migration20260508160000_ai_agent_loop_overrides.ts +25 -0
- package/src/modules/ai_assistant/migrations/Migration20260508170000_ai_token_usage.ts +89 -0
- package/src/modules/ai_assistant/setup.ts +49 -0
- package/src/modules/ai_assistant/workers/__tests__/ai-token-usage-prune.test.ts +144 -0
- package/src/modules/ai_assistant/workers/ai-token-usage-prune.ts +188 -0
|
@@ -1,13 +1,21 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { createContainer } from "awilix";
|
|
2
3
|
import {
|
|
3
4
|
convertToModelMessages,
|
|
4
5
|
generateObject,
|
|
6
|
+
hasToolCall,
|
|
5
7
|
stepCountIs,
|
|
6
8
|
streamObject,
|
|
7
|
-
streamText
|
|
9
|
+
streamText,
|
|
10
|
+
Experimental_Agent as ToolLoopAgent
|
|
8
11
|
} from "ai";
|
|
9
|
-
import { createModelFactory } from "./model-factory.js";
|
|
10
|
-
import {
|
|
12
|
+
import { createModelFactory, resolveAllowRuntimeOverride } from "./model-factory.js";
|
|
13
|
+
import {
|
|
14
|
+
resolveAiAgentTools,
|
|
15
|
+
AgentPolicyError,
|
|
16
|
+
desanitizeToolNameForDisplay,
|
|
17
|
+
sanitizeToolNameForModel
|
|
18
|
+
} from "./agent-tools.js";
|
|
11
19
|
import { resolveEffectiveMutationPolicy } from "./agent-policy.js";
|
|
12
20
|
import { toolRegistry } from "./tool-registry.js";
|
|
13
21
|
import {
|
|
@@ -21,10 +29,377 @@ import { AiAgentRuntimeOverrideRepository } from "../data/repositories/AiAgentRu
|
|
|
21
29
|
import { AiTenantModelAllowlistRepository } from "../data/repositories/AiTenantModelAllowlistRepository.js";
|
|
22
30
|
import { composeSystemPromptWithOverride } from "./prompt-override-merge.js";
|
|
23
31
|
import { isKnownMutationPolicy } from "./agent-policy.js";
|
|
32
|
+
import { recordTokenUsage } from "./token-usage-recorder.js";
|
|
24
33
|
import "./llm-bootstrap.js";
|
|
34
|
+
const WRAPPER_DEFAULT_LOOP_CHAT = { maxSteps: 10 };
|
|
35
|
+
const WRAPPER_DEFAULT_LOOP_OBJECT = {};
|
|
36
|
+
function resolveLoopBudgetPreset(preset) {
|
|
37
|
+
switch (preset) {
|
|
38
|
+
case "tight":
|
|
39
|
+
return { maxSteps: 3, budget: { maxToolCalls: 3, maxWallClockMs: 1e4, maxTokens: 5e4 } };
|
|
40
|
+
case "loose":
|
|
41
|
+
return { maxSteps: 20, budget: { maxToolCalls: 20, maxWallClockMs: 12e4, maxTokens: 5e5 } };
|
|
42
|
+
case "default":
|
|
43
|
+
return void 0;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const SSE_ENCODER = new TextEncoder();
|
|
47
|
+
function appendLoopFinishToStream(baseResponse, finalizeLoopTrace) {
|
|
48
|
+
const { readable, writable } = new TransformStream();
|
|
49
|
+
const writer = writable.getWriter();
|
|
50
|
+
async function pump() {
|
|
51
|
+
if (!baseResponse.body) {
|
|
52
|
+
await writer.close();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const reader = baseResponse.body.getReader();
|
|
56
|
+
try {
|
|
57
|
+
for (; ; ) {
|
|
58
|
+
const { value, done } = await reader.read();
|
|
59
|
+
if (done) break;
|
|
60
|
+
await writer.write(value);
|
|
61
|
+
}
|
|
62
|
+
const trace = finalizeLoopTrace();
|
|
63
|
+
const eventLine = `data: ${JSON.stringify({ type: "loop-finish", trace })}
|
|
64
|
+
|
|
65
|
+
`;
|
|
66
|
+
await writer.write(SSE_ENCODER.encode(eventLine));
|
|
67
|
+
} catch {
|
|
68
|
+
} finally {
|
|
69
|
+
reader.releaseLock();
|
|
70
|
+
await writer.close().catch(() => void 0);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
void pump();
|
|
74
|
+
return new Response(readable, {
|
|
75
|
+
status: baseResponse.status,
|
|
76
|
+
headers: baseResponse.headers
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function assertLoopRuntimeOverrideAllowed(agent, callerLoop) {
|
|
80
|
+
if (!callerLoop) return;
|
|
81
|
+
const allowed = agent.loop?.allowRuntimeOverride ?? true;
|
|
82
|
+
if (!allowed) {
|
|
83
|
+
throw new AgentPolicyError(
|
|
84
|
+
"loop_runtime_override_disabled",
|
|
85
|
+
`Agent "${agent.id}" has disabled per-call loop overrides (loop.allowRuntimeOverride: false). Remove the loop override to proceed.`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function readModuleLoopEnv(moduleId) {
|
|
90
|
+
const prefix = moduleId.toUpperCase();
|
|
91
|
+
const partial = {};
|
|
92
|
+
const maxStepsRaw = process.env[`${prefix}_AI_LOOP_MAX_STEPS`];
|
|
93
|
+
if (maxStepsRaw) {
|
|
94
|
+
const parsed = parseInt(maxStepsRaw.trim(), 10);
|
|
95
|
+
if (!isNaN(parsed) && parsed > 0) partial.maxSteps = parsed;
|
|
96
|
+
}
|
|
97
|
+
const maxWallClockRaw = process.env[`${prefix}_AI_LOOP_MAX_WALL_CLOCK_MS`];
|
|
98
|
+
const maxTokensRaw = process.env[`${prefix}_AI_LOOP_MAX_TOKENS`];
|
|
99
|
+
if (maxWallClockRaw || maxTokensRaw) {
|
|
100
|
+
const budgetPartial = {};
|
|
101
|
+
if (maxWallClockRaw) {
|
|
102
|
+
const parsed = parseInt(maxWallClockRaw.trim(), 10);
|
|
103
|
+
if (!isNaN(parsed) && parsed > 0) budgetPartial.maxWallClockMs = parsed;
|
|
104
|
+
}
|
|
105
|
+
if (maxTokensRaw) {
|
|
106
|
+
const parsed = parseInt(maxTokensRaw.trim(), 10);
|
|
107
|
+
if (!isNaN(parsed) && parsed > 0) budgetPartial.maxTokens = parsed;
|
|
108
|
+
}
|
|
109
|
+
if (Object.keys(budgetPartial).length > 0) partial.budget = budgetPartial;
|
|
110
|
+
}
|
|
111
|
+
return partial;
|
|
112
|
+
}
|
|
113
|
+
function resolveEffectiveLoopConfig(agent, callerLoop, wrapperDefault) {
|
|
114
|
+
assertLoopRuntimeOverrideAllowed(agent, callerLoop);
|
|
115
|
+
const effectiveDefault = wrapperDefault ?? WRAPPER_DEFAULT_LOOP_CHAT;
|
|
116
|
+
const legacyMaxSteps = typeof agent.maxSteps === "number" && agent.maxSteps > 0 && !agent.loop ? { maxSteps: agent.maxSteps } : void 0;
|
|
117
|
+
const base = {
|
|
118
|
+
...effectiveDefault,
|
|
119
|
+
...legacyMaxSteps ?? {},
|
|
120
|
+
...agent.loop ?? {}
|
|
121
|
+
};
|
|
122
|
+
const envOverride = readModuleLoopEnv(agent.moduleId);
|
|
123
|
+
const withEnv = {
|
|
124
|
+
...base,
|
|
125
|
+
...envOverride,
|
|
126
|
+
...envOverride.budget != null ? { budget: { ...base.budget ?? {}, ...envOverride.budget } } : {}
|
|
127
|
+
};
|
|
128
|
+
const withCaller = callerLoop ? { ...withEnv, ...callerLoop } : withEnv;
|
|
129
|
+
if (withCaller.disabled === true) {
|
|
130
|
+
return { ...withCaller, maxSteps: 1 };
|
|
131
|
+
}
|
|
132
|
+
return withCaller;
|
|
133
|
+
}
|
|
134
|
+
class BudgetEnforcer {
|
|
135
|
+
constructor(budget, abortController) {
|
|
136
|
+
this.budget = budget;
|
|
137
|
+
this.abortController = abortController;
|
|
138
|
+
this.toolCallsUsed = 0;
|
|
139
|
+
this.tokensUsed = 0;
|
|
140
|
+
this.abortReason = null;
|
|
141
|
+
this.turnStartMs = Date.now();
|
|
142
|
+
}
|
|
143
|
+
get hasActiveBudget() {
|
|
144
|
+
const b = this.budget;
|
|
145
|
+
return b !== void 0 && (b.maxToolCalls !== void 0 || b.maxWallClockMs !== void 0 || b.maxTokens !== void 0);
|
|
146
|
+
}
|
|
147
|
+
recordStep(usage) {
|
|
148
|
+
if (!this.budget) return;
|
|
149
|
+
this.toolCallsUsed += usage.toolCalls ?? 0;
|
|
150
|
+
this.tokensUsed += (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
|
|
151
|
+
this.checkLimits();
|
|
152
|
+
}
|
|
153
|
+
checkLimits() {
|
|
154
|
+
const b = this.budget;
|
|
155
|
+
if (!b) return;
|
|
156
|
+
if (b.maxToolCalls !== void 0 && this.toolCallsUsed >= b.maxToolCalls) {
|
|
157
|
+
this.abort("budget-tool-calls");
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const elapsedMs = Date.now() - this.turnStartMs;
|
|
161
|
+
if (b.maxWallClockMs !== void 0 && elapsedMs >= b.maxWallClockMs) {
|
|
162
|
+
this.abort("budget-wall-clock");
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (b.maxTokens !== void 0 && this.tokensUsed >= b.maxTokens) {
|
|
166
|
+
this.abort("budget-tokens");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
abort(reason) {
|
|
170
|
+
if (this.abortReason !== null) return;
|
|
171
|
+
this.abortReason = reason;
|
|
172
|
+
console.info(
|
|
173
|
+
`[AI Agents] Budget exceeded \u2014 aborting turn. Reason: ${reason}. toolCalls=${this.toolCallsUsed}, tokens=${this.tokensUsed}, elapsedMs=${Date.now() - this.turnStartMs}.`
|
|
174
|
+
);
|
|
175
|
+
this.abortController.abort(reason);
|
|
176
|
+
}
|
|
177
|
+
wire(userOnStepFinish) {
|
|
178
|
+
if (!this.hasActiveBudget) return userOnStepFinish;
|
|
179
|
+
return async (event) => {
|
|
180
|
+
this.recordStep({
|
|
181
|
+
inputTokens: event.usage?.inputTokens,
|
|
182
|
+
outputTokens: event.usage?.outputTokens,
|
|
183
|
+
toolCalls: event.toolCalls?.length
|
|
184
|
+
});
|
|
185
|
+
if (userOnStepFinish) {
|
|
186
|
+
try {
|
|
187
|
+
await userOnStepFinish(event);
|
|
188
|
+
} catch (err) {
|
|
189
|
+
console.error("[AI Agents] User onStepFinish threw; ignoring:", err);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function buildLoopTraceCollector(agentId, sessionId, turnId, userOnStepFinish) {
|
|
196
|
+
const turnStartMs = Date.now();
|
|
197
|
+
const steps = [];
|
|
198
|
+
const onStepFinish = async (event) => {
|
|
199
|
+
const stepIndex = steps.length;
|
|
200
|
+
const toolCalls = (event.toolCalls ?? []).map((tc) => {
|
|
201
|
+
const raw = tc;
|
|
202
|
+
return {
|
|
203
|
+
toolName: raw.toolName ? desanitizeToolNameForDisplay(raw.toolName) : "unknown",
|
|
204
|
+
args: raw.args ?? {},
|
|
205
|
+
result: raw.result,
|
|
206
|
+
error: raw.experimental_toToolResultError ? {
|
|
207
|
+
code: String(raw.experimental_toToolResultError?.code ?? "unknown"),
|
|
208
|
+
message: String(raw.experimental_toToolResultError?.message ?? "")
|
|
209
|
+
} : void 0,
|
|
210
|
+
repairAttempted: raw.repairAttempted === true,
|
|
211
|
+
durationMs: typeof raw.startTime === "number" && typeof raw.endTime === "number" ? raw.endTime - raw.startTime : 0
|
|
212
|
+
};
|
|
213
|
+
});
|
|
214
|
+
const textDelta = event.text ?? "";
|
|
215
|
+
const finishReason = event.finishReason ?? "stop";
|
|
216
|
+
const modelId = event.response?.modelId ?? "unknown";
|
|
217
|
+
steps.push({
|
|
218
|
+
stepIndex,
|
|
219
|
+
modelId,
|
|
220
|
+
toolCalls,
|
|
221
|
+
textDelta,
|
|
222
|
+
usage: {
|
|
223
|
+
inputTokens: event.usage?.inputTokens ?? 0,
|
|
224
|
+
outputTokens: event.usage?.outputTokens ?? 0
|
|
225
|
+
},
|
|
226
|
+
finishReason
|
|
227
|
+
});
|
|
228
|
+
if (userOnStepFinish) {
|
|
229
|
+
try {
|
|
230
|
+
await userOnStepFinish(event);
|
|
231
|
+
} catch (err) {
|
|
232
|
+
console.error("[AI Agents] User onStepFinish in LoopTrace collector threw; ignoring:", err);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
function finalize(abortReason) {
|
|
237
|
+
const totalDurationMs = Date.now() - turnStartMs;
|
|
238
|
+
const totalUsage = steps.reduce(
|
|
239
|
+
(acc, step) => ({
|
|
240
|
+
inputTokens: acc.inputTokens + step.usage.inputTokens,
|
|
241
|
+
outputTokens: acc.outputTokens + step.usage.outputTokens
|
|
242
|
+
}),
|
|
243
|
+
{ inputTokens: 0, outputTokens: 0 }
|
|
244
|
+
);
|
|
245
|
+
let stopReason = "finish-reason";
|
|
246
|
+
if (abortReason === "budget-tool-calls") stopReason = "budget-tool-calls";
|
|
247
|
+
else if (abortReason === "budget-wall-clock") stopReason = "budget-wall-clock";
|
|
248
|
+
else if (abortReason === "budget-tokens") stopReason = "budget-tokens";
|
|
249
|
+
return {
|
|
250
|
+
agentId,
|
|
251
|
+
sessionId,
|
|
252
|
+
turnId,
|
|
253
|
+
steps,
|
|
254
|
+
stopReason,
|
|
255
|
+
totalDurationMs,
|
|
256
|
+
totalUsage
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
return { onStepFinish, finalize };
|
|
260
|
+
}
|
|
261
|
+
function translateStopConditions(loopConfig, mapToolName = (toolName) => toolName) {
|
|
262
|
+
const effectiveMaxSteps = loopConfig.maxSteps ?? 10;
|
|
263
|
+
const userConditions = [];
|
|
264
|
+
const rawStopWhen = loopConfig.stopWhen;
|
|
265
|
+
if (rawStopWhen) {
|
|
266
|
+
const items = Array.isArray(rawStopWhen) ? rawStopWhen : [rawStopWhen];
|
|
267
|
+
for (const item of items) {
|
|
268
|
+
if (item.kind === "stepCount") {
|
|
269
|
+
userConditions.push(stepCountIs(item.count));
|
|
270
|
+
} else if (item.kind === "hasToolCall") {
|
|
271
|
+
userConditions.push(hasToolCall(mapToolName(item.toolName)));
|
|
272
|
+
} else if (item.kind === "custom") {
|
|
273
|
+
userConditions.push(item.stop);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return [...userConditions, stepCountIs(effectiveMaxSteps)];
|
|
278
|
+
}
|
|
279
|
+
function normalizeAllowedToolNameForAgent(toolName, agent) {
|
|
280
|
+
if (agent.allowedTools.includes(toolName)) return toolName;
|
|
281
|
+
const dottedName = desanitizeToolNameForDisplay(toolName);
|
|
282
|
+
return agent.allowedTools.includes(dottedName) ? dottedName : null;
|
|
283
|
+
}
|
|
284
|
+
function mergeStepOverrides(wrapperOverride, userOverride, agent, wrappedToolRegistry) {
|
|
285
|
+
if (!userOverride) return wrapperOverride;
|
|
286
|
+
const merged = { ...wrapperOverride };
|
|
287
|
+
const userWithTools = userOverride;
|
|
288
|
+
if (userOverride.model !== void 0) {
|
|
289
|
+
merged.model = userOverride.model;
|
|
290
|
+
}
|
|
291
|
+
if (userOverride.toolChoice !== void 0) {
|
|
292
|
+
merged.toolChoice = userOverride.toolChoice;
|
|
293
|
+
}
|
|
294
|
+
if (userOverride.activeTools !== void 0) {
|
|
295
|
+
const filtered = userOverride.activeTools.flatMap((name) => {
|
|
296
|
+
const normalized = normalizeAllowedToolNameForAgent(name, agent);
|
|
297
|
+
const allowed = normalized !== null;
|
|
298
|
+
if (!allowed) {
|
|
299
|
+
console.warn(
|
|
300
|
+
`[AI Agents] loop:active_tools_filtered \u2014 tool "${name}" is not in agent "${agent.id}" allowedTools; dropping from activeTools.`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
return normalized ? [normalized] : [];
|
|
304
|
+
});
|
|
305
|
+
merged.activeTools = filtered;
|
|
306
|
+
}
|
|
307
|
+
if (userWithTools.tools !== void 0) {
|
|
308
|
+
const userTools = userWithTools.tools;
|
|
309
|
+
const mergedTools = {};
|
|
310
|
+
for (const [toolKey, userHandler] of Object.entries(userTools)) {
|
|
311
|
+
const wrappedHandler = wrappedToolRegistry[toolKey];
|
|
312
|
+
if (!wrappedHandler) {
|
|
313
|
+
console.warn(
|
|
314
|
+
`[AI Agents] mergeStepOverrides \u2014 tool "${toolKey}" from user prepareStep is not in the wrapper tool registry; dropping.`
|
|
315
|
+
);
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (userHandler !== wrappedHandler) {
|
|
319
|
+
const toolDef = toolRegistry.getTool(
|
|
320
|
+
toolKey.replace(/__/g, ".")
|
|
321
|
+
);
|
|
322
|
+
if (toolDef?.isMutation === true) {
|
|
323
|
+
throw new AgentPolicyError(
|
|
324
|
+
"loop_violates_mutation_policy",
|
|
325
|
+
`User prepareStep returned a tools map with raw (unwrapped) mutation handler for "${toolKey}". This bypasses the mutation-approval gate and is rejected.`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
mergedTools[toolKey] = wrappedHandler;
|
|
330
|
+
}
|
|
331
|
+
merged.tools = mergedTools;
|
|
332
|
+
}
|
|
333
|
+
return merged;
|
|
334
|
+
}
|
|
335
|
+
function mapPrepareStepResultForModel(result, wrappedTools) {
|
|
336
|
+
if (!result?.activeTools) return result;
|
|
337
|
+
const activeTools = result.activeTools.map((toolName) => sanitizeToolNameForModel(toolName)).filter((toolName) => wrappedTools[toolName] !== void 0);
|
|
338
|
+
return { ...result, activeTools };
|
|
339
|
+
}
|
|
340
|
+
function mapToolChoiceForModel(toolChoice) {
|
|
341
|
+
if (!toolChoice || typeof toolChoice !== "object" || toolChoice.type !== "tool") {
|
|
342
|
+
return toolChoice;
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
...toolChoice,
|
|
346
|
+
toolName: sanitizeToolNameForModel(toolChoice.toolName)
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function mapActiveToolsForModel(activeTools, wrappedTools) {
|
|
350
|
+
if (!activeTools) return void 0;
|
|
351
|
+
return activeTools.map((toolName) => sanitizeToolNameForModel(toolName)).filter((toolName) => wrappedTools[toolName] !== void 0);
|
|
352
|
+
}
|
|
353
|
+
function buildWrapperPrepareStep(agent, effectiveLoop, wrappedTools) {
|
|
354
|
+
return async (state) => {
|
|
355
|
+
const wrapperOverride = {};
|
|
356
|
+
if (effectiveLoop.activeTools && effectiveLoop.activeTools.length > 0) {
|
|
357
|
+
wrapperOverride.activeTools = effectiveLoop.activeTools.flatMap((name) => {
|
|
358
|
+
const normalized = normalizeAllowedToolNameForAgent(name, agent);
|
|
359
|
+
const allowed = normalized !== null;
|
|
360
|
+
if (!allowed) {
|
|
361
|
+
console.warn(
|
|
362
|
+
`[AI Agents] loop:active_tools_filtered \u2014 tool "${name}" is not in agent "${agent.id}" allowedTools; dropping from activeTools.`
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
return normalized ? [normalized] : [];
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
if (effectiveLoop.prepareStep) {
|
|
369
|
+
let userOverride;
|
|
370
|
+
try {
|
|
371
|
+
userOverride = await effectiveLoop.prepareStep(state);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
console.error(
|
|
374
|
+
`[AI Agents] User prepareStep threw for agent "${agent.id}"; ignoring user override:`,
|
|
375
|
+
error
|
|
376
|
+
);
|
|
377
|
+
return mapPrepareStepResultForModel(wrapperOverride, wrappedTools);
|
|
378
|
+
}
|
|
379
|
+
return mapPrepareStepResultForModel(
|
|
380
|
+
mergeStepOverrides(wrapperOverride, userOverride, agent, wrappedTools),
|
|
381
|
+
wrappedTools
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
return mapPrepareStepResultForModel(wrapperOverride, wrappedTools);
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
function assertLoopObjectModeCompatible(loopConfig) {
|
|
388
|
+
const unsupportedFields = [];
|
|
389
|
+
if (loopConfig.prepareStep !== void 0) unsupportedFields.push("prepareStep");
|
|
390
|
+
if (loopConfig.repairToolCall !== void 0) unsupportedFields.push("repairToolCall");
|
|
391
|
+
if (loopConfig.stopWhen !== void 0) unsupportedFields.push("stopWhen");
|
|
392
|
+
if (loopConfig.activeTools !== void 0) unsupportedFields.push("activeTools");
|
|
393
|
+
if (loopConfig.toolChoice !== void 0) unsupportedFields.push("toolChoice");
|
|
394
|
+
if (unsupportedFields.length > 0) {
|
|
395
|
+
throw new AgentPolicyError(
|
|
396
|
+
"loop_unsupported_in_object_mode",
|
|
397
|
+
`Object-mode agents do not support these loop primitives: ${unsupportedFields.join(", ")}. Use runAiAgentText for agents that require these loop controls.`
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
25
401
|
function resolveAgentModel(agent, modelOverride, providerOverride, container, baseUrlOverride, tenantOverride, requestOverride, tenantAllowlist) {
|
|
26
402
|
const effectiveContainer = container ?? createContainer();
|
|
27
|
-
const allowRuntimeModelOverride = agent.allowRuntimeModelOverride !== false;
|
|
28
403
|
const resolution = createModelFactory(effectiveContainer).resolveModel({
|
|
29
404
|
moduleId: agent.moduleId,
|
|
30
405
|
agentDefaultModel: agent.defaultModel,
|
|
@@ -33,7 +408,7 @@ function resolveAgentModel(agent, modelOverride, providerOverride, container, ba
|
|
|
33
408
|
callerOverride: modelOverride,
|
|
34
409
|
providerOverride,
|
|
35
410
|
baseUrlOverride,
|
|
36
|
-
|
|
411
|
+
allowRuntimeOverride: resolveAllowRuntimeOverride(agent),
|
|
37
412
|
tenantOverride: tenantOverride ?? void 0,
|
|
38
413
|
requestOverride: requestOverride ?? void 0,
|
|
39
414
|
tenantAllowlist: tenantAllowlist ?? null
|
|
@@ -353,6 +728,7 @@ async function runAiAgentText(input) {
|
|
|
353
728
|
input.authContext.organizationId
|
|
354
729
|
)
|
|
355
730
|
]);
|
|
731
|
+
const effectiveSessionId = (input.sessionId ?? input.conversationId) || randomUUID();
|
|
356
732
|
const { agent, tools } = await resolveAiAgentTools({
|
|
357
733
|
agentId: input.agentId,
|
|
358
734
|
authContext: input.authContext,
|
|
@@ -360,7 +736,7 @@ async function runAiAgentText(input) {
|
|
|
360
736
|
attachmentIds: input.attachmentIds,
|
|
361
737
|
mutationPolicyOverride,
|
|
362
738
|
container: input.container,
|
|
363
|
-
conversationId:
|
|
739
|
+
conversationId: effectiveSessionId
|
|
364
740
|
});
|
|
365
741
|
const resolvedAttachments = await resolveAttachmentPartsForAgent({
|
|
366
742
|
agent,
|
|
@@ -380,7 +756,7 @@ async function runAiAgentText(input) {
|
|
|
380
756
|
agent,
|
|
381
757
|
mutationPolicyOverride
|
|
382
758
|
);
|
|
383
|
-
const
|
|
759
|
+
const resolvedModel = resolveAgentModel(
|
|
384
760
|
agent,
|
|
385
761
|
input.modelOverride,
|
|
386
762
|
input.providerOverride,
|
|
@@ -390,26 +766,162 @@ async function runAiAgentText(input) {
|
|
|
390
766
|
input.requestOverride,
|
|
391
767
|
tenantAllowlistSnapshot
|
|
392
768
|
);
|
|
769
|
+
const { model } = resolvedModel;
|
|
393
770
|
const normalizedMessages = ensureUiMessageShape(input.messages);
|
|
394
771
|
const hydratedMessages = attachAttachmentsToMessages(normalizedMessages, resolvedAttachments);
|
|
395
772
|
const modelMessages = await convertToModelMessages(hydratedMessages);
|
|
396
|
-
const
|
|
397
|
-
const
|
|
398
|
-
const
|
|
773
|
+
const effectiveLoop = resolveEffectiveLoopConfig(agent, input.loop, WRAPPER_DEFAULT_LOOP_CHAT);
|
|
774
|
+
const stopConditions = translateStopConditions(effectiveLoop, sanitizeToolNameForModel);
|
|
775
|
+
const wrapperPrepareStep = buildWrapperPrepareStep(agent, effectiveLoop, tools);
|
|
776
|
+
const sdkActiveTools = mapActiveToolsForModel(effectiveLoop.activeTools, tools);
|
|
777
|
+
const sdkToolChoice = mapToolChoiceForModel(effectiveLoop.toolChoice);
|
|
778
|
+
const turnId = randomUUID();
|
|
779
|
+
const loopTraceCollector = buildLoopTraceCollector(agent.id, effectiveSessionId, turnId, effectiveLoop.onStepFinish);
|
|
780
|
+
const abortController = new AbortController();
|
|
781
|
+
const budgetEnforcer = new BudgetEnforcer(effectiveLoop.budget, abortController);
|
|
782
|
+
const tracedOnStepFinish = budgetEnforcer.wire(loopTraceCollector.onStepFinish);
|
|
783
|
+
let currentStepIndex = 0;
|
|
784
|
+
const wiredOnStepFinish = async (event) => {
|
|
785
|
+
const capturedStepIndex = currentStepIndex;
|
|
786
|
+
currentStepIndex += 1;
|
|
787
|
+
if (tracedOnStepFinish) {
|
|
788
|
+
await tracedOnStepFinish(event);
|
|
789
|
+
}
|
|
790
|
+
if (input.container) {
|
|
791
|
+
const rawEvent = event;
|
|
792
|
+
void recordTokenUsage(
|
|
793
|
+
{
|
|
794
|
+
authContext: input.authContext,
|
|
795
|
+
agentId: agent.id,
|
|
796
|
+
moduleId: agent.moduleId,
|
|
797
|
+
sessionId: effectiveSessionId,
|
|
798
|
+
turnId,
|
|
799
|
+
stepIndex: capturedStepIndex,
|
|
800
|
+
providerId: resolvedModel.providerId,
|
|
801
|
+
modelId: resolvedModel.modelId,
|
|
802
|
+
usage: {
|
|
803
|
+
inputTokens: rawEvent.usage?.inputTokens,
|
|
804
|
+
outputTokens: rawEvent.usage?.outputTokens,
|
|
805
|
+
cachedInputTokens: rawEvent.usage?.cachedInputTokens,
|
|
806
|
+
reasoningTokens: rawEvent.usage?.reasoningTokens
|
|
807
|
+
},
|
|
808
|
+
finishReason: rawEvent.finishReason,
|
|
809
|
+
loopAbortReason: budgetEnforcer.abortReason ?? void 0
|
|
810
|
+
},
|
|
811
|
+
input.container
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
let wallClockTimer;
|
|
816
|
+
if (effectiveLoop.budget?.maxWallClockMs) {
|
|
817
|
+
wallClockTimer = setTimeout(() => {
|
|
818
|
+
budgetEnforcer.recordStep({ toolCalls: 0 });
|
|
819
|
+
}, effectiveLoop.budget.maxWallClockMs);
|
|
820
|
+
}
|
|
821
|
+
let builtToolLoopAgent;
|
|
822
|
+
if (agent.executionEngine === "tool-loop-agent") {
|
|
823
|
+
const agentSettings = {
|
|
824
|
+
model,
|
|
825
|
+
tools,
|
|
826
|
+
stopWhen: stopConditions,
|
|
827
|
+
prepareStep: wrapperPrepareStep,
|
|
828
|
+
onStepFinish: wiredOnStepFinish,
|
|
829
|
+
...effectiveLoop.repairToolCall !== void 0 ? { experimental_repairToolCall: effectiveLoop.repairToolCall } : {},
|
|
830
|
+
...sdkActiveTools !== void 0 ? { activeTools: sdkActiveTools } : {},
|
|
831
|
+
...sdkToolChoice !== void 0 ? { toolChoice: sdkToolChoice } : {}
|
|
832
|
+
};
|
|
833
|
+
builtToolLoopAgent = new ToolLoopAgent(agentSettings);
|
|
834
|
+
}
|
|
835
|
+
const preparedOptions = {
|
|
399
836
|
model,
|
|
837
|
+
tools,
|
|
400
838
|
system: systemPrompt,
|
|
401
839
|
messages: modelMessages,
|
|
402
|
-
|
|
403
|
-
stopWhen
|
|
840
|
+
maxSteps: effectiveLoop.maxSteps ?? 10,
|
|
841
|
+
stopWhen: stopConditions,
|
|
842
|
+
prepareStep: wrapperPrepareStep,
|
|
843
|
+
onStepFinish: wiredOnStepFinish,
|
|
844
|
+
onStepStart: effectiveLoop.onStepStart,
|
|
845
|
+
onToolCallStart: effectiveLoop.onToolCallStart,
|
|
846
|
+
onToolCallFinish: effectiveLoop.onToolCallFinish,
|
|
847
|
+
experimental_repairToolCall: effectiveLoop.repairToolCall,
|
|
848
|
+
activeTools: sdkActiveTools,
|
|
849
|
+
toolChoice: sdkToolChoice,
|
|
850
|
+
abortSignal: abortController.signal,
|
|
851
|
+
finalizeLoopTrace: () => loopTraceCollector.finalize(budgetEnforcer.abortReason),
|
|
852
|
+
...builtToolLoopAgent !== void 0 ? { toolLoopAgent: builtToolLoopAgent } : {}
|
|
404
853
|
};
|
|
405
|
-
|
|
406
|
-
|
|
854
|
+
if (input.generateText) {
|
|
855
|
+
try {
|
|
856
|
+
const callbackResult = await input.generateText(preparedOptions);
|
|
857
|
+
const baseResponse2 = callbackResult.toUIMessageStreamResponse({
|
|
858
|
+
sendReasoning: true,
|
|
859
|
+
headers: {
|
|
860
|
+
"Cache-Control": "no-cache, no-transform",
|
|
861
|
+
Connection: "keep-alive"
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
if (input.emitLoopTrace) {
|
|
865
|
+
return appendLoopFinishToStream(baseResponse2, preparedOptions.finalizeLoopTrace);
|
|
866
|
+
}
|
|
867
|
+
return baseResponse2;
|
|
868
|
+
} finally {
|
|
869
|
+
if (wallClockTimer !== void 0) clearTimeout(wallClockTimer);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
if (builtToolLoopAgent !== void 0) {
|
|
873
|
+
const agentStreamResult = await builtToolLoopAgent.stream({
|
|
874
|
+
messages: modelMessages,
|
|
875
|
+
abortSignal: abortController.signal,
|
|
876
|
+
onStepFinish: wiredOnStepFinish
|
|
877
|
+
});
|
|
878
|
+
if (wallClockTimer !== void 0) {
|
|
879
|
+
const clearTimer = () => clearTimeout(wallClockTimer);
|
|
880
|
+
Promise.resolve(agentStreamResult.consumeStream()).then(clearTimer, clearTimer);
|
|
881
|
+
}
|
|
882
|
+
const baseResponse2 = agentStreamResult.toUIMessageStreamResponse({
|
|
883
|
+
sendReasoning: true,
|
|
884
|
+
headers: {
|
|
885
|
+
"Cache-Control": "no-cache, no-transform",
|
|
886
|
+
Connection: "keep-alive"
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
if (input.emitLoopTrace) {
|
|
890
|
+
return appendLoopFinishToStream(baseResponse2, preparedOptions.finalizeLoopTrace);
|
|
891
|
+
}
|
|
892
|
+
return baseResponse2;
|
|
893
|
+
}
|
|
894
|
+
const result = streamText({
|
|
895
|
+
model,
|
|
896
|
+
system: systemPrompt,
|
|
897
|
+
messages: modelMessages,
|
|
898
|
+
tools,
|
|
899
|
+
stopWhen: stopConditions,
|
|
900
|
+
prepareStep: wrapperPrepareStep,
|
|
901
|
+
onStepFinish: wiredOnStepFinish,
|
|
902
|
+
experimental_onStepStart: effectiveLoop.onStepStart,
|
|
903
|
+
experimental_onToolCallStart: effectiveLoop.onToolCallStart,
|
|
904
|
+
experimental_onToolCallFinish: effectiveLoop.onToolCallFinish,
|
|
905
|
+
experimental_repairToolCall: effectiveLoop.repairToolCall,
|
|
906
|
+
...sdkActiveTools !== void 0 ? { activeTools: sdkActiveTools } : {},
|
|
907
|
+
...sdkToolChoice !== void 0 ? { toolChoice: sdkToolChoice } : {},
|
|
908
|
+
abortSignal: abortController.signal
|
|
909
|
+
});
|
|
910
|
+
if (wallClockTimer !== void 0) {
|
|
911
|
+
const clearTimer = () => clearTimeout(wallClockTimer);
|
|
912
|
+
Promise.resolve(result.consumeStream()).then(clearTimer, clearTimer);
|
|
913
|
+
}
|
|
914
|
+
const baseResponse = result.toUIMessageStreamResponse({
|
|
407
915
|
sendReasoning: true,
|
|
408
916
|
headers: {
|
|
409
917
|
"Cache-Control": "no-cache, no-transform",
|
|
410
918
|
Connection: "keep-alive"
|
|
411
919
|
}
|
|
412
920
|
});
|
|
921
|
+
if (input.emitLoopTrace) {
|
|
922
|
+
return appendLoopFinishToStream(baseResponse, preparedOptions.finalizeLoopTrace);
|
|
923
|
+
}
|
|
924
|
+
return baseResponse;
|
|
413
925
|
}
|
|
414
926
|
function normalizeObjectMessages(input) {
|
|
415
927
|
if (typeof input === "string") {
|
|
@@ -508,14 +1020,60 @@ async function runAiAgentObject(input) {
|
|
|
508
1020
|
resolvedAttachments
|
|
509
1021
|
);
|
|
510
1022
|
const modelMessages = await convertToModelMessages(hydratedMessages);
|
|
511
|
-
|
|
1023
|
+
void tools;
|
|
1024
|
+
const effectiveObjectSessionId = input.sessionId ?? randomUUID();
|
|
1025
|
+
const objectTurnId = randomUUID();
|
|
1026
|
+
void effectiveObjectSessionId;
|
|
1027
|
+
void objectTurnId;
|
|
1028
|
+
if (input.loop) {
|
|
1029
|
+
assertLoopObjectModeCompatible(input.loop);
|
|
1030
|
+
}
|
|
1031
|
+
const effectiveLoop = resolveEffectiveLoopConfig(agent, input.loop, WRAPPER_DEFAULT_LOOP_OBJECT);
|
|
1032
|
+
const abortController = new AbortController();
|
|
1033
|
+
const preparedObjectOptions = {
|
|
1034
|
+
model,
|
|
1035
|
+
system: systemPrompt,
|
|
1036
|
+
messages: modelMessages,
|
|
1037
|
+
schemaName: resolvedOutput.schemaName,
|
|
1038
|
+
schema: resolvedOutput.schema,
|
|
1039
|
+
maxSteps: effectiveLoop.maxSteps,
|
|
1040
|
+
onStepFinish: effectiveLoop.onStepFinish,
|
|
1041
|
+
onStepStart: effectiveLoop.onStepStart,
|
|
1042
|
+
abortSignal: abortController.signal
|
|
1043
|
+
};
|
|
1044
|
+
if (input.generateObject) {
|
|
1045
|
+
const callbackResult = await input.generateObject(preparedObjectOptions);
|
|
1046
|
+
const typedResult = callbackResult;
|
|
1047
|
+
if ("partialObjectStream" in typedResult) {
|
|
1048
|
+
const streamResult = typedResult;
|
|
1049
|
+
return {
|
|
1050
|
+
mode: "stream",
|
|
1051
|
+
object: streamResult.object,
|
|
1052
|
+
partialObjectStream: streamResult.partialObjectStream,
|
|
1053
|
+
textStream: streamResult.textStream,
|
|
1054
|
+
finishReason: streamResult.finishReason,
|
|
1055
|
+
usage: streamResult.usage
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
const genResult = typedResult;
|
|
1059
|
+
return {
|
|
1060
|
+
mode: "generate",
|
|
1061
|
+
object: genResult.object,
|
|
1062
|
+
finishReason: genResult.finishReason,
|
|
1063
|
+
usage: genResult.usage
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
512
1066
|
if (resolvedOutput.mode === "stream") {
|
|
513
1067
|
const streamArgs = {
|
|
514
1068
|
model,
|
|
515
1069
|
system: systemPrompt,
|
|
516
1070
|
messages: modelMessages,
|
|
517
1071
|
schema: resolvedOutput.schema,
|
|
518
|
-
schemaName: resolvedOutput.schemaName
|
|
1072
|
+
schemaName: resolvedOutput.schemaName,
|
|
1073
|
+
...effectiveLoop.maxSteps !== void 0 ? { maxSteps: effectiveLoop.maxSteps } : {},
|
|
1074
|
+
onStepFinish: effectiveLoop.onStepFinish,
|
|
1075
|
+
onStepStart: effectiveLoop.onStepStart,
|
|
1076
|
+
abortSignal: abortController.signal
|
|
519
1077
|
};
|
|
520
1078
|
const result2 = streamObject(streamArgs);
|
|
521
1079
|
return {
|
|
@@ -532,13 +1090,12 @@ async function runAiAgentObject(input) {
|
|
|
532
1090
|
system: systemPrompt,
|
|
533
1091
|
messages: modelMessages,
|
|
534
1092
|
schema: resolvedOutput.schema,
|
|
535
|
-
schemaName: resolvedOutput.schemaName
|
|
1093
|
+
schemaName: resolvedOutput.schemaName,
|
|
1094
|
+
...effectiveLoop.maxSteps !== void 0 ? { maxSteps: effectiveLoop.maxSteps } : {},
|
|
1095
|
+
onStepFinish: effectiveLoop.onStepFinish,
|
|
1096
|
+
onStepStart: effectiveLoop.onStepStart,
|
|
1097
|
+
abortSignal: abortController.signal
|
|
536
1098
|
};
|
|
537
|
-
if (stopWhen) {
|
|
538
|
-
;
|
|
539
|
-
generateArgs.stopWhen = stopWhen;
|
|
540
|
-
}
|
|
541
|
-
void tools;
|
|
542
1099
|
const result = await generateObject(generateArgs);
|
|
543
1100
|
return {
|
|
544
1101
|
mode: "generate",
|
|
@@ -549,8 +1106,16 @@ async function runAiAgentObject(input) {
|
|
|
549
1106
|
}
|
|
550
1107
|
export {
|
|
551
1108
|
AgentPolicyError,
|
|
1109
|
+
BudgetEnforcer,
|
|
1110
|
+
assertLoopObjectModeCompatible,
|
|
1111
|
+
buildLoopTraceCollector,
|
|
1112
|
+
buildWrapperPrepareStep,
|
|
552
1113
|
composeSystemPrompt,
|
|
1114
|
+
mergeStepOverrides,
|
|
1115
|
+
resolveEffectiveLoopConfig,
|
|
1116
|
+
resolveLoopBudgetPreset,
|
|
553
1117
|
runAiAgentObject,
|
|
554
|
-
runAiAgentText
|
|
1118
|
+
runAiAgentText,
|
|
1119
|
+
translateStopConditions
|
|
555
1120
|
};
|
|
556
1121
|
//# sourceMappingURL=agent-runtime.js.map
|