@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
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import Ajv from "ajv";
|
|
2
|
+
import addFormats from "ajv-formats";
|
|
3
|
+
import { FlinkContext } from "../FlinkContext";
|
|
4
|
+
import { forbidden } from "../FlinkErrors";
|
|
5
|
+
import { FlinkLogFactory } from "../FlinkLogFactory";
|
|
6
|
+
import { getRequestContext, getRequestUser } from "../FlinkRequestContext";
|
|
7
|
+
import { FlinkTool, FlinkToolProps, ToolResult } from "./FlinkTool";
|
|
8
|
+
import { FlinkToolSchema } from "./LLMAdapter";
|
|
9
|
+
|
|
10
|
+
const toolLog = FlinkLogFactory.createLogger("flink.ai.tool");
|
|
11
|
+
|
|
12
|
+
export class ToolExecutor<Ctx extends FlinkContext> {
|
|
13
|
+
private ajv: Ajv;
|
|
14
|
+
private compiledInputValidator?: ReturnType<Ajv["compile"]>;
|
|
15
|
+
private compiledOutputValidator?: ReturnType<Ajv["compile"]>;
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
private toolProps: FlinkToolProps,
|
|
19
|
+
private toolFn: FlinkTool<Ctx, any, any>,
|
|
20
|
+
private ctx: Ctx,
|
|
21
|
+
private autoSchemas?: {
|
|
22
|
+
inputSchema?: any;
|
|
23
|
+
outputSchema?: any;
|
|
24
|
+
inputTypeHint?: 'void' | 'any' | 'named';
|
|
25
|
+
outputTypeHint?: 'void' | 'any' | 'named';
|
|
26
|
+
},
|
|
27
|
+
private allSchemas?: Record<string, any>
|
|
28
|
+
) {
|
|
29
|
+
this.ajv = new Ajv({ allErrors: true });
|
|
30
|
+
addFormats(this.ajv);
|
|
31
|
+
|
|
32
|
+
// Pre-populate AJV with all schemas so $ref references resolve across schema boundaries
|
|
33
|
+
if (allSchemas) {
|
|
34
|
+
for (const schema of Object.values(allSchemas)) {
|
|
35
|
+
if (schema && schema.$id) {
|
|
36
|
+
try {
|
|
37
|
+
this.ajv.addSchema(schema);
|
|
38
|
+
} catch {
|
|
39
|
+
// Ignore duplicate schema errors (schema may already be added)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Pre-compile validators once at construction time (not per invocation)
|
|
46
|
+
if (toolProps.inputJsonSchema) {
|
|
47
|
+
this.compiledInputValidator = this.ajv.compile(toolProps.inputJsonSchema);
|
|
48
|
+
} else if (autoSchemas?.inputSchema) {
|
|
49
|
+
this.compiledInputValidator = this.ajv.compile(autoSchemas.inputSchema);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (toolProps.outputJsonSchema) {
|
|
53
|
+
this.compiledOutputValidator = this.ajv.compile(toolProps.outputJsonSchema);
|
|
54
|
+
} else if (autoSchemas?.outputSchema) {
|
|
55
|
+
this.compiledOutputValidator = this.ajv.compile(autoSchemas.outputSchema);
|
|
56
|
+
}
|
|
57
|
+
// Log when using auto-schemas
|
|
58
|
+
if (autoSchemas?.inputSchema && !toolProps.inputSchema && !toolProps.inputJsonSchema) {
|
|
59
|
+
toolLog.debug(`Tool ${toolProps.id}: Using auto-generated schemas from type parameters`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Log when no schema is provided (valid for void/any input types)
|
|
63
|
+
const hasInputSchema = toolProps.inputSchema || toolProps.inputJsonSchema || autoSchemas?.inputSchema;
|
|
64
|
+
if (!hasInputSchema) {
|
|
65
|
+
toolLog.debug(`Tool ${toolProps.id}: No input schema provided (input type is void or any)`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Warn if both manual and auto provided (manual takes precedence)
|
|
69
|
+
if ((toolProps.inputSchema || toolProps.inputJsonSchema) && autoSchemas?.inputSchema) {
|
|
70
|
+
toolLog.debug(`Tool ${toolProps.id}: Manual schemas take precedence over auto-generated schemas`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Log warning if both manual Zod and JSON schemas are provided
|
|
74
|
+
if (toolProps.inputSchema && toolProps.inputJsonSchema) {
|
|
75
|
+
toolLog.warn(`Tool ${toolProps.id}: Both 'inputSchema' (Zod) and 'inputJsonSchema' are provided. ` + `Using 'inputSchema' for validation.`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (toolProps.outputSchema && toolProps.outputJsonSchema) {
|
|
79
|
+
toolLog.warn(`Tool ${toolProps.id}: Both 'outputSchema' (Zod) and 'outputJsonSchema' are provided. ` + `Using 'outputSchema' for validation.`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Execute the tool with input
|
|
85
|
+
* @param input - Tool input data
|
|
86
|
+
* @param overrides - Optional overrides for user/permissions/conversationContext (for testing)
|
|
87
|
+
*/
|
|
88
|
+
async execute(input: any, overrides?: { user?: any; permissions?: string[]; conversationContext?: any }): Promise<ToolResult<any>> {
|
|
89
|
+
// Get user, permissions, and conversationContext from AsyncLocalStorage or overrides
|
|
90
|
+
const user = overrides?.user ?? getRequestUser();
|
|
91
|
+
const userPermissions = overrides?.permissions ?? getRequestContext()?.userPermissions;
|
|
92
|
+
const conversationContext = overrides?.conversationContext;
|
|
93
|
+
|
|
94
|
+
// 1. Permission check
|
|
95
|
+
if (this.toolProps.permissions) {
|
|
96
|
+
const hasPermission = await this.checkPermissionsInternal(input, user, userPermissions);
|
|
97
|
+
if (!hasPermission) {
|
|
98
|
+
toolLog.debug("Tool invocator is missing required permission(s)", this.toolProps.permissions, "user has", userPermissions);
|
|
99
|
+
throw forbidden(`Permission denied for tool ${this.toolProps.id}`, "PERMISSION_DENIED");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 2. Input validation (priority: Zod > JSON Schema > Auto-generated)
|
|
104
|
+
let validatedInput: any;
|
|
105
|
+
try {
|
|
106
|
+
if (this.toolProps.inputSchema) {
|
|
107
|
+
// Priority 1: Use Zod validation
|
|
108
|
+
validatedInput = this.toolProps.inputSchema.parse(input);
|
|
109
|
+
} else if (this.compiledInputValidator) {
|
|
110
|
+
// Priority 2 & 3: Use pre-compiled JSON Schema validator (manual or auto-generated)
|
|
111
|
+
const valid = this.compiledInputValidator(input);
|
|
112
|
+
if (!valid) {
|
|
113
|
+
const errorDetails = this.formatAjvErrors(this.compiledInputValidator.errors || [], input);
|
|
114
|
+
toolLog.warn(`Tool ${this.toolProps.id} input validation failed:`, errorDetails);
|
|
115
|
+
return {
|
|
116
|
+
success: false,
|
|
117
|
+
error: `Invalid input for tool '${this.toolProps.id}': ${errorDetails}`,
|
|
118
|
+
code: "VALIDATION_ERROR",
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
validatedInput = input;
|
|
122
|
+
} else {
|
|
123
|
+
// No schema available - skip validation
|
|
124
|
+
validatedInput = input;
|
|
125
|
+
}
|
|
126
|
+
} catch (err: any) {
|
|
127
|
+
toolLog.warn(`Tool ${this.toolProps.id} input validation failed:`, err.message);
|
|
128
|
+
|
|
129
|
+
// Format Zod validation errors for better LLM understanding
|
|
130
|
+
const errorDetails = this.formatZodError(err, input);
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
success: false,
|
|
134
|
+
error: `Invalid input for tool '${this.toolProps.id}': ${errorDetails}`,
|
|
135
|
+
code: "VALIDATION_ERROR",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 3. Execute tool
|
|
140
|
+
toolLog.debug(`Executing tool ${this.toolProps.id}`);
|
|
141
|
+
let result: ToolResult<any>;
|
|
142
|
+
try {
|
|
143
|
+
toolLog.trace(this.toolFn.name + " input:", validatedInput);
|
|
144
|
+
|
|
145
|
+
result = await this.toolFn({
|
|
146
|
+
input: validatedInput,
|
|
147
|
+
ctx: this.ctx,
|
|
148
|
+
user,
|
|
149
|
+
permissions: userPermissions,
|
|
150
|
+
conversationCtx: conversationContext,
|
|
151
|
+
});
|
|
152
|
+
} catch (err: any) {
|
|
153
|
+
toolLog.error(`Tool ${this.toolProps.id} threw error:`, err.message);
|
|
154
|
+
return {
|
|
155
|
+
success: false,
|
|
156
|
+
error: `Tool execution failed: ${err.message}`,
|
|
157
|
+
code: "EXECUTION_ERROR",
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 4. Handle error results
|
|
162
|
+
if (!result.success) {
|
|
163
|
+
toolLog.warn(`Tool ${this.toolProps.id} returned error:`, result.error);
|
|
164
|
+
return result; // Return error result as-is
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 5. Output validation (priority: Zod > JSON Schema > Auto-generated)
|
|
168
|
+
if (this.toolProps.outputSchema) {
|
|
169
|
+
// Priority 1: Use Zod validation
|
|
170
|
+
try {
|
|
171
|
+
const validatedData = this.toolProps.outputSchema.parse(result.data);
|
|
172
|
+
return { success: true, data: validatedData };
|
|
173
|
+
} catch (err: any) {
|
|
174
|
+
toolLog.error(`Tool ${this.toolProps.id} output validation failed:`, err.message);
|
|
175
|
+
return {
|
|
176
|
+
success: false,
|
|
177
|
+
error: `Invalid output from tool ${this.toolProps.id}: ${err.message}`,
|
|
178
|
+
code: "OUTPUT_VALIDATION_ERROR",
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
} else if (this.compiledOutputValidator) {
|
|
182
|
+
// Priority 2 & 3: Use pre-compiled JSON Schema validator (manual or auto-generated)
|
|
183
|
+
try {
|
|
184
|
+
const valid = this.compiledOutputValidator(result.data);
|
|
185
|
+
if (!valid) {
|
|
186
|
+
const errorDetails = this.formatAjvErrors(this.compiledOutputValidator.errors || []);
|
|
187
|
+
toolLog.error(`Tool ${this.toolProps.id} output validation failed:`, errorDetails);
|
|
188
|
+
return {
|
|
189
|
+
success: false,
|
|
190
|
+
error: `Invalid output from tool ${this.toolProps.id}: ${errorDetails}`,
|
|
191
|
+
code: "OUTPUT_VALIDATION_ERROR",
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return { success: true, data: result.data };
|
|
195
|
+
} catch (err: any) {
|
|
196
|
+
toolLog.error(`Tool ${this.toolProps.id} output validation failed:`, err.message);
|
|
197
|
+
return {
|
|
198
|
+
success: false,
|
|
199
|
+
error: `Invalid output from tool ${this.toolProps.id}: ${err.message}`,
|
|
200
|
+
code: "OUTPUT_VALIDATION_ERROR",
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// No output validation - return result as-is
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
getToolSchema(): FlinkToolSchema {
|
|
210
|
+
// Priority order: inputJsonSchema > inputSchema (Zod 4.x) > autoSchemas
|
|
211
|
+
|
|
212
|
+
// Priority 1: Manual JSON schema
|
|
213
|
+
if (this.toolProps.inputJsonSchema) {
|
|
214
|
+
return {
|
|
215
|
+
name: this.toolProps.id,
|
|
216
|
+
description: this.toolProps.description,
|
|
217
|
+
inputSchema: this.toolProps.inputJsonSchema,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Priority 2: Zod schema (convert to JSON Schema using Zod 4.x)
|
|
222
|
+
if (this.toolProps.inputSchema) {
|
|
223
|
+
const z = require("zod") as any;
|
|
224
|
+
|
|
225
|
+
if (!z.toJSONSchema) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`Tool ${this.toolProps.id}: Zod 4.x is required for automatic schema generation. ` +
|
|
228
|
+
`Either upgrade to Zod 4.x or provide 'inputJsonSchema' in your tool definition, ` +
|
|
229
|
+
`or use TypeScript type parameters for auto-generation.`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
name: this.toolProps.id,
|
|
235
|
+
description: this.toolProps.description,
|
|
236
|
+
inputSchema: z.toJSONSchema(this.toolProps.inputSchema),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Priority 3: Auto-generated schema from type parameters
|
|
241
|
+
if (this.autoSchemas?.inputSchema) {
|
|
242
|
+
return {
|
|
243
|
+
name: this.toolProps.id,
|
|
244
|
+
description: this.toolProps.description,
|
|
245
|
+
inputSchema: this.resolveSchemaRefs(this.autoSchemas.inputSchema),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// No schema provided - return schema based on type hint
|
|
250
|
+
const typeHint = this.autoSchemas?.inputTypeHint;
|
|
251
|
+
|
|
252
|
+
if (typeHint === 'void') {
|
|
253
|
+
// void: Tool takes no input - reject any properties
|
|
254
|
+
return {
|
|
255
|
+
name: this.toolProps.id,
|
|
256
|
+
description: this.toolProps.description,
|
|
257
|
+
inputSchema: {
|
|
258
|
+
type: "object",
|
|
259
|
+
properties: {},
|
|
260
|
+
additionalProperties: false,
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
} else if (typeHint === 'any') {
|
|
264
|
+
// any: Tool accepts any input - allow any properties
|
|
265
|
+
return {
|
|
266
|
+
name: this.toolProps.id,
|
|
267
|
+
description: this.toolProps.description,
|
|
268
|
+
inputSchema: {
|
|
269
|
+
type: "object",
|
|
270
|
+
additionalProperties: true,
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
} else {
|
|
274
|
+
// No type hint - default to void behavior (no input expected)
|
|
275
|
+
return {
|
|
276
|
+
name: this.toolProps.id,
|
|
277
|
+
description: this.toolProps.description,
|
|
278
|
+
inputSchema: {
|
|
279
|
+
type: "object",
|
|
280
|
+
properties: {},
|
|
281
|
+
additionalProperties: false,
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Resolve cross-schema $ref values into $defs so the schema is self-contained.
|
|
289
|
+
*
|
|
290
|
+
* Flink's schema manifest stores schemas as separate documents with IDs like
|
|
291
|
+
* "Canvas.ElementInput". These are valid for AJV (which uses a schema registry),
|
|
292
|
+
* but LLM providers like OpenAI require a single self-contained schema where all
|
|
293
|
+
* $ref values point to #/$defs/... entries at the top level.
|
|
294
|
+
*/
|
|
295
|
+
private resolveSchemaRefs(schema: any): any {
|
|
296
|
+
if (!this.allSchemas) return schema;
|
|
297
|
+
|
|
298
|
+
const defs: Record<string, any> = {};
|
|
299
|
+
|
|
300
|
+
const collectRefs = (node: any, visited: Set<string>): void => {
|
|
301
|
+
if (!node || typeof node !== "object") return;
|
|
302
|
+
|
|
303
|
+
if (node.$ref && typeof node.$ref === "string" && !node.$ref.startsWith("#")) {
|
|
304
|
+
const refId = node.$ref;
|
|
305
|
+
if (!visited.has(refId) && this.allSchemas![refId]) {
|
|
306
|
+
visited.add(refId);
|
|
307
|
+
// Clone and strip top-level JSON Schema meta fields not valid inside $defs
|
|
308
|
+
const def = JSON.parse(JSON.stringify(this.allSchemas![refId]));
|
|
309
|
+
delete def.$id;
|
|
310
|
+
delete def.$schema;
|
|
311
|
+
defs[refId] = def;
|
|
312
|
+
// Recurse into the referenced schema to collect its deps
|
|
313
|
+
collectRefs(def, visited);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
for (const value of Object.values(node)) {
|
|
318
|
+
collectRefs(value, visited);
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const visited = new Set<string>();
|
|
323
|
+
collectRefs(schema, visited);
|
|
324
|
+
|
|
325
|
+
if (Object.keys(defs).length === 0) return schema;
|
|
326
|
+
|
|
327
|
+
// Deep clone and rewrite all non-standard $ref values to #/$defs/<id>
|
|
328
|
+
const resolved = JSON.parse(JSON.stringify(schema));
|
|
329
|
+
delete resolved.$id;
|
|
330
|
+
delete resolved.$schema;
|
|
331
|
+
|
|
332
|
+
const rewriteRefs = (node: any): void => {
|
|
333
|
+
if (!node || typeof node !== "object") return;
|
|
334
|
+
|
|
335
|
+
if (node.$ref && typeof node.$ref === "string" && !node.$ref.startsWith("#")) {
|
|
336
|
+
node.$ref = `#/$defs/${node.$ref}`;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
for (const value of Object.values(node)) {
|
|
340
|
+
rewriteRefs(value);
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
rewriteRefs(resolved);
|
|
345
|
+
// Also rewrite refs inside the collected defs
|
|
346
|
+
for (const def of Object.values(defs)) {
|
|
347
|
+
rewriteRefs(def);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
resolved.$defs = defs;
|
|
351
|
+
return resolved;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Get tool result for AI consumption
|
|
356
|
+
* Formats ToolResult into string for AI context
|
|
357
|
+
*/
|
|
358
|
+
formatResultForAI(result: ToolResult<any>): string {
|
|
359
|
+
if (result.success) {
|
|
360
|
+
return JSON.stringify(result.data);
|
|
361
|
+
} else {
|
|
362
|
+
return `Error: ${result.error}${result.code ? ` (${result.code})` : ""}`;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Check if user has permission to use this tool
|
|
368
|
+
* Used by AgentRunner to filter tools before showing to LLM
|
|
369
|
+
*
|
|
370
|
+
* @param user - User object
|
|
371
|
+
* @param input - Tool input (for function-based permissions)
|
|
372
|
+
* @param userPermissions - Optional resolved permissions from auth plugin (preferred)
|
|
373
|
+
* @param conversationContext - Optional conversation context
|
|
374
|
+
*/
|
|
375
|
+
async checkPermissions(user?: any, input?: any, userPermissions?: string[], conversationContext?: any): Promise<boolean> {
|
|
376
|
+
const perms = this.toolProps.permissions;
|
|
377
|
+
if (!perms) return true;
|
|
378
|
+
if (typeof perms === "function") return await perms(input ?? {}, user);
|
|
379
|
+
|
|
380
|
+
// Get effective permissions (prefer explicit userPermissions)
|
|
381
|
+
const effectivePerms = userPermissions || user?.permissions || [];
|
|
382
|
+
|
|
383
|
+
// If no user and no explicit permissions, deny access
|
|
384
|
+
if (!user && !userPermissions) return false;
|
|
385
|
+
|
|
386
|
+
const requiredPerms = Array.isArray(perms) ? perms : [perms];
|
|
387
|
+
return requiredPerms.every((p) => effectivePerms.includes(p));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private async checkPermissionsInternal(input: any, user?: any, userPermissions?: string[], conversationContext?: any): Promise<boolean> {
|
|
391
|
+
return this.checkPermissions(user, input, userPermissions, conversationContext);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Format Zod validation errors into LLM-friendly error messages
|
|
396
|
+
* Provides specific guidance on what's missing or incorrect
|
|
397
|
+
*/
|
|
398
|
+
private formatZodError(err: any, receivedInput?: any): string {
|
|
399
|
+
// Check if it's a Zod error with issues array
|
|
400
|
+
if (err.issues && Array.isArray(err.issues)) {
|
|
401
|
+
const issues = err.issues.map((issue: any) => {
|
|
402
|
+
const path = issue.path.join(".");
|
|
403
|
+
const field = path || "input";
|
|
404
|
+
|
|
405
|
+
if (issue.code === "invalid_type") {
|
|
406
|
+
if (issue.received === "undefined") {
|
|
407
|
+
return `Missing required field '${field}' (expected ${issue.expected})`;
|
|
408
|
+
}
|
|
409
|
+
return `Field '${field}' has wrong type: expected ${issue.expected}, got ${issue.received}`;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (issue.code === "too_small") {
|
|
413
|
+
if (issue.type === "string") {
|
|
414
|
+
return `Field '${field}' is too short (minimum ${issue.minimum} characters)`;
|
|
415
|
+
}
|
|
416
|
+
return `Field '${field}' is too small (minimum ${issue.minimum})`;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (issue.code === "too_big") {
|
|
420
|
+
if (issue.type === "string") {
|
|
421
|
+
return `Field '${field}' is too long (maximum ${issue.maximum} characters)`;
|
|
422
|
+
}
|
|
423
|
+
return `Field '${field}' is too large (maximum ${issue.maximum})`;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Generic fallback
|
|
427
|
+
return `${field}: ${issue.message}`;
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
if (receivedInput) {
|
|
431
|
+
const inputInfo =
|
|
432
|
+
Object.keys(receivedInput || {}).length === 0 ? "You provided an empty object {}." : `You provided: ${JSON.stringify(receivedInput)}`;
|
|
433
|
+
return `${issues.join("; ")}. ${inputInfo}`;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return issues.join("; ");
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Fallback for non-Zod errors
|
|
440
|
+
return err.message || "Unknown validation error";
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Format AJV validation errors into LLM-friendly error messages
|
|
445
|
+
*/
|
|
446
|
+
private formatAjvErrors(errors: any[], receivedInput?: any): string {
|
|
447
|
+
const issues = errors.map((error) => {
|
|
448
|
+
const field = error.instancePath ? error.instancePath.substring(1).replace(/\//g, ".") : "input";
|
|
449
|
+
|
|
450
|
+
if (error.keyword === "required") {
|
|
451
|
+
return `Missing required field '${error.params.missingProperty}'`;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (error.keyword === "type") {
|
|
455
|
+
return `Field '${field}' has wrong type: expected ${error.params.type}`;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (error.keyword === "minLength") {
|
|
459
|
+
return `Field '${field}' is too short (minimum ${error.params.limit} characters)`;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (error.keyword === "maxLength") {
|
|
463
|
+
return `Field '${field}' is too long (maximum ${error.params.limit} characters)`;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (error.keyword === "minimum") {
|
|
467
|
+
return `Field '${field}' is too small (minimum ${error.params.limit})`;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (error.keyword === "maximum") {
|
|
471
|
+
return `Field '${field}' is too large (maximum ${error.params.limit})`;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return `${field}: ${error.message}`;
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
if (receivedInput) {
|
|
478
|
+
const inputInfo =
|
|
479
|
+
Object.keys(receivedInput || {}).length === 0 ? "You provided an empty object {}." : `You provided: ${JSON.stringify(receivedInput)}`;
|
|
480
|
+
return `${issues.join("; ")}. ${inputInfo}`;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return issues.join("; ");
|
|
484
|
+
}
|
|
485
|
+
}
|