From edf3c5d63ac850ae3bb7c6167c62bf82760ec97a Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 24 Sep 2026 18:47:30 -0700 Subject: [PATCH] fix(cli): strip endpoint trailing slashes in linear time, deflake projection test --- .../knowledge-projection.integration.ts | 8 +++++++- packages/sim-cli/src/config/profile.test.ts | 12 ++++++++++++ packages/sim-cli/src/config/profile.ts | 13 ++++++++++++- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/apps/sim/lib/knowledge/__integration__/knowledge-projection.integration.ts b/apps/sim/lib/knowledge/__integration__/knowledge-projection.integration.ts index 7f12a726888..671d0c85517 100644 --- a/apps/sim/lib/knowledge/__integration__/knowledge-projection.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/knowledge-projection.integration.ts @@ -98,7 +98,13 @@ const members = { const subjectToken = (subject: string) => `s:github-repositories:-:${subject}` const aclOf = (...who: Array<'alice' | 'bob'>) => who.map((name) => subjectToken(members[name].subject)).sort() -const vector = [1, ...Array(1535).fill(0)] +/** + * A direction no other integration file writes. The vector legs here walk the approximate index, + * which the files sharing this database crowd with `[1, 0, …]` rows; tied at distance zero with + * this file's chunks, those can exhaust the walk's tuple cap before it reaches them. Inside the + * first 512 dimensions, the only ones the candidate projection keeps. + */ +const vector = Array.from({ length: 1536 }, (_, index) => (index === 511 ? 1 : 0)) const queryVector = { vector: JSON.stringify(vector), dimensions: 1536 as const, diff --git a/packages/sim-cli/src/config/profile.test.ts b/packages/sim-cli/src/config/profile.test.ts index f16b9d41db1..cde7073b573 100644 --- a/packages/sim-cli/src/config/profile.test.ts +++ b/packages/sim-cli/src/config/profile.test.ts @@ -257,6 +257,18 @@ describe('profile resolution', () => { expect(resolveProfile({ endpoint: 'https://sim.ai///' }).endpoint).toBe('https://sim.ai') }) + it('keeps inner slashes and strips a long trailing run in linear time', () => { + expect(resolveProfile({ endpoint: 'https://sim.ai/a//b//' }).endpoint).toBe( + 'https://sim.ai/a//b' + ) + const slashes = '/'.repeat(100_000) + const started = performance.now() + expect(resolveProfile({ endpoint: `https://sim.ai/${slashes}x` }).endpoint).toBe( + `https://sim.ai/${slashes}x` + ) + expect(performance.now() - started).toBeLessThan(1000) + }) + it('trims a padded endpoint instead of storing text the writer would refuse', () => { // `new URL()` tolerates padding and hands the string straight back, but the // config writer refuses it. Untrimmed, `login --endpoint " https://…"` threw diff --git a/packages/sim-cli/src/config/profile.ts b/packages/sim-cli/src/config/profile.ts index 64a81840b18..3ee5a442040 100644 --- a/packages/sim-cli/src/config/profile.ts +++ b/packages/sim-cli/src/config/profile.ts @@ -576,6 +576,17 @@ export function deleteProfile(profile: string): { config: boolean; credentials: return { config, credentials } } +/** + * Removes every trailing `/`. A backward scan rather than `/\/+$/`: that regex + * restarts at each `/` in a long run that does not reach the end, so it is + * quadratic in the run length. + */ +function stripTrailingSlashes(value: string): string { + let end = value.length + while (end > 0 && value.charCodeAt(end - 1) === 0x2f) end-- + return value.slice(0, end) +} + /** * Validates an endpoint and strips its trailing slashes. * @@ -597,7 +608,7 @@ export function normalizeEndpoint(endpoint: string, source: string): string { // naming the flag. It also has to come first so the slash strip sees the real // end of the URL — and that strip is there because a trailing slash produces // `https://sim.ai//api/v2/...`, which some proxies 404 rather than normalize. - const trimmed = endpoint.trim().replace(/\/+$/, '') + const trimmed = stripTrailingSlashes(endpoint.trim()) // Trimming only reaches the ends, and a control character in the middle is // the one that matters: the URL parser deletes tabs and line breaks from