Skip to content

Commit 39fa3fc

Browse files
committed
fix: tighten follow-up review edge cases
1 parent 2e9a5b0 commit 39fa3fc

4 files changed

Lines changed: 58 additions & 21 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,10 @@ top-level aliases remain supported for backward compatibility. Precedence is:
231231
1. `redis.*` (canonical)
232232
2. top-level Graphiti aliases such as `endpoint` and `groupIdPrefix`
233233

234+
Endpoint values must be valid URLs, so include the scheme explicitly - for
235+
example `redis://localhost:6379` for Redis and `http://localhost:8000/mcp` for
236+
Graphiti.
237+
234238
### Legacy Top-Level Keys
235239

236240
For backward compatibility, the following original Graphiti top-level keys are

src/services/connection-manager.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,34 @@ describe("connection manager", () => {
792792
assertEquals(aborted, true);
793793
});
794794

795+
it("maps AbortError rejections during shutdown to GraphitiOfflineError", async () => {
796+
const manager = new GraphitiConnectionManager({
797+
endpoint: "http://test",
798+
connectionFactory: () => ({
799+
connect: () => Promise.resolve(),
800+
close: () => Promise.resolve(),
801+
callTool: (_request, options) =>
802+
new Promise<unknown>((_resolve, reject) => {
803+
options?.signal?.addEventListener("abort", () => {
804+
reject(new DOMException("aborted", "AbortError"));
805+
}, { once: true });
806+
}),
807+
}),
808+
});
809+
810+
manager.start();
811+
assertEquals(await manager.ready(10), true);
812+
813+
const request = manager.callTool("search", {});
814+
await manager.stop();
815+
816+
const error = await assertRejects(
817+
() => request,
818+
GraphitiOfflineError,
819+
);
820+
assertEquals(error.state, "closing");
821+
});
822+
795823
it("stop keeps reconnect from transitioning back to connected", async () => {
796824
let connectionIndex = 0;
797825
let failed = false;

src/services/connection-manager.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,15 @@ function isSessionExpired(err: unknown): boolean {
214214
);
215215
}
216216

217+
function isAbortError(err: unknown): boolean {
218+
if (!err || typeof err !== "object") return false;
219+
if (typeof (err as { name?: unknown }).name === "string") {
220+
return (err as { name: string }).name === "AbortError";
221+
}
222+
return typeof DOMException !== "undefined" && err instanceof DOMException &&
223+
err.name === "AbortError";
224+
}
225+
217226
function isTransportFailure(err: unknown): boolean {
218227
if (!err) return false;
219228
if (isRequestTimeout(err) || isSessionExpired(err)) return false;
@@ -497,6 +506,14 @@ export class GraphitiConnectionManager implements GraphitiToolCaller {
497506
controller,
498507
);
499508
} catch (err) {
509+
if (err instanceof GraphitiOfflineError) {
510+
throw err;
511+
}
512+
513+
if (this.stopPromise && isAbortError(err)) {
514+
throw new GraphitiOfflineError("closing");
515+
}
516+
500517
if (isRequestTimeout(err)) {
501518
throw new GraphitiRequestTimeoutError(
502519
getErrorMessage(err) || undefined,

src/services/context-limit.test.ts

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { assertEquals } from "jsr:@std/assert@^1.0.0";
2-
import { resolveContextLimit } from "./context-limit.ts";
2+
import {
3+
type ContextLimitCacheEntry,
4+
resolveContextLimit,
5+
} from "./context-limit.ts";
36

47
Deno.test("resolveContextLimit re-probes after fallback cache expiry", async () => {
58
let now = 100_000;
6-
const cache = new Map<
7-
string,
8-
number | { value: number; expiresAt?: number }
9-
>();
9+
const cache = new Map<string, ContextLimitCacheEntry>();
1010
let calls = 0;
1111
const client = {
1212
provider: {
@@ -70,10 +70,7 @@ Deno.test("resolveContextLimit re-probes after fallback cache expiry", async ()
7070

7171
Deno.test("resolveContextLimit keeps fallback caches scoped per normalized directory until expiry", async () => {
7272
let now = 200_000;
73-
const cache = new Map<
74-
string,
75-
number | { value: number; expiresAt?: number }
76-
>();
73+
const cache = new Map<string, ContextLimitCacheEntry>();
7774
const calls: string[] = [];
7875
const client = {
7976
provider: {
@@ -158,10 +155,7 @@ Deno.test("resolveContextLimit keeps fallback caches scoped per normalized direc
158155
});
159156

160157
Deno.test("resolveContextLimit keeps positive cache entries without expiry re-probes", async () => {
161-
const cache = new Map<
162-
string,
163-
number | { value: number; expiresAt?: number }
164-
>();
158+
const cache = new Map<string, ContextLimitCacheEntry>();
165159
let calls = 0;
166160
const client = {
167161
provider: {
@@ -204,10 +198,7 @@ Deno.test("resolveContextLimit keeps positive cache entries without expiry re-pr
204198
});
205199

206200
Deno.test("resolveContextLimit re-probes when legacy numeric cache entry is non-positive", async () => {
207-
const cache = new Map<
208-
string,
209-
number | { value: number; expiresAt?: number }
210-
>();
201+
const cache = new Map<string, ContextLimitCacheEntry>();
211202
cache.set("openai/gpt-5", -1);
212203

213204
let calls = 0;
@@ -243,10 +234,7 @@ Deno.test("resolveContextLimit re-probes when legacy numeric cache entry is non-
243234
});
244235

245236
Deno.test("resolveContextLimit re-probes when legacy object cache entry is non-positive without expiry", async () => {
246-
const cache = new Map<
247-
string,
248-
number | { value: number; expiresAt?: number }
249-
>();
237+
const cache = new Map<string, ContextLimitCacheEntry>();
250238
cache.set("openai/gpt-5", { value: -1 });
251239

252240
let calls = 0;

0 commit comments

Comments
 (0)