@flink-app/flink 1.0.0 → 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 +6 -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 +247 -27
- package/dist/src/FlinkContext.d.ts +21 -0
- package/dist/src/FlinkHttpHandler.d.ts +90 -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 +14 -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/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 +251 -7
- package/src/FlinkContext.ts +22 -0
- package/src/FlinkHttpHandler.ts +100 -2
- 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,633 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
14
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
15
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
16
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
17
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
18
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
19
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
23
|
+
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);
|
|
24
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
25
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
26
|
+
function step(op) {
|
|
27
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
28
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
29
|
+
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;
|
|
30
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
31
|
+
switch (op[0]) {
|
|
32
|
+
case 0: case 1: t = op; break;
|
|
33
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
34
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
35
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
36
|
+
default:
|
|
37
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
38
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
39
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
40
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
41
|
+
if (t[2]) _.ops.pop();
|
|
42
|
+
_.trys.pop(); continue;
|
|
43
|
+
}
|
|
44
|
+
op = body.call(thisArg, _);
|
|
45
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
46
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
|
|
50
|
+
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
|
51
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
52
|
+
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
|
53
|
+
return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
|
|
54
|
+
function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
|
|
55
|
+
function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
|
|
56
|
+
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
|
57
|
+
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
|
58
|
+
function fulfill(value) { resume("next", value); }
|
|
59
|
+
function reject(value) { resume("throw", value); }
|
|
60
|
+
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
|
61
|
+
};
|
|
62
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
63
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
64
|
+
var m = o[Symbol.asyncIterator], i;
|
|
65
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
66
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
67
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
68
|
+
};
|
|
69
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
70
|
+
exports.FlinkAgent = void 0;
|
|
71
|
+
var FlinkLog_1 = require("../FlinkLog");
|
|
72
|
+
var utils_1 = require("../utils");
|
|
73
|
+
var AgentRunner_1 = require("./AgentRunner");
|
|
74
|
+
var FlinkErrors_1 = require("../FlinkErrors");
|
|
75
|
+
/**
|
|
76
|
+
* Base class for Flink agents (similar to FlinkRepo pattern)
|
|
77
|
+
*
|
|
78
|
+
* Agents extend this class and define their configuration as properties.
|
|
79
|
+
* Auto-registered by scanning src/agents/ directory.
|
|
80
|
+
*
|
|
81
|
+
* Tool references are validated at startup to ensure all referenced tools exist.
|
|
82
|
+
*
|
|
83
|
+
* Agents define their own domain-specific entry points that call `this.execute()`.
|
|
84
|
+
* This provides better type safety and developer experience than a generic `run()` method.
|
|
85
|
+
*
|
|
86
|
+
* ## Lifecycle Hooks
|
|
87
|
+
*
|
|
88
|
+
* Agents support lifecycle hooks for advanced orchestration:
|
|
89
|
+
* - `beforeRun`: Load conversation history, prepare context
|
|
90
|
+
* - `onStep`: Save state after each LLM turn
|
|
91
|
+
* - `afterRun`: Persist final conversation state
|
|
92
|
+
* - `transformSubAgentInput`: Compact/brief context before delegating to sub-agents
|
|
93
|
+
* - `onSubAgentCall`: Log/track delegations
|
|
94
|
+
* - `onSubAgentComplete`: Process sub-agent results
|
|
95
|
+
*
|
|
96
|
+
* Example:
|
|
97
|
+
* ```typescript
|
|
98
|
+
* export default class CarAgent extends FlinkAgent<AppCtx> {
|
|
99
|
+
* id = "car-agent"; // Optional: defaults to kebab-case class name "car-agent"
|
|
100
|
+
* description = "Expert in car models";
|
|
101
|
+
* instructions = "You are a car expert...";
|
|
102
|
+
* tools = ["get-cars-tool"]; // Validated at startup
|
|
103
|
+
* agents = [UserAgent, "external-agent"]; // Can use class references or strings
|
|
104
|
+
*
|
|
105
|
+
* // Domain-specific entry points with proper types
|
|
106
|
+
* async searchByBrand(brand: string) {
|
|
107
|
+
* const response = this.execute({
|
|
108
|
+
* message: `Find all ${brand} cars`
|
|
109
|
+
* });
|
|
110
|
+
* return await response.result;
|
|
111
|
+
* }
|
|
112
|
+
*
|
|
113
|
+
* async compareModels(model1: string, model2: string) {
|
|
114
|
+
* const response = this.execute({
|
|
115
|
+
* message: `Compare ${model1} and ${model2}`
|
|
116
|
+
* });
|
|
117
|
+
* return await response.result;
|
|
118
|
+
* }
|
|
119
|
+
*
|
|
120
|
+
* // Hook: Compact context when delegating to sub-agents
|
|
121
|
+
* protected async transformSubAgentInput(subAgentId, input, context) {
|
|
122
|
+
* const summary = await this.summarizeContext(context.conversationId);
|
|
123
|
+
* return { ...input, query: `Context: ${summary}\n\n${input.query}` };
|
|
124
|
+
* }
|
|
125
|
+
* }
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
var FlinkAgent = /** @class */ (function () {
|
|
129
|
+
function FlinkAgent() {
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Internal initialization called by FlinkApp
|
|
133
|
+
* @internal
|
|
134
|
+
*/
|
|
135
|
+
FlinkAgent.prototype.__init = function (llmAdapters, tools) {
|
|
136
|
+
this._llmAdapters = llmAdapters;
|
|
137
|
+
this._tools = tools;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Bind a user to this agent for permission checks
|
|
141
|
+
*
|
|
142
|
+
* This creates a new agent instance with the user bound, allowing clean API:
|
|
143
|
+
* ```typescript
|
|
144
|
+
* const result = await ctx.agents.carAgent
|
|
145
|
+
* .withUser(req.user)
|
|
146
|
+
* .searchByBrand("Volvo");
|
|
147
|
+
* ```
|
|
148
|
+
*
|
|
149
|
+
* The bound user is used for:
|
|
150
|
+
* - Agent-level permission checks
|
|
151
|
+
* - Tool filtering (only allowed tools shown to LLM)
|
|
152
|
+
* - Tool-level permission checks
|
|
153
|
+
*/
|
|
154
|
+
FlinkAgent.prototype.withUser = function (user) {
|
|
155
|
+
var bound = Object.create(Object.getPrototypeOf(this));
|
|
156
|
+
Object.assign(bound, this);
|
|
157
|
+
bound._boundUser = user;
|
|
158
|
+
bound.runner = undefined; // Clear runner cache to use new user
|
|
159
|
+
// Explicitly ensure ctx and internal properties are copied (in case they're not enumerable)
|
|
160
|
+
if (this.ctx) {
|
|
161
|
+
bound.ctx = this.ctx;
|
|
162
|
+
}
|
|
163
|
+
if (this._llmAdapters) {
|
|
164
|
+
bound._llmAdapters = this._llmAdapters;
|
|
165
|
+
}
|
|
166
|
+
if (this._tools) {
|
|
167
|
+
bound._tools = this._tools;
|
|
168
|
+
}
|
|
169
|
+
if (this._boundUserPermissions !== undefined) {
|
|
170
|
+
bound._boundUserPermissions = this._boundUserPermissions;
|
|
171
|
+
}
|
|
172
|
+
return bound;
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* Bind resolved permissions to this agent for permission checks
|
|
176
|
+
*
|
|
177
|
+
* This creates a new agent instance with permissions bound, allowing clean API:
|
|
178
|
+
* ```typescript
|
|
179
|
+
* const result = await ctx.agents.carAgent
|
|
180
|
+
* .withUser(req.user)
|
|
181
|
+
* .withPermissions(req.userPermissions) // Resolved permissions from auth plugin
|
|
182
|
+
* .searchByBrand("Volvo");
|
|
183
|
+
* ```
|
|
184
|
+
*
|
|
185
|
+
* The bound permissions are used for:
|
|
186
|
+
* - Tool filtering (only allowed tools shown to LLM)
|
|
187
|
+
* - Tool-level permission checks
|
|
188
|
+
*
|
|
189
|
+
* Permissions are typically populated by auth plugins during authentication
|
|
190
|
+
* based on roles, dynamic roles, or custom permission resolution.
|
|
191
|
+
*/
|
|
192
|
+
FlinkAgent.prototype.withPermissions = function (userPermissions) {
|
|
193
|
+
var bound = Object.create(Object.getPrototypeOf(this));
|
|
194
|
+
Object.assign(bound, this);
|
|
195
|
+
bound._boundUserPermissions = userPermissions;
|
|
196
|
+
bound.runner = undefined; // Clear runner cache to use new permissions
|
|
197
|
+
// Explicitly ensure ctx and internal properties are copied (in case they're not enumerable)
|
|
198
|
+
if (this.ctx) {
|
|
199
|
+
bound.ctx = this.ctx;
|
|
200
|
+
}
|
|
201
|
+
if (this._llmAdapters) {
|
|
202
|
+
bound._llmAdapters = this._llmAdapters;
|
|
203
|
+
}
|
|
204
|
+
if (this._tools) {
|
|
205
|
+
bound._tools = this._tools;
|
|
206
|
+
}
|
|
207
|
+
if (this._boundUser !== undefined) {
|
|
208
|
+
bound._boundUser = this._boundUser;
|
|
209
|
+
}
|
|
210
|
+
return bound;
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* Override the LLM adapter for this agent
|
|
214
|
+
*
|
|
215
|
+
* This creates a new agent instance with a different LLM adapter, allowing runtime selection:
|
|
216
|
+
* ```typescript
|
|
217
|
+
* const result = await ctx.agents.carAgent
|
|
218
|
+
* .withUser(req.user)
|
|
219
|
+
* .withLlm("fast") // Use fast LLM instead of default
|
|
220
|
+
* .searchByBrand("Volvo");
|
|
221
|
+
* ```
|
|
222
|
+
*
|
|
223
|
+
* The LLM adapter ID must be registered in FlinkApp's ai.llms configuration.
|
|
224
|
+
*
|
|
225
|
+
* @param adapterId - The ID of the LLM adapter to use (e.g., "default", "fake", "fast", "anthropic")
|
|
226
|
+
* @returns A new agent instance with the specified LLM adapter
|
|
227
|
+
*/
|
|
228
|
+
FlinkAgent.prototype.withLlm = function (adapterId) {
|
|
229
|
+
var bound = Object.create(Object.getPrototypeOf(this));
|
|
230
|
+
Object.assign(bound, this);
|
|
231
|
+
// Override the model configuration with the new adapter ID
|
|
232
|
+
bound.model = __assign(__assign({}, (this.model || {})), { adapterId: adapterId });
|
|
233
|
+
bound.runner = undefined; // Clear runner cache to use new adapter
|
|
234
|
+
// Explicitly ensure ctx and internal properties are copied (in case they're not enumerable)
|
|
235
|
+
if (this.ctx) {
|
|
236
|
+
bound.ctx = this.ctx;
|
|
237
|
+
}
|
|
238
|
+
if (this._llmAdapters) {
|
|
239
|
+
bound._llmAdapters = this._llmAdapters;
|
|
240
|
+
}
|
|
241
|
+
if (this._tools) {
|
|
242
|
+
bound._tools = this._tools;
|
|
243
|
+
}
|
|
244
|
+
if (this._boundUser !== undefined) {
|
|
245
|
+
bound._boundUser = this._boundUser;
|
|
246
|
+
}
|
|
247
|
+
if (this._boundUserPermissions !== undefined) {
|
|
248
|
+
bound._boundUserPermissions = this._boundUserPermissions;
|
|
249
|
+
}
|
|
250
|
+
return bound;
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* Public execution method for external callers (handlers, sub-agents, etc.)
|
|
254
|
+
*
|
|
255
|
+
* Use this when calling an agent from outside the agent class.
|
|
256
|
+
* For internal use within agent subclasses, use `execute()` instead.
|
|
257
|
+
*/
|
|
258
|
+
FlinkAgent.prototype.run = function (input) {
|
|
259
|
+
return this.execute(input);
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
262
|
+
* Internal execution method - supports both awaiting and streaming
|
|
263
|
+
*
|
|
264
|
+
* Uses lazy generator pattern (similar to Vercel AI SDK) to allow
|
|
265
|
+
* multiple consumption without re-execution.
|
|
266
|
+
*
|
|
267
|
+
* Agents call this method from their run() implementation to execute the AI logic.
|
|
268
|
+
*
|
|
269
|
+
* Examples:
|
|
270
|
+
* const response = this.execute({ message: "Hello" });
|
|
271
|
+
* const result = await response.result; // Await final result
|
|
272
|
+
* for await (const text of response.textStream) { ... } // Stream text
|
|
273
|
+
* for await (const chunk of response.fullStream) { ... } // Stream all events
|
|
274
|
+
*/
|
|
275
|
+
FlinkAgent.prototype.execute = function (input) {
|
|
276
|
+
var _this = this;
|
|
277
|
+
var _a, _b, _c, _d, _e, _f;
|
|
278
|
+
// Use bound user if not explicitly provided in input
|
|
279
|
+
var user = (_a = input.user) !== null && _a !== void 0 ? _a : this._boundUser;
|
|
280
|
+
var userPermissions = (_b = input.userPermissions) !== null && _b !== void 0 ? _b : this._boundUserPermissions;
|
|
281
|
+
var executeInput = __assign(__assign({}, input), { user: user, userPermissions: userPermissions });
|
|
282
|
+
// Permission check
|
|
283
|
+
if (this.permissions) {
|
|
284
|
+
this.checkPermissionsSync(user, userPermissions);
|
|
285
|
+
}
|
|
286
|
+
// Build execution context
|
|
287
|
+
var execContext = {
|
|
288
|
+
agentId: this.getAgentId(),
|
|
289
|
+
conversationId: executeInput.conversationId,
|
|
290
|
+
user: user,
|
|
291
|
+
isSubAgent: ((_c = executeInput.metadata) === null || _c === void 0 ? void 0 : _c.isSubAgentCall) === true,
|
|
292
|
+
parentAgentId: (_d = executeInput.metadata) === null || _d === void 0 ? void 0 : _d.parentAgentId,
|
|
293
|
+
subAgentDepth: (_f = (_e = executeInput.metadata) === null || _e === void 0 ? void 0 : _e.subAgentDepth) !== null && _f !== void 0 ? _f : 0,
|
|
294
|
+
metadata: executeInput.metadata,
|
|
295
|
+
};
|
|
296
|
+
// Call optional beforeRun hook with context
|
|
297
|
+
if (this.beforeRun) {
|
|
298
|
+
var result = this.beforeRun(executeInput, execContext);
|
|
299
|
+
if (result instanceof Promise) {
|
|
300
|
+
// If beforeRun is async, we need to handle it properly
|
|
301
|
+
// For now, we'll let the runner handle async hooks
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
var runner = this.getRunner();
|
|
305
|
+
FlinkLog_1.log.debug("Running agent ".concat(this.constructor.name));
|
|
306
|
+
// Lazy evaluation - generator only starts when first consumed
|
|
307
|
+
var baseGenerator = null;
|
|
308
|
+
var buffer = [];
|
|
309
|
+
var done = false;
|
|
310
|
+
var fetchPromise = null; // Prevent concurrent fetches
|
|
311
|
+
var getBaseGenerator = function () {
|
|
312
|
+
if (!baseGenerator) {
|
|
313
|
+
baseGenerator = runner.streamGenerator(executeInput);
|
|
314
|
+
}
|
|
315
|
+
return baseGenerator;
|
|
316
|
+
};
|
|
317
|
+
// Fetch next chunk from base generator (only one consumer at a time)
|
|
318
|
+
var fetchNext = function () { return __awaiter(_this, void 0, void 0, function () {
|
|
319
|
+
var _this = this;
|
|
320
|
+
return __generator(this, function (_a) {
|
|
321
|
+
switch (_a.label) {
|
|
322
|
+
case 0:
|
|
323
|
+
if (!fetchPromise) return [3 /*break*/, 2];
|
|
324
|
+
// Another iterator is already fetching, wait for it
|
|
325
|
+
return [4 /*yield*/, fetchPromise];
|
|
326
|
+
case 1:
|
|
327
|
+
// Another iterator is already fetching, wait for it
|
|
328
|
+
_a.sent();
|
|
329
|
+
return [2 /*return*/];
|
|
330
|
+
case 2:
|
|
331
|
+
if (done) {
|
|
332
|
+
return [2 /*return*/];
|
|
333
|
+
}
|
|
334
|
+
fetchPromise = (function () { return __awaiter(_this, void 0, void 0, function () {
|
|
335
|
+
var gen, _a, value, isDone;
|
|
336
|
+
return __generator(this, function (_b) {
|
|
337
|
+
switch (_b.label) {
|
|
338
|
+
case 0:
|
|
339
|
+
gen = getBaseGenerator();
|
|
340
|
+
return [4 /*yield*/, gen.next()];
|
|
341
|
+
case 1:
|
|
342
|
+
_a = _b.sent(), value = _a.value, isDone = _a.done;
|
|
343
|
+
if (isDone) {
|
|
344
|
+
done = true;
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
buffer.push(value);
|
|
348
|
+
}
|
|
349
|
+
return [2 /*return*/];
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
}); })();
|
|
353
|
+
return [4 /*yield*/, fetchPromise];
|
|
354
|
+
case 3:
|
|
355
|
+
_a.sent();
|
|
356
|
+
fetchPromise = null;
|
|
357
|
+
return [2 /*return*/];
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}); };
|
|
361
|
+
// Create independent iterators that share buffered chunks
|
|
362
|
+
var createIterator = function () {
|
|
363
|
+
var index = 0;
|
|
364
|
+
return (function () {
|
|
365
|
+
return __asyncGenerator(this, arguments, function () {
|
|
366
|
+
return __generator(this, function (_a) {
|
|
367
|
+
switch (_a.label) {
|
|
368
|
+
case 0:
|
|
369
|
+
if (!true) return [3 /*break*/, 9];
|
|
370
|
+
if (!(index < buffer.length)) return [3 /*break*/, 3];
|
|
371
|
+
return [4 /*yield*/, __await(buffer[index++])];
|
|
372
|
+
case 1: return [4 /*yield*/, _a.sent()];
|
|
373
|
+
case 2:
|
|
374
|
+
_a.sent();
|
|
375
|
+
return [3 /*break*/, 0];
|
|
376
|
+
case 3:
|
|
377
|
+
// If already done, exit
|
|
378
|
+
if (done) {
|
|
379
|
+
return [3 /*break*/, 9];
|
|
380
|
+
}
|
|
381
|
+
// Fetch next chunk (synchronized)
|
|
382
|
+
return [4 /*yield*/, __await(fetchNext())];
|
|
383
|
+
case 4:
|
|
384
|
+
// Fetch next chunk (synchronized)
|
|
385
|
+
_a.sent();
|
|
386
|
+
if (!(index < buffer.length)) return [3 /*break*/, 7];
|
|
387
|
+
return [4 /*yield*/, __await(buffer[index++])];
|
|
388
|
+
case 5: return [4 /*yield*/, _a.sent()];
|
|
389
|
+
case 6:
|
|
390
|
+
_a.sent();
|
|
391
|
+
return [3 /*break*/, 8];
|
|
392
|
+
case 7:
|
|
393
|
+
if (done) {
|
|
394
|
+
return [3 /*break*/, 9];
|
|
395
|
+
}
|
|
396
|
+
_a.label = 8;
|
|
397
|
+
case 8: return [3 /*break*/, 0];
|
|
398
|
+
case 9: return [2 /*return*/];
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
})();
|
|
403
|
+
};
|
|
404
|
+
return {
|
|
405
|
+
result: this.consumeAsResult(createIterator()),
|
|
406
|
+
textStream: this.consumeAsTextStream(createIterator()),
|
|
407
|
+
fullStream: createIterator(),
|
|
408
|
+
};
|
|
409
|
+
};
|
|
410
|
+
/**
|
|
411
|
+
* Consume stream as final result (for await pattern)
|
|
412
|
+
*/
|
|
413
|
+
FlinkAgent.prototype.consumeAsResult = function (stream) {
|
|
414
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
415
|
+
var result, chunk, e_1_1, err_1;
|
|
416
|
+
var _a, stream_1, stream_1_1;
|
|
417
|
+
var _b, e_1, _c, _d;
|
|
418
|
+
return __generator(this, function (_e) {
|
|
419
|
+
switch (_e.label) {
|
|
420
|
+
case 0:
|
|
421
|
+
_e.trys.push([0, 13, , 14]);
|
|
422
|
+
_e.label = 1;
|
|
423
|
+
case 1:
|
|
424
|
+
_e.trys.push([1, 6, 7, 12]);
|
|
425
|
+
_a = true, stream_1 = __asyncValues(stream);
|
|
426
|
+
_e.label = 2;
|
|
427
|
+
case 2: return [4 /*yield*/, stream_1.next()];
|
|
428
|
+
case 3:
|
|
429
|
+
if (!(stream_1_1 = _e.sent(), _b = stream_1_1.done, !_b)) return [3 /*break*/, 5];
|
|
430
|
+
_d = stream_1_1.value;
|
|
431
|
+
_a = false;
|
|
432
|
+
chunk = _d;
|
|
433
|
+
if (chunk.type === "complete") {
|
|
434
|
+
result = chunk.result;
|
|
435
|
+
return [3 /*break*/, 5]; // Exit early once we have the result
|
|
436
|
+
}
|
|
437
|
+
_e.label = 4;
|
|
438
|
+
case 4:
|
|
439
|
+
_a = true;
|
|
440
|
+
return [3 /*break*/, 2];
|
|
441
|
+
case 5: return [3 /*break*/, 12];
|
|
442
|
+
case 6:
|
|
443
|
+
e_1_1 = _e.sent();
|
|
444
|
+
e_1 = { error: e_1_1 };
|
|
445
|
+
return [3 /*break*/, 12];
|
|
446
|
+
case 7:
|
|
447
|
+
_e.trys.push([7, , 10, 11]);
|
|
448
|
+
if (!(!_a && !_b && (_c = stream_1.return))) return [3 /*break*/, 9];
|
|
449
|
+
return [4 /*yield*/, _c.call(stream_1)];
|
|
450
|
+
case 8:
|
|
451
|
+
_e.sent();
|
|
452
|
+
_e.label = 9;
|
|
453
|
+
case 9: return [3 /*break*/, 11];
|
|
454
|
+
case 10:
|
|
455
|
+
if (e_1) throw e_1.error;
|
|
456
|
+
return [7 /*endfinally*/];
|
|
457
|
+
case 11: return [7 /*endfinally*/];
|
|
458
|
+
case 12: return [3 /*break*/, 14];
|
|
459
|
+
case 13:
|
|
460
|
+
err_1 = _e.sent();
|
|
461
|
+
// If stream was already consumed or interrupted, that's okay
|
|
462
|
+
// as long as we got the result
|
|
463
|
+
if (!result) {
|
|
464
|
+
throw err_1;
|
|
465
|
+
}
|
|
466
|
+
return [3 /*break*/, 14];
|
|
467
|
+
case 14:
|
|
468
|
+
if (!result) {
|
|
469
|
+
throw new Error("Agent execution did not complete");
|
|
470
|
+
}
|
|
471
|
+
// Note: afterRun hook is called by AgentRunner now with full context
|
|
472
|
+
return [2 /*return*/, result];
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
};
|
|
477
|
+
/**
|
|
478
|
+
* Consume stream as text-only stream (for simple streaming UX)
|
|
479
|
+
*/
|
|
480
|
+
FlinkAgent.prototype.consumeAsTextStream = function (stream) {
|
|
481
|
+
return __asyncGenerator(this, arguments, function consumeAsTextStream_1() {
|
|
482
|
+
var _a, stream_2, stream_2_1, chunk, e_2_1;
|
|
483
|
+
var _b, e_2, _c, _d;
|
|
484
|
+
return __generator(this, function (_e) {
|
|
485
|
+
switch (_e.label) {
|
|
486
|
+
case 0:
|
|
487
|
+
_e.trys.push([0, 7, 8, 13]);
|
|
488
|
+
_a = true, stream_2 = __asyncValues(stream);
|
|
489
|
+
_e.label = 1;
|
|
490
|
+
case 1: return [4 /*yield*/, __await(stream_2.next())];
|
|
491
|
+
case 2:
|
|
492
|
+
if (!(stream_2_1 = _e.sent(), _b = stream_2_1.done, !_b)) return [3 /*break*/, 6];
|
|
493
|
+
_d = stream_2_1.value;
|
|
494
|
+
_a = false;
|
|
495
|
+
chunk = _d;
|
|
496
|
+
if (!(chunk.type === "text_delta")) return [3 /*break*/, 5];
|
|
497
|
+
return [4 /*yield*/, __await(chunk.delta)];
|
|
498
|
+
case 3: return [4 /*yield*/, _e.sent()];
|
|
499
|
+
case 4:
|
|
500
|
+
_e.sent();
|
|
501
|
+
_e.label = 5;
|
|
502
|
+
case 5:
|
|
503
|
+
_a = true;
|
|
504
|
+
return [3 /*break*/, 1];
|
|
505
|
+
case 6: return [3 /*break*/, 13];
|
|
506
|
+
case 7:
|
|
507
|
+
e_2_1 = _e.sent();
|
|
508
|
+
e_2 = { error: e_2_1 };
|
|
509
|
+
return [3 /*break*/, 13];
|
|
510
|
+
case 8:
|
|
511
|
+
_e.trys.push([8, , 11, 12]);
|
|
512
|
+
if (!(!_a && !_b && (_c = stream_2.return))) return [3 /*break*/, 10];
|
|
513
|
+
return [4 /*yield*/, __await(_c.call(stream_2))];
|
|
514
|
+
case 9:
|
|
515
|
+
_e.sent();
|
|
516
|
+
_e.label = 10;
|
|
517
|
+
case 10: return [3 /*break*/, 12];
|
|
518
|
+
case 11:
|
|
519
|
+
if (e_2) throw e_2.error;
|
|
520
|
+
return [7 /*endfinally*/];
|
|
521
|
+
case 12: return [7 /*endfinally*/];
|
|
522
|
+
case 13: return [2 /*return*/];
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
};
|
|
527
|
+
FlinkAgent.prototype.getRunner = function () {
|
|
528
|
+
if (!this.runner) {
|
|
529
|
+
if (!this._llmAdapters) {
|
|
530
|
+
throw new Error("Agent not initialized - __init() must be called by FlinkApp");
|
|
531
|
+
}
|
|
532
|
+
// Get tools map and LLM adapters from internal properties
|
|
533
|
+
var toolsMap = this.resolveTools();
|
|
534
|
+
var llmAdapters = this._llmAdapters;
|
|
535
|
+
this.runner = new AgentRunner_1.AgentRunner(this.toAgentProps(), toolsMap, llmAdapters, this.getAgentId());
|
|
536
|
+
}
|
|
537
|
+
return this.runner;
|
|
538
|
+
};
|
|
539
|
+
/**
|
|
540
|
+
* Get agent id - uses explicit id property or falls back to kebab-case class name
|
|
541
|
+
*
|
|
542
|
+
* Examples:
|
|
543
|
+
* - CarAgent → car-agent
|
|
544
|
+
* - APIAgent → api-agent
|
|
545
|
+
* - HTMLParserAgent → html-parser-agent
|
|
546
|
+
*/
|
|
547
|
+
FlinkAgent.prototype.getAgentId = function () {
|
|
548
|
+
if (this.id) {
|
|
549
|
+
return this.id;
|
|
550
|
+
}
|
|
551
|
+
// Convert class name to kebab-case using shared utility
|
|
552
|
+
return (0, utils_1.toKebabCase)(this.constructor.name);
|
|
553
|
+
};
|
|
554
|
+
FlinkAgent.prototype.toAgentProps = function () {
|
|
555
|
+
var _a, _b, _c, _d, _e, _f;
|
|
556
|
+
return {
|
|
557
|
+
id: this.getAgentId(),
|
|
558
|
+
description: this.description,
|
|
559
|
+
instructions: this.instructions,
|
|
560
|
+
tools: this.tools,
|
|
561
|
+
agents: this.agents,
|
|
562
|
+
model: this.model,
|
|
563
|
+
limits: this.limits,
|
|
564
|
+
permissions: this.permissions,
|
|
565
|
+
conversationStrategy: this.conversationStrategy,
|
|
566
|
+
debug: this.debug,
|
|
567
|
+
// Pass lifecycle hooks
|
|
568
|
+
beforeRun: (_a = this.beforeRun) === null || _a === void 0 ? void 0 : _a.bind(this),
|
|
569
|
+
onStep: (_b = this.onStep) === null || _b === void 0 ? void 0 : _b.bind(this),
|
|
570
|
+
afterRun: (_c = this.afterRun) === null || _c === void 0 ? void 0 : _c.bind(this),
|
|
571
|
+
transformSubAgentInput: (_d = this.transformSubAgentInput) === null || _d === void 0 ? void 0 : _d.bind(this),
|
|
572
|
+
onSubAgentCall: (_e = this.onSubAgentCall) === null || _e === void 0 ? void 0 : _e.bind(this),
|
|
573
|
+
onSubAgentComplete: (_f = this.onSubAgentComplete) === null || _f === void 0 ? void 0 : _f.bind(this),
|
|
574
|
+
};
|
|
575
|
+
};
|
|
576
|
+
FlinkAgent.prototype.resolveTools = function () {
|
|
577
|
+
var _this = this;
|
|
578
|
+
var toolsMap = new Map();
|
|
579
|
+
if (!this._tools) {
|
|
580
|
+
throw new Error("Agent not initialized - __init() must be called by FlinkApp");
|
|
581
|
+
}
|
|
582
|
+
var getTool = function (name) { return _this._tools[name]; };
|
|
583
|
+
// Resolve tool names/references to tool executors
|
|
584
|
+
for (var _i = 0, _a = this.tools; _i < _a.length; _i++) {
|
|
585
|
+
var toolRef = _a[_i];
|
|
586
|
+
// Handle both string IDs and tool file references (similar to agent resolution)
|
|
587
|
+
var toolId = typeof toolRef === "string" ? toolRef : toolRef.Tool.id; // Extract ID from FlinkToolFile
|
|
588
|
+
var tool = getTool(toolId);
|
|
589
|
+
if (!tool) {
|
|
590
|
+
throw new Error("Tool ".concat(toolId, " not found in context"));
|
|
591
|
+
}
|
|
592
|
+
toolsMap.set(toolId, tool);
|
|
593
|
+
}
|
|
594
|
+
// Auto-include sub-agent tools
|
|
595
|
+
if (this.agents && this.agents.length > 0) {
|
|
596
|
+
for (var _b = 0, _c = this.agents; _b < _c.length; _b++) {
|
|
597
|
+
var agentRef = _c[_b];
|
|
598
|
+
// Convert class reference to kebab-case id
|
|
599
|
+
var agentId = typeof agentRef === "string" ? agentRef : agentRef.name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
600
|
+
var subAgentToolName = "ask_".concat(agentId.replace(/-/g, "_"));
|
|
601
|
+
var subAgentTool = getTool(subAgentToolName);
|
|
602
|
+
if (!subAgentTool) {
|
|
603
|
+
throw new Error("Sub-agent tool ".concat(subAgentToolName, " not found in context for agent ").concat(agentId));
|
|
604
|
+
}
|
|
605
|
+
toolsMap.set(subAgentToolName, subAgentTool);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return toolsMap;
|
|
609
|
+
};
|
|
610
|
+
FlinkAgent.prototype.checkPermissionsSync = function (user, userPermissions) {
|
|
611
|
+
var perms = this.permissions;
|
|
612
|
+
if (!perms)
|
|
613
|
+
return;
|
|
614
|
+
if (typeof perms === "function") {
|
|
615
|
+
var hasPermission = perms(user);
|
|
616
|
+
if (!hasPermission) {
|
|
617
|
+
throw (0, FlinkErrors_1.forbidden)("Permission denied for agent ".concat(this.getAgentId()));
|
|
618
|
+
}
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (!user) {
|
|
622
|
+
throw (0, FlinkErrors_1.forbidden)("Permission denied for agent ".concat(this.getAgentId()));
|
|
623
|
+
}
|
|
624
|
+
// Prefer userPermissions from request if available
|
|
625
|
+
var effectivePerms = userPermissions || user.permissions || [];
|
|
626
|
+
var requiredPerms = Array.isArray(perms) ? perms : [perms];
|
|
627
|
+
if (!requiredPerms.every(function (p) { return effectivePerms.includes(p); })) {
|
|
628
|
+
throw (0, FlinkErrors_1.forbidden)("Permission denied for agent ".concat(this.getAgentId()));
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
return FlinkAgent;
|
|
632
|
+
}());
|
|
633
|
+
exports.FlinkAgent = FlinkAgent;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { FlinkContext } from "../FlinkContext";
|
|
3
|
+
/**
|
|
4
|
+
* Standardized tool result format for better error handling
|
|
5
|
+
*/
|
|
6
|
+
export type ToolResult<T = any> = {
|
|
7
|
+
success: true;
|
|
8
|
+
data: T;
|
|
9
|
+
} | {
|
|
10
|
+
success: false;
|
|
11
|
+
error: string;
|
|
12
|
+
code?: string;
|
|
13
|
+
};
|
|
14
|
+
export interface FlinkToolProps {
|
|
15
|
+
/**
|
|
16
|
+
* Unique identifier for the tool (kebab-case)
|
|
17
|
+
* Used to reference the tool in agents and for registration
|
|
18
|
+
* Example: "get-cars-tool", "search-users-tool"
|
|
19
|
+
*/
|
|
20
|
+
id: string;
|
|
21
|
+
description: string;
|
|
22
|
+
inputSchema: z.ZodType<any>;
|
|
23
|
+
outputSchema?: z.ZodType<any>;
|
|
24
|
+
permissions?: string | string[] | ((input: any, user?: any) => boolean | Promise<boolean>);
|
|
25
|
+
}
|
|
26
|
+
export interface FlinkTool<Ctx extends FlinkContext, Input = any, Output = any> {
|
|
27
|
+
(params: {
|
|
28
|
+
input: Input;
|
|
29
|
+
ctx: Ctx;
|
|
30
|
+
user?: any;
|
|
31
|
+
}): Promise<ToolResult<Output>>;
|
|
32
|
+
}
|
|
33
|
+
export type FlinkToolFile = {
|
|
34
|
+
default: FlinkTool<any, any, any>;
|
|
35
|
+
Tool: FlinkToolProps;
|
|
36
|
+
__file?: string;
|
|
37
|
+
};
|