@t0ken.ai/memoryx-openclaw-plugin 2.2.63 → 2.2.65

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.63";
1
+ export declare const PLUGIN_VERSION = "2.2.65";
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.63";
7
+ export const PLUGIN_VERSION = "2.2.65";
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 的每个厂商的 targetUrl 均来自此映射表(初始化时的真实 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 的每个厂商的 targetUrl 均来自此映射表(初始化时的真实 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,5 +1,7 @@
1
1
  /**
2
- * Local HTTP Sidecar: 仅转发到服务端 proxy(记忆注入),无本地回落。
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.
3
5
  */
4
6
  import type { ProviderCredentials } from "./types.js";
5
7
  import type { PluginConfig } from "./types.js";
@@ -26,11 +28,6 @@ export declare class SidecarServer {
26
28
  start(): Promise<void>;
27
29
  stop(): Promise<void>;
28
30
  getPort(): number;
29
- /** For logging: redact headers (Authorization, x-api-key, etc. show as ***) */
30
- private redactHeaders;
31
- /** Build forward request headers from apiKey in OpenClaw style; does not overwrite user config */
32
- private buildForwardHeaders;
33
- /** 仅转发到服务端 proxy(记忆注入),无本地回落。 */
34
31
  private handleRequest;
35
32
  private readBody;
36
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar.d.ts","sourceRoot":"","sources":["../src/sidecar.ts"],"names":[],"mappings":"AAAA;;GAEG;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,+EAA+E;IAC/E,OAAO,CAAC,aAAa;IAUrB,kGAAkG;IAClG,OAAO,CAAC,mBAAmB;IAa3B,iCAAiC;YACnB,aAAa;IA6O3B,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;IA+K3B,OAAO,CAAC,QAAQ;CAQnB"}
package/dist/sidecar.js CHANGED
@@ -1,10 +1,12 @@
1
1
  /**
2
- * Local HTTP Sidecar: 仅转发到服务端 proxy(记忆注入),无本地回落。
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.
3
5
  */
4
6
  import * as http from "http";
5
7
  import { SIDECAR_PORT } from "./constants.js";
6
8
  import { log, LOG_FILE } from "./logger.js";
7
- import { loadRealUpstreamBaseUrlCacheStrict } from "./proxy-credentials.js";
9
+ import { loadRealUpstreamBaseUrlCache } from "./proxy-credentials.js";
8
10
  export class SidecarServer {
9
11
  server = null;
10
12
  credentials;
@@ -59,32 +61,6 @@ export class SidecarServer {
59
61
  ? this.server.address().port
60
62
  : SIDECAR_PORT;
61
63
  }
62
- /** For logging: redact headers (Authorization, x-api-key, etc. show as ***) */
63
- redactHeaders(h) {
64
- const out = {};
65
- const secretKeys = ["authorization", "x-api-key", "cookie"];
66
- for (const [k, v] of Object.entries(h)) {
67
- const lower = k.toLowerCase();
68
- out[k] = secretKeys.some((s) => lower === s || lower.includes("api-key")) ? "***" : v;
69
- }
70
- return out;
71
- }
72
- /** Build forward request headers from apiKey in OpenClaw style; does not overwrite user config */
73
- buildForwardHeaders(provider, apiKey) {
74
- const h = { "Content-Type": "application/json" };
75
- const key = (apiKey || "").trim();
76
- if (!key)
77
- return h;
78
- if (provider === "anthropic") {
79
- h["x-api-key"] = key;
80
- h["anthropic-version"] = "2023-06-01";
81
- }
82
- else {
83
- h["Authorization"] = `Bearer ${key}`;
84
- }
85
- return h;
86
- }
87
- /** 仅转发到服务端 proxy(记忆注入),无本地回落。 */
88
64
  async handleRequest(req, res) {
89
65
  const url = req.url || "/";
90
66
  const method = req.method?.toUpperCase();
@@ -95,6 +71,7 @@ export class SidecarServer {
95
71
  return;
96
72
  }
97
73
  const reqId = `req-${Date.now()}`;
74
+ log(`[Sidecar] ${reqId} in ${method} ${url}`, { console: true });
98
75
  let body;
99
76
  try {
100
77
  body = await this.readBody(req);
@@ -112,91 +89,37 @@ export class SidecarServer {
112
89
  catch {
113
90
  openaiRequest = {};
114
91
  }
115
- const messages = openaiRequest.messages || [];
116
- const modelStr = openaiRequest.model || "";
117
92
  const stream = !!openaiRequest.stream;
118
- const rawProvider = req.headers["x-memoryx-real-provider"]?.trim() || "";
119
- let provider = rawProvider || (modelStr.includes("/") ? modelStr.split("/")[0] : "") || "";
120
- if (provider === "memoryx-gateway") {
121
- const real = this.availableProviders.find((p) => p.provider !== "memoryx-gateway");
122
- provider = real ? real.provider : "";
123
- }
124
- if (!provider || !this.credentials.has(provider)) {
125
- provider = this.defaultProvider?.provider || "";
126
- }
127
- if (!provider || !this.credentials.has(provider)) {
128
- for (const [id] of this.credentials) {
129
- if (id !== "memoryx-gateway") {
130
- provider = id;
131
- break;
132
- }
133
- }
134
- }
135
- let creds = provider ? this.credentials.get(provider) : undefined;
136
- if (!creds || !(creds.apiKey ?? "").trim()) {
137
- for (const [id, c] of this.credentials) {
138
- if (id !== "memoryx-gateway" && (c.apiKey ?? "").trim()) {
139
- provider = id;
140
- creds = c;
141
- break;
142
- }
143
- }
144
- }
145
- let apiKey = (creds?.apiKey ?? "").trim();
146
- if (!provider || !creds || !apiKey) {
147
- res.writeHead(502, { "Content-Type": "application/json" });
148
- res.end(JSON.stringify({ error: "No upstream (no apiKey for provider)" }));
149
- return;
150
- }
151
- if (provider === "memoryx-gateway") {
152
- const real = this.availableProviders.find((p) => p.provider !== "memoryx-gateway");
153
- if (!real) {
154
- res.writeHead(502, { "Content-Type": "application/json" });
155
- res.end(JSON.stringify({
156
- error: "memoryx-gateway is virtual; no real LLM provider in config. Add a provider (e.g. zai, anthropic) in OpenClaw models.providers.",
157
- }));
158
- return;
159
- }
160
- provider = real.provider;
161
- const realCreds = this.credentials.get(provider);
162
- if (!realCreds || !(realCreds.apiKey ?? "").trim()) {
163
- res.writeHead(502, { "Content-Type": "application/json" });
164
- res.end(JSON.stringify({
165
- error: `No apiKey for real provider '${provider}'. Configure it in OpenClaw (models.providers or auth-profiles).`,
166
- }));
167
- return;
168
- }
169
- creds = realCreds;
170
- apiKey = (creds.apiKey ?? "").trim();
171
- }
93
+ // Read base_url_map from cache file (async)
172
94
  let baseUrlMap;
173
95
  try {
174
- baseUrlMap = await loadRealUpstreamBaseUrlCacheStrict();
96
+ baseUrlMap = await loadRealUpstreamBaseUrlCache();
175
97
  }
176
- catch (e) {
177
- res.writeHead(502, { "Content-Type": "application/json" });
178
- res.end(JSON.stringify({
179
- error: "real-upstream-baseurl.json could not be read: " + (e?.message ?? String(e)),
180
- }));
181
- return;
98
+ catch {
99
+ baseUrlMap = new Map();
182
100
  }
183
- const baseUrl = baseUrlMap.get(provider)?.trim();
184
- if (!baseUrl) {
185
- res.writeHead(502, { "Content-Type": "application/json" });
186
- res.end(JSON.stringify({
187
- error: `Missing real upstream baseUrl for provider '${provider}'. Ensure real-upstream-baseurl.json is populated (e.g. from OpenClaw config).`,
188
- }));
189
- return;
101
+ const baseUrlMapObj = {};
102
+ for (const [id, u] of baseUrlMap) {
103
+ if (id && u?.trim())
104
+ baseUrlMapObj[id] = u.trim();
190
105
  }
191
- const base = baseUrl.replace(/\/$/, "");
192
- const pathFromOpenClaw = (url.split("?")[0] || "/").trim() || "/";
193
- const targetUrl = base + (pathFromOpenClaw.startsWith("/") ? pathFromOpenClaw : "/" + pathFromOpenClaw);
194
- const currentModel = openaiRequest.model || "";
195
- if (!currentModel || currentModel === "auto" || currentModel.startsWith("memoryx-gateway/")) {
196
- const firstId = creds?.models?.[0]?.id || creds?.models?.[0]?.name;
197
- if (firstId)
198
- openaiRequest.model = firstId;
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
112
+ const credentialsObj = {};
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 };
199
119
  }
120
+ const messages = openaiRequest.messages || [];
121
+ const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
122
+ const searchQuery = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
200
123
  let memoryxApiKey = "";
201
124
  let agentId = "";
202
125
  try {
@@ -206,21 +129,20 @@ export class SidecarServer {
206
129
  agentId = accountInfo.agentId || "";
207
130
  }
208
131
  catch (_e) {
209
- /* Do not block; proxy may still accept request */
132
+ /* do not block */
210
133
  }
211
- const headers = this.buildForwardHeaders(provider, apiKey);
212
- const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
213
- const searchQuery = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
214
134
  const proxyRequestBody = {
215
- targetUrl,
216
- api_base: base,
217
- headers,
135
+ base_url_map: baseUrlMapObj,
136
+ credentials: credentialsObj,
137
+ incoming_api_key: incomingApiKey || undefined,
218
138
  body: openaiRequest,
219
- searchQuery,
139
+ request_headers: { "x-memoryx-real-provider": rawProvider },
140
+ search_query: searchQuery,
220
141
  agent_id: agentId,
221
142
  available_models: this.availableProviders.map((p) => `${p.provider}/${p.model}`),
222
143
  };
223
- log(`[Sidecar] ${reqId} ${method} ${(url.split("?")[0] || "/").trim()} provider=${provider} model=${openaiRequest.model ?? "-"} stream=${stream} → proxy target=${targetUrl}`);
144
+ const pathLog = (req.url || "/").split("?")[0] || "/";
145
+ log(`[Sidecar] ${reqId} ${method} ${pathLog} model=${openaiRequest.model ?? "-"} stream=${stream} → proxy ${proxyUrl}`, { console: true });
224
146
  let proxyResponse = null;
225
147
  try {
226
148
  proxyResponse = await fetch(proxyUrl, {
@@ -245,7 +167,7 @@ export class SidecarServer {
245
167
  }
246
168
  }
247
169
  catch (_) {
248
- /* drain failed, ignore */
170
+ /* drain */
249
171
  }
250
172
  }
251
173
  const status = proxyResponse?.status ?? 502;
@@ -262,7 +184,7 @@ export class SidecarServer {
262
184
  try {
263
185
  firstChunk = await Promise.race([
264
186
  reader.read(),
265
- new Promise((_, rej) => setTimeout(() => rej(new Error("First chunk timeout")), 15000)),
187
+ new Promise((_, rej) => setTimeout(() => rej(new Error("First chunk timeout")), 60000)),
266
188
  ]);
267
189
  }
268
190
  catch (firstErr) {
@@ -289,7 +211,7 @@ export class SidecarServer {
289
211
  reader.releaseLock();
290
212
  }
291
213
  res.end();
292
- log(`[Sidecar] ${reqId} ← ${proxy.status} stream`);
214
+ log(`[Sidecar] ${reqId} ← ${proxy.status} stream`, { console: true });
293
215
  return;
294
216
  }
295
217
  if (!proxy.body) {
@@ -297,11 +219,11 @@ export class SidecarServer {
297
219
  "Content-Type": proxy.headers.get("content-type") || "application/json",
298
220
  });
299
221
  res.end();
300
- log(`[Sidecar] ${reqId} ← ${proxy.status} (no body)`);
222
+ log(`[Sidecar] ${reqId} ← ${proxy.status} (no body)`, { console: true });
301
223
  return;
302
224
  }
303
225
  const text = await proxy.text();
304
- log(`[Sidecar] ${reqId} ← ${proxy.status} body=${text.length}`);
226
+ log(`[Sidecar] ${reqId} ← ${proxy.status} body=${text.length}`, { console: true });
305
227
  res.writeHead(proxy.status, {
306
228
  "Content-Type": proxy.headers.get("content-type") || "application/json",
307
229
  });
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * 与服务端 llm_proxy 一致的厂商请求体规范化。
3
- * 部分 LLM 厂商非标准 OpenAI 格式,需在此做兼容后再转发;服务端用 LiteLLM,Sidecar 直连时用本模块保证同一套逻辑。
3
+ * 当前插件内无调用方,规范化由服务端 llm_proxy 完成;保留本模块供将来如需在插件侧做兼容时复用。
4
4
  */
5
5
  /**
6
- * 判断是否为 Z.AI GLM-5:按 provider 或 targetUrl 与 model 判断。
6
+ * 判断是否为 Z.AI GLM-5:按 provider 与 model 判断(不再依赖 targetUrl)。
7
7
  */
8
- export declare function isZaiGlm5(provider: string, targetUrl: string, model: string): boolean;
8
+ export declare function isZaiGlm5(provider: string, model: string): boolean;
9
9
  /**
10
- * 按厂商与 URL/model 规范化请求体;与服务端逻辑一致,供 Sidecar 直连时使用。
10
+ * 按厂商与 model 规范化请求体;与服务端逻辑一致。当前插件内无调用,规范化在服务端完成。
11
11
  * 返回规范化后的 body(不修改入参)。
12
12
  */
13
- export declare function normalizeBodyForVendor(provider: string, targetUrl: string, body: Record<string, unknown>): Record<string, unknown>;
13
+ export declare function normalizeBodyForVendor(provider: string, body: Record<string, unknown>): Record<string, unknown>;
14
14
  //# sourceMappingURL=vendor-normalize.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"vendor-normalize.d.ts","sourceRoot":"","sources":["../src/vendor-normalize.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAQrF;AA4DD;;;GAGG;AACH,wBAAgB,sBAAsB,CAClC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMzB"}
1
+ {"version":3,"file":"vendor-normalize.d.ts","sourceRoot":"","sources":["../src/vendor-normalize.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAIlE;AA4DD;;;GAGG;AACH,wBAAgB,sBAAsB,CAClC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMzB"}
@@ -1,19 +1,14 @@
1
1
  /**
2
2
  * 与服务端 llm_proxy 一致的厂商请求体规范化。
3
- * 部分 LLM 厂商非标准 OpenAI 格式,需在此做兼容后再转发;服务端用 LiteLLM,Sidecar 直连时用本模块保证同一套逻辑。
3
+ * 当前插件内无调用方,规范化由服务端 llm_proxy 完成;保留本模块供将来如需在插件侧做兼容时复用。
4
4
  */
5
5
  /**
6
- * 判断是否为 Z.AI GLM-5:按 provider 或 targetUrl 与 model 判断。
6
+ * 判断是否为 Z.AI GLM-5:按 provider 与 model 判断(不再依赖 targetUrl)。
7
7
  */
8
- export function isZaiGlm5(provider, targetUrl, model) {
8
+ export function isZaiGlm5(provider, model) {
9
9
  const prov = (provider || "").toLowerCase();
10
- const url = (targetUrl || "").toLowerCase();
11
10
  const m = (model || "").toLowerCase();
12
- if (prov === "zai" && (m === "glm-5" || m.endsWith("/glm-5")))
13
- return true;
14
- if ((url.includes("open.bigmodel.cn") || url.includes("api.z.ai")) && (m === "glm-5" || m.endsWith("/glm-5")))
15
- return true;
16
- return false;
11
+ return prov === "zai" && (m === "glm-5" || m.endsWith("/glm-5"));
17
12
  }
18
13
  /**
19
14
  * Z.AI GLM-5 兼容:与 api/app/routers/llm_proxy.py _normalize_body_for_zai_glm5 保持一致。
@@ -70,12 +65,12 @@ function normalizeBodyForZaiGlm5(body) {
70
65
  return out;
71
66
  }
72
67
  /**
73
- * 按厂商与 URL/model 规范化请求体;与服务端逻辑一致,供 Sidecar 直连时使用。
68
+ * 按厂商与 model 规范化请求体;与服务端逻辑一致。当前插件内无调用,规范化在服务端完成。
74
69
  * 返回规范化后的 body(不修改入参)。
75
70
  */
76
- export function normalizeBodyForVendor(provider, targetUrl, body) {
71
+ export function normalizeBodyForVendor(provider, body) {
77
72
  const model = String(body?.model || "").trim();
78
- if (isZaiGlm5(provider, targetUrl, model)) {
73
+ if (isZaiGlm5(provider, model)) {
79
74
  return normalizeBodyForZaiGlm5(body);
80
75
  }
81
76
  return body;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@t0ken.ai/memoryx-openclaw-plugin",
3
- "version": "2.2.63",
3
+ "version": "2.2.65",
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",