@t0ken.ai/memoryx-openclaw-plugin 2.2.64 → 2.2.66

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.
@@ -1,4 +1,4 @@
1
- export declare const PLUGIN_VERSION = "2.2.64";
1
+ export declare const PLUGIN_VERSION = "2.2.66";
2
2
  export declare const DEFAULT_API_BASE = "https://t0ken.ai/api";
3
3
  export declare const PLUGIN_DIR: string;
4
4
  /** 真实上游 baseUrl 缓存文件:重启后若配置已被改成 localhost,从此文件恢复各厂商真实地址。 */
package/dist/constants.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import * as path from "path";
5
5
  import * as os from "os";
6
6
  // Plugin version - synced from package.json by prebuild script
7
- export const PLUGIN_VERSION = "2.2.64";
7
+ export const PLUGIN_VERSION = "2.2.66";
8
8
  export const DEFAULT_API_BASE = "https://t0ken.ai/api";
9
9
  export const PLUGIN_DIR = path.join(os.homedir(), ".openclaw", "extensions", "memoryx-openclaw-plugin");
10
10
  /** 真实上游 baseUrl 缓存文件:重启后若配置已被改成 localhost,从此文件恢复各厂商真实地址。 */
package/dist/hooks.d.ts CHANGED
@@ -4,5 +4,5 @@
4
4
  import type { MemoryXPlugin } from "./plugin-core.js";
5
5
  export declare function registerHooks(api: any, plugin: MemoryXPlugin, wrapProvidersWithProxy: () => void, applySidecarRedirect: (opts?: {
6
6
  quiet?: boolean;
7
- }) => void, shouldApplyProxy: () => boolean, syncRealUpstreamBaseUrlCache?: () => Promise<void>): void;
7
+ }) => void, shouldApplyProxy: () => boolean): void;
8
8
  //# sourceMappingURL=hooks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,wBAAgB,aAAa,CACzB,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,aAAa,EACrB,sBAAsB,EAAE,MAAM,IAAI,EAClC,oBAAoB,EAAE,CAAC,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,KAAK,IAAI,EAC1D,gBAAgB,EAAE,MAAM,OAAO,EAC/B,4BAA4B,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GACnD,IAAI,CAuCN"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,wBAAgB,aAAa,CACzB,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,aAAa,EACrB,sBAAsB,EAAE,MAAM,IAAI,EAClC,oBAAoB,EAAE,CAAC,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,KAAK,IAAI,EAC1D,gBAAgB,EAAE,MAAM,OAAO,GAChC,IAAI,CAoCN"}
package/dist/hooks.js CHANGED
@@ -1,4 +1,4 @@
1
- export function registerHooks(api, plugin, wrapProvidersWithProxy, applySidecarRedirect, shouldApplyProxy, syncRealUpstreamBaseUrlCache) {
1
+ export function registerHooks(api, plugin, wrapProvidersWithProxy, applySidecarRedirect, shouldApplyProxy) {
2
2
  let useVirtualProviderInLastRun = false;
3
3
  api.on("message_received", async (event) => {
4
4
  const { content } = event;
@@ -16,16 +16,13 @@ export function registerHooks(api, plugin, wrapProvidersWithProxy, applySidecarR
16
16
  await plugin.onMessage("assistant", fullContent);
17
17
  }
18
18
  });
19
- api.on("before_agent_start", async (event) => {
19
+ api.on("before_agent_start", async (_event) => {
20
20
  if (shouldApplyProxy()) {
21
- await syncRealUpstreamBaseUrlCache?.();
22
21
  wrapProvidersWithProxy();
23
22
  applySidecarRedirect({ quiet: true });
24
23
  }
25
24
  try {
26
25
  await plugin.startTimersIfNeeded();
27
- // 用户消息只由 message_received 写入一次,此处不再 onMessage("user", prompt),
28
- // 避免与 message_received 重复,且 before_agent_start 可能被多次触发(如 tool 多轮)导致同条 prompt 重复入队。
29
26
  }
30
27
  catch (error) {
31
28
  api.logger.warn(`[MemoryX] before_agent_start failed: ${error}`);
package/dist/index.d.ts CHANGED
@@ -9,6 +9,8 @@
9
9
  * - All initialization (DB, config loading, etc.) must be lazy-loaded
10
10
  * - Use setImmediate() for deferred operations
11
11
  *
12
+ * All file I/O (baseUrl cache) happens in service start (async).
13
+ *
12
14
  * This version uses @t0ken.ai/memoryx-sdk with conversation preset:
13
15
  * - maxTokens: 30000 (flush when reaching token limit)
14
16
  * - intervalMs: 300000 (flush after 5 minutes idle)
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,OAAO,EAAE,aAAa,EAAU,MAAM,kBAAkB,CAAC;;;;;;kBAsBvC,GAAG,iBAAiB,YAAY,GAAG,IAAI;;AARzD,wBA6FE;AAEF,OAAO,EAAE,aAAa,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,OAAO,EAAE,aAAa,EAAU,MAAM,kBAAkB,CAAC;;;;;;kBAsBvC,GAAG,iBAAiB,YAAY,GAAG,IAAI;;AARzD,wBAmGE;AAEF,OAAO,EAAE,aAAa,EAAE,CAAC"}
package/dist/index.js CHANGED
@@ -9,6 +9,8 @@
9
9
  * - All initialization (DB, config loading, etc.) must be lazy-loaded
10
10
  * - Use setImmediate() for deferred operations
11
11
  *
12
+ * All file I/O (baseUrl cache) happens in service start (async).
13
+ *
12
14
  * This version uses @t0ken.ai/memoryx-sdk with conversation preset:
13
15
  * - maxTokens: 30000 (flush when reaching token limit)
14
16
  * - intervalMs: 300000 (flush after 5 minutes idle)
@@ -37,50 +39,56 @@ export default {
37
39
  }
38
40
  plugin = new MemoryXPlugin(pluginConfig);
39
41
  registerTools(api, plugin);
40
- const providerCredentials = extractProviderCredentials(api.config);
41
- log(`[Proxy] Found ${providerCredentials.size} providers in config`);
42
- const defaultConfig = getDefaultModelAndProvider(providerCredentials, api.config);
42
+ // Pure memory: read provider list from api.config (no file I/O, same as slimclaw)
43
+ const configCredentials = extractProviderCredentials(api.config);
44
+ log(`[Proxy] Found ${configCredentials.size} providers in config`);
45
+ const defaultConfig = getDefaultModelAndProvider(configCredentials, api.config);
43
46
  log(`[Proxy] Default: ${defaultConfig.provider}/${defaultConfig.model}`);
44
47
  const realProviderHeader = "X-MemoryX-Real-Provider";
45
48
  const sidecarBaseUrl = `http://localhost:${SIDECAR_PORT}`;
46
- const realUpstreamCredentials = realUpstreamCredentialsForSidecar(providerCredentials, new Map());
47
49
  const proxyUrl = (pluginConfig?.apiBaseUrl || DEFAULT_API_BASE) + "/llm/proxy/chat/completions";
48
- const sidecar = new SidecarServer(realUpstreamCredentials, { model: defaultConfig.model, provider: defaultConfig.provider }, defaultConfig.availableProviders, {
49
- proxyUrl,
50
- getSDK,
51
- pluginConfig,
52
- onStarted: (logFile) => api.logger.info(`[MemoryX] Plugin log file: ${logFile}`),
53
- });
54
- const getSidecarBase = () => `http://localhost:${sidecar.getPort()}`;
50
+ let sidecar = null;
51
+ const getSidecarBase = () => sidecar ? `http://localhost:${sidecar.getPort()}` : sidecarBaseUrl;
55
52
  const { applySidecarRedirect, wrapProvidersWithProxy } = createProxyRedirect(api, getSidecarBase, realProviderHeader);
56
- /** 仅当用户当前默认使用的是 memoryx-gateway 时才做拦截/注入,否则不碰配置,避免装完插件就用不了 OpenClaw。 */
57
- const shouldApplyProxy = () => getDefaultModelAndProvider(extractProviderCredentials(api.config), api.config).provider === "memoryx-gateway";
58
- const syncRealUpstreamBaseUrlCache = () => syncRealUpstreamBaseUrlCacheFromConfig(api.config, getSidecarBase());
59
- registerHooks(api, plugin, wrapProvidersWithProxy, applySidecarRedirect, shouldApplyProxy, syncRealUpstreamBaseUrlCache);
53
+ const shouldApplyProxy = () => {
54
+ const creds = extractProviderCredentials(api.config);
55
+ return getDefaultModelAndProvider(creds, api.config).provider === "memoryx-gateway";
56
+ };
57
+ registerHooks(api, plugin, wrapProvidersWithProxy, applySidecarRedirect, shouldApplyProxy);
60
58
  api.registerService({
61
59
  id: "memoryx-sidecar",
62
60
  start: async () => {
63
61
  try {
62
+ // Re-read credentials from api.config (pure memory, may have updated since register)
63
+ const credentials = extractProviderCredentials(api.config);
64
+ const defaults = getDefaultModelAndProvider(credentials, api.config);
65
+ // Sync baseUrl cache: config → compare with cache → update non-localhost
66
+ await syncRealUpstreamBaseUrlCacheFromConfig(api.config, getSidecarBase());
67
+ const realUpstreamCredentials = realUpstreamCredentialsForSidecar(credentials, new Map());
68
+ sidecar = new SidecarServer(realUpstreamCredentials, { model: defaults.model, provider: defaults.provider }, defaults.availableProviders, {
69
+ proxyUrl,
70
+ getSDK,
71
+ pluginConfig,
72
+ onStarted: (logFile) => api.logger.info(`[MemoryX] Plugin log file: ${logFile}`),
73
+ });
64
74
  await sidecar.start();
65
75
  api.logger.info(`[MemoryX] Sidecar started on port ${sidecar.getPort()}`);
76
+ if (defaults.provider === "memoryx-gateway") {
77
+ wrapProvidersWithProxy();
78
+ applySidecarRedirect();
79
+ }
66
80
  }
67
81
  catch (e) {
68
82
  api.logger.error(`[MemoryX] Sidecar failed to start: ${e.message}`);
69
83
  }
70
84
  },
71
85
  stop: async () => {
72
- await sidecar.stop();
86
+ if (sidecar)
87
+ await sidecar.stop();
73
88
  api.logger.info("[MemoryX] Sidecar stopped");
74
89
  },
75
90
  });
76
- if (defaultConfig.provider === "memoryx-gateway") {
77
- wrapProvidersWithProxy();
78
- applySidecarRedirect();
79
- api.logger.info(`[MemoryX] ✅ Plugin v${PLUGIN_VERSION} ready! Default is MemoryX Gateway, redirect applied.`);
80
- }
81
- else {
82
- api.logger.info(`[MemoryX] ✅ Plugin v${PLUGIN_VERSION} ready! Default is ${defaultConfig.provider}/${defaultConfig.model}; switch to MemoryX Gateway to use MemoryX.`);
83
- }
91
+ api.logger.info(`[MemoryX] ✅ Plugin v${PLUGIN_VERSION} registered (I/O deferred to service start).`);
84
92
  setImmediate(async () => {
85
93
  try {
86
94
  await plugin.init();
@@ -89,13 +97,7 @@ export default {
89
97
  api.logger.info(`[MemoryX] 💡 Manage your memories: https://t0ken.ai/portal?api_key=${info.apiKey}`);
90
98
  }
91
99
  }
92
- catch (e) {
93
- // ignore
94
- }
95
- try {
96
- await syncRealUpstreamBaseUrlCacheFromConfig(api.config, sidecarBaseUrl);
97
- }
98
- catch (e) {
100
+ catch (_) {
99
101
  // ignore
100
102
  }
101
103
  });
@@ -1,67 +1,42 @@
1
1
  /**
2
- * Extract and resolve provider credentials from OpenClaw config; default model/provider and available list for fallback.
2
+ * Extract and resolve provider credentials from OpenClaw config (api.config).
3
3
  *
4
- * API key sources:
5
- * - OpenClaw may store keys in auth-profiles.json (e.g. openclaw models auth paste-token);
6
- * the gateway does auth lookup at runtime and does not merge back into api.config.
7
- * - The plugin only receives api.config (from openclaw.json), so it can read:
8
- * config.models.providers[id].apiKey, env vars, and auth-profiles.json (this module).
9
- * - If the user configured key only via auth-profiles and not in config/env, we try to
10
- * fill apiKey from ~/.openclaw/agents/main/agent/auth-profiles.json by provider.
4
+ * API key resolution order (same as slimclaw):
5
+ * 1. providerOverrides[id].apiKey (explicit)
6
+ * 2. api.config.models.providers[id].apiKey (openclaw.json)
7
+ * 3. providerOverrides[id].apiKeyEnv process.env[name]
8
+ * 4. process.env[${PROVIDER_ID}_API_KEY]
9
+ *
10
+ * NO file I/O in this module's credential extraction.
11
+ * Only real-upstream-baseurl.json cache uses async file I/O.
11
12
  */
12
13
  import type { ProviderCredentials } from "./types.js";
14
+ /**
15
+ * Extract provider credentials from api.config only.
16
+ * Follows slimclaw pattern: config.models.providers + env vars, no system file access.
17
+ */
13
18
  export declare function extractProviderCredentials(config: any, providerOverrides?: Record<string, {
14
19
  baseUrl?: string;
15
20
  apiKeyEnv?: string;
16
21
  apiKey?: string;
17
22
  }>): Map<string, ProviderCredentials>;
18
- /**
19
- * 在 init 时从 OpenClaw 配置(api.config)读到的「所有厂商 -> baseUrl」做一次快照,不写死任何厂商地址。
20
- * - 来源唯一:插件启动时通过 api 读到的 openclaw 配置文件(openclaw.json 等)里的 models.providers[id].baseUrl。
21
- * - 若某条已是 localhost(例如上次 redirect 被持久化),本次仍写入 localhost;恢复依赖 setImmediate 里从缓存文件合并(首次正常时已落盘)。
22
- * 返回 Map<providerId, baseUrl>,memoryx-gateway 不写入。
23
- */
24
23
  export declare function buildRealUpstreamBaseUrlMap(credentials: Map<string, ProviderCredentials>, _sidecarBaseUrl: string): Map<string, string>;
25
- /**
26
- * 用 init 时缓存的「真实上游 baseUrl 映射表」生成供 Sidecar 使用的 credentials。
27
- * 发给 llm_proxy 的 base_url_map(各厂商 api_base)均来自此映射表(真实 baseUrl),不会被 redirect 后的 localhost 覆盖。
28
- * 用户改配置后重启即可生效。
29
- */
30
24
  export declare function realUpstreamCredentialsForSidecar(credentials: Map<string, ProviderCredentials>, realUpstreamBaseUrlMap: Map<string, string>): Map<string, ProviderCredentials>;
31
- /**
32
- * 从缓存文件加载「厂商 -> 真实 baseUrl」映射。文件不存在或解析失败返回空 Map。
33
- * 供插件 sync 时使用(先读已有缓存,没有则当空)。
34
- */
35
25
  export declare function loadRealUpstreamBaseUrlCache(): Promise<Map<string, string>>;
26
+ export declare function saveRealUpstreamBaseUrlCache(map: Map<string, string>): Promise<void>;
27
+ export declare function isLocalhostBaseUrl(url: string, sidecarBaseUrl: string): boolean;
36
28
  /**
37
- * 从缓存文件加载「厂商 -> 真实 baseUrl」映射;文件不存在、IO 或解析错误时 throw。
38
- * 供 Sidecar 每次请求读盘使用,不缓存。
39
- */
40
- export declare function loadRealUpstreamBaseUrlCacheStrict(): Promise<Map<string, string>>;
41
- /**
42
- * 根据 OpenClaw 配置更新 real-upstream-baseurl.json:非 localhost 的 baseUrl 写入/覆盖,localhost 绝不覆盖。
29
+ * Sync real-upstream-baseurl.json: config baseUrl vs cache, update non-localhost entries.
43
30
  */
44
31
  export declare function syncRealUpstreamBaseUrlCacheFromConfig(config: any, sidecarBaseUrl: string): Promise<void>;
45
- /**
46
- * 将「厂商 -> 真实 baseUrl」映射写入缓存文件,重启后若配置被改成 localhost 可从此恢复。
47
- */
48
- export declare function saveRealUpstreamBaseUrlCache(map: Map<string, string>): Promise<void>;
49
- /** 判断是否为 localhost(或等于 sidecarBaseUrl),用于合并缓存时决定是否用缓存值覆盖。 */
50
- export declare function isLocalhostBaseUrl(url: string, sidecarBaseUrl: string): boolean;
51
- /** Get all available provider/model list (for server fallback/retry); only providers with apiKey set */
52
32
  export declare function getAvailableProviders(credentials: Map<string, ProviderCredentials>): Array<{
53
33
  provider: string;
54
34
  model: string;
55
35
  }>;
56
- /**
57
- * Default primary: one model per provider (first listed); only providers with apiKey set.
58
- * Order follows config.models.providers key order (Map insertion order).
59
- */
60
36
  export declare function getDefaultProviderModelList(credentials: Map<string, ProviderCredentials>): Array<{
61
37
  provider: string;
62
38
  model: string;
63
39
  }>;
64
- /** Get default model/provider and full availableProviders; prefer config.agents.defaults.model.primary, else first in list. */
65
40
  export declare function getDefaultModelAndProvider(credentials: Map<string, ProviderCredentials>, config?: any): {
66
41
  model: string;
67
42
  provider: string;
@@ -1 +1 @@
1
- {"version":3,"file":"proxy-credentials.d.ts","sourceRoot":"","sources":["../src/proxy-credentials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAuCtD,wBAAgB,0BAA0B,CACtC,MAAM,EAAE,GAAG,EACX,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAC9F,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAiDlC;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACvC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAC7C,eAAe,EAAE,MAAM,GACxB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAQrB;AAED;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC7C,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAC7C,sBAAsB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5C,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAWlC;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAcjF;AAED;;;GAGG;AACH,wBAAsB,kCAAkC,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAUvF;AAED;;GAEG;AACH,wBAAsB,sCAAsC,CACxD,MAAM,EAAE,GAAG,EACX,cAAc,EAAE,MAAM,GACvB,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED;;GAEG;AACH,wBAAsB,4BAA4B,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB1F;AAED,6DAA6D;AAC7D,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAI/E;AAOD,wGAAwG;AACxG,wBAAgB,qBAAqB,CACjC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,GAC9C,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAc5C;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACvC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,GAC9C,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAU5C;AAoBD,+HAA+H;AAC/H,wBAAgB,0BAA0B,CACtC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAC7C,MAAM,CAAC,EAAE,GAAG,GACb;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CA8BrG"}
1
+ {"version":3,"file":"proxy-credentials.d.ts","sourceRoot":"","sources":["../src/proxy-credentials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAStD;;;GAGG;AACH,wBAAgB,0BAA0B,CACtC,MAAM,EAAE,GAAG,EACX,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAC9F,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAuClC;AAMD,wBAAgB,2BAA2B,CACvC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAC7C,eAAe,EAAE,MAAM,GACxB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAQrB;AAED,wBAAgB,iCAAiC,CAC7C,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAC7C,sBAAsB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5C,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAWlC;AAED,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAcjF;AAED,wBAAsB,4BAA4B,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB1F;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAI/E;AAED;;GAEG;AACH,wBAAsB,sCAAsC,CACxD,MAAM,EAAE,GAAG,EACX,cAAc,EAAE,MAAM,GACvB,OAAO,CAAC,IAAI,CAAC,CAiBf;AAMD,wBAAgB,qBAAqB,CACjC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,GAC9C,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAa5C;AAED,wBAAgB,2BAA2B,CACvC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,GAC9C,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAS5C;AAmBD,wBAAgB,0BAA0B,CACtC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAC7C,MAAM,CAAC,EAAE,GAAG,GACb;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CA8BrG"}
@@ -1,40 +1,15 @@
1
1
  import { log } from "./logger.js";
2
2
  import { PLUGIN_DIR, REAL_UPSTREAM_BASEURL_CACHE_FILE } from "./constants.js";
3
3
  import * as fs from "fs";
4
- import * as path from "path";
5
- import * as os from "os";
6
- const DEFAULT_AUTH_PROFILES_PATH = path.join(os.homedir(), ".openclaw", "agents", "main", "agent", "auth-profiles.json");
7
- /** Parse apiKey by provider from auth-profiles.json. Profile keys are often "providerId" or "providerId:name", matching models.providers id. */
8
- function loadAuthProfilesKeys(profilesPath) {
9
- const out = new Map();
10
- try {
11
- if (!fs.existsSync(profilesPath))
12
- return out;
13
- const raw = fs.readFileSync(profilesPath, "utf8");
14
- const data = JSON.parse(raw);
15
- const profiles = data?.profiles ?? {};
16
- for (const [profileId, p] of Object.entries(profiles)) {
17
- if (!p || typeof p !== "object")
18
- continue;
19
- const isApiKey = (p.type === "api_key" || p.mode === "api_key");
20
- const value = (p.key ?? p.token ?? "")?.trim();
21
- if (!isApiKey || !value)
22
- continue;
23
- const providerId = profileId.includes(":") ? profileId.split(":")[0] : profileId;
24
- if (!out.has(providerId))
25
- out.set(providerId, value);
26
- if (p.provider && p.provider !== providerId && !out.has(p.provider))
27
- out.set(p.provider, value);
28
- }
29
- }
30
- catch (_) {
31
- // ignore
32
- }
33
- return out;
34
- }
4
+ // ---------------------------------------------------------------------------
5
+ // Provider credentials from api.config (pure memory, no file I/O)
6
+ // ---------------------------------------------------------------------------
7
+ /**
8
+ * Extract provider credentials from api.config only.
9
+ * Follows slimclaw pattern: config.models.providers + env vars, no system file access.
10
+ */
35
11
  export function extractProviderCredentials(config, providerOverrides) {
36
12
  const credentials = new Map();
37
- const authKeys = loadAuthProfilesKeys(DEFAULT_AUTH_PROFILES_PATH);
38
13
  if (config?.models?.providers) {
39
14
  for (const [id, providerConfig] of Object.entries(config.models.providers)) {
40
15
  if (typeof providerConfig !== "object" ||
@@ -51,7 +26,6 @@ export function extractProviderCredentials(config, providerOverrides) {
51
26
  (typeof pc.apiKey === "string" ? pc.apiKey.trim() : "") ||
52
27
  (customEnvName ? process.env[customEnvName] : undefined) ||
53
28
  process.env[`${id.toUpperCase()}_API_KEY`] ||
54
- authKeys.get(id) ||
55
29
  "";
56
30
  const isMemoryxGateway = (id === "memoryx-gateway");
57
31
  credentials.set(id, {
@@ -63,21 +37,13 @@ export function extractProviderCredentials(config, providerOverrides) {
63
37
  }
64
38
  }
65
39
  if (credentials.size > 0) {
66
- const parts = [...credentials.entries()].map(([id, c]) => `${id}(${c.models?.length ?? 0})`);
67
- const authFromProfiles = [...credentials.keys()].filter((id) => authKeys.has(id));
68
- const summary = authFromProfiles.length > 0
69
- ? `[Proxy] Credentials: ${parts.join(", ")}; apiKey from auth-profiles: ${authFromProfiles.join(", ")}`
70
- : `[Proxy] Credentials: ${parts.join(", ")}`;
71
- log(summary);
40
+ log(`[Proxy] Providers: ${[...credentials.keys()].join(", ")}`);
72
41
  }
73
42
  return credentials;
74
43
  }
75
- /**
76
- * init 时从 OpenClaw 配置(api.config)读到的「所有厂商 -> baseUrl」做一次快照,不写死任何厂商地址。
77
- * - 来源唯一:插件启动时通过 api 读到的 openclaw 配置文件(openclaw.json 等)里的 models.providers[id].baseUrl。
78
- * - 若某条已是 localhost(例如上次 redirect 被持久化),本次仍写入 localhost;恢复依赖 setImmediate 里从缓存文件合并(首次正常时已落盘)。
79
- * 返回 Map<providerId, baseUrl>,memoryx-gateway 不写入。
80
- */
44
+ // ---------------------------------------------------------------------------
45
+ // baseUrl cache (real-upstream-baseurl.json) async file I/O only
46
+ // ---------------------------------------------------------------------------
81
47
  export function buildRealUpstreamBaseUrlMap(credentials, _sidecarBaseUrl) {
82
48
  const map = new Map();
83
49
  for (const [id, creds] of credentials) {
@@ -88,11 +54,6 @@ export function buildRealUpstreamBaseUrlMap(credentials, _sidecarBaseUrl) {
88
54
  }
89
55
  return map;
90
56
  }
91
- /**
92
- * 用 init 时缓存的「真实上游 baseUrl 映射表」生成供 Sidecar 使用的 credentials。
93
- * 发给 llm_proxy 的 base_url_map(各厂商 api_base)均来自此映射表(真实 baseUrl),不会被 redirect 后的 localhost 覆盖。
94
- * 用户改配置后重启即可生效。
95
- */
96
57
  export function realUpstreamCredentialsForSidecar(credentials, realUpstreamBaseUrlMap) {
97
58
  const out = new Map();
98
59
  for (const [id, creds] of credentials) {
@@ -105,10 +66,6 @@ export function realUpstreamCredentialsForSidecar(credentials, realUpstreamBaseU
105
66
  }
106
67
  return out;
107
68
  }
108
- /**
109
- * 从缓存文件加载「厂商 -> 真实 baseUrl」映射。文件不存在或解析失败返回空 Map。
110
- * 供插件 sync 时使用(先读已有缓存,没有则当空)。
111
- */
112
69
  export async function loadRealUpstreamBaseUrlCache() {
113
70
  try {
114
71
  const raw = await fs.promises.readFile(REAL_UPSTREAM_BASEURL_CACHE_FILE, "utf8");
@@ -126,24 +83,28 @@ export async function loadRealUpstreamBaseUrlCache() {
126
83
  return new Map();
127
84
  }
128
85
  }
129
- /**
130
- * 从缓存文件加载「厂商 -> 真实 baseUrl」映射;文件不存在、IO 或解析错误时 throw。
131
- * Sidecar 每次请求读盘使用,不缓存。
132
- */
133
- export async function loadRealUpstreamBaseUrlCacheStrict() {
134
- const raw = await fs.promises.readFile(REAL_UPSTREAM_BASEURL_CACHE_FILE, "utf8");
135
- const obj = JSON.parse(raw);
136
- const map = new Map();
137
- if (obj && typeof obj === "object") {
138
- for (const [id, url] of Object.entries(obj)) {
139
- if (id && typeof url === "string" && url.trim())
140
- map.set(id, url.trim());
86
+ export async function saveRealUpstreamBaseUrlCache(map) {
87
+ try {
88
+ await fs.promises.mkdir(PLUGIN_DIR, { recursive: true });
89
+ const obj = {};
90
+ for (const [id, url] of map) {
91
+ if (id !== "memoryx-gateway" && url)
92
+ obj[id] = url;
141
93
  }
94
+ await fs.promises.writeFile(REAL_UPSTREAM_BASEURL_CACHE_FILE, JSON.stringify(obj, null, 2), "utf8");
95
+ log(`[Proxy] baseUrl cache saved`);
96
+ }
97
+ catch (e) {
98
+ log(`[Proxy] Failed to save real upstream cache: ${e?.message ?? e}`);
142
99
  }
143
- return map;
100
+ }
101
+ export function isLocalhostBaseUrl(url, sidecarBaseUrl) {
102
+ const u = (url || "").trim().toLowerCase().replace(/\/$/, "");
103
+ const sidecarNorm = (sidecarBaseUrl || "").toLowerCase().replace(/\/$/, "");
104
+ return !u || u.includes("localhost") || (!!sidecarNorm && u === sidecarNorm);
144
105
  }
145
106
  /**
146
- * 根据 OpenClaw 配置更新 real-upstream-baseurl.json:非 localhost baseUrl 写入/覆盖,localhost 绝不覆盖。
107
+ * Sync real-upstream-baseurl.json: config baseUrl vs cache, update non-localhost entries.
147
108
  */
148
109
  export async function syncRealUpstreamBaseUrlCacheFromConfig(config, sidecarBaseUrl) {
149
110
  let cached;
@@ -165,40 +126,12 @@ export async function syncRealUpstreamBaseUrlCacheFromConfig(config, sidecarBase
165
126
  }
166
127
  await saveRealUpstreamBaseUrlCache(merged);
167
128
  }
168
- /**
169
- * 将「厂商 -> 真实 baseUrl」映射写入缓存文件,重启后若配置被改成 localhost 可从此恢复。
170
- */
171
- export async function saveRealUpstreamBaseUrlCache(map) {
172
- try {
173
- await fs.promises.mkdir(PLUGIN_DIR, { recursive: true });
174
- const obj = {};
175
- for (const [id, url] of map) {
176
- if (id !== "memoryx-gateway" && url)
177
- obj[id] = url;
178
- }
179
- await fs.promises.writeFile(REAL_UPSTREAM_BASEURL_CACHE_FILE, JSON.stringify(obj, null, 2), "utf8");
180
- log(`[Proxy] Saved real upstream baseUrl cache to ${REAL_UPSTREAM_BASEURL_CACHE_FILE}`);
181
- }
182
- catch (e) {
183
- log(`[Proxy] Failed to save real upstream cache: ${e?.message ?? e}`);
184
- }
185
- }
186
- /** 判断是否为 localhost(或等于 sidecarBaseUrl),用于合并缓存时决定是否用缓存值覆盖。 */
187
- export function isLocalhostBaseUrl(url, sidecarBaseUrl) {
188
- const u = (url || "").trim().toLowerCase().replace(/\/$/, "");
189
- const sidecarNorm = (sidecarBaseUrl || "").toLowerCase().replace(/\/$/, "");
190
- return !u || u.includes("localhost") || (!!sidecarNorm && u === sidecarNorm);
191
- }
192
- /** Only include providers with non-empty apiKey to avoid invalid Bearer header */
193
- function hasApiKey(creds) {
194
- return ((creds.apiKey ?? "").trim().length > 0);
195
- }
196
- /** Get all available provider/model list (for server fallback/retry); only providers with apiKey set */
129
+ // ---------------------------------------------------------------------------
130
+ // Provider list helpers (pure memory)
131
+ // ---------------------------------------------------------------------------
197
132
  export function getAvailableProviders(credentials) {
198
133
  const result = [];
199
134
  for (const [providerId, creds] of credentials) {
200
- if (!hasApiKey(creds))
201
- continue;
202
135
  if (creds.models && creds.models.length > 0) {
203
136
  for (const m of creds.models) {
204
137
  const id = m.id || m.name;
@@ -206,21 +139,15 @@ export function getAvailableProviders(credentials) {
206
139
  result.push({ provider: providerId, model: id });
207
140
  }
208
141
  }
209
- else {
142
+ else if (creds.baseUrl) {
210
143
  result.push({ provider: providerId, model: "" });
211
144
  }
212
145
  }
213
146
  return result;
214
147
  }
215
- /**
216
- * Default primary: one model per provider (first listed); only providers with apiKey set.
217
- * Order follows config.models.providers key order (Map insertion order).
218
- */
219
148
  export function getDefaultProviderModelList(credentials) {
220
149
  const result = [];
221
150
  for (const [providerId, creds] of credentials) {
222
- if (!hasApiKey(creds))
223
- continue;
224
151
  if (creds.models?.length) {
225
152
  const id = creds.models[0].id || creds.models[0].name;
226
153
  if (id)
@@ -229,7 +156,6 @@ export function getDefaultProviderModelList(credentials) {
229
156
  }
230
157
  return result;
231
158
  }
232
- /** Find provider/model in available list matching primary (primary format: "provider/modelId") */
233
159
  function resolvePrimaryInAvailable(primary, available) {
234
160
  if (!primary || typeof primary !== "string")
235
161
  return null;
@@ -247,7 +173,6 @@ function resolvePrimaryInAvailable(primary, available) {
247
173
  return { provider: wantProvider, model: wantModel };
248
174
  return null;
249
175
  }
250
- /** Get default model/provider and full availableProviders; prefer config.agents.defaults.model.primary, else first in list. */
251
176
  export function getDefaultModelAndProvider(credentials, config) {
252
177
  const defaultList = getDefaultProviderModelList(credentials);
253
178
  const availableAll = getAvailableProviders(credentials);
@@ -1 +1 @@
1
- {"version":3,"file":"proxy-redirect.d.ts","sourceRoot":"","sources":["../src/proxy-redirect.ts"],"names":[],"mappings":"AAMA,wBAAgB,mBAAmB,CAC/B,GAAG,EAAE,GAAG,EACR,cAAc,EAAE,MAAM,MAAM,EAC5B,kBAAkB,EAAE,MAAM,GAC3B;IACC,oBAAoB,EAAE,CAAC,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IAC3D,sBAAsB,EAAE,MAAM,IAAI,CAAC;CACtC,CAoEA"}
1
+ {"version":3,"file":"proxy-redirect.d.ts","sourceRoot":"","sources":["../src/proxy-redirect.ts"],"names":[],"mappings":"AAyBA,wBAAgB,mBAAmB,CAC/B,GAAG,EAAE,GAAG,EACR,cAAc,EAAE,MAAM,MAAM,EAC5B,kBAAkB,EAAE,MAAM,GAC3B;IACC,oBAAoB,EAAE,CAAC,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IAC3D,sBAAsB,EAAE,MAAM,IAAI,CAAC;CACtC,CAyDA"}
@@ -1,22 +1,40 @@
1
1
  /**
2
2
  * Redirect provider baseUrl to Sidecar and wrap config so reads always resolve to Sidecar.
3
+ *
4
+ * Neither function performs file I/O. They only mutate api.config in memory.
3
5
  */
4
- import { extractProviderCredentials } from "./proxy-credentials.js";
5
6
  import { log } from "./logger.js";
7
+ /**
8
+ * Get all non-memoryx-gateway provider IDs from api.config.models.providers.
9
+ * Pure memory read, no file I/O.
10
+ */
11
+ function getProviderIds(config) {
12
+ const providers = config?.models?.providers;
13
+ if (!providers || typeof providers !== "object")
14
+ return [];
15
+ const ids = [];
16
+ for (const id of Object.keys(providers)) {
17
+ if (id === "memoryx-gateway")
18
+ continue;
19
+ const p = providers[id];
20
+ if (p && typeof p === "object" && "baseUrl" in p) {
21
+ ids.push(id);
22
+ }
23
+ }
24
+ return ids;
25
+ }
6
26
  export function createProxyRedirect(api, getSidecarBase, realProviderHeader) {
7
27
  const applySidecarRedirect = (opts) => {
8
28
  const sidecarBase = getSidecarBase();
9
- const creds = extractProviderCredentials(api.config);
10
- if (!api.config?.models?.providers || creds.size === 0) {
29
+ const providerIds = getProviderIds(api.config);
30
+ if (providerIds.length === 0) {
11
31
  if (!opts?.quiet)
12
- log(`[Proxy] No providers to redirect (providers exists: ${!!api.config?.models?.providers}, creds: ${creds.size})`, { console: true });
32
+ log(`[Proxy] No providers to redirect`, { console: true });
13
33
  return;
14
34
  }
15
35
  const providers = api.config.models.providers;
16
36
  let n = 0;
17
- for (const providerId of creds.keys()) {
18
- if (providerId === "memoryx-gateway")
19
- continue;
37
+ for (const providerId of providerIds) {
20
38
  const p = providers[providerId];
21
39
  if (!p || typeof p !== "object")
22
40
  continue;
@@ -28,31 +46,20 @@ export function createProxyRedirect(api, getSidecarBase, realProviderHeader) {
28
46
  log(`[Proxy] Redirected provider "${providerId}" baseUrl → Sidecar, header ${realProviderHeader}=${providerId}`);
29
47
  }
30
48
  if (n > 0 && !opts?.quiet) {
31
- api.logger.info(`[MemoryX] ✅ Sidecar redirect applied for ${n} provider(s). Use your configured model (e.g. zai/glm-5).`);
32
- log(`[Proxy] Sidecar redirect applied for ${n} provider(s)`, { console: true });
33
- }
34
- if (n > 0) {
35
- for (const providerId of creds.keys()) {
36
- if (providerId === "memoryx-gateway")
37
- continue;
38
- const p = providers[providerId];
39
- const readBack = (p && typeof p === "object" && p.baseUrl) ? p.baseUrl : "<missing>";
40
- const isSidecar = readBack === sidecarBase;
41
- log(`[Proxy] Readback ${providerId}.baseUrl = ${readBack} ${isSidecar ? "(Sidecar ✓)" : "(NOT Sidecar!)"}`);
42
- }
49
+ api.logger.info(`[MemoryX] ✅ Sidecar redirect applied for ${n} provider(s).`);
43
50
  }
44
51
  };
45
52
  const wrapProvidersWithProxy = () => {
46
53
  if (!api.config?.models?.providers || typeof api.config.models.providers !== "object")
47
54
  return;
48
- const creds = extractProviderCredentials(api.config);
49
- if (creds.size === 0)
55
+ const providerIds = new Set(getProviderIds(api.config));
56
+ if (providerIds.size === 0)
50
57
  return;
51
58
  const raw = api.config.models.providers;
52
59
  const handler = {
53
60
  get(target, prop) {
54
61
  const val = target[prop];
55
- if (val && typeof val === "object" && creds.has(prop) && prop !== "memoryx-gateway") {
62
+ if (val && typeof val === "object" && providerIds.has(prop)) {
56
63
  return new Proxy(val, {
57
64
  get(t, k) {
58
65
  if (k === "baseUrl")
package/dist/sidecar.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
- * Local HTTP Sidecar: 轻量化转发。只读 base_url_map + credentials,打包发给服务端;
3
- * 服务端解析 provider、用 api_base LiteLLM(不拼 URL),做记忆注入。无本地解析、无 target。
2
+ * Local HTTP Sidecar: lightweight forwarder.
3
+ * Reads base_url_map from local cache file, credentials from constructor (api.config).
4
+ * Packages payload and POSTs to MemoryX API server.
4
5
  */
5
6
  import type { ProviderCredentials } from "./types.js";
6
7
  import type { PluginConfig } from "./types.js";
@@ -27,7 +28,6 @@ export declare class SidecarServer {
27
28
  start(): Promise<void>;
28
29
  stop(): Promise<void>;
29
30
  getPort(): number;
30
- /** 轻量化:只读 base_url_map + credentials,打包发给服务端;不做 provider 解析、无 target。 */
31
31
  private handleRequest;
32
32
  private readBody;
33
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar.d.ts","sourceRoot":"","sources":["../src/sidecar.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK/C,MAAM,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,EAAE,YAAY,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;AAE7D,MAAM,WAAW,cAAc;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,qBAAa,aAAa;IACtB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,WAAW,CAAmC;IACtD,OAAO,CAAC,eAAe,CAAsC;IAC7D,OAAO,CAAC,kBAAkB,CAA6C;IACvE,OAAO,CAAC,OAAO,CAAiB;gBAG5B,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAC7C,eAAe,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EACpD,kBAAkB,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,EAC9D,OAAO,EAAE,cAAc;IAQrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAa3B,OAAO,IAAI,MAAM;IAMjB,yEAAyE;YAC3D,aAAa;IAqK3B,OAAO,CAAC,QAAQ;CAQnB"}
1
+ {"version":3,"file":"sidecar.d.ts","sourceRoot":"","sources":["../src/sidecar.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK/C,MAAM,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,EAAE,YAAY,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;AAE7D,MAAM,WAAW,cAAc;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,qBAAa,aAAa;IACtB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,WAAW,CAAmC;IACtD,OAAO,CAAC,eAAe,CAAsC;IAC7D,OAAO,CAAC,kBAAkB,CAA6C;IACvE,OAAO,CAAC,OAAO,CAAiB;gBAG5B,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAC7C,eAAe,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EACpD,kBAAkB,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,EAC9D,OAAO,EAAE,cAAc;IAQrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAa3B,OAAO,IAAI,MAAM;YAMH,aAAa;IAgM3B,OAAO,CAAC,QAAQ;CAQnB"}
package/dist/sidecar.js CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
- * Local HTTP Sidecar: 轻量化转发。只读 base_url_map + credentials,打包发给服务端;
3
- * 服务端解析 provider、用 api_base LiteLLM(不拼 URL),做记忆注入。无本地解析、无 target。
2
+ * Local HTTP Sidecar: lightweight forwarder.
3
+ * Reads base_url_map from local cache file, credentials from constructor (api.config).
4
+ * Packages payload and POSTs to MemoryX API server.
4
5
  */
5
6
  import * as http from "http";
6
7
  import { SIDECAR_PORT } from "./constants.js";
@@ -60,7 +61,6 @@ export class SidecarServer {
60
61
  ? this.server.address().port
61
62
  : SIDECAR_PORT;
62
63
  }
63
- /** 轻量化:只读 base_url_map + credentials,打包发给服务端;不做 provider 解析、无 target。 */
64
64
  async handleRequest(req, res) {
65
65
  const url = req.url || "/";
66
66
  const method = req.method?.toUpperCase();
@@ -71,6 +71,7 @@ export class SidecarServer {
71
71
  return;
72
72
  }
73
73
  const reqId = `req-${Date.now()}`;
74
+ log(`[Sidecar] ${reqId} in ${method} ${url}`, { console: true });
74
75
  let body;
75
76
  try {
76
77
  body = await this.readBody(req);
@@ -89,6 +90,7 @@ export class SidecarServer {
89
90
  openaiRequest = {};
90
91
  }
91
92
  const stream = !!openaiRequest.stream;
93
+ // Read base_url_map from cache file (async)
92
94
  let baseUrlMap;
93
95
  try {
94
96
  baseUrlMap = await loadRealUpstreamBaseUrlCache();
@@ -101,16 +103,20 @@ export class SidecarServer {
101
103
  if (id && u?.trim())
102
104
  baseUrlMapObj[id] = u.trim();
103
105
  }
106
+ const rawProvider = req.headers["x-memoryx-real-provider"]?.trim() || "";
107
+ const authHeader = req.headers["authorization"]?.trim() || "";
108
+ const incomingApiKey = authHeader.startsWith("Bearer ")
109
+ ? authHeader.slice(7).trim()
110
+ : authHeader;
111
+ // Build credentials: use incoming Authorization header as apiKey for the target provider
104
112
  const credentialsObj = {};
105
- for (const [id, c] of this.credentials) {
106
- if (!c)
107
- continue;
108
- credentialsObj[id] = {
109
- apiKey: (c.apiKey ?? "").trim(),
110
- models: c.models,
111
- };
113
+ for (const [id, creds] of this.credentials) {
114
+ const realBaseUrl = baseUrlMapObj[id] || creds.baseUrl || "";
115
+ const apiKey = (id === rawProvider && incomingApiKey)
116
+ ? incomingApiKey
117
+ : (creds.apiKey || "");
118
+ credentialsObj[id] = { baseUrl: realBaseUrl, apiKey };
112
119
  }
113
- const rawProvider = req.headers["x-memoryx-real-provider"]?.trim() || "";
114
120
  const messages = openaiRequest.messages || [];
115
121
  const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
116
122
  const searchQuery = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
@@ -128,6 +134,7 @@ export class SidecarServer {
128
134
  const proxyRequestBody = {
129
135
  base_url_map: baseUrlMapObj,
130
136
  credentials: credentialsObj,
137
+ incoming_api_key: incomingApiKey || undefined,
131
138
  body: openaiRequest,
132
139
  request_headers: { "x-memoryx-real-provider": rawProvider },
133
140
  search_query: searchQuery,
@@ -135,18 +142,25 @@ export class SidecarServer {
135
142
  available_models: this.availableProviders.map((p) => `${p.provider}/${p.model}`),
136
143
  };
137
144
  const pathLog = (req.url || "/").split("?")[0] || "/";
138
- log(`[Sidecar] ${reqId} ${method} ${pathLog} model=${openaiRequest.model ?? "-"} stream=${stream} → proxy`);
145
+ log(`[Sidecar] ${reqId} ${method} ${pathLog} model=${openaiRequest.model ?? "-"} stream=${stream} → proxy ${proxyUrl}`, { console: true });
146
+ const fetchAbort = new AbortController();
147
+ const fetchTimer = setTimeout(() => fetchAbort.abort(), 120_000);
139
148
  let proxyResponse = null;
140
149
  try {
141
150
  proxyResponse = await fetch(proxyUrl, {
142
151
  method: "POST",
143
152
  headers: { "Content-Type": "application/json", "X-API-Key": memoryxApiKey },
144
153
  body: JSON.stringify(proxyRequestBody),
154
+ signal: fetchAbort.signal,
145
155
  });
146
156
  }
147
157
  catch (e) {
148
158
  proxyResponse = null;
149
- log(`[Sidecar] ${reqId} err proxy fetch: ${e?.message ?? String(e)}`, { console: true });
159
+ const msg = fetchAbort.signal.aborted ? "fetch timeout (120s)" : (e?.message ?? String(e));
160
+ log(`[Sidecar] ${reqId} err proxy fetch: ${msg}`, { console: true });
161
+ }
162
+ finally {
163
+ clearTimeout(fetchTimer);
150
164
  }
151
165
  const proxyFailed = !proxyResponse || proxyResponse.status >= 500;
152
166
  if (proxyFailed) {
@@ -173,15 +187,20 @@ export class SidecarServer {
173
187
  const proxy = proxyResponse;
174
188
  if (openaiRequest.stream && proxy.body) {
175
189
  const reader = proxy.body.getReader();
190
+ const CHUNK_TIMEOUT = 90_000;
191
+ const readWithTimeout = () => Promise.race([
192
+ reader.read(),
193
+ new Promise((_, rej) => setTimeout(() => rej(new Error("chunk timeout")), CHUNK_TIMEOUT)),
194
+ ]);
176
195
  let firstChunk;
177
196
  try {
178
- firstChunk = await Promise.race([
179
- reader.read(),
180
- new Promise((_, rej) => setTimeout(() => rej(new Error("First chunk timeout")), 15000)),
181
- ]);
197
+ firstChunk = await readWithTimeout();
182
198
  }
183
199
  catch (firstErr) {
184
- reader.releaseLock();
200
+ try {
201
+ reader.releaseLock();
202
+ }
203
+ catch (_) { }
185
204
  log(`[Sidecar] ${reqId} err stream first chunk: ${firstErr?.message ?? "timeout"}`, { console: true });
186
205
  res.writeHead(502, { "Content-Type": "application/json" });
187
206
  res.end(JSON.stringify({ error: "MemoryX proxy stream failed" }));
@@ -194,17 +213,27 @@ export class SidecarServer {
194
213
  if (!firstChunk.done && firstChunk.value)
195
214
  res.write(Buffer.from(firstChunk.value));
196
215
  while (true) {
197
- const { done, value } = await reader.read();
198
- if (done)
216
+ if (res.destroyed) {
217
+ log(`[Sidecar] ${reqId} client disconnected, aborting stream`, { console: true });
218
+ break;
219
+ }
220
+ const { done, value } = await readWithTimeout();
221
+ if (done || !value)
199
222
  break;
200
223
  res.write(Buffer.from(value));
201
224
  }
202
225
  }
226
+ catch (streamErr) {
227
+ log(`[Sidecar] ${reqId} stream error: ${streamErr?.message ?? "unknown"}`, { console: true });
228
+ }
203
229
  finally {
204
- reader.releaseLock();
230
+ try {
231
+ reader.releaseLock();
232
+ }
233
+ catch (_) { }
205
234
  }
206
235
  res.end();
207
- log(`[Sidecar] ${reqId} ← ${proxy.status} stream`);
236
+ log(`[Sidecar] ${reqId} ← ${proxy.status} stream`, { console: true });
208
237
  return;
209
238
  }
210
239
  if (!proxy.body) {
@@ -212,11 +241,11 @@ export class SidecarServer {
212
241
  "Content-Type": proxy.headers.get("content-type") || "application/json",
213
242
  });
214
243
  res.end();
215
- log(`[Sidecar] ${reqId} ← ${proxy.status} (no body)`);
244
+ log(`[Sidecar] ${reqId} ← ${proxy.status} (no body)`, { console: true });
216
245
  return;
217
246
  }
218
247
  const text = await proxy.text();
219
- log(`[Sidecar] ${reqId} ← ${proxy.status} body=${text.length}`);
248
+ log(`[Sidecar] ${reqId} ← ${proxy.status} body=${text.length}`, { console: true });
220
249
  res.writeHead(proxy.status, {
221
250
  "Content-Type": proxy.headers.get("content-type") || "application/json",
222
251
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@t0ken.ai/memoryx-openclaw-plugin",
3
- "version": "2.2.64",
3
+ "version": "2.2.66",
4
4
  "description": "MemoryX real-time memory capture and recall plugin for OpenClaw (powered by @t0ken.ai/memoryx-sdk)",
5
5
  "type": "module",
6
6
  "author": "MemoryX Team",