@flink-app/flink 2.0.0-alpha.95 → 2.0.0-alpha.97
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 +29 -0
- package/dist/src/FlinkApp.js +1 -1
- package/dist/src/ai/ConversationAgent.js +3 -1
- package/dist/src/auth/FlinkAuthPlugin.d.ts +1 -1
- package/package.json +1 -1
- package/spec/ai/ConversationAgent.spec.ts +29 -0
- package/src/FlinkApp.ts +1 -1
- package/src/ai/ConversationAgent.ts +3 -1
- package/src/auth/FlinkAuthPlugin.ts +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @flink-app/flink
|
|
2
2
|
|
|
3
|
+
## 2.0.0-alpha.97
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- eed6bc1: Pass host application context to auth plugins.
|
|
8
|
+
|
|
9
|
+
`FlinkAuthPlugin.authenticateRequest` now receives the host app's context as an optional third argument. Flink core supplies it on every authenticated request, so auth plugins can access repos/services without the caller wiring up a lazy accessor or maintaining a module-level mutable reference.
|
|
10
|
+
|
|
11
|
+
For `@flink-app/jwt-auth-plugin`, this means `getUser` receives the context directly:
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
jwtAuthPlugin({
|
|
15
|
+
getUser: async (tokenData, req, ctx) => {
|
|
16
|
+
const user = await ctx?.repos.userRepo.getById(tokenData._id);
|
|
17
|
+
return user ? { username: user.username, _id: user._id } : null;
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The `getContext` option previously added in alpha.96 is no longer needed and has been removed — remove it from your `jwtAuthPlugin({ … })` call. The third `ctx` parameter on `getUser` is optional, so callbacks that ignore it are unaffected.
|
|
23
|
+
|
|
24
|
+
## 2.0.0-alpha.96
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- Fix double-JSON-encoding of tool results on conversation reload.
|
|
29
|
+
|
|
30
|
+
`ConversationAgent.convertToAgentMessages` was re-stringifying tool result payloads that `ToolExecutor.formatResultForAI` had already serialized to a JSON string. Each reload escaped the value one more level, corrupting tool history on every continuation turn (visible through any LLM adapter, including Anthropic). Strings are now passed through untouched; non-string values are still stringified for backward compatibility with older storage rows.
|
|
31
|
+
|
|
3
32
|
## 2.0.0-alpha.95
|
|
4
33
|
|
|
5
34
|
## 2.0.0-alpha.94
|
package/dist/src/FlinkApp.js
CHANGED
|
@@ -1300,7 +1300,7 @@ var FlinkApp = /** @class */ (function () {
|
|
|
1300
1300
|
if (!this.auth) {
|
|
1301
1301
|
throw new Error("Attempting to authenticate request (".concat(req.method, " ").concat(req.path, ") but no authPlugin is set"));
|
|
1302
1302
|
}
|
|
1303
|
-
return [4 /*yield*/, this.auth.authenticateRequest(req, permissions)];
|
|
1303
|
+
return [4 /*yield*/, this.auth.authenticateRequest(req, permissions, this._ctx)];
|
|
1304
1304
|
case 1: return [2 /*return*/, _a.sent()];
|
|
1305
1305
|
}
|
|
1306
1306
|
});
|
|
@@ -297,7 +297,9 @@ var ConversationAgent = /** @class */ (function (_super) {
|
|
|
297
297
|
role: "tool",
|
|
298
298
|
toolCallId: toolResult.id,
|
|
299
299
|
toolName: "", // Not stored, but required by type
|
|
300
|
-
|
|
300
|
+
// Tool results are persisted as already-stringified payloads (see formatResultForAI).
|
|
301
|
+
// JSON.stringify-ing again would double-encode strings into escaped JSON literals.
|
|
302
|
+
result: typeof toolResult.result === "string" ? toolResult.result : JSON.stringify(toolResult.result),
|
|
301
303
|
}); });
|
|
302
304
|
}
|
|
303
305
|
else if (msg.role === "user") {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FlinkRequest } from "../FlinkHttpHandler";
|
|
2
2
|
export interface FlinkAuthPlugin {
|
|
3
|
-
authenticateRequest: (req: FlinkRequest, permissions: string | string[]) => Promise<boolean>;
|
|
3
|
+
authenticateRequest: (req: FlinkRequest, permissions: string | string[], ctx?: any) => Promise<boolean>;
|
|
4
4
|
createToken: (payload: any, roles: string[]) => Promise<string>;
|
|
5
5
|
}
|
package/package.json
CHANGED
|
@@ -324,6 +324,35 @@ describe("ConversationAgent", () => {
|
|
|
324
324
|
});
|
|
325
325
|
});
|
|
326
326
|
|
|
327
|
+
it("should not double-encode string tool results on reload", async () => {
|
|
328
|
+
// Real production flow: ToolExecutor.formatResultForAI returns a JSON string,
|
|
329
|
+
// which is what AgentRunner stores on tool_result content blocks. On the
|
|
330
|
+
// round-trip back from storage, that string must NOT be JSON.stringified
|
|
331
|
+
// again — otherwise the model sees escaped JSON literals on every reload.
|
|
332
|
+
const alreadyStringified = JSON.stringify({ temp: "22C", condition: "sunny" });
|
|
333
|
+
|
|
334
|
+
const conversationData: ConversationData = {
|
|
335
|
+
messages: [
|
|
336
|
+
{
|
|
337
|
+
role: "user",
|
|
338
|
+
toolResults: [{ id: "call_1", result: alreadyStringified }],
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
agent.loadConversationSpy.and.returnValue(Promise.resolve(conversationData));
|
|
344
|
+
|
|
345
|
+
const input: any = { conversationId: "conv-123" };
|
|
346
|
+
await agent.testBeforeRun(input, {});
|
|
347
|
+
|
|
348
|
+
expect(input.history[0]).toEqual({
|
|
349
|
+
role: "tool",
|
|
350
|
+
toolCallId: "call_1",
|
|
351
|
+
toolName: "",
|
|
352
|
+
result: alreadyStringified, // not JSON.stringify(alreadyStringified)
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
327
356
|
it("should preserve assistant messages with toolCalls", async () => {
|
|
328
357
|
const conversationData: ConversationData = {
|
|
329
358
|
messages: [
|
package/src/FlinkApp.ts
CHANGED
|
@@ -1580,7 +1580,7 @@ export class FlinkApp<C extends FlinkContext> {
|
|
|
1580
1580
|
if (!this.auth) {
|
|
1581
1581
|
throw new Error(`Attempting to authenticate request (${req.method} ${req.path}) but no authPlugin is set`);
|
|
1582
1582
|
}
|
|
1583
|
-
return await this.auth.authenticateRequest(req as FlinkRequest, permissions);
|
|
1583
|
+
return await this.auth.authenticateRequest(req as FlinkRequest, permissions, this._ctx);
|
|
1584
1584
|
}
|
|
1585
1585
|
|
|
1586
1586
|
public getRegisteredRoutes() {
|
|
@@ -308,7 +308,9 @@ export abstract class ConversationAgent<Context extends FlinkContext = FlinkCont
|
|
|
308
308
|
role: "tool",
|
|
309
309
|
toolCallId: toolResult.id,
|
|
310
310
|
toolName: "", // Not stored, but required by type
|
|
311
|
-
|
|
311
|
+
// Tool results are persisted as already-stringified payloads (see formatResultForAI).
|
|
312
|
+
// JSON.stringify-ing again would double-encode strings into escaped JSON literals.
|
|
313
|
+
result: typeof toolResult.result === "string" ? toolResult.result : JSON.stringify(toolResult.result),
|
|
312
314
|
})
|
|
313
315
|
);
|
|
314
316
|
} else if (msg.role === "user") {
|
|
@@ -3,7 +3,8 @@ import { FlinkRequest } from "../FlinkHttpHandler";
|
|
|
3
3
|
export interface FlinkAuthPlugin {
|
|
4
4
|
authenticateRequest: (
|
|
5
5
|
req: FlinkRequest,
|
|
6
|
-
permissions: string | string[]
|
|
6
|
+
permissions: string | string[],
|
|
7
|
+
ctx?: any
|
|
7
8
|
) => Promise<boolean>;
|
|
8
9
|
createToken: (payload: any, roles: string[]) => Promise<string>;
|
|
9
10
|
}
|