@agentmemory/agentmemory 0.9.17 → 0.9.18
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/.env.example +6 -0
- package/README.md +15 -1
- package/dist/.env.example +6 -0
- package/dist/cli.mjs +12 -6
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +186 -28
- package/dist/index.mjs.map +1 -1
- package/dist/{src-TiNuQ3Ub.mjs → src-C7vygXCj.mjs} +186 -28
- package/dist/src-C7vygXCj.mjs.map +1 -0
- package/dist/{standalone-BIXq6S80.mjs → standalone-kg2TedgD.mjs} +16 -7
- package/dist/standalone-kg2TedgD.mjs.map +1 -0
- package/dist/standalone.mjs +16 -7
- package/dist/standalone.mjs.map +1 -1
- package/dist/viewer/favicon.svg +1 -0
- package/dist/viewer/index.html +190 -60
- package/package.json +2 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/dist/src-TiNuQ3Ub.mjs.map +0 -1
- package/dist/standalone-BIXq6S80.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -325,6 +325,20 @@ var AnthropicProvider = class {
|
|
|
325
325
|
}
|
|
326
326
|
};
|
|
327
327
|
|
|
328
|
+
//#endregion
|
|
329
|
+
//#region src/providers/_fetch.ts
|
|
330
|
+
function fetchWithTimeout(url, init, timeoutMs) {
|
|
331
|
+
const parsed = timeoutMs ?? Number.parseInt(getEnvVar("AGENTMEMORY_LLM_TIMEOUT_MS") ?? "60000", 10);
|
|
332
|
+
const ms = Number.isFinite(parsed) && parsed > 0 ? parsed : 6e4;
|
|
333
|
+
const ctl = new AbortController();
|
|
334
|
+
const signal = init.signal ? AbortSignal.any([init.signal, ctl.signal]) : ctl.signal;
|
|
335
|
+
const t = setTimeout(() => ctl.abort(), ms);
|
|
336
|
+
return fetch(url, {
|
|
337
|
+
...init,
|
|
338
|
+
signal
|
|
339
|
+
}).finally(() => clearTimeout(t));
|
|
340
|
+
}
|
|
341
|
+
|
|
328
342
|
//#endregion
|
|
329
343
|
//#region src/providers/minimax.ts
|
|
330
344
|
/**
|
|
@@ -360,8 +374,7 @@ var MinimaxProvider = class {
|
|
|
360
374
|
return this.call(systemPrompt, userPrompt);
|
|
361
375
|
}
|
|
362
376
|
async call(systemPrompt, userPrompt) {
|
|
363
|
-
const
|
|
364
|
-
const response = await fetch(url, {
|
|
377
|
+
const response = await fetchWithTimeout(`${this.baseUrl}/v1/messages`, {
|
|
365
378
|
method: "POST",
|
|
366
379
|
headers: {
|
|
367
380
|
"Content-Type": "application/json",
|
|
@@ -429,7 +442,12 @@ const DEFAULT_AZURE_API_VERSION = "2024-08-01-preview";
|
|
|
429
442
|
* Azure: https://<resource>.openai.azure.com/openai/deployments/<deployment>
|
|
430
443
|
* OPENAI_MODEL — model name (default: gpt-4o-mini)
|
|
431
444
|
* OPENAI_API_VERSION — Azure api-version query param (default: 2024-08-01-preview)
|
|
432
|
-
* OPENAI_TIMEOUT_MS — outbound fetch timeout in ms (
|
|
445
|
+
* OPENAI_TIMEOUT_MS — outbound fetch timeout in ms (OpenAI-scoped alias,
|
|
446
|
+
* takes precedence over AGENTMEMORY_LLM_TIMEOUT_MS
|
|
447
|
+
* for back-compat with the v0.9.17 shipping name).
|
|
448
|
+
* AGENTMEMORY_LLM_TIMEOUT_MS — outbound fetch timeout in ms shared across all
|
|
449
|
+
* raw-fetch LLM + embedding providers. Used when
|
|
450
|
+
* OPENAI_TIMEOUT_MS is not set. Default: 60000.
|
|
433
451
|
* MAX_TOKENS — max output tokens (default: from config or 4096)
|
|
434
452
|
* OPENAI_REASONING_EFFORT — "low" | "medium" | "high" | "none"
|
|
435
453
|
* Passthrough for reasoning models (e.g. Ollama Cloud
|
|
@@ -453,7 +471,7 @@ var OpenAIProvider = class {
|
|
|
453
471
|
this.maxTokens = maxTokens;
|
|
454
472
|
this.baseUrl = (baseURL || getEnvVar("OPENAI_BASE_URL") || DEFAULT_BASE_URL$1).replace(/\/+$/, "");
|
|
455
473
|
this.reasoningEffort = getEnvVar("OPENAI_REASONING_EFFORT") || void 0;
|
|
456
|
-
this.timeoutMs =
|
|
474
|
+
this.timeoutMs = resolveTimeout();
|
|
457
475
|
this.azureApiVersion = getEnvVar("OPENAI_API_VERSION") || DEFAULT_AZURE_API_VERSION;
|
|
458
476
|
this.isAzure = detectAzure(this.baseUrl);
|
|
459
477
|
}
|
|
@@ -494,21 +512,16 @@ var OpenAIProvider = class {
|
|
|
494
512
|
}]
|
|
495
513
|
};
|
|
496
514
|
if (this.reasoningEffort) body.reasoning_effort = this.reasoningEffort;
|
|
497
|
-
const ac = new AbortController();
|
|
498
|
-
const t = setTimeout(() => ac.abort(), this.timeoutMs);
|
|
499
515
|
let response;
|
|
500
516
|
try {
|
|
501
|
-
response = await
|
|
517
|
+
response = await fetchWithTimeout(url, {
|
|
502
518
|
method: "POST",
|
|
503
519
|
headers: this.buildHeaders(),
|
|
504
|
-
body: JSON.stringify(body)
|
|
505
|
-
|
|
506
|
-
});
|
|
520
|
+
body: JSON.stringify(body)
|
|
521
|
+
}, this.timeoutMs);
|
|
507
522
|
} catch (err) {
|
|
508
|
-
if (
|
|
523
|
+
if (err instanceof Error && err.name === "AbortError") throw new Error(`OpenAI API request timed out after ${this.timeoutMs}ms — set OPENAI_TIMEOUT_MS (or AGENTMEMORY_LLM_TIMEOUT_MS) to raise the bound or check the provider status.`);
|
|
509
524
|
throw err;
|
|
510
|
-
} finally {
|
|
511
|
-
clearTimeout(t);
|
|
512
525
|
}
|
|
513
526
|
if (!response.ok) {
|
|
514
527
|
const text = await response.text();
|
|
@@ -523,10 +536,19 @@ var OpenAIProvider = class {
|
|
|
523
536
|
throw new Error(`OpenAI returned unexpected response: ${JSON.stringify(data).slice(0, 200)}`);
|
|
524
537
|
}
|
|
525
538
|
};
|
|
526
|
-
function
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
539
|
+
function resolveTimeout() {
|
|
540
|
+
const openai = parsePositiveInt(getEnvVar("OPENAI_TIMEOUT_MS"));
|
|
541
|
+
if (openai !== void 0) return openai;
|
|
542
|
+
const globalMs = parsePositiveInt(getEnvVar("AGENTMEMORY_LLM_TIMEOUT_MS"));
|
|
543
|
+
if (globalMs !== void 0) return globalMs;
|
|
544
|
+
return DEFAULT_TIMEOUT_MS;
|
|
545
|
+
}
|
|
546
|
+
function parsePositiveInt(raw) {
|
|
547
|
+
if (!raw) return void 0;
|
|
548
|
+
const trimmed = raw.trim();
|
|
549
|
+
if (!/^\d+$/.test(trimmed)) return void 0;
|
|
550
|
+
const n = Number(trimmed);
|
|
551
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
530
552
|
}
|
|
531
553
|
function detectAzure(baseUrl) {
|
|
532
554
|
try {
|
|
@@ -558,7 +580,7 @@ var OpenRouterProvider = class {
|
|
|
558
580
|
return this.call(systemPrompt, userPrompt);
|
|
559
581
|
}
|
|
560
582
|
async call(systemPrompt, userPrompt) {
|
|
561
|
-
const response = await
|
|
583
|
+
const response = await fetchWithTimeout(this.baseUrl, {
|
|
562
584
|
method: "POST",
|
|
563
585
|
headers: {
|
|
564
586
|
"Content-Type": "application/json",
|
|
@@ -727,7 +749,7 @@ var GeminiEmbeddingProvider = class {
|
|
|
727
749
|
const results = [];
|
|
728
750
|
for (let i = 0; i < texts.length; i += BATCH_LIMIT) {
|
|
729
751
|
const chunk = texts.slice(i, i + BATCH_LIMIT);
|
|
730
|
-
const response = await
|
|
752
|
+
const response = await fetchWithTimeout(`${API_BASE}?key=${this.apiKey}`, {
|
|
731
753
|
method: "POST",
|
|
732
754
|
headers: { "Content-Type": "application/json" },
|
|
733
755
|
body: JSON.stringify({ requests: chunk.map((t) => ({
|
|
@@ -816,8 +838,7 @@ var OpenAIEmbeddingProvider = class {
|
|
|
816
838
|
return result;
|
|
817
839
|
}
|
|
818
840
|
async embedBatch(texts) {
|
|
819
|
-
const
|
|
820
|
-
const response = await fetch(url, {
|
|
841
|
+
const response = await fetchWithTimeout(`${this.baseUrl}/v1/embeddings`, {
|
|
821
842
|
method: "POST",
|
|
822
843
|
headers: {
|
|
823
844
|
Authorization: `Bearer ${this.apiKey}`,
|
|
@@ -852,7 +873,7 @@ var VoyageEmbeddingProvider = class {
|
|
|
852
873
|
return result;
|
|
853
874
|
}
|
|
854
875
|
async embedBatch(texts) {
|
|
855
|
-
const response = await
|
|
876
|
+
const response = await fetchWithTimeout(API_URL$2, {
|
|
856
877
|
method: "POST",
|
|
857
878
|
headers: {
|
|
858
879
|
Authorization: `Bearer ${this.apiKey}`,
|
|
@@ -888,7 +909,7 @@ var CohereEmbeddingProvider = class {
|
|
|
888
909
|
return result;
|
|
889
910
|
}
|
|
890
911
|
async embedBatch(texts) {
|
|
891
|
-
const response = await
|
|
912
|
+
const response = await fetchWithTimeout(API_URL$1, {
|
|
892
913
|
method: "POST",
|
|
893
914
|
headers: {
|
|
894
915
|
Authorization: `Bearer ${this.apiKey}`,
|
|
@@ -926,7 +947,7 @@ var OpenRouterEmbeddingProvider = class {
|
|
|
926
947
|
return result;
|
|
927
948
|
}
|
|
928
949
|
async embedBatch(texts) {
|
|
929
|
-
const response = await
|
|
950
|
+
const response = await fetchWithTimeout(API_URL, {
|
|
930
951
|
method: "POST",
|
|
931
952
|
headers: {
|
|
932
953
|
Authorization: `Bearer ${this.apiKey}`,
|
|
@@ -4543,7 +4564,11 @@ function registerContextFunction(sdk, kv, tokenBudget) {
|
|
|
4543
4564
|
sdk.registerFunction("mem::context", async (data) => {
|
|
4544
4565
|
const budget = data.budget || tokenBudget;
|
|
4545
4566
|
const blocks = [];
|
|
4546
|
-
const [pinnedSlots, profile] = await Promise.all([
|
|
4567
|
+
const [pinnedSlots, profile, lessons] = await Promise.all([
|
|
4568
|
+
isSlotsEnabled() ? listPinnedSlots(kv).catch(() => []) : Promise.resolve([]),
|
|
4569
|
+
kv.get(KV.profiles, data.project).catch(() => null),
|
|
4570
|
+
kv.list(KV.lessons).catch(() => [])
|
|
4571
|
+
]);
|
|
4547
4572
|
const slotContent = renderPinnedContext(pinnedSlots);
|
|
4548
4573
|
if (slotContent) blocks.push({
|
|
4549
4574
|
type: "memory",
|
|
@@ -4567,6 +4592,24 @@ function registerContextFunction(sdk, kv, tokenBudget) {
|
|
|
4567
4592
|
});
|
|
4568
4593
|
}
|
|
4569
4594
|
}
|
|
4595
|
+
const relevantLessons = lessons.filter((l) => !l.deleted && (!l.project || l.project === data.project)).sort((a, b) => {
|
|
4596
|
+
const scoreA = (a.project === data.project ? 1.5 : 1) * a.confidence;
|
|
4597
|
+
return (b.project === data.project ? 1.5 : 1) * b.confidence - scoreA;
|
|
4598
|
+
}).slice(0, 10);
|
|
4599
|
+
if (relevantLessons.length > 0) {
|
|
4600
|
+
const lessonsContent = `## Lessons Learned\n${relevantLessons.map((l) => `- (${l.confidence.toFixed(2)}) ${l.content}${l.context ? ` — ${l.context}` : ""}`).join("\n")}`;
|
|
4601
|
+
const mostRecent = relevantLessons.reduce((acc, l) => {
|
|
4602
|
+
const t = new Date(l.lastReinforcedAt || l.updatedAt).getTime();
|
|
4603
|
+
return t > acc ? t : acc;
|
|
4604
|
+
}, 0);
|
|
4605
|
+
blocks.push({
|
|
4606
|
+
type: "memory",
|
|
4607
|
+
content: lessonsContent,
|
|
4608
|
+
tokens: estimateTokens$1(lessonsContent),
|
|
4609
|
+
recency: mostRecent,
|
|
4610
|
+
sourceIds: relevantLessons.map((l) => l.id)
|
|
4611
|
+
});
|
|
4612
|
+
}
|
|
4570
4613
|
const sessions = (await kv.list(KV.sessions)).filter((s) => s.project === data.project && s.id !== data.sessionId).sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime()).slice(0, 10);
|
|
4571
4614
|
const summariesPerSession = await Promise.all(sessions.map((s) => kv.get(KV.summaries, s.id).catch(() => null)));
|
|
4572
4615
|
const sessionsNeedingObs = [];
|
|
@@ -5389,6 +5432,46 @@ const DEFAULTS$1 = {
|
|
|
5389
5432
|
lowImportanceThreshold: 3,
|
|
5390
5433
|
maxObservationsPerProject: 1e4
|
|
5391
5434
|
};
|
|
5435
|
+
function isValidRecoveryResult(result) {
|
|
5436
|
+
if (!result || typeof result !== "object") return false;
|
|
5437
|
+
if (!("success" in result)) return true;
|
|
5438
|
+
return result.success !== false;
|
|
5439
|
+
}
|
|
5440
|
+
function isCompressedObservation(observation) {
|
|
5441
|
+
return "title" in observation && typeof observation.title === "string" && observation.title.length > 0;
|
|
5442
|
+
}
|
|
5443
|
+
async function recoverStaleSession(sdk, sessionId) {
|
|
5444
|
+
try {
|
|
5445
|
+
const result = await sdk.trigger({
|
|
5446
|
+
function_id: "event::session::stopped",
|
|
5447
|
+
payload: { sessionId }
|
|
5448
|
+
});
|
|
5449
|
+
if (!isValidRecoveryResult(result)) {
|
|
5450
|
+
logger.warn("Stale session recovery failed", {
|
|
5451
|
+
sessionId,
|
|
5452
|
+
result
|
|
5453
|
+
});
|
|
5454
|
+
return false;
|
|
5455
|
+
}
|
|
5456
|
+
return true;
|
|
5457
|
+
} catch (err) {
|
|
5458
|
+
logger.warn("Stale session recovery failed", {
|
|
5459
|
+
sessionId,
|
|
5460
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5461
|
+
});
|
|
5462
|
+
return false;
|
|
5463
|
+
}
|
|
5464
|
+
}
|
|
5465
|
+
async function runRecoveredSessionConsolidation(sdk) {
|
|
5466
|
+
try {
|
|
5467
|
+
await sdk.trigger({
|
|
5468
|
+
function_id: "mem::consolidate-pipeline",
|
|
5469
|
+
payload: { tier: "all" }
|
|
5470
|
+
});
|
|
5471
|
+
} catch (err) {
|
|
5472
|
+
logger.warn("Recovered session consolidation failed", { error: err instanceof Error ? err.message : String(err) });
|
|
5473
|
+
}
|
|
5474
|
+
}
|
|
5392
5475
|
function registerEvictFunction(sdk, kv) {
|
|
5393
5476
|
sdk.registerFunction("mem::evict", async (data) => {
|
|
5394
5477
|
const dryRun = data?.dryRun ?? false;
|
|
@@ -5407,6 +5490,7 @@ function registerEvictFunction(sdk, kv) {
|
|
|
5407
5490
|
nonLatestMemories: 0,
|
|
5408
5491
|
dryRun
|
|
5409
5492
|
};
|
|
5493
|
+
let recoveredStaleSessions = 0;
|
|
5410
5494
|
const sessions = await kv.list(KV.sessions).catch(() => []);
|
|
5411
5495
|
const summaries = await kv.list(KV.summaries).catch(() => []);
|
|
5412
5496
|
const summaryIds = new Set(summaries.map((s) => s.sessionId));
|
|
@@ -5414,6 +5498,23 @@ function registerEvictFunction(sdk, kv) {
|
|
|
5414
5498
|
if (!session.startedAt) continue;
|
|
5415
5499
|
if (now - new Date(session.startedAt).getTime() > cfg.staleSessionDays * MS_PER_DAY$1 && !summaryIds.has(session.id)) if (dryRun) stats.staleSessions++;
|
|
5416
5500
|
else {
|
|
5501
|
+
const observations = await kv.list(KV.observations(session.id)).catch((err) => {
|
|
5502
|
+
logger.warn("Stale session observation scan failed", {
|
|
5503
|
+
sessionId: session.id,
|
|
5504
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5505
|
+
});
|
|
5506
|
+
return null;
|
|
5507
|
+
});
|
|
5508
|
+
if (!observations) continue;
|
|
5509
|
+
let recovered = false;
|
|
5510
|
+
if (observations.some(isCompressedObservation)) {
|
|
5511
|
+
recovered = await recoverStaleSession(sdk, session.id);
|
|
5512
|
+
if (!recovered) continue;
|
|
5513
|
+
recoveredStaleSessions++;
|
|
5514
|
+
} else if (observations.length > 0) {
|
|
5515
|
+
logger.warn("Stale session has no compressed observations", { sessionId: session.id });
|
|
5516
|
+
continue;
|
|
5517
|
+
}
|
|
5417
5518
|
try {
|
|
5418
5519
|
await kv.delete(KV.sessions, session.id);
|
|
5419
5520
|
stats.staleSessions++;
|
|
@@ -5427,11 +5528,12 @@ function registerEvictFunction(sdk, kv) {
|
|
|
5427
5528
|
}
|
|
5428
5529
|
await recordAudit(kv, "delete", "mem::evict", [session.id], {
|
|
5429
5530
|
resource: "session",
|
|
5430
|
-
reason: "stale_session_without_summary",
|
|
5531
|
+
reason: recovered ? "stale_session_recovered_then_evicted" : "stale_session_without_summary",
|
|
5431
5532
|
dryRun
|
|
5432
5533
|
});
|
|
5433
5534
|
}
|
|
5434
5535
|
}
|
|
5536
|
+
if (!dryRun && recoveredStaleSessions > 0) await runRecoveredSessionConsolidation(sdk);
|
|
5435
5537
|
const projectObs = /* @__PURE__ */ new Map();
|
|
5436
5538
|
for (const session of sessions) {
|
|
5437
5539
|
const compressed = (await kv.list(KV.observations(session.id)).catch(() => [])).filter((o) => o.title);
|
|
@@ -6110,7 +6212,7 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
6110
6212
|
|
|
6111
6213
|
//#endregion
|
|
6112
6214
|
//#region src/version.ts
|
|
6113
|
-
const VERSION = "0.9.
|
|
6215
|
+
const VERSION = "0.9.18";
|
|
6114
6216
|
|
|
6115
6217
|
//#endregion
|
|
6116
6218
|
//#region src/functions/export-import.ts
|
|
@@ -6245,7 +6347,8 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
6245
6347
|
"0.9.14",
|
|
6246
6348
|
"0.9.15",
|
|
6247
6349
|
"0.9.16",
|
|
6248
|
-
"0.9.17"
|
|
6350
|
+
"0.9.17",
|
|
6351
|
+
"0.9.18"
|
|
6249
6352
|
]).has(importData.version)) return {
|
|
6250
6353
|
success: false,
|
|
6251
6354
|
error: `Unsupported export version: ${importData.version}`
|
|
@@ -19512,7 +19615,38 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
19512
19615
|
|
|
19513
19616
|
//#endregion
|
|
19514
19617
|
//#region src/viewer/server.ts
|
|
19618
|
+
function loadViewerFavicon() {
|
|
19619
|
+
const base = dirname(fileURLToPath(import.meta.url));
|
|
19620
|
+
const candidates = [
|
|
19621
|
+
join(base, "..", "src", "viewer", "favicon.svg"),
|
|
19622
|
+
join(base, "..", "viewer", "favicon.svg"),
|
|
19623
|
+
join(base, "viewer", "favicon.svg")
|
|
19624
|
+
];
|
|
19625
|
+
for (const path of candidates) try {
|
|
19626
|
+
return readFileSync(path);
|
|
19627
|
+
} catch {}
|
|
19628
|
+
return null;
|
|
19629
|
+
}
|
|
19515
19630
|
const ALLOWED_ORIGINS = (process.env.VIEWER_ALLOWED_ORIGINS || "http://localhost:3111,http://localhost:3113,http://127.0.0.1:3111,http://127.0.0.1:3113").split(",").map((o) => o.trim());
|
|
19631
|
+
const ALLOWED_HOSTS_OVERRIDE = (process.env.VIEWER_ALLOWED_HOSTS || "").split(",").map((h) => h.trim().toLowerCase()).filter(Boolean);
|
|
19632
|
+
function buildAllowedHosts(origins, listenPort) {
|
|
19633
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
19634
|
+
for (const o of origins) try {
|
|
19635
|
+
const parsed = new URL(o);
|
|
19636
|
+
if (parsed.host) hosts.add(parsed.host.toLowerCase());
|
|
19637
|
+
} catch {}
|
|
19638
|
+
hosts.add(`localhost:${listenPort}`);
|
|
19639
|
+
hosts.add(`127.0.0.1:${listenPort}`);
|
|
19640
|
+
hosts.add(`[::1]:${listenPort}`);
|
|
19641
|
+
for (const h of ALLOWED_HOSTS_OVERRIDE) hosts.add(h);
|
|
19642
|
+
return hosts;
|
|
19643
|
+
}
|
|
19644
|
+
function isHostAllowed(headerHost, allowed) {
|
|
19645
|
+
if (typeof headerHost !== "string") return false;
|
|
19646
|
+
const lower = headerHost.toLowerCase().trim();
|
|
19647
|
+
if (!lower) return false;
|
|
19648
|
+
return allowed.has(lower);
|
|
19649
|
+
}
|
|
19516
19650
|
function corsHeaders(req) {
|
|
19517
19651
|
const origin = req.headers.origin || "";
|
|
19518
19652
|
const allowed = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
|
|
@@ -19556,7 +19690,17 @@ const MAX_VIEWER_PORT_RETRIES = 10;
|
|
|
19556
19690
|
function startViewerServer(port, _kv, _sdk, secret, restPort) {
|
|
19557
19691
|
const resolvedRestPort = restPort ?? port - 2;
|
|
19558
19692
|
const requestedPort = port;
|
|
19693
|
+
let allowedHosts = null;
|
|
19559
19694
|
const server = createServer(async (req, res) => {
|
|
19695
|
+
if (!allowedHosts) {
|
|
19696
|
+
const addr = server.address();
|
|
19697
|
+
allowedHosts = buildAllowedHosts(ALLOWED_ORIGINS, addr && typeof addr === "object" && "port" in addr ? addr.port : port);
|
|
19698
|
+
}
|
|
19699
|
+
if (!isHostAllowed(req.headers.host, allowedHosts)) {
|
|
19700
|
+
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
19701
|
+
res.end("forbidden host");
|
|
19702
|
+
return;
|
|
19703
|
+
}
|
|
19560
19704
|
const raw = req.url || "/";
|
|
19561
19705
|
const qIdx = raw.indexOf("?");
|
|
19562
19706
|
const pathname = qIdx >= 0 ? raw.slice(0, qIdx) : raw;
|
|
@@ -19585,6 +19729,20 @@ function startViewerServer(port, _kv, _sdk, secret, restPort) {
|
|
|
19585
19729
|
res.end("viewer not found");
|
|
19586
19730
|
return;
|
|
19587
19731
|
}
|
|
19732
|
+
if (method === "GET" && pathname === "/favicon.svg") {
|
|
19733
|
+
const favicon = loadViewerFavicon();
|
|
19734
|
+
if (favicon) {
|
|
19735
|
+
res.writeHead(200, {
|
|
19736
|
+
"Content-Type": "image/svg+xml",
|
|
19737
|
+
"Cache-Control": "public, max-age=3600"
|
|
19738
|
+
});
|
|
19739
|
+
res.end(favicon);
|
|
19740
|
+
return;
|
|
19741
|
+
}
|
|
19742
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
19743
|
+
res.end("favicon not found");
|
|
19744
|
+
return;
|
|
19745
|
+
}
|
|
19588
19746
|
try {
|
|
19589
19747
|
await proxyToRestApi(resolvedRestPort, pathname, qs, method, req, res, secret);
|
|
19590
19748
|
} catch (err) {
|