@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,465 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ToolExecutor } from "../src/ai/ToolExecutor";
|
|
3
|
+
import { FlinkTool, FlinkToolProps, ToolResult } from "../src/ai/FlinkTool";
|
|
4
|
+
import { FlinkContext } from "../src/FlinkContext";
|
|
5
|
+
|
|
6
|
+
describe("ToolExecutor", () => {
|
|
7
|
+
let mockCtx: FlinkContext;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
mockCtx = {
|
|
11
|
+
repos: {},
|
|
12
|
+
plugins: {},
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe("Input validation", () => {
|
|
17
|
+
it("should validate input with Zod schema", async () => {
|
|
18
|
+
const toolProps: FlinkToolProps = {
|
|
19
|
+
id: "test_tool",
|
|
20
|
+
description: "Test tool",
|
|
21
|
+
inputSchema: z.object({
|
|
22
|
+
name: z.string(),
|
|
23
|
+
age: z.number(),
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const toolFn: FlinkTool<any> = async ({ input }) => {
|
|
28
|
+
return { success: true, data: { result: "ok" } };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const executor = new ToolExecutor(toolProps, toolFn, mockCtx);
|
|
32
|
+
|
|
33
|
+
const result = await executor.execute({ name: "John", age: 30 });
|
|
34
|
+
|
|
35
|
+
expect(result.success).toBe(true);
|
|
36
|
+
if (result.success) {
|
|
37
|
+
expect(result.data).toEqual({ result: "ok" });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should reject invalid input", async () => {
|
|
42
|
+
const toolProps: FlinkToolProps = {
|
|
43
|
+
id: "test_tool",
|
|
44
|
+
description: "Test tool",
|
|
45
|
+
inputSchema: z.object({
|
|
46
|
+
name: z.string(),
|
|
47
|
+
age: z.number(),
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const toolFn: FlinkTool<any> = async ({ input }) => {
|
|
52
|
+
return { success: true, data: { result: "ok" } };
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const executor = new ToolExecutor(toolProps, toolFn, mockCtx);
|
|
56
|
+
|
|
57
|
+
const result = await executor.execute({ name: "John", age: "invalid" });
|
|
58
|
+
|
|
59
|
+
expect(result.success).toBe(false);
|
|
60
|
+
if (!result.success) {
|
|
61
|
+
expect(result.error).toContain("Invalid input");
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("Output validation", () => {
|
|
67
|
+
it("should validate output when schema provided", async () => {
|
|
68
|
+
const toolProps: FlinkToolProps = {
|
|
69
|
+
id: "test_tool",
|
|
70
|
+
description: "Test tool",
|
|
71
|
+
inputSchema: z.object({ input: z.string() }),
|
|
72
|
+
outputSchema: z.object({ result: z.string() }),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const toolFn: FlinkTool<any> = async ({ input }) => {
|
|
76
|
+
return { success: true, data: { result: "valid" } };
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const executor = new ToolExecutor(toolProps, toolFn, mockCtx);
|
|
80
|
+
|
|
81
|
+
const result = await executor.execute({ input: "test" });
|
|
82
|
+
|
|
83
|
+
expect(result.success).toBe(true);
|
|
84
|
+
if (result.success) {
|
|
85
|
+
expect(result.data).toEqual({ result: "valid" });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should reject invalid output", async () => {
|
|
90
|
+
const toolProps: FlinkToolProps = {
|
|
91
|
+
id: "test_tool",
|
|
92
|
+
description: "Test tool",
|
|
93
|
+
inputSchema: z.object({ input: z.string() }),
|
|
94
|
+
outputSchema: z.object({ result: z.string() }),
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const toolFn: FlinkTool<any> = async ({ input }) => {
|
|
98
|
+
return { success: true, data: { result: 123 } }; // Invalid: number instead of string
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const executor = new ToolExecutor(toolProps, toolFn, mockCtx);
|
|
102
|
+
|
|
103
|
+
const result = await executor.execute({ input: "test" });
|
|
104
|
+
|
|
105
|
+
expect(result.success).toBe(false);
|
|
106
|
+
if (!result.success) {
|
|
107
|
+
expect(result.error).toContain("Invalid output");
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("Permission checks", () => {
|
|
113
|
+
it("should allow execution with no permissions", async () => {
|
|
114
|
+
const toolProps: FlinkToolProps = {
|
|
115
|
+
id: "test_tool",
|
|
116
|
+
description: "Test tool",
|
|
117
|
+
inputSchema: z.object({ input: z.string() }),
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const toolFn: FlinkTool<any> = async ({ input }) => {
|
|
121
|
+
return { success: true, data: { result: "ok" } };
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const executor = new ToolExecutor(toolProps, toolFn, mockCtx);
|
|
125
|
+
|
|
126
|
+
const result = await executor.execute({ input: "test" });
|
|
127
|
+
|
|
128
|
+
expect(result.success).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should check static string permission", async () => {
|
|
132
|
+
const toolProps: FlinkToolProps = {
|
|
133
|
+
id: "test_tool",
|
|
134
|
+
description: "Test tool",
|
|
135
|
+
inputSchema: z.object({ input: z.string() }),
|
|
136
|
+
permissions: "admin",
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const toolFn: FlinkTool<any> = async ({ input }) => {
|
|
140
|
+
return { success: true, data: { result: "ok" } };
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const executor = new ToolExecutor(toolProps, toolFn, mockCtx);
|
|
144
|
+
|
|
145
|
+
// Without user - should throw forbidden error
|
|
146
|
+
await expectAsync(executor.execute({ input: "test" })).toBeRejected();
|
|
147
|
+
|
|
148
|
+
// With user but no permission - should throw forbidden error
|
|
149
|
+
await expectAsync(executor.execute({ input: "test" }, { permissions: [] })).toBeRejected();
|
|
150
|
+
|
|
151
|
+
// With user and permission - should succeed
|
|
152
|
+
const result3 = await executor.execute({ input: "test" }, { permissions: ["admin"] });
|
|
153
|
+
expect(result3.success).toBe(true);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("should check array of permissions", async () => {
|
|
157
|
+
const toolProps: FlinkToolProps = {
|
|
158
|
+
id: "test_tool",
|
|
159
|
+
description: "Test tool",
|
|
160
|
+
inputSchema: z.object({ input: z.string() }),
|
|
161
|
+
permissions: ["admin", "user"],
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const toolFn: FlinkTool<any> = async ({ input }) => {
|
|
165
|
+
return { success: true, data: { result: "ok" } };
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const executor = new ToolExecutor(toolProps, toolFn, mockCtx);
|
|
169
|
+
|
|
170
|
+
// With partial permissions - should throw forbidden error
|
|
171
|
+
await expectAsync(executor.execute({ input: "test" }, { permissions: ["admin"] })).toBeRejected();
|
|
172
|
+
|
|
173
|
+
// With all permissions - should succeed
|
|
174
|
+
const result2 = await executor.execute({ input: "test" }, { permissions: ["admin", "user"] });
|
|
175
|
+
expect(result2.success).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("should check function-based permissions", async () => {
|
|
179
|
+
const toolProps: FlinkToolProps = {
|
|
180
|
+
id: "test_tool",
|
|
181
|
+
description: "Test tool",
|
|
182
|
+
inputSchema: z.object({ value: z.number() }),
|
|
183
|
+
permissions: (input, user) => {
|
|
184
|
+
return input.value > 10 && user?.isAdmin === true;
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const toolFn: FlinkTool<any> = async ({ input }) => {
|
|
189
|
+
return { success: true, data: { result: "ok" } };
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const executor = new ToolExecutor(toolProps, toolFn, mockCtx);
|
|
193
|
+
|
|
194
|
+
// Invalid input value - should throw forbidden error
|
|
195
|
+
await expectAsync(executor.execute({ value: 5 }, { user: { isAdmin: true } })).toBeRejected();
|
|
196
|
+
|
|
197
|
+
// Not admin - should throw forbidden error
|
|
198
|
+
await expectAsync(executor.execute({ value: 15 }, { user: { isAdmin: false } })).toBeRejected();
|
|
199
|
+
|
|
200
|
+
// Valid - should succeed
|
|
201
|
+
const result3 = await executor.execute({ value: 15 }, { user: { isAdmin: true } });
|
|
202
|
+
expect(result3.success).toBe(true);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe("Error handling", () => {
|
|
207
|
+
it("should handle tool errors gracefully", async () => {
|
|
208
|
+
const toolProps: FlinkToolProps = {
|
|
209
|
+
id: "test_tool",
|
|
210
|
+
description: "Test tool",
|
|
211
|
+
inputSchema: z.object({ input: z.string() }),
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const toolFn: FlinkTool<any> = async ({ input }) => {
|
|
215
|
+
return {
|
|
216
|
+
success: false,
|
|
217
|
+
error: "Something went wrong",
|
|
218
|
+
code: "TOOL_ERROR",
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const executor = new ToolExecutor(toolProps, toolFn, mockCtx);
|
|
223
|
+
|
|
224
|
+
const result = await executor.execute({ input: "test" });
|
|
225
|
+
|
|
226
|
+
expect(result.success).toBe(false);
|
|
227
|
+
if (!result.success) {
|
|
228
|
+
expect(result.error).toBe("Something went wrong");
|
|
229
|
+
expect(result.code).toBe("TOOL_ERROR");
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("should catch thrown errors", async () => {
|
|
234
|
+
const toolProps: FlinkToolProps = {
|
|
235
|
+
id: "test_tool",
|
|
236
|
+
description: "Test tool",
|
|
237
|
+
inputSchema: z.object({ input: z.string() }),
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const toolFn: FlinkTool<any> = async ({ input }) => {
|
|
241
|
+
throw new Error("Unexpected error");
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const executor = new ToolExecutor(toolProps, toolFn, mockCtx);
|
|
245
|
+
|
|
246
|
+
const result = await executor.execute({ input: "test" });
|
|
247
|
+
|
|
248
|
+
expect(result.success).toBe(false);
|
|
249
|
+
if (!result.success) {
|
|
250
|
+
expect(result.error).toContain("Tool execution failed");
|
|
251
|
+
expect(result.code).toBe("EXECUTION_ERROR");
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe("Tool schema generation", () => {
|
|
257
|
+
it("should generate tool schema for AI", () => {
|
|
258
|
+
const toolProps: FlinkToolProps = {
|
|
259
|
+
id: "get_weather",
|
|
260
|
+
description: "Get weather for a city",
|
|
261
|
+
inputSchema: z.object({
|
|
262
|
+
city: z.string().describe("City name"),
|
|
263
|
+
units: z.enum(["celsius", "fahrenheit"]).optional(),
|
|
264
|
+
}),
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const toolFn: FlinkTool<any> = async ({ input }) => {
|
|
268
|
+
return { success: true, data: {} };
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const executor = new ToolExecutor(toolProps, toolFn, mockCtx);
|
|
272
|
+
|
|
273
|
+
const schema = executor.getToolSchema();
|
|
274
|
+
|
|
275
|
+
expect(schema.name).toBe("get_weather");
|
|
276
|
+
expect(schema.description).toBe("Get weather for a city");
|
|
277
|
+
expect(schema.inputSchema).toBeDefined();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe("cross-schema $ref resolution", () => {
|
|
281
|
+
const allSchemas = {
|
|
282
|
+
"MyTool.Input": {
|
|
283
|
+
$id: "MyTool.Input",
|
|
284
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
285
|
+
type: "object",
|
|
286
|
+
properties: {
|
|
287
|
+
id: { type: "string" },
|
|
288
|
+
item: { $ref: "Shared.Item" },
|
|
289
|
+
},
|
|
290
|
+
required: ["id", "item"],
|
|
291
|
+
},
|
|
292
|
+
"Shared.Item": {
|
|
293
|
+
$id: "Shared.Item",
|
|
294
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
295
|
+
type: "object",
|
|
296
|
+
properties: {
|
|
297
|
+
name: { type: "string" },
|
|
298
|
+
tag: { $ref: "Shared.Tag" },
|
|
299
|
+
},
|
|
300
|
+
required: ["name"],
|
|
301
|
+
},
|
|
302
|
+
"Shared.Tag": {
|
|
303
|
+
$id: "Shared.Tag",
|
|
304
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
305
|
+
type: "string",
|
|
306
|
+
enum: ["foo", "bar"],
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
it("should resolve cross-schema $refs into $defs", () => {
|
|
311
|
+
const toolProps: FlinkToolProps = { id: "my-tool", description: "Test" };
|
|
312
|
+
const toolFn: FlinkTool<any> = async () => ({ success: true, data: {} });
|
|
313
|
+
const executor = new ToolExecutor(
|
|
314
|
+
toolProps,
|
|
315
|
+
toolFn,
|
|
316
|
+
mockCtx,
|
|
317
|
+
{ inputSchema: allSchemas["MyTool.Input"], inputTypeHint: "named" },
|
|
318
|
+
allSchemas
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
const { inputSchema } = executor.getToolSchema();
|
|
322
|
+
|
|
323
|
+
// $ref values should use JSON pointer format
|
|
324
|
+
expect(inputSchema.properties.item.$ref).toBe("#/$defs/Shared.Item");
|
|
325
|
+
|
|
326
|
+
// All transitively referenced schemas should be in $defs
|
|
327
|
+
expect(inputSchema.$defs).toBeDefined();
|
|
328
|
+
expect(inputSchema.$defs["Shared.Item"]).toBeDefined();
|
|
329
|
+
expect(inputSchema.$defs["Shared.Tag"]).toBeDefined();
|
|
330
|
+
|
|
331
|
+
// $refs inside $defs should also be rewritten
|
|
332
|
+
expect(inputSchema.$defs["Shared.Item"].properties.tag.$ref).toBe("#/$defs/Shared.Tag");
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it("should strip $id and $schema from inlined $defs", () => {
|
|
336
|
+
const toolProps: FlinkToolProps = { id: "my-tool", description: "Test" };
|
|
337
|
+
const toolFn: FlinkTool<any> = async () => ({ success: true, data: {} });
|
|
338
|
+
const executor = new ToolExecutor(
|
|
339
|
+
toolProps,
|
|
340
|
+
toolFn,
|
|
341
|
+
mockCtx,
|
|
342
|
+
{ inputSchema: allSchemas["MyTool.Input"], inputTypeHint: "named" },
|
|
343
|
+
allSchemas
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
const { inputSchema } = executor.getToolSchema();
|
|
347
|
+
|
|
348
|
+
expect(inputSchema.$defs["Shared.Item"].$id).toBeUndefined();
|
|
349
|
+
expect(inputSchema.$defs["Shared.Item"].$schema).toBeUndefined();
|
|
350
|
+
expect(inputSchema.$defs["Shared.Tag"].$id).toBeUndefined();
|
|
351
|
+
expect(inputSchema.$defs["Shared.Tag"].$schema).toBeUndefined();
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it("should strip $id and $schema from root schema", () => {
|
|
355
|
+
const toolProps: FlinkToolProps = { id: "my-tool", description: "Test" };
|
|
356
|
+
const toolFn: FlinkTool<any> = async () => ({ success: true, data: {} });
|
|
357
|
+
const executor = new ToolExecutor(
|
|
358
|
+
toolProps,
|
|
359
|
+
toolFn,
|
|
360
|
+
mockCtx,
|
|
361
|
+
{ inputSchema: allSchemas["MyTool.Input"], inputTypeHint: "named" },
|
|
362
|
+
allSchemas
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
const { inputSchema } = executor.getToolSchema();
|
|
366
|
+
|
|
367
|
+
expect(inputSchema.$id).toBeUndefined();
|
|
368
|
+
expect(inputSchema.$schema).toBeUndefined();
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it("should return schema unchanged when no cross-schema refs exist", () => {
|
|
372
|
+
const simpleSchema = {
|
|
373
|
+
$id: "Simple.Input",
|
|
374
|
+
type: "object",
|
|
375
|
+
properties: { name: { type: "string" } },
|
|
376
|
+
};
|
|
377
|
+
const toolProps: FlinkToolProps = { id: "simple-tool", description: "Test" };
|
|
378
|
+
const toolFn: FlinkTool<any> = async () => ({ success: true, data: {} });
|
|
379
|
+
const executor = new ToolExecutor(
|
|
380
|
+
toolProps,
|
|
381
|
+
toolFn,
|
|
382
|
+
mockCtx,
|
|
383
|
+
{ inputSchema: simpleSchema, inputTypeHint: "named" },
|
|
384
|
+
allSchemas
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
const { inputSchema } = executor.getToolSchema();
|
|
388
|
+
|
|
389
|
+
expect(inputSchema.$defs).toBeUndefined();
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
describe("Result formatting for AI", () => {
|
|
395
|
+
it("should format success result as JSON", () => {
|
|
396
|
+
const toolProps: FlinkToolProps = {
|
|
397
|
+
id: "test_tool",
|
|
398
|
+
description: "Test tool",
|
|
399
|
+
inputSchema: z.object({ input: z.string() }),
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
const toolFn: FlinkTool<any> = async ({ input }) => {
|
|
403
|
+
return { success: true, data: {} };
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const executor = new ToolExecutor(toolProps, toolFn, mockCtx);
|
|
407
|
+
|
|
408
|
+
const result: ToolResult<any> = {
|
|
409
|
+
success: true,
|
|
410
|
+
data: { temperature: 22, conditions: "sunny" },
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
const formatted = executor.formatResultForAI(result);
|
|
414
|
+
|
|
415
|
+
expect(formatted).toBe('{"temperature":22,"conditions":"sunny"}');
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it("should format error result with code", () => {
|
|
419
|
+
const toolProps: FlinkToolProps = {
|
|
420
|
+
id: "test_tool",
|
|
421
|
+
description: "Test tool",
|
|
422
|
+
inputSchema: z.object({ input: z.string() }),
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const toolFn: FlinkTool<any> = async ({ input }) => {
|
|
426
|
+
return { success: true, data: {} };
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const executor = new ToolExecutor(toolProps, toolFn, mockCtx);
|
|
430
|
+
|
|
431
|
+
const result: ToolResult<any> = {
|
|
432
|
+
success: false,
|
|
433
|
+
error: "API rate limit exceeded",
|
|
434
|
+
code: "RATE_LIMIT",
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const formatted = executor.formatResultForAI(result);
|
|
438
|
+
|
|
439
|
+
expect(formatted).toBe("Error: API rate limit exceeded (RATE_LIMIT)");
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it("should format error result without code", () => {
|
|
443
|
+
const toolProps: FlinkToolProps = {
|
|
444
|
+
id: "test_tool",
|
|
445
|
+
description: "Test tool",
|
|
446
|
+
inputSchema: z.object({ input: z.string() }),
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
const toolFn: FlinkTool<any> = async ({ input }) => {
|
|
450
|
+
return { success: true, data: {} };
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
const executor = new ToolExecutor(toolProps, toolFn, mockCtx);
|
|
454
|
+
|
|
455
|
+
const result: ToolResult<any> = {
|
|
456
|
+
success: false,
|
|
457
|
+
error: "Something went wrong",
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const formatted = executor.formatResultForAI(result);
|
|
461
|
+
|
|
462
|
+
expect(formatted).toBe("Error: Something went wrong");
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
});
|
|
@@ -26,7 +26,7 @@ describe("TypeScriptCompiler", () => {
|
|
|
26
26
|
expect(generatedFile.getText()).toContain(`import { autoRegisteredHandlers, HttpMethod } from "@flink-app/flink"`);
|
|
27
27
|
expect(generatedFile.getText()).toContain(`import * as GetCar_0 from "../src/handlers/GetCar"`);
|
|
28
28
|
expect(generatedFile.getText()).toContain(`export const handlers =`);
|
|
29
|
-
expect(generatedFile.getText()).toContain(`{handler: GetCar_0, assumedHttpMethod: HttpMethod.get}`);
|
|
29
|
+
expect(generatedFile.getText()).toContain(`{handler: GetCar_0, assumedHttpMethod: HttpMethod.get, __file: "src/handlers/GetCar.ts"}`);
|
|
30
30
|
// expect(generatedFile.getText()).toContain(
|
|
31
31
|
// `{routeProps: PostCar_5.Route, handlerFn: PostCar_5.default, assumedHttpMethod: HttpMethod.post}`
|
|
32
32
|
// );
|