From cfda49ec72750d816ffa7f165a71b7d33120ceca Mon Sep 17 00:00:00 2001 From: DizzyMii Date: Fri, 29 May 2026 11:57:39 -0600 Subject: [PATCH 1/3] fix(flint): type ConversationMemory.append as async The interface declared append() returning void, but the implementation is async and awaits the summarizer. Callers typing against the interface would not await it, silently dropping summarization work. Correct the return type to Promise; all existing callers already await. --- .changeset/fix-memory-append-type.md | 5 +++++ packages/flint/src/memory.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/fix-memory-append-type.md diff --git a/.changeset/fix-memory-append-type.md b/.changeset/fix-memory-append-type.md new file mode 100644 index 0000000..68d7610 --- /dev/null +++ b/.changeset/fix-memory-append-type.md @@ -0,0 +1,5 @@ +--- +"flint": patch +--- + +Fix `ConversationMemory.append` type signature: it was declared as returning `void` but the implementation is `async` and performs summarization. The interface now correctly returns `Promise` so callers don't silently drop the promise. diff --git a/packages/flint/src/memory.ts b/packages/flint/src/memory.ts index d10be24..72e64fe 100644 --- a/packages/flint/src/memory.ts +++ b/packages/flint/src/memory.ts @@ -58,7 +58,7 @@ export type ConversationMemoryOpts = { }; export type ConversationMemory = { - append(m: Message): void; + append(m: Message): Promise; messages(): Message[]; summary(): string | undefined; clear(): void; From af3034d69c41b527cf7305965cf8e2888acf4903 Mon Sep 17 00:00:00 2001 From: DizzyMii Date: Fri, 29 May 2026 11:57:47 -0600 Subject: [PATCH 2/3] fix(flint): guard approxCount against non-serializable tool arguments tc.arguments is typed unknown, so JSON.stringify could throw on circular references or BigInt values, or return undefined for a bare undefined, crashing a pure token estimate. Wrap the stringify and treat unserializable arguments as contributing zero tokens. --- .changeset/fix-approx-count-serialize.md | 5 +++++ packages/flint/src/primitives/approx-count.ts | 11 ++++++++++- packages/flint/test/count.test.ts | 19 +++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 .changeset/fix-approx-count-serialize.md diff --git a/.changeset/fix-approx-count-serialize.md b/.changeset/fix-approx-count-serialize.md new file mode 100644 index 0000000..b2c0f0d --- /dev/null +++ b/.changeset/fix-approx-count-serialize.md @@ -0,0 +1,5 @@ +--- +"flint": patch +--- + +Harden `approxCount` (and the `count` fallback) against non-serializable tool-call arguments. `JSON.stringify` could throw on circular references or `BigInt` values, or return `undefined` for a bare `undefined`, crashing a pure token estimate. Such arguments now contribute 0 tokens instead of throwing. diff --git a/packages/flint/src/primitives/approx-count.ts b/packages/flint/src/primitives/approx-count.ts index 5bb61f1..98ebb67 100644 --- a/packages/flint/src/primitives/approx-count.ts +++ b/packages/flint/src/primitives/approx-count.ts @@ -29,7 +29,16 @@ export function approxCount(messages: Message[]): number { if (msg.role === 'assistant' && msg.toolCalls) { for (const tc of msg.toolCalls) { total += ROLE_OVERHEAD; - total += textTokens(JSON.stringify(tc.arguments)); + // tc.arguments is `unknown` — JSON.stringify can throw (circular refs, + // BigInt) or return undefined (a bare undefined value). Either case must + // not crash a pure token estimate, so fall back to 0 for the arguments. + let serialized: string | undefined; + try { + serialized = JSON.stringify(tc.arguments); + } catch { + serialized = undefined; + } + total += serialized === undefined ? 0 : textTokens(serialized); } } } diff --git a/packages/flint/test/count.test.ts b/packages/flint/test/count.test.ts index 03964fc..6c69a04 100644 --- a/packages/flint/test/count.test.ts +++ b/packages/flint/test/count.test.ts @@ -55,6 +55,25 @@ describe('approxCount', () => { const more: Message[] = [...base, { role: 'assistant', content: 'hi back' }]; expect(approxCount(more)).toBeGreaterThanOrEqual(approxCount(base)); }); + + it('does not throw on non-serializable tool arguments', () => { + const circular: Record = {}; + circular.self = circular; + const msgs: Message[] = [ + { + role: 'assistant', + content: '', + toolCalls: [ + { id: 'c1', name: 'circular', arguments: circular }, + { id: 'c2', name: 'bigint', arguments: { n: 1n } }, + { id: 'c3', name: 'undef', arguments: undefined }, + ], + }, + ]; + // role 4 + 3 tool-call overheads (4 each); unserializable args contribute 0. + expect(() => approxCount(msgs)).not.toThrow(); + expect(approxCount(msgs)).toBe(4 + 4 * 3); + }); }); describe('count', () => { From 39a1bfe00714b1e24a5cecbf494c287dc0a318f7 Mon Sep 17 00:00:00 2001 From: DizzyMii Date: Fri, 29 May 2026 11:57:48 -0600 Subject: [PATCH 3/3] fix(flint): redact PKCS#8 and other private-key formats The private-key pattern in secretPatterns used [A-Z ]+ for the key type, which failed to match the most common modern formats: generic PKCS#8 (-----BEGIN PRIVATE KEY-----, no type word), ENCRYPTED PRIVATE KEY, and types containing digits or hyphens. Make the type segment optional and allow digits/hyphens so these keys are redacted. --- .changeset/fix-redact-private-keys.md | 5 +++++ packages/flint/src/safety/redact.ts | 2 +- packages/flint/test/safety/redact.test.ts | 12 ++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 .changeset/fix-redact-private-keys.md diff --git a/.changeset/fix-redact-private-keys.md b/.changeset/fix-redact-private-keys.md new file mode 100644 index 0000000..5697ca7 --- /dev/null +++ b/.changeset/fix-redact-private-keys.md @@ -0,0 +1,5 @@ +--- +"flint": patch +--- + +Broaden the private-key pattern in the `secretPatterns` redaction preset. The previous regex only matched key types made of uppercase letters and spaces, so it missed the most common modern formats — generic PKCS#8 `-----BEGIN PRIVATE KEY-----` (no type word), `ENCRYPTED PRIVATE KEY`, and types containing digits or hyphens. These are now redacted. diff --git a/packages/flint/src/safety/redact.ts b/packages/flint/src/safety/redact.ts index c057368..f81c722 100644 --- a/packages/flint/src/safety/redact.ts +++ b/packages/flint/src/safety/redact.ts @@ -39,7 +39,7 @@ export const secretPatterns: RegExp[] = [ /gho_[a-zA-Z0-9]{36}/g, /xox[baprs]-[a-zA-Z0-9-]{10,}/g, /sk_(?:live|test)_[a-zA-Z0-9]{24,}/g, - /-----BEGIN [A-Z ]+ PRIVATE KEY-----[\s\S]+?-----END [A-Z ]+ PRIVATE KEY-----/g, + /-----BEGIN (?:[A-Z0-9 -]+ )?PRIVATE KEY-----[\s\S]+?-----END (?:[A-Z0-9 -]+ )?PRIVATE KEY-----/g, /\b\d{3}-\d{2}-\d{4}\b/g, /\b(?:\d{4}[\s-]?){3}\d{4}\b/g, ]; diff --git a/packages/flint/test/safety/redact.test.ts b/packages/flint/test/safety/redact.test.ts index 426c7d1..5f8b2d5 100644 --- a/packages/flint/test/safety/redact.test.ts +++ b/packages/flint/test/safety/redact.test.ts @@ -91,6 +91,18 @@ describe('secretPatterns preset', () => { ['Stripe live key', 'sk_live_abcdefghijklmnopqrstuvwx'], ['SSN', 'SSN: 123-45-6789'], ['Credit card', 'card 4111-1111-1111-1111'], + [ + 'RSA private key', + '-----BEGIN RSA PRIVATE KEY-----\nMIIBOgIBAAJBAKj\n-----END RSA PRIVATE KEY-----', + ], + [ + 'generic PKCS#8 private key', + '-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGBy\n-----END PRIVATE KEY-----', + ], + [ + 'encrypted private key', + '-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIFLTBXBgkq\n-----END ENCRYPTED PRIVATE KEY-----', + ], ]; for (const [label, text] of cases) {