@flink-app/flink 0.14.3 → 2.0.0-alpha.48
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 +66 -0
- package/cli/build.ts +8 -1
- package/cli/run.ts +8 -1
- package/dist/cli/build.js +8 -1
- package/dist/cli/run.js +8 -1
- package/dist/src/FlinkApp.d.ts +33 -0
- package/dist/src/FlinkApp.js +279 -35
- package/dist/src/FlinkContext.d.ts +21 -0
- package/dist/src/FlinkHttpHandler.d.ts +152 -9
- package/dist/src/FlinkHttpHandler.js +37 -1
- package/dist/src/TypeScriptCompiler.d.ts +42 -0
- package/dist/src/TypeScriptCompiler.js +346 -4
- package/dist/src/TypeScriptUtils.js +4 -0
- package/dist/src/ai/AgentRunner.d.ts +39 -0
- package/dist/src/ai/AgentRunner.js +625 -0
- package/dist/src/ai/FlinkAgent.d.ts +446 -0
- package/dist/src/ai/FlinkAgent.js +633 -0
- package/dist/src/ai/FlinkTool.d.ts +37 -0
- package/dist/src/ai/FlinkTool.js +2 -0
- package/dist/src/ai/LLMAdapter.d.ts +119 -0
- package/dist/src/ai/LLMAdapter.js +2 -0
- package/dist/src/ai/SubAgentExecutor.d.ts +36 -0
- package/dist/src/ai/SubAgentExecutor.js +220 -0
- package/dist/src/ai/ToolExecutor.d.ts +35 -0
- package/dist/src/ai/ToolExecutor.js +237 -0
- package/dist/src/ai/index.d.ts +5 -0
- package/dist/src/ai/index.js +21 -0
- package/dist/src/handlers/StreamWriterFactory.d.ts +20 -0
- package/dist/src/handlers/StreamWriterFactory.js +83 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +4 -0
- package/dist/src/utils.d.ts +30 -0
- package/dist/src/utils.js +52 -0
- package/package.json +16 -2
- package/readme.md +425 -0
- package/spec/AgentDuplicateDetection.spec.ts +112 -0
- package/spec/AgentRunner.spec.ts +527 -0
- package/spec/ConversationHooks.spec.ts +290 -0
- package/spec/FlinkAgent.spec.ts +310 -0
- package/spec/FlinkApp.onError.spec.ts +1 -2
- package/spec/FlinkApp.query.spec.ts +107 -0
- package/spec/FlinkApp.validationMode.spec.ts +155 -0
- package/spec/StreamingIntegration.spec.ts +138 -0
- package/spec/SubAgentSupport.spec.ts +941 -0
- package/spec/ToolExecutor.spec.ts +360 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCar.js +57 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCar2.js +59 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema.js +53 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema2.js +53 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema3.js +53 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithLiteralSchema.js +55 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithLiteralSchema2.js +55 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithSchemaInFile.js +58 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithSchemaInFile2.js +58 -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 +58 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchOnboardingSession.js +76 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchOrderWithComplexTypes.js +58 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchProductWithIntersection.js +59 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchUserWithUnion.js +59 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PostCar.js +55 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PostLogin.js +56 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PostLogout.js +55 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PutCar.js +55 -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 +1012 -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 +26 -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/FlinkResponse.js +2 -0
- package/spec/mock-project/dist/src/ai/AgentExecutor.js +279 -0
- package/spec/mock-project/dist/src/ai/AgentRunner.js +625 -0
- package/spec/mock-project/dist/src/ai/FlinkAgent.js +633 -0
- package/spec/mock-project/dist/src/ai/FlinkTool.js +2 -0
- package/spec/mock-project/dist/src/ai/LLMAdapter.js +2 -0
- package/spec/mock-project/dist/src/ai/SubAgentExecutor.js +220 -0
- package/spec/mock-project/dist/src/ai/ToolExecutor.js +237 -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/StreamWriterFactory.js +83 -0
- package/spec/mock-project/dist/src/index.js +17 -69
- package/spec/mock-project/dist/src/mock-data-generator.js +9 -0
- package/spec/mock-project/dist/src/utils.js +290 -0
- package/spec/mock-project/tsconfig.json +6 -1
- package/spec/testHelpers.ts +49 -0
- package/spec/utils.caseConversion.spec.ts +80 -0
- package/spec/utils.spec.ts +13 -13
- package/src/FlinkApp.ts +275 -8
- package/src/FlinkContext.ts +22 -0
- package/src/FlinkHttpHandler.ts +164 -10
- package/src/TypeScriptCompiler.ts +398 -7
- package/src/TypeScriptUtils.ts +5 -0
- package/src/ai/AgentRunner.ts +549 -0
- package/src/ai/FlinkAgent.ts +770 -0
- package/src/ai/FlinkTool.ts +40 -0
- package/src/ai/LLMAdapter.ts +96 -0
- package/src/ai/SubAgentExecutor.ts +199 -0
- package/src/ai/ToolExecutor.ts +193 -0
- package/src/ai/index.ts +5 -0
- package/src/handlers/StreamWriterFactory.ts +84 -0
- package/src/index.ts +4 -0
- package/src/utils.ts +52 -0
- package/tsconfig.json +6 -1
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
13
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
|
+
};
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.handlersPath = handlersPath;
|
|
43
|
+
exports.schemasPath = schemasPath;
|
|
44
|
+
exports.isRouteMatch = isRouteMatch;
|
|
45
|
+
exports.isError = isError;
|
|
46
|
+
exports.getHandlerFiles = getHandlerFiles;
|
|
47
|
+
exports.getSchemaFiles = getSchemaFiles;
|
|
48
|
+
exports.getCollectionNameForRepo = getCollectionNameForRepo;
|
|
49
|
+
exports.getRepoInstanceName = getRepoInstanceName;
|
|
50
|
+
exports.toKebabCase = toKebabCase;
|
|
51
|
+
exports.toSnakeCase = toSnakeCase;
|
|
52
|
+
exports.getHttpMethodFromHandlerName = getHttpMethodFromHandlerName;
|
|
53
|
+
exports.getJsDocComment = getJsDocComment;
|
|
54
|
+
exports.getPathParams = getPathParams;
|
|
55
|
+
exports.getDataAtPath = getDataAtPath;
|
|
56
|
+
exports.formatValidationErrors = formatValidationErrors;
|
|
57
|
+
exports.generateShortId = generateShortId;
|
|
58
|
+
var path_1 = require("path");
|
|
59
|
+
var tiny_glob_1 = __importDefault(require("tiny-glob"));
|
|
60
|
+
var FlinkHttpHandler_1 = require("./FlinkHttpHandler");
|
|
61
|
+
var FlinkLog_1 = require("./FlinkLog");
|
|
62
|
+
function handlersPath(appRoot) {
|
|
63
|
+
return (0, path_1.join)(appRoot, "src", "handlers");
|
|
64
|
+
}
|
|
65
|
+
function schemasPath(appRoot) {
|
|
66
|
+
return (0, path_1.join)(appRoot, "src", "schemas");
|
|
67
|
+
}
|
|
68
|
+
function isRouteMatch(req, routes) {
|
|
69
|
+
var match = routes.find(function (_a) {
|
|
70
|
+
var method = _a.method, path = _a.path;
|
|
71
|
+
var sameMethod = req.method.toLowerCase() === method;
|
|
72
|
+
var samePath = req.route.path === path;
|
|
73
|
+
return sameMethod && samePath;
|
|
74
|
+
});
|
|
75
|
+
return !!match;
|
|
76
|
+
}
|
|
77
|
+
function isError(message) {
|
|
78
|
+
return message.status && message.status > 399;
|
|
79
|
+
}
|
|
80
|
+
function getHandlerFiles(appRoot) {
|
|
81
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
82
|
+
var err_1;
|
|
83
|
+
return __generator(this, function (_a) {
|
|
84
|
+
switch (_a.label) {
|
|
85
|
+
case 0:
|
|
86
|
+
_a.trys.push([0, 2, , 3]);
|
|
87
|
+
return [4 /*yield*/, (0, tiny_glob_1.default)("**/*.ts", {
|
|
88
|
+
cwd: handlersPath(appRoot),
|
|
89
|
+
absolute: true,
|
|
90
|
+
})];
|
|
91
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
92
|
+
case 2:
|
|
93
|
+
err_1 = _a.sent();
|
|
94
|
+
FlinkLog_1.log.debug("Failed getting handler files: ".concat(err_1));
|
|
95
|
+
return [2 /*return*/, []];
|
|
96
|
+
case 3: return [2 /*return*/];
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function getSchemaFiles(appRoot) {
|
|
102
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
103
|
+
var err_2;
|
|
104
|
+
return __generator(this, function (_a) {
|
|
105
|
+
switch (_a.label) {
|
|
106
|
+
case 0:
|
|
107
|
+
_a.trys.push([0, 2, , 3]);
|
|
108
|
+
return [4 /*yield*/, (0, tiny_glob_1.default)("**/*.ts", {
|
|
109
|
+
cwd: schemasPath(appRoot),
|
|
110
|
+
absolute: true,
|
|
111
|
+
})];
|
|
112
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
113
|
+
case 2:
|
|
114
|
+
err_2 = _a.sent();
|
|
115
|
+
return [2 /*return*/, []];
|
|
116
|
+
case 3: return [2 /*return*/];
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function getCollectionNameForRepo(repoFilename) {
|
|
122
|
+
return repoFilename.replace("Repo.ts", "").toLowerCase();
|
|
123
|
+
}
|
|
124
|
+
function getRepoInstanceName(fn) {
|
|
125
|
+
var name = fn.split(".ts")[0];
|
|
126
|
+
return name.charAt(0).toLowerCase() + name.substr(1);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Convert PascalCase or camelCase to kebab-case
|
|
130
|
+
*
|
|
131
|
+
* Examples:
|
|
132
|
+
* - FooBarBaz → foo-bar-baz
|
|
133
|
+
* - CarAgent → car-agent
|
|
134
|
+
* - APIAgent → api-agent
|
|
135
|
+
* - HTMLParser → html-parser
|
|
136
|
+
* - IOManager → io-manager
|
|
137
|
+
*
|
|
138
|
+
* Handles:
|
|
139
|
+
* - Single uppercase followed by lowercase: CarAgent → car-agent
|
|
140
|
+
* - Multiple consecutive uppercase: APIAgent → api-agent
|
|
141
|
+
* - All caps: HTML → html
|
|
142
|
+
*/
|
|
143
|
+
function toKebabCase(str) {
|
|
144
|
+
return str
|
|
145
|
+
// Insert hyphen before uppercase letters that follow lowercase letters
|
|
146
|
+
// CarAgent → Car-Agent
|
|
147
|
+
.replace(/([a-z])([A-Z])/g, "$1-$2")
|
|
148
|
+
// Insert hyphen before uppercase letter that follows uppercase and precedes lowercase
|
|
149
|
+
// APIAgent → API-Agent
|
|
150
|
+
.replace(/([A-Z])([A-Z][a-z])/g, "$1-$2")
|
|
151
|
+
// Convert to lowercase
|
|
152
|
+
.toLowerCase();
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Convert kebab-case to snake_case
|
|
156
|
+
*
|
|
157
|
+
* Examples:
|
|
158
|
+
* - car-agent → car_agent
|
|
159
|
+
* - api-agent → api_agent
|
|
160
|
+
* - html-parser → html_parser
|
|
161
|
+
*/
|
|
162
|
+
function toSnakeCase(str) {
|
|
163
|
+
return str.replace(/-/g, "_");
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get http method from props or convention based on file name
|
|
167
|
+
* if it starts with i.e "GetFoo"
|
|
168
|
+
*/
|
|
169
|
+
function getHttpMethodFromHandlerName(handlerFilename) {
|
|
170
|
+
if (handlerFilename.includes(path_1.sep)) {
|
|
171
|
+
var split = handlerFilename.split(path_1.sep);
|
|
172
|
+
handlerFilename = split[split.length - 1];
|
|
173
|
+
}
|
|
174
|
+
handlerFilename = handlerFilename.toLocaleLowerCase();
|
|
175
|
+
if (handlerFilename.startsWith(FlinkHttpHandler_1.HttpMethod.get))
|
|
176
|
+
return FlinkHttpHandler_1.HttpMethod.get;
|
|
177
|
+
if (handlerFilename.startsWith(FlinkHttpHandler_1.HttpMethod.post))
|
|
178
|
+
return FlinkHttpHandler_1.HttpMethod.post;
|
|
179
|
+
if (handlerFilename.startsWith(FlinkHttpHandler_1.HttpMethod.put))
|
|
180
|
+
return FlinkHttpHandler_1.HttpMethod.put;
|
|
181
|
+
if (handlerFilename.startsWith(FlinkHttpHandler_1.HttpMethod.delete))
|
|
182
|
+
return FlinkHttpHandler_1.HttpMethod.delete;
|
|
183
|
+
if (handlerFilename.startsWith(FlinkHttpHandler_1.HttpMethod.patch))
|
|
184
|
+
return FlinkHttpHandler_1.HttpMethod.patch;
|
|
185
|
+
}
|
|
186
|
+
function getJsDocComment(comment) {
|
|
187
|
+
var rows = comment.split("\n").map(function (line) {
|
|
188
|
+
line = line.trim();
|
|
189
|
+
if (line.startsWith("//")) {
|
|
190
|
+
return line.replace("//", line).trim();
|
|
191
|
+
}
|
|
192
|
+
return line.replace(/\/\*\*|\*\/|\*/, "").trim();
|
|
193
|
+
});
|
|
194
|
+
return rows.join("\n").trim();
|
|
195
|
+
}
|
|
196
|
+
var pathParamsRegex = /:([a-zA-Z0-9]+)/g;
|
|
197
|
+
/**
|
|
198
|
+
* Returns array of path params from path string.
|
|
199
|
+
* For example `/user/:id` will return `["id"]`
|
|
200
|
+
* @param path
|
|
201
|
+
* @returns
|
|
202
|
+
*/
|
|
203
|
+
function getPathParams(path) {
|
|
204
|
+
var _a;
|
|
205
|
+
return ((_a = path.match(pathParamsRegex)) === null || _a === void 0 ? void 0 : _a.map(function (match) { return match.slice(1); })) || [];
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Extracts data at a given JSON path (e.g., "/jobs/5/result")
|
|
209
|
+
* Returns the value at that path, or undefined if not found
|
|
210
|
+
*/
|
|
211
|
+
function getDataAtPath(data, instancePath) {
|
|
212
|
+
if (!instancePath || instancePath === "/") {
|
|
213
|
+
return data;
|
|
214
|
+
}
|
|
215
|
+
var parts = instancePath.split("/").filter(function (p) { return p.length > 0; });
|
|
216
|
+
var current = data;
|
|
217
|
+
for (var _i = 0, parts_1 = parts; _i < parts_1.length; _i++) {
|
|
218
|
+
var part = parts_1[_i];
|
|
219
|
+
if (current === undefined || current === null) {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
current = current[part];
|
|
223
|
+
}
|
|
224
|
+
return current;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Formats validation errors with context about the problematic data
|
|
228
|
+
* @param errors AJV validation errors
|
|
229
|
+
* @param data The full data object that failed validation
|
|
230
|
+
* @param maxDataLength Maximum length of data to show (default 500)
|
|
231
|
+
*/
|
|
232
|
+
function formatValidationErrors(errors, data, maxDataLength) {
|
|
233
|
+
if (maxDataLength === void 0) { maxDataLength = 500; }
|
|
234
|
+
if (!errors || errors.length === 0) {
|
|
235
|
+
return "Unknown validation error";
|
|
236
|
+
}
|
|
237
|
+
var formatted = [];
|
|
238
|
+
// Group errors by instance path to avoid repetition
|
|
239
|
+
var errorsByPath = new Map();
|
|
240
|
+
for (var _i = 0, errors_1 = errors; _i < errors_1.length; _i++) {
|
|
241
|
+
var error = errors_1[_i];
|
|
242
|
+
var path = error.instancePath || "/";
|
|
243
|
+
if (!errorsByPath.has(path)) {
|
|
244
|
+
errorsByPath.set(path, []);
|
|
245
|
+
}
|
|
246
|
+
errorsByPath.get(path).push(error);
|
|
247
|
+
}
|
|
248
|
+
errorsByPath.forEach(function (pathErrors, path) {
|
|
249
|
+
var dataAtPath = getDataAtPath(data, path);
|
|
250
|
+
var dataStr = JSON.stringify(dataAtPath);
|
|
251
|
+
// Truncate if too long
|
|
252
|
+
if (dataStr.length > maxDataLength) {
|
|
253
|
+
dataStr = dataStr.substring(0, maxDataLength) + "... (truncated)";
|
|
254
|
+
}
|
|
255
|
+
formatted.push("\nPath: ".concat(path));
|
|
256
|
+
formatted.push("Data: ".concat(dataStr));
|
|
257
|
+
formatted.push("Errors:");
|
|
258
|
+
for (var _i = 0, pathErrors_1 = pathErrors; _i < pathErrors_1.length; _i++) {
|
|
259
|
+
var error = pathErrors_1[_i];
|
|
260
|
+
if (error.keyword === "required") {
|
|
261
|
+
formatted.push(" - Missing required property: ".concat(error.params.missingProperty));
|
|
262
|
+
}
|
|
263
|
+
else if (error.keyword === "type") {
|
|
264
|
+
formatted.push(" - Invalid type at ".concat(error.schemaPath, ": expected ").concat(error.params.type, ", got ").concat(typeof dataAtPath));
|
|
265
|
+
}
|
|
266
|
+
else if (error.keyword === "additionalProperties") {
|
|
267
|
+
formatted.push(" - Additional property not allowed: ".concat(error.params.additionalProperty));
|
|
268
|
+
}
|
|
269
|
+
else if (error.keyword === "anyOf" || error.keyword === "oneOf") {
|
|
270
|
+
formatted.push(" - ".concat(error.message));
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
formatted.push(" - ".concat(error.message, " (").concat(error.keyword, ")"));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
return formatted.join("\n");
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Generate a short random ID for nested conversations
|
|
281
|
+
* Format: 8 character alphanumeric string (e.g., "a3b7c9d2")
|
|
282
|
+
*/
|
|
283
|
+
function generateShortId() {
|
|
284
|
+
var chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
285
|
+
var result = "";
|
|
286
|
+
for (var i = 0; i < 8; i++) {
|
|
287
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
288
|
+
}
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
@@ -15,7 +15,12 @@
|
|
|
15
15
|
"noEmit": true,
|
|
16
16
|
"experimentalDecorators": true,
|
|
17
17
|
"checkJs": false,
|
|
18
|
-
"typeRoots": ["node_modules/@types", "node_modules/@flink-app"]
|
|
18
|
+
"typeRoots": ["node_modules/@types", "node_modules/@flink-app"],
|
|
19
|
+
"baseUrl": ".",
|
|
20
|
+
"paths": {
|
|
21
|
+
"@flink-app/flink": ["../../src/index.ts"],
|
|
22
|
+
"@flink-app/flink/*": ["../../src/*"]
|
|
23
|
+
}
|
|
19
24
|
},
|
|
20
25
|
"include": ["./src/**/*.ts", "./.flink/**/*.ts"],
|
|
21
26
|
"exclude": ["./node_modules/*"]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { LLMAdapter, LLMStreamChunk } from "../src/ai/LLMAdapter";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mock response data for tests
|
|
5
|
+
*/
|
|
6
|
+
export interface MockLLMResponse {
|
|
7
|
+
textContent?: string;
|
|
8
|
+
toolCalls: Array<{ id: string; name: string; input: any }>;
|
|
9
|
+
usage: { inputTokens: number; outputTokens: number };
|
|
10
|
+
stopReason: "end_turn" | "tool_use" | "max_tokens";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Helper to create a streaming mock LLM adapter from responses.
|
|
15
|
+
* LLM adapters are streaming-only, so this creates a mock stream() implementation.
|
|
16
|
+
*
|
|
17
|
+
* @param responses Array of responses to return on subsequent calls
|
|
18
|
+
* @returns LLMAdapter with stream method implemented
|
|
19
|
+
*/
|
|
20
|
+
export function createStreamingMock(responses: MockLLMResponse[]): LLMAdapter {
|
|
21
|
+
let callIndex = 0;
|
|
22
|
+
|
|
23
|
+
const streamGenerator = async function* () {
|
|
24
|
+
const response = responses[callIndex] || responses[responses.length - 1];
|
|
25
|
+
callIndex++;
|
|
26
|
+
|
|
27
|
+
// Yield text content if present
|
|
28
|
+
if (response.textContent) {
|
|
29
|
+
yield { type: "text", delta: response.textContent } as LLMStreamChunk;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Yield tool calls if present
|
|
33
|
+
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
34
|
+
for (const toolCall of response.toolCalls) {
|
|
35
|
+
yield { type: "tool_call", toolCall } as LLMStreamChunk;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Yield usage information
|
|
40
|
+
yield { type: "usage", usage: response.usage } as LLMStreamChunk;
|
|
41
|
+
|
|
42
|
+
// Yield done event
|
|
43
|
+
yield { type: "done", stopReason: response.stopReason } as LLMStreamChunk;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
stream: jasmine.createSpy("stream").and.callFake(streamGenerator),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { toKebabCase, toSnakeCase } from "../src/utils";
|
|
2
|
+
|
|
3
|
+
describe("Case Conversion Utilities", () => {
|
|
4
|
+
describe("toKebabCase", () => {
|
|
5
|
+
it("should handle standard PascalCase", () => {
|
|
6
|
+
expect(toKebabCase("CarAgent")).toBe("car-agent");
|
|
7
|
+
expect(toKebabCase("FooBarBaz")).toBe("foo-bar-baz");
|
|
8
|
+
expect(toKebabCase("CarPricingAgent")).toBe("car-pricing-agent");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("should handle consecutive capitals (acronyms)", () => {
|
|
12
|
+
expect(toKebabCase("APIAgent")).toBe("api-agent");
|
|
13
|
+
expect(toKebabCase("HTMLParser")).toBe("html-parser");
|
|
14
|
+
expect(toKebabCase("XMLHttpRequest")).toBe("xml-http-request");
|
|
15
|
+
expect(toKebabCase("IOManager")).toBe("io-manager");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("should handle camelCase with acronyms", () => {
|
|
19
|
+
expect(toKebabCase("getHTMLContent")).toBe("get-html-content");
|
|
20
|
+
expect(toKebabCase("parseJSONData")).toBe("parse-json-data");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should handle single and double letter strings", () => {
|
|
24
|
+
expect(toKebabCase("A")).toBe("a");
|
|
25
|
+
expect(toKebabCase("AB")).toBe("ab");
|
|
26
|
+
expect(toKebabCase("ABC")).toBe("abc");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should handle already lowercase strings", () => {
|
|
30
|
+
expect(toKebabCase("a")).toBe("a");
|
|
31
|
+
expect(toKebabCase("abc")).toBe("abc");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should handle camelCase starting with lowercase", () => {
|
|
35
|
+
expect(toKebabCase("aB")).toBe("a-b");
|
|
36
|
+
expect(toKebabCase("carAgent")).toBe("car-agent");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should handle real-world agent class names", () => {
|
|
40
|
+
expect(toKebabCase("OrchestratorAgent")).toBe("orchestrator-agent");
|
|
41
|
+
expect(toKebabCase("CarRecommendationAgent")).toBe("car-recommendation-agent");
|
|
42
|
+
expect(toKebabCase("UserAuthenticationAgent")).toBe("user-authentication-agent");
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("toSnakeCase", () => {
|
|
47
|
+
it("should convert kebab-case to snake_case", () => {
|
|
48
|
+
expect(toSnakeCase("car-agent")).toBe("car_agent");
|
|
49
|
+
expect(toSnakeCase("foo-bar-baz")).toBe("foo_bar_baz");
|
|
50
|
+
expect(toSnakeCase("api-agent")).toBe("api_agent");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should handle single words", () => {
|
|
54
|
+
expect(toSnakeCase("car")).toBe("car");
|
|
55
|
+
expect(toSnakeCase("a")).toBe("a");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should handle multiple hyphens", () => {
|
|
59
|
+
expect(toSnakeCase("xml-http-request")).toBe("xml_http_request");
|
|
60
|
+
expect(toSnakeCase("car-pricing-agent")).toBe("car_pricing_agent");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should convert agent IDs to tool names correctly", () => {
|
|
64
|
+
// Simulating the full conversion pipeline
|
|
65
|
+
const agentClass = "CarAgent";
|
|
66
|
+
const kebab = toKebabCase(agentClass);
|
|
67
|
+
const toolName = `ask_${toSnakeCase(kebab)}`;
|
|
68
|
+
|
|
69
|
+
expect(toolName).toBe("ask_car_agent");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should handle acronym-based agent names", () => {
|
|
73
|
+
const agentClass = "APIAgent";
|
|
74
|
+
const kebab = toKebabCase(agentClass);
|
|
75
|
+
const toolName = `ask_${toSnakeCase(kebab)}`;
|
|
76
|
+
|
|
77
|
+
expect(toolName).toBe("ask_api_agent");
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
package/spec/utils.spec.ts
CHANGED
|
@@ -48,9 +48,9 @@ describe("Utils", () => {
|
|
|
48
48
|
|
|
49
49
|
const formatted = formatValidationErrors(errors, data);
|
|
50
50
|
|
|
51
|
-
expect(formatted).toContain(
|
|
52
|
-
expect(formatted).toContain(
|
|
53
|
-
expect(formatted).toContain(
|
|
51
|
+
expect(formatted).toContain("Path: /name");
|
|
52
|
+
expect(formatted).toContain("Data: 123");
|
|
53
|
+
expect(formatted).toContain("expected string");
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
it("should format missing required property errors", () => {
|
|
@@ -67,9 +67,9 @@ describe("Utils", () => {
|
|
|
67
67
|
|
|
68
68
|
const formatted = formatValidationErrors(errors, data);
|
|
69
69
|
|
|
70
|
-
expect(formatted).toContain(
|
|
70
|
+
expect(formatted).toContain("Path: /user");
|
|
71
71
|
expect(formatted).toContain('Data: {"name":"John"}');
|
|
72
|
-
expect(formatted).toContain(
|
|
72
|
+
expect(formatted).toContain("Missing required property: email");
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
it("should format errors for array items", () => {
|
|
@@ -95,9 +95,9 @@ describe("Utils", () => {
|
|
|
95
95
|
|
|
96
96
|
const formatted = formatValidationErrors(errors, data);
|
|
97
97
|
|
|
98
|
-
expect(formatted).toContain(
|
|
98
|
+
expect(formatted).toContain("Path: /jobs/5/result");
|
|
99
99
|
expect(formatted).toContain('Data: {"seed":"wrong-type"}');
|
|
100
|
-
expect(formatted).toContain(
|
|
100
|
+
expect(formatted).toContain("must match a schema in anyOf");
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
it("should truncate very long data", () => {
|
|
@@ -115,7 +115,7 @@ describe("Utils", () => {
|
|
|
115
115
|
|
|
116
116
|
const formatted = formatValidationErrors(errors, data, 100);
|
|
117
117
|
|
|
118
|
-
expect(formatted).toContain(
|
|
118
|
+
expect(formatted).toContain("(truncated)");
|
|
119
119
|
expect(formatted.length).toBeLessThan(500);
|
|
120
120
|
});
|
|
121
121
|
|
|
@@ -145,8 +145,8 @@ describe("Utils", () => {
|
|
|
145
145
|
expect(pathCount).toBe(1);
|
|
146
146
|
|
|
147
147
|
// But should list both missing properties
|
|
148
|
-
expect(formatted).toContain(
|
|
149
|
-
expect(formatted).toContain(
|
|
148
|
+
expect(formatted).toContain("Missing required property: email");
|
|
149
|
+
expect(formatted).toContain("Missing required property: age");
|
|
150
150
|
});
|
|
151
151
|
|
|
152
152
|
it("should show specific additional property names", () => {
|
|
@@ -171,9 +171,9 @@ describe("Utils", () => {
|
|
|
171
171
|
const formatted = formatValidationErrors(errors, data);
|
|
172
172
|
|
|
173
173
|
// This formatted output is used in HTTP error responses (400/500) in the error.detail field
|
|
174
|
-
expect(formatted).toContain(
|
|
175
|
-
expect(formatted).toContain(
|
|
176
|
-
expect(formatted).toContain(
|
|
174
|
+
expect(formatted).toContain("Path: /");
|
|
175
|
+
expect(formatted).toContain("Additional property not allowed: extraField1");
|
|
176
|
+
expect(formatted).toContain("Additional property not allowed: extraField2");
|
|
177
177
|
});
|
|
178
178
|
});
|
|
179
179
|
});
|