@flink-app/flink 0.14.3 → 2.0.0-alpha.100
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/CHANGELOG.md +1051 -0
- package/SCHEMA_EXTRACTION_ANALYSIS.md +494 -0
- package/SIMPLE_AST_FEASIBILITY.md +570 -0
- package/bin/flink.ts +13 -2
- package/cli/build.ts +24 -44
- package/cli/clean.ts +13 -25
- package/cli/cli-utils.ts +190 -17
- package/cli/dev.ts +252 -0
- package/cli/loadEnvFiles.ts +116 -0
- package/cli/run.ts +45 -62
- package/dist/bin/flink.js +61 -2
- package/dist/cli/build.js +20 -25
- package/dist/cli/clean.js +12 -10
- package/dist/cli/cli-utils.d.ts +34 -3
- package/dist/cli/cli-utils.js +193 -12
- package/dist/cli/dev.d.ts +2 -0
- package/dist/cli/dev.js +279 -0
- package/dist/cli/loadEnvFiles.d.ts +30 -0
- package/dist/cli/loadEnvFiles.js +113 -0
- package/dist/cli/run.js +47 -46
- package/dist/src/DependencyTracker.d.ts +44 -0
- package/dist/src/DependencyTracker.js +239 -0
- package/dist/src/FlinkApp.d.ts +163 -10
- package/dist/src/FlinkApp.js +847 -184
- package/dist/src/FlinkContext.d.ts +41 -0
- package/dist/src/FlinkErrors.d.ts +19 -6
- package/dist/src/FlinkErrors.js +36 -42
- package/dist/src/FlinkHttpHandler.d.ts +219 -26
- package/dist/src/FlinkHttpHandler.js +37 -1
- package/dist/src/FlinkJob.d.ts +10 -0
- package/dist/src/FlinkLog.d.ts +82 -18
- package/dist/src/FlinkLog.js +165 -13
- package/dist/src/FlinkLogFactory.d.ts +288 -0
- package/dist/src/FlinkLogFactory.js +619 -0
- package/dist/src/FlinkRepo.d.ts +10 -2
- package/dist/src/FlinkRepo.js +11 -1
- package/dist/src/FlinkRequestContext.d.ts +63 -0
- package/dist/src/FlinkRequestContext.js +74 -0
- package/dist/src/FlinkResponse.d.ts +6 -0
- package/dist/src/FlinkService.d.ts +38 -0
- package/dist/src/FlinkService.js +46 -0
- package/dist/src/LeaderElection.d.ts +45 -0
- package/dist/src/LeaderElection.js +269 -0
- package/dist/src/SchemaCache.d.ts +84 -0
- package/dist/src/SchemaCache.js +289 -0
- package/dist/src/TypeScriptCompiler.d.ts +161 -51
- package/dist/src/TypeScriptCompiler.js +1253 -617
- package/dist/src/TypeScriptUtils.js +4 -0
- package/dist/src/ai/AgentRunner.d.ts +39 -0
- package/dist/src/ai/AgentRunner.js +760 -0
- package/dist/src/ai/ConversationAgent.d.ts +279 -0
- package/dist/src/ai/ConversationAgent.js +404 -0
- package/dist/src/ai/ConversationFlinkAgent.d.ts +278 -0
- package/dist/src/ai/ConversationFlinkAgent.js +404 -0
- package/dist/src/ai/FlinkAgent.d.ts +690 -0
- package/dist/src/ai/FlinkAgent.js +729 -0
- package/dist/src/ai/FlinkTool.d.ts +135 -0
- package/dist/src/ai/FlinkTool.js +2 -0
- package/dist/src/ai/InMemoryConversationAgent.d.ts +121 -0
- package/dist/src/ai/InMemoryConversationAgent.js +209 -0
- package/dist/src/ai/LLMAdapter.d.ts +148 -0
- package/dist/src/ai/LLMAdapter.js +2 -0
- package/dist/src/ai/PersistentFlinkAgent.d.ts +278 -0
- package/dist/src/ai/PersistentFlinkAgent.js +403 -0
- package/dist/src/ai/SubAgentExecutor.d.ts +38 -0
- package/dist/src/ai/SubAgentExecutor.js +223 -0
- package/dist/src/ai/ToolExecutor.d.ts +64 -0
- package/dist/src/ai/ToolExecutor.js +497 -0
- package/dist/src/ai/agentInstructions.d.ts +68 -0
- package/dist/src/ai/agentInstructions.js +286 -0
- package/dist/src/ai/index.d.ts +8 -0
- package/dist/src/ai/index.js +26 -0
- package/dist/src/ai/instructionFileLoader.d.ts +44 -0
- package/dist/src/ai/instructionFileLoader.js +179 -0
- package/dist/src/auth/FlinkAuthPlugin.d.ts +1 -1
- package/dist/src/handlers/StreamWriterFactory.d.ts +20 -0
- package/dist/src/handlers/StreamWriterFactory.js +83 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.js +17 -0
- package/dist/src/loadPluginSchemas.d.ts +45 -0
- package/dist/src/loadPluginSchemas.js +143 -0
- package/dist/src/schema-extraction/ComplexTypeDetection.d.ts +40 -0
- package/dist/src/schema-extraction/ComplexTypeDetection.js +75 -0
- package/dist/src/schema-extraction/TypeScriptSourceParser.d.ts +321 -0
- package/dist/src/schema-extraction/TypeScriptSourceParser.js +925 -0
- package/dist/src/schema-extraction/TypeScriptSourceParser.spec.d.ts +1 -0
- package/dist/src/schema-extraction/TypeScriptSourceParser.spec.js +233 -0
- package/dist/src/schema-extraction/TypeScriptTokenizer.d.ts +57 -0
- package/dist/src/schema-extraction/TypeScriptTokenizer.js +177 -0
- package/dist/src/schema-extraction/index.d.ts +2 -0
- package/dist/src/schema-extraction/index.js +20 -0
- package/dist/src/schema-extraction/types.d.ts +31 -0
- package/dist/src/schema-extraction/types.js +2 -0
- package/dist/src/utils/loadFlinkConfig.d.ts +53 -0
- package/dist/src/utils/loadFlinkConfig.js +77 -0
- package/dist/src/utils.d.ts +30 -0
- package/dist/src/utils.js +52 -0
- package/dist/src/workers/SchemaGeneratorWorker.d.ts +1 -0
- package/dist/src/workers/SchemaGeneratorWorker.js +49 -0
- package/dist/src/workers/WorkerPool.d.ts +60 -0
- package/dist/src/workers/WorkerPool.js +306 -0
- package/examples/logging-hierarchical-example.ts +125 -0
- package/package.json +29 -4
- package/readme.md +499 -0
- package/spec/AgentDescendantDetection.spec.ts +335 -0
- package/spec/AgentDuplicateDetection.spec.ts +112 -0
- package/spec/AgentObserver.spec.ts +266 -0
- package/spec/AgentRunner.spec.ts +1062 -0
- package/spec/AsyncLocalStorageContext.spec.ts +223 -0
- package/spec/ConversationHooks.spec.ts +257 -0
- package/spec/FlinkAgent.spec.ts +681 -0
- package/spec/FlinkApp.htmlResponse.spec.ts +260 -0
- package/spec/FlinkApp.onError.invocation.spec.ts +151 -0
- package/spec/FlinkApp.onError.spec.ts +1 -2
- package/spec/FlinkApp.query.spec.ts +107 -0
- package/spec/FlinkApp.routeOrdering.spec.ts +61 -0
- package/spec/FlinkApp.undefinedResponse.spec.ts +123 -0
- package/spec/FlinkApp.validationMode.spec.ts +155 -0
- package/spec/FlinkJob.spec.ts +171 -0
- package/spec/FlinkLogFactory.spec.ts +337 -0
- package/spec/FlinkRepo.spec.ts +1 -1
- package/spec/LeaderElection.spec.ts +174 -0
- package/spec/StreamingIntegration.spec.ts +139 -0
- package/spec/ToolExecutor.spec.ts +465 -0
- package/spec/TypeScriptCompiler.spec.ts +1 -1
- package/spec/TypeScriptSourceParser.spec.ts +1215 -0
- package/spec/TypeScriptTokenizer.spec.ts +366 -0
- package/spec/ai/ContextCompaction.spec.ts +405 -0
- package/spec/ai/ConversationAgent.spec.ts +520 -0
- package/spec/ai/InMemoryConversationAgent.spec.ts +144 -0
- package/spec/ai/agentInstructions.spec.ts +358 -0
- package/spec/fixtures/agent-instructions/TestAgent.ts +24 -0
- package/spec/fixtures/agent-instructions/simple.md +3 -0
- package/spec/fixtures/agent-instructions/template.md +18 -0
- package/spec/fixtures/agent-instructions/yaml-format.yaml +9 -0
- package/spec/mock-project/dist/.tsbuildinfo +1 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCar.js +56 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCar2.js +58 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema.js +52 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema2.js +52 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema3.js +52 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithLiteralSchema.js +54 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithLiteralSchema2.js +54 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithSchemaInFile.js +57 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithSchemaInFile2.js +57 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/ManuallyAddedHandler.js +53 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/ManuallyAddedHandler2.js +55 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchCar.js +57 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchOnboardingSession.js +75 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchOrderWithComplexTypes.js +57 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchProductWithIntersection.js +58 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchUserWithUnion.js +58 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PostCar.js +54 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PostLogin.js +55 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PostLogout.js +54 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PutCar.js +54 -0
- package/spec/mock-project/dist/spec/mock-project/src/index.js +83 -0
- package/spec/mock-project/dist/spec/mock-project/src/repos/CarRepo.js +26 -0
- package/spec/mock-project/dist/spec/mock-project/src/schemas/Car.js +2 -0
- package/spec/mock-project/dist/spec/mock-project/src/schemas/DefaultExportSchema.js +2 -0
- package/spec/mock-project/dist/spec/mock-project/src/schemas/FileWithTwoSchemas.js +2 -0
- package/spec/mock-project/dist/src/FlinkApp.js +1000 -0
- package/spec/mock-project/dist/src/FlinkContext.js +2 -0
- package/spec/mock-project/dist/src/FlinkErrors.js +143 -0
- package/spec/mock-project/dist/src/FlinkHttpHandler.js +47 -0
- package/spec/mock-project/dist/src/FlinkJob.js +2 -0
- package/spec/mock-project/dist/src/FlinkLog.js +119 -0
- package/spec/mock-project/dist/src/FlinkLogFactory.js +617 -0
- package/spec/mock-project/dist/src/FlinkPlugin.js +2 -0
- package/spec/mock-project/dist/src/FlinkRepo.js +224 -0
- package/spec/mock-project/dist/src/FlinkRequestContext.js +74 -0
- package/spec/mock-project/dist/src/FlinkResponse.js +2 -0
- package/spec/mock-project/dist/src/ai/AgentExecutor.js +279 -0
- package/spec/mock-project/dist/src/ai/AgentRunner.js +632 -0
- package/spec/mock-project/dist/src/ai/ConversationAgent.js +402 -0
- package/spec/mock-project/dist/src/ai/ConversationFlinkAgent.js +422 -0
- package/spec/mock-project/dist/src/ai/FlinkAgent.js +699 -0
- package/spec/mock-project/dist/src/ai/FlinkTool.js +2 -0
- package/spec/mock-project/dist/src/ai/InMemoryConversationAgent.js +209 -0
- package/spec/mock-project/dist/src/ai/LLMAdapter.js +2 -0
- package/spec/mock-project/dist/src/ai/SubAgentExecutor.js +223 -0
- package/spec/mock-project/dist/src/ai/ToolExecutor.js +412 -0
- package/spec/mock-project/dist/src/ai/agentInstructions.js +246 -0
- package/spec/mock-project/dist/src/auth/FlinkAuthPlugin.js +2 -0
- package/spec/mock-project/dist/src/auth/FlinkAuthUser.js +2 -0
- package/spec/mock-project/dist/src/handlers/GetCar.js +26 -52
- package/spec/mock-project/dist/src/handlers/GetCar.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/GetCar2.js +32 -54
- package/spec/mock-project/dist/src/handlers/GetCar2.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema.js +26 -48
- package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema2.js +28 -48
- package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema2.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema3.js +29 -48
- package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema3.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema.js +26 -50
- package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema2.js +28 -50
- package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema2.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile.js +27 -53
- package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile2.js +29 -53
- package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile2.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler.js +16 -49
- package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler2.js +25 -50
- package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler2.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/PatchCar.js +27 -53
- package/spec/mock-project/dist/src/handlers/PatchCar.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/PatchOnboardingSession.js +44 -70
- package/spec/mock-project/dist/src/handlers/PatchOnboardingSession.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/PatchOrderWithComplexTypes.js +27 -53
- package/spec/mock-project/dist/src/handlers/PatchOrderWithComplexTypes.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/PatchProductWithIntersection.js +28 -54
- package/spec/mock-project/dist/src/handlers/PatchProductWithIntersection.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/PatchUserWithUnion.js +28 -54
- package/spec/mock-project/dist/src/handlers/PatchUserWithUnion.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/PostCar.js +24 -50
- package/spec/mock-project/dist/src/handlers/PostCar.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/PostLogin.js +25 -51
- package/spec/mock-project/dist/src/handlers/PostLogin.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/PostLogout.js +24 -50
- package/spec/mock-project/dist/src/handlers/PostLogout.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/PutCar.js +24 -50
- package/spec/mock-project/dist/src/handlers/PutCar.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/StreamWriterFactory.js +83 -0
- package/spec/mock-project/dist/src/index.js +52 -76
- package/spec/mock-project/dist/src/index.js.map +1 -0
- package/spec/mock-project/dist/src/mock-data-generator.js +9 -0
- package/spec/mock-project/dist/src/repos/CarRepo.js +12 -24
- package/spec/mock-project/dist/src/repos/CarRepo.js.map +1 -0
- package/spec/mock-project/dist/src/schemas/Car.js +3 -1
- package/spec/mock-project/dist/src/schemas/Car.js.map +1 -0
- package/spec/mock-project/dist/src/schemas/DefaultExportSchema.js +3 -1
- package/spec/mock-project/dist/src/schemas/DefaultExportSchema.js.map +1 -0
- package/spec/mock-project/dist/src/schemas/FileWithTwoSchemas.js +3 -1
- package/spec/mock-project/dist/src/schemas/FileWithTwoSchemas.js.map +1 -0
- package/spec/mock-project/dist/src/utils.js +290 -0
- package/spec/mock-project/tsconfig.json +6 -1
- package/spec/schema-generation-nested-objects.spec.ts +97 -0
- package/spec/testHelpers.ts +49 -0
- package/spec/utils.caseConversion.spec.ts +78 -0
- package/spec/utils.spec.ts +13 -13
- package/src/DependencyTracker.ts +166 -0
- package/src/FlinkApp.ts +919 -155
- package/src/FlinkContext.ts +43 -0
- package/src/FlinkErrors.ts +32 -12
- package/src/FlinkHttpHandler.ts +246 -28
- package/src/FlinkJob.ts +11 -0
- package/src/FlinkLog.ts +119 -12
- package/src/FlinkLogFactory.ts +699 -0
- package/src/FlinkRepo.ts +10 -3
- package/src/FlinkRequestContext.ts +95 -0
- package/src/FlinkResponse.ts +6 -0
- package/src/FlinkService.ts +49 -0
- package/src/LeaderElection.ts +203 -0
- package/src/SchemaCache.ts +232 -0
- package/src/TypeScriptCompiler.ts +1347 -610
- package/src/TypeScriptUtils.ts +5 -0
- package/src/ai/AgentRunner.ts +646 -0
- package/src/ai/ConversationAgent.ts +413 -0
- package/src/ai/FlinkAgent.ts +1069 -0
- package/src/ai/FlinkTool.ts +165 -0
- package/src/ai/InMemoryConversationAgent.ts +149 -0
- package/src/ai/LLMAdapter.ts +126 -0
- package/src/ai/ToolExecutor.ts +485 -0
- package/src/ai/agentInstructions.ts +245 -0
- package/src/ai/index.ts +8 -0
- package/src/ai/instructionFileLoader.ts +156 -0
- package/src/auth/FlinkAuthPlugin.ts +2 -1
- package/src/handlers/StreamWriterFactory.ts +84 -0
- package/src/index.ts +14 -0
- package/src/loadPluginSchemas.ts +141 -0
- package/src/schema-extraction/TypeScriptSourceParser.ts +1058 -0
- package/src/schema-extraction/TypeScriptTokenizer.ts +205 -0
- package/src/schema-extraction/index.ts +2 -0
- package/src/schema-extraction/types.ts +34 -0
- package/src/utils/loadFlinkConfig.ts +89 -0
- package/src/utils.ts +52 -0
- package/tsconfig.json +6 -1
package/src/TypeScriptUtils.ts
CHANGED
|
@@ -214,6 +214,11 @@ export function getTypeMetadata(type: Type<ts.Type>) {
|
|
|
214
214
|
return [];
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
// Handle empty object literal {} (used in streaming handlers)
|
|
218
|
+
if (type.getText() === "{}") {
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
|
|
217
222
|
const symbol = getSymbolOrAlias(type);
|
|
218
223
|
|
|
219
224
|
if (!symbol) {
|
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from "uuid";
|
|
2
|
+
import {
|
|
3
|
+
FlinkAgentProps,
|
|
4
|
+
AgentExecuteResult,
|
|
5
|
+
AgentExecuteInput,
|
|
6
|
+
StreamChunk,
|
|
7
|
+
Message,
|
|
8
|
+
AgentExecuteContext,
|
|
9
|
+
AgentStepContext,
|
|
10
|
+
AgentFinishContext,
|
|
11
|
+
AgentObserver,
|
|
12
|
+
} from "./FlinkAgent";
|
|
13
|
+
import { ToolExecutor } from "./ToolExecutor";
|
|
14
|
+
import { LLMAdapter, LLMMessage, LLMContentBlock, FlinkToolSchema } from "./LLMAdapter";
|
|
15
|
+
import { FlinkLogFactory } from "../FlinkLogFactory";
|
|
16
|
+
import { log } from "../FlinkLog";
|
|
17
|
+
|
|
18
|
+
const observerLog = FlinkLogFactory.createLogger("flink.ai.observer");
|
|
19
|
+
|
|
20
|
+
export class AgentRunner {
|
|
21
|
+
private llmAdapter: LLMAdapter;
|
|
22
|
+
private maxTokens: number;
|
|
23
|
+
private temperature: number;
|
|
24
|
+
private maxSteps: number;
|
|
25
|
+
private timeoutMs: number;
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
private agentProps: FlinkAgentProps<any>,
|
|
29
|
+
private tools: Map<string, ToolExecutor<any>>,
|
|
30
|
+
llmAdapters: Map<string, LLMAdapter>,
|
|
31
|
+
private agentName?: string, // Optional agent name for logging
|
|
32
|
+
private ctx?: any, // FlinkContext for instruction callbacks (any for flexibility)
|
|
33
|
+
private observer?: AgentObserver
|
|
34
|
+
) {
|
|
35
|
+
// Get appropriate LLM adapter based on adapterId
|
|
36
|
+
const adapterId = agentProps.model?.adapterId || "default";
|
|
37
|
+
|
|
38
|
+
const adapter = llmAdapters.get(adapterId);
|
|
39
|
+
if (!adapter) {
|
|
40
|
+
throw new Error(`LLM adapter "${adapterId}" not configured - register it in FlinkOptions.ai.llms`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this.llmAdapter = adapter;
|
|
44
|
+
this.maxTokens = agentProps.model?.maxTokens || 4096;
|
|
45
|
+
this.temperature = agentProps.model?.temperature || 0.7;
|
|
46
|
+
this.maxSteps = agentProps.limits?.maxSteps || 10;
|
|
47
|
+
this.timeoutMs = agentProps.limits?.timeoutMs || 60000;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async *streamGenerator(input: AgentExecuteInput): AsyncGenerator<StreamChunk> {
|
|
51
|
+
const maxSteps = input.options?.maxSteps || this.maxSteps;
|
|
52
|
+
const toolCalls: AgentExecuteResult["toolCalls"] = [];
|
|
53
|
+
const runId = uuidv4();
|
|
54
|
+
const runStartedAt = Date.now();
|
|
55
|
+
const agentId = this.agentName || "unknown";
|
|
56
|
+
const modelInfo = {
|
|
57
|
+
adapterId: this.agentProps.model?.adapterId,
|
|
58
|
+
maxTokens: this.maxTokens,
|
|
59
|
+
temperature: this.temperature,
|
|
60
|
+
};
|
|
61
|
+
const declaredToolNames = Array.from(this.tools.keys());
|
|
62
|
+
|
|
63
|
+
// Build execution context
|
|
64
|
+
const execContext: AgentExecuteContext = {
|
|
65
|
+
agentId,
|
|
66
|
+
conversationId: input.conversationId,
|
|
67
|
+
user: input.user,
|
|
68
|
+
metadata: input.metadata,
|
|
69
|
+
conversationContext: input.conversationContext,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Resolve instructions (callback or string)
|
|
73
|
+
let resolvedInstructions: string;
|
|
74
|
+
try {
|
|
75
|
+
resolvedInstructions =
|
|
76
|
+
typeof this.agentProps.instructions === "function" ? await this.agentProps.instructions(this.ctx, execContext) : this.agentProps.instructions;
|
|
77
|
+
} catch (err: any) {
|
|
78
|
+
throw new Error(`Failed to resolve instructions for agent ${this.agentName}: ${err.message}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Initialize messages from history + new input
|
|
82
|
+
let messages: LLMMessage[];
|
|
83
|
+
|
|
84
|
+
if (input.history && input.history.length > 0) {
|
|
85
|
+
// Start with history
|
|
86
|
+
messages = this.convertMessages(input.history);
|
|
87
|
+
} else {
|
|
88
|
+
messages = [];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Add new user message
|
|
92
|
+
if (typeof input.message === "string") {
|
|
93
|
+
messages.push({ role: "user", content: input.message });
|
|
94
|
+
} else if (Array.isArray(input.message) && input.message.length > 0 && "type" in input.message[0]) {
|
|
95
|
+
// LLMContentBlock[] — multimodal content (e.g. text + images)
|
|
96
|
+
messages.push({ role: "user", content: input.message as LLMContentBlock[] });
|
|
97
|
+
} else {
|
|
98
|
+
messages.push(...this.convertMessages(input.message as Message[]));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Dispatch observer onRun (pre-loop, before compaction / tool filtering)
|
|
102
|
+
this.safeDispatch("onRun", () =>
|
|
103
|
+
this.observer?.onRun?.({
|
|
104
|
+
runId,
|
|
105
|
+
agentId,
|
|
106
|
+
instructions: resolvedInstructions,
|
|
107
|
+
input,
|
|
108
|
+
messages: [...messages],
|
|
109
|
+
tools: declaredToolNames,
|
|
110
|
+
model: modelInfo,
|
|
111
|
+
context: execContext,
|
|
112
|
+
})
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
let step = 0;
|
|
116
|
+
let finalMessage = "";
|
|
117
|
+
let stoppedEarly = false;
|
|
118
|
+
let totalInputTokens = 0;
|
|
119
|
+
let totalOutputTokens = 0;
|
|
120
|
+
let totalCachedInputTokens = 0;
|
|
121
|
+
let totalCacheCreationInputTokens = 0;
|
|
122
|
+
let finalProviderMetadata: Record<string, any> = {};
|
|
123
|
+
|
|
124
|
+
const buildResult = (): AgentExecuteResult => ({
|
|
125
|
+
runId,
|
|
126
|
+
message: finalMessage,
|
|
127
|
+
toolCalls,
|
|
128
|
+
stepsUsed: step,
|
|
129
|
+
stoppedEarly,
|
|
130
|
+
usage: {
|
|
131
|
+
inputTokens: totalInputTokens,
|
|
132
|
+
outputTokens: totalOutputTokens,
|
|
133
|
+
...(totalCachedInputTokens > 0 && { cachedInputTokens: totalCachedInputTokens }),
|
|
134
|
+
...(totalCacheCreationInputTokens > 0 && { cacheCreationInputTokens: totalCacheCreationInputTokens }),
|
|
135
|
+
},
|
|
136
|
+
providerMetadata: Object.keys(finalProviderMetadata).length > 0 ? finalProviderMetadata : undefined,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
while (step < maxSteps) {
|
|
141
|
+
step++;
|
|
142
|
+
|
|
143
|
+
// Filter tools based on user permissions (only show allowed tools to LLM)
|
|
144
|
+
const availableTools = await this.filterToolsByPermissions(input.user, input.userPermissions, input.conversationContext);
|
|
145
|
+
|
|
146
|
+
// Check if context compaction is needed
|
|
147
|
+
if (this.agentProps.shouldCompact) {
|
|
148
|
+
try {
|
|
149
|
+
const needsCompaction = await this.agentProps.shouldCompact(messages, step);
|
|
150
|
+
|
|
151
|
+
if (needsCompaction) {
|
|
152
|
+
const beforeCount = messages.length;
|
|
153
|
+
const originalMessages = messages; // Save original in case of error
|
|
154
|
+
|
|
155
|
+
// Perform compaction using provided callback or default strategy
|
|
156
|
+
let compactedMessages: LLMMessage[];
|
|
157
|
+
if (this.agentProps.compactHistory) {
|
|
158
|
+
compactedMessages = await this.agentProps.compactHistory(messages, step);
|
|
159
|
+
} else {
|
|
160
|
+
// Default strategy: sliding window keeping last 10 messages
|
|
161
|
+
compactedMessages = messages.slice(-10);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Validation: ensure compacted array is not empty
|
|
165
|
+
if (!compactedMessages || compactedMessages.length === 0) {
|
|
166
|
+
throw new Error("compactHistory must return at least one message");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Apply compaction
|
|
170
|
+
messages = compactedMessages;
|
|
171
|
+
|
|
172
|
+
// Log compaction for debugging
|
|
173
|
+
log.debug(`[Agent:${this.agentName}] Step ${step}: Compacted ${beforeCount} messages → ${messages.length}`);
|
|
174
|
+
|
|
175
|
+
log.debug(`[Agent:${this.agentName}] Compacted messages:`, {
|
|
176
|
+
messageCount: messages.length,
|
|
177
|
+
messages: messages.map((m) => ({
|
|
178
|
+
role: m.role,
|
|
179
|
+
contentPreview:
|
|
180
|
+
typeof m.content === "string"
|
|
181
|
+
? m.content.substring(0, 100) + (m.content.length > 100 ? "..." : "")
|
|
182
|
+
: `${(m.content as any[]).length} blocks`,
|
|
183
|
+
})),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
} catch (error: any) {
|
|
187
|
+
// Log error but don't fail execution - compaction is optional optimization
|
|
188
|
+
log.error(`[Agent:${this.agentName}] Context compaction failed:`, error.message);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
log.debug(`[Agent:${this.agentName}] Step ${step}/${maxSteps} - Calling LLM with:`, {
|
|
193
|
+
instructionsType: typeof this.agentProps.instructions === "function" ? "dynamic-callback" : "static",
|
|
194
|
+
messageCount: messages.length,
|
|
195
|
+
messages: messages.map((m) => ({
|
|
196
|
+
role: m.role,
|
|
197
|
+
contentPreview:
|
|
198
|
+
typeof m.content === "string"
|
|
199
|
+
? m.content.substring(0, 100) + (m.content.length > 100 ? "..." : "")
|
|
200
|
+
: `${(m.content as any[]).length} blocks`,
|
|
201
|
+
})),
|
|
202
|
+
toolCount: availableTools.length,
|
|
203
|
+
tools: availableTools.map((t) => t.name),
|
|
204
|
+
maxTokens: this.maxTokens,
|
|
205
|
+
temperature: this.temperature,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Dispatch observer onLlmCall — messages reflect post-compaction state;
|
|
209
|
+
// tools reflect per-step permission filtering
|
|
210
|
+
this.safeDispatch("onLlmCall", () =>
|
|
211
|
+
this.observer?.onLlmCall?.({
|
|
212
|
+
runId,
|
|
213
|
+
agentId,
|
|
214
|
+
step,
|
|
215
|
+
maxSteps,
|
|
216
|
+
instructions: resolvedInstructions,
|
|
217
|
+
messages: [...messages],
|
|
218
|
+
tools: availableTools.map((t) => t.name),
|
|
219
|
+
model: modelInfo,
|
|
220
|
+
context: execContext,
|
|
221
|
+
})
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// Track tool call count before this step so we can slice the per-step
|
|
225
|
+
// tool calls for the onStep observer event
|
|
226
|
+
const toolCallsBeforeStep = toolCalls.length;
|
|
227
|
+
|
|
228
|
+
// Call AI model via adapter using streaming
|
|
229
|
+
const llmStream = this.llmAdapter.stream({
|
|
230
|
+
instructions: resolvedInstructions,
|
|
231
|
+
messages,
|
|
232
|
+
tools: availableTools,
|
|
233
|
+
maxTokens: this.maxTokens,
|
|
234
|
+
temperature: this.temperature,
|
|
235
|
+
providerMetadata: input.providerMetadata,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Accumulate response from stream
|
|
239
|
+
let textContent = "";
|
|
240
|
+
const toolCallsFromStream: Array<{ id: string; name: string; input: any }> = [];
|
|
241
|
+
let usage: import("./LLMAdapter").LLMUsage = { inputTokens: 0, outputTokens: 0 };
|
|
242
|
+
let stopReason: "end_turn" | "tool_use" | "max_tokens" = "end_turn";
|
|
243
|
+
let providerMetadata: Record<string, any> = {};
|
|
244
|
+
|
|
245
|
+
// Process streaming chunks
|
|
246
|
+
for await (const chunk of llmStream) {
|
|
247
|
+
switch (chunk.type) {
|
|
248
|
+
case "text":
|
|
249
|
+
textContent += chunk.delta;
|
|
250
|
+
// Yield text_delta event in real-time
|
|
251
|
+
yield { type: "text_delta", delta: chunk.delta };
|
|
252
|
+
break;
|
|
253
|
+
|
|
254
|
+
case "tool_call":
|
|
255
|
+
toolCallsFromStream.push(chunk.toolCall);
|
|
256
|
+
break;
|
|
257
|
+
|
|
258
|
+
case "usage":
|
|
259
|
+
usage = chunk.usage;
|
|
260
|
+
totalInputTokens += chunk.usage.inputTokens;
|
|
261
|
+
totalOutputTokens += chunk.usage.outputTokens;
|
|
262
|
+
totalCachedInputTokens += chunk.usage.cachedInputTokens || 0;
|
|
263
|
+
totalCacheCreationInputTokens += chunk.usage.cacheCreationInputTokens || 0;
|
|
264
|
+
break;
|
|
265
|
+
|
|
266
|
+
case "metadata":
|
|
267
|
+
// Merge provider metadata (e.g., responseId for continuation)
|
|
268
|
+
providerMetadata = { ...providerMetadata, ...chunk.metadata };
|
|
269
|
+
finalProviderMetadata = { ...finalProviderMetadata, ...chunk.metadata };
|
|
270
|
+
break;
|
|
271
|
+
|
|
272
|
+
case "done":
|
|
273
|
+
stopReason = chunk.stopReason as "end_turn" | "tool_use" | "max_tokens";
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Create llmResponse structure for compatibility with existing code
|
|
279
|
+
const llmResponse = {
|
|
280
|
+
textContent: textContent || undefined,
|
|
281
|
+
toolCalls: toolCallsFromStream,
|
|
282
|
+
usage,
|
|
283
|
+
stopReason,
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
log.debug(`[Agent:${this.agentName}] Step ${step} - LLM Response:`, {
|
|
287
|
+
textLength: llmResponse.textContent?.length || 0,
|
|
288
|
+
textPreview: llmResponse.textContent?.substring(0, 200) + (llmResponse.textContent && llmResponse.textContent.length > 200 ? "..." : ""),
|
|
289
|
+
toolCallsCount: llmResponse.toolCalls.length,
|
|
290
|
+
toolCalls: llmResponse.toolCalls.map((tc) => ({
|
|
291
|
+
name: tc.name,
|
|
292
|
+
inputKeys: Object.keys(tc.input),
|
|
293
|
+
input: tc.input,
|
|
294
|
+
})),
|
|
295
|
+
stopReason: llmResponse.stopReason,
|
|
296
|
+
usage: llmResponse.usage,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Extract text response
|
|
300
|
+
if (llmResponse.textContent) {
|
|
301
|
+
finalMessage = llmResponse.textContent;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Build assistant message
|
|
305
|
+
const assistantContent: LLMContentBlock[] = [];
|
|
306
|
+
|
|
307
|
+
if (llmResponse.textContent) {
|
|
308
|
+
assistantContent.push({
|
|
309
|
+
type: "text",
|
|
310
|
+
text: llmResponse.textContent,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
for (const toolCall of llmResponse.toolCalls) {
|
|
315
|
+
assistantContent.push({
|
|
316
|
+
type: "tool_use",
|
|
317
|
+
id: toolCall.id,
|
|
318
|
+
name: toolCall.name,
|
|
319
|
+
input: toolCall.input,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
messages.push({
|
|
324
|
+
role: "assistant",
|
|
325
|
+
content: assistantContent,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Check for tool calls - if none, we're done after calling onStep
|
|
329
|
+
if (llmResponse.toolCalls.length === 0) {
|
|
330
|
+
// Call onStep hook before breaking
|
|
331
|
+
if (this.agentProps.onStep) {
|
|
332
|
+
const stepContext: AgentStepContext = {
|
|
333
|
+
...execContext,
|
|
334
|
+
step,
|
|
335
|
+
maxSteps,
|
|
336
|
+
messages: [...messages],
|
|
337
|
+
};
|
|
338
|
+
await this.agentProps.onStep(stepContext);
|
|
339
|
+
}
|
|
340
|
+
// Dispatch observer onStep (no tool calls executed this step)
|
|
341
|
+
this.safeDispatch("onStep", () =>
|
|
342
|
+
this.observer?.onStep?.({
|
|
343
|
+
runId,
|
|
344
|
+
agentId,
|
|
345
|
+
step,
|
|
346
|
+
maxSteps,
|
|
347
|
+
messages: [...messages],
|
|
348
|
+
assistantText: llmResponse.textContent,
|
|
349
|
+
toolCalls: toolCalls.slice(toolCallsBeforeStep),
|
|
350
|
+
usage,
|
|
351
|
+
context: execContext,
|
|
352
|
+
})
|
|
353
|
+
);
|
|
354
|
+
break; // No more tool calls - done
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Execute all tool calls
|
|
358
|
+
const toolResults: LLMContentBlock[] = [];
|
|
359
|
+
|
|
360
|
+
for (const toolCall of llmResponse.toolCalls) {
|
|
361
|
+
const toolExecutor = this.tools.get(toolCall.name);
|
|
362
|
+
let toolOutput: any;
|
|
363
|
+
let toolError: string | undefined;
|
|
364
|
+
|
|
365
|
+
log.debug(`[Agent:${this.agentName}] Executing tool '${toolCall.name}':`, {
|
|
366
|
+
input: toolCall.input,
|
|
367
|
+
inputSize: JSON.stringify(toolCall.input).length,
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
if (!toolExecutor) {
|
|
372
|
+
throw new Error(`Tool ${toolCall.name} not found`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Yield tool_call_start event
|
|
376
|
+
yield {
|
|
377
|
+
type: "tool_call_start",
|
|
378
|
+
toolCall: {
|
|
379
|
+
id: toolCall.id,
|
|
380
|
+
name: toolCall.name,
|
|
381
|
+
input: toolCall.input,
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
// Execute tool
|
|
386
|
+
const toolResult = await toolExecutor.execute(toolCall.input, {
|
|
387
|
+
user: input.user,
|
|
388
|
+
permissions: input.userPermissions,
|
|
389
|
+
conversationContext: input.conversationContext,
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Format result for AI using new ToolResult format
|
|
393
|
+
const formattedResult = toolExecutor.formatResultForAI(toolResult);
|
|
394
|
+
|
|
395
|
+
toolResults.push({
|
|
396
|
+
type: "tool_result",
|
|
397
|
+
tool_use_id: toolCall.id,
|
|
398
|
+
content: formattedResult,
|
|
399
|
+
is_error: !toolResult.success,
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// Yield tool_call_result event
|
|
403
|
+
yield {
|
|
404
|
+
type: "tool_call_result",
|
|
405
|
+
toolCall: {
|
|
406
|
+
id: toolCall.id,
|
|
407
|
+
name: toolCall.name,
|
|
408
|
+
input: toolCall.input,
|
|
409
|
+
},
|
|
410
|
+
output: toolResult.success ? toolResult.data : null,
|
|
411
|
+
error: toolResult.success ? undefined : toolResult.error,
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
toolCalls.push({
|
|
415
|
+
name: toolCall.name,
|
|
416
|
+
input: toolCall.input,
|
|
417
|
+
output: toolResult.success ? toolResult.data : null,
|
|
418
|
+
error: toolResult.success ? undefined : toolResult.error,
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
log.debug(`[Agent:${this.agentName}] Tool '${toolCall.name}' ${toolResult.success ? "succeeded" : "failed"}:`, {
|
|
422
|
+
success: toolResult.success,
|
|
423
|
+
outputSize: toolResult.success ? JSON.stringify(toolResult.data).length : 0,
|
|
424
|
+
outputPreview: toolResult.success
|
|
425
|
+
? JSON.stringify(toolResult.data).substring(0, 200) + (JSON.stringify(toolResult.data).length > 200 ? "..." : "")
|
|
426
|
+
: toolResult.error,
|
|
427
|
+
code: (toolResult as any).code,
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
if (!toolResult.success) {
|
|
431
|
+
log.warn(`Tool ${toolCall.name} returned error:`, toolResult.error);
|
|
432
|
+
}
|
|
433
|
+
} catch (err: any) {
|
|
434
|
+
// Unexpected errors (not from tool itself)
|
|
435
|
+
toolError = err.message;
|
|
436
|
+
|
|
437
|
+
// Yield tool_call_result with error
|
|
438
|
+
yield {
|
|
439
|
+
type: "tool_call_result",
|
|
440
|
+
toolCall: {
|
|
441
|
+
id: toolCall.id,
|
|
442
|
+
name: toolCall.name,
|
|
443
|
+
input: toolCall.input,
|
|
444
|
+
},
|
|
445
|
+
output: null,
|
|
446
|
+
error: toolError,
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
toolResults.push({
|
|
450
|
+
type: "tool_result",
|
|
451
|
+
tool_use_id: toolCall.id,
|
|
452
|
+
content: `Error: ${err.message}`,
|
|
453
|
+
is_error: true,
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
toolCalls.push({
|
|
457
|
+
name: toolCall.name,
|
|
458
|
+
input: toolCall.input,
|
|
459
|
+
output: null,
|
|
460
|
+
error: toolError,
|
|
461
|
+
});
|
|
462
|
+
log.error(`Tool ${toolCall.name} execution failed:`, err.message);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Add tool results to conversation
|
|
467
|
+
messages.push({
|
|
468
|
+
role: "user",
|
|
469
|
+
content: toolResults,
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// Call onStep hook after each step
|
|
473
|
+
if (this.agentProps.onStep) {
|
|
474
|
+
const stepContext: AgentStepContext = {
|
|
475
|
+
...execContext,
|
|
476
|
+
step,
|
|
477
|
+
maxSteps,
|
|
478
|
+
messages: [...messages], // Clone to prevent mutation
|
|
479
|
+
};
|
|
480
|
+
await this.agentProps.onStep(stepContext);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Dispatch observer onStep with per-step tool calls slice
|
|
484
|
+
this.safeDispatch("onStep", () =>
|
|
485
|
+
this.observer?.onStep?.({
|
|
486
|
+
runId,
|
|
487
|
+
agentId,
|
|
488
|
+
step,
|
|
489
|
+
maxSteps,
|
|
490
|
+
messages: [...messages],
|
|
491
|
+
assistantText: llmResponse.textContent,
|
|
492
|
+
toolCalls: toolCalls.slice(toolCallsBeforeStep),
|
|
493
|
+
usage,
|
|
494
|
+
context: execContext,
|
|
495
|
+
})
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (step >= maxSteps && toolCalls.length > 0) {
|
|
500
|
+
stoppedEarly = true;
|
|
501
|
+
log.warn(`Agent ${this.agentName || "unknown"} stopped early after ${maxSteps} steps`);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const result = buildResult();
|
|
505
|
+
|
|
506
|
+
// Call afterRun hook with full context
|
|
507
|
+
if (this.agentProps.afterRun) {
|
|
508
|
+
const finishContext: AgentFinishContext = {
|
|
509
|
+
...execContext,
|
|
510
|
+
messages: [...messages],
|
|
511
|
+
result,
|
|
512
|
+
};
|
|
513
|
+
await this.agentProps.afterRun(result, finishContext);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Dispatch observer onFinish (success path)
|
|
517
|
+
this.safeDispatch("onFinish", () =>
|
|
518
|
+
this.observer?.onFinish?.({
|
|
519
|
+
runId,
|
|
520
|
+
agentId,
|
|
521
|
+
result,
|
|
522
|
+
messages: [...messages],
|
|
523
|
+
durationMs: Date.now() - runStartedAt,
|
|
524
|
+
context: execContext,
|
|
525
|
+
})
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
// Phase 1: Yield only complete event
|
|
529
|
+
// Phase 2: Will yield text_delta and tool events during loop
|
|
530
|
+
yield { type: "complete", result };
|
|
531
|
+
|
|
532
|
+
return result;
|
|
533
|
+
} catch (err: any) {
|
|
534
|
+
// Dispatch observer onFinish with error before rethrowing
|
|
535
|
+
this.safeDispatch("onFinish", () =>
|
|
536
|
+
this.observer?.onFinish?.({
|
|
537
|
+
runId,
|
|
538
|
+
agentId,
|
|
539
|
+
result: buildResult(),
|
|
540
|
+
messages: [...messages],
|
|
541
|
+
durationMs: Date.now() - runStartedAt,
|
|
542
|
+
error: err?.message || String(err),
|
|
543
|
+
context: execContext,
|
|
544
|
+
})
|
|
545
|
+
);
|
|
546
|
+
throw err;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Fire-and-forget observer dispatch: catches synchronous throws and
|
|
552
|
+
* rejected promises so observer failures never break agent execution.
|
|
553
|
+
*/
|
|
554
|
+
private safeDispatch(eventName: string, invoke: () => void | Promise<void> | undefined): void {
|
|
555
|
+
try {
|
|
556
|
+
const maybePromise = invoke();
|
|
557
|
+
if (maybePromise && typeof (maybePromise as Promise<void>).then === "function") {
|
|
558
|
+
(maybePromise as Promise<void>).catch((err: any) => {
|
|
559
|
+
observerLog.warn(`AgentObserver.${eventName} threw (async):`, err?.message || err);
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
} catch (err: any) {
|
|
563
|
+
observerLog.warn(`AgentObserver.${eventName} threw (sync):`, err?.message || err);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Convert Message[] to LLM message format
|
|
569
|
+
* Supports multi-turn conversations with history
|
|
570
|
+
*/
|
|
571
|
+
private convertMessages(messages: Message[]): LLMMessage[] {
|
|
572
|
+
return messages.map((m) => {
|
|
573
|
+
if (m.role === "user") {
|
|
574
|
+
return { role: "user" as const, content: m.content };
|
|
575
|
+
} else if (m.role === "assistant") {
|
|
576
|
+
// If assistant message has tool calls, convert to content blocks
|
|
577
|
+
if (m.toolCalls && m.toolCalls.length > 0) {
|
|
578
|
+
const contentBlocks: LLMContentBlock[] = [];
|
|
579
|
+
|
|
580
|
+
// Add text content if present
|
|
581
|
+
if (m.content) {
|
|
582
|
+
contentBlocks.push({ type: "text", text: m.content });
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Add tool use blocks
|
|
586
|
+
for (const toolCall of m.toolCalls) {
|
|
587
|
+
contentBlocks.push({
|
|
588
|
+
type: "tool_use",
|
|
589
|
+
id: toolCall.id,
|
|
590
|
+
name: toolCall.name,
|
|
591
|
+
input: toolCall.input,
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return { role: "assistant" as const, content: contentBlocks };
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Text-only assistant message
|
|
599
|
+
return { role: "assistant" as const, content: m.content };
|
|
600
|
+
} else {
|
|
601
|
+
// For tool messages, convert to user message with tool_result content block
|
|
602
|
+
return {
|
|
603
|
+
role: "user" as const,
|
|
604
|
+
content: [
|
|
605
|
+
{
|
|
606
|
+
type: "tool_result",
|
|
607
|
+
tool_use_id: m.toolCallId,
|
|
608
|
+
content: m.result,
|
|
609
|
+
},
|
|
610
|
+
] as LLMContentBlock[],
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
private getToolSchemas(): FlinkToolSchema[] {
|
|
617
|
+
return Array.from(this.tools.values()).map((t) => t.getToolSchema());
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Filter tools based on user permissions
|
|
622
|
+
* Only returns schemas for tools the user has permission to use
|
|
623
|
+
*
|
|
624
|
+
* @param user - User object
|
|
625
|
+
* @param userPermissions - Optional resolved permissions from auth plugin (preferred)
|
|
626
|
+
* @param conversationContext - Optional conversation context
|
|
627
|
+
*/
|
|
628
|
+
private async filterToolsByPermissions(user?: any, userPermissions?: string[], conversationContext?: any): Promise<FlinkToolSchema[]> {
|
|
629
|
+
const allowedTools: FlinkToolSchema[] = [];
|
|
630
|
+
const toolExecutors = Array.from(this.tools.values());
|
|
631
|
+
|
|
632
|
+
for (const tool of toolExecutors) {
|
|
633
|
+
const hasPermission = await tool.checkPermissions(user, undefined, userPermissions, conversationContext);
|
|
634
|
+
if (hasPermission) {
|
|
635
|
+
allowedTools.push(tool.getToolSchema());
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
return allowedTools;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Build delegation chain from metadata for error messages
|
|
644
|
+
* Extracts parent agent IDs to show the full call stack
|
|
645
|
+
*/
|
|
646
|
+
}
|