memorix 1.9.8 → 1.9.10

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +2 -0
  3. package/README.zh-CN.md +2 -0
  4. package/dist/cli/index.js +125 -31
  5. package/dist/index.js +117 -31
  6. package/dist/maintenance-runner.js +22 -5
  7. package/dist/media-audio-runner.js +22 -5
  8. package/dist/media-video-runner.js +22 -5
  9. package/dist/memcode-runtime/CHANGELOG.md +17 -0
  10. package/dist/sdk.js +116 -30
  11. package/dist/vector-backfill-runner.js +22 -5
  12. package/docs/CONFIGURATION.md +22 -0
  13. package/docs/DEVELOPMENT.md +1 -1
  14. package/docs/INTEGRATIONS.md +1 -1
  15. package/docs/README.md +1 -1
  16. package/docs/SETUP.md +1 -1
  17. package/package.json +1 -1
  18. package/plugins/antigravity/memorix/plugin.json +1 -1
  19. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  20. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  21. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  22. package/plugins/copilot/memorix/plugin.json +1 -1
  23. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  24. package/plugins/hermes/memorix/plugin.yaml +1 -1
  25. package/plugins/omp/memorix/package.json +1 -1
  26. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  27. package/plugins/pi/memorix/package.json +1 -1
  28. package/server.json +2 -2
  29. package/src/cli/commands/init.ts +7 -1
  30. package/src/cli/tui/ConfigureView.tsx +2 -0
  31. package/src/config/dotenv-loader.ts +1 -0
  32. package/src/config/resolved-config.ts +19 -5
  33. package/src/config/yaml-loader.ts +2 -2
  34. package/src/config.ts +5 -1
  35. package/src/dashboard/server.ts +3 -0
  36. package/src/hooks/installers/index.ts +89 -21
  37. package/src/llm/provider.ts +2 -1
  38. package/src/multimodal/image-loader.ts +2 -2
package/CHANGELOG.md CHANGED
@@ -6,6 +6,23 @@ All notable changes to this project will be documented in this file.
6
6
 
7
7
  No unreleased changes.
8
8
 
9
+ ## [1.9.10] - 2026-10-08
10
+
11
+ ### Fixed
12
+ - **OpenCode 1.x regression** -- generated plugins now include the v1
13
+ `server` field alongside the v2 `setup` contract, so OpenCode 1.x can load
14
+ the legacy named export. Lifecycle, file, command, message, and compaction
15
+ events are also forwarded through the v1 generic `event` hook, with a longer
16
+ Windows hook timeout to reduce stale Bun timer failures.
17
+
18
+ ## [1.9.9] - 2026-10-02
19
+
20
+ ### Added
21
+ - **Requesty memory preset** -- `memorix init` and the configuration TUI can
22
+ select Requesty for optional background memory LLM work through its
23
+ OpenAI-compatible endpoint, with provider-specific key isolation.
24
+ `REQUESTY_API_KEY` is also accepted for Requesty embedding endpoints.
25
+
9
26
  ## [1.9.8] - 2026-10-02
10
27
 
11
28
  ### Fixed
package/README.md CHANGED
@@ -533,6 +533,8 @@ OpenAI-compatible `https://api.atlascloud.ai/v1` endpoint and reads
533
533
 
534
534
  For OpenRouter embeddings, use `provider = "api"`, `base_url = "https://openrouter.ai/api/v1"`, and `model = "qwen/qwen3-embedding-8b"`. Memorix accepts `OPENROUTER_API_KEY` for that embedding endpoint; `MEMORIX_EMBEDDING_API_KEY` remains the explicit override.
535
535
 
536
+ For Requesty, choose Requesty in `memorix init` or set `provider = "requesty"` in `[memory.llm]` (endpoint `https://router.requesty.ai/v1`, key from `REQUESTY_API_KEY` or `MEMORIX_LLM_API_KEY`). Requesty embeddings use `provider = "api"`, `base_url = "https://router.requesty.ai/v1"`, and `model = "openai/text-embedding-3-small"` with `REQUESTY_API_KEY`.
537
+
536
538
  For controlled MiniMax media generation, set `MINIMAX_API_KEY` (global) or
537
539
  `MINIMAX_CN_API_KEY` (China region) in your environment or `.env`. The media
538
540
  library never stores that key, a signed output URL, or base64 payloads. CLI
package/README.zh-CN.md CHANGED
@@ -506,6 +506,8 @@ OpenAI-compatible 的 `https://api.atlascloud.ai/v1` endpoint,并读取
506
506
 
507
507
  如果使用 OpenRouter embedding,可以设置 `provider = "api"`、`base_url = "https://openrouter.ai/api/v1"`、`model = "qwen/qwen3-embedding-8b"`。这个 endpoint 下 Memorix 会读取官方 `OPENROUTER_API_KEY`;需要单独覆盖 embedding key 时仍可用 `MEMORIX_EMBEDDING_API_KEY`。
508
508
 
509
+ 使用 Requesty 时,可在 `memorix init` 中选择 Requesty,或在 `[memory.llm]` 中设置 `provider = "requesty"`(endpoint 为 `https://router.requesty.ai/v1`,key 读取 `REQUESTY_API_KEY` 或 `MEMORIX_LLM_API_KEY`)。Requesty embedding 使用 `provider = "api"`、`base_url = "https://router.requesty.ai/v1"`、`model = "openai/text-embedding-3-small"`,并读取 `REQUESTY_API_KEY`。
510
+
509
511
  受控 MiniMax 媒体生成可在环境变量或 `.env` 中设置全局 `MINIMAX_API_KEY`,或中国区 `MINIMAX_CN_API_KEY`。媒体库不会保存该 key、临时签名 URL 或 base64 负载。CLI 生成本身就是显式操作;若要让 MCP 中的 Agent 发起生成,还必须明确设置 `MEMORIX_MCP_MEDIA_GENERATION=1`。
510
512
 
511
513
  <h2 id="docker"><picture><source media="(prefers-color-scheme: dark)" srcset="assets/tags/light/section-docker.svg"><img src="assets/tags/section-docker.svg" alt="Docker" height="32" /></picture></h2>
package/dist/cli/index.js CHANGED
@@ -3232,7 +3232,7 @@ ${import_picocolors.default.gray(m3)} ${s4}
3232
3232
 
3233
3233
  // src/cli/version.ts
3234
3234
  function getCliVersion() {
3235
- return true ? "1.9.8" : pkg.version;
3235
+ return true ? "1.9.10" : pkg.version;
3236
3236
  }
3237
3237
  var init_version = __esm({
3238
3238
  "src/cli/version.ts"() {
@@ -22122,6 +22122,7 @@ function getResolvedConfig(options = {}) {
22122
22122
  const legacy = loadFileConfig(homeDir);
22123
22123
  const embeddingBaseUrl = first(process.env.MEMORIX_EMBEDDING_BASE_URL, toml.embedding?.base_url, yaml.embedding?.baseUrl, legacy.embeddingApi?.baseUrl);
22124
22124
  const openRouterEmbeddingApiKey = isOpenRouterUrl(embeddingBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
22125
+ const requestyEmbeddingApiKey = isRequestyUrl(embeddingBaseUrl) ? process.env.REQUESTY_API_KEY : void 0;
22125
22126
  const memoryLlmProvider = first(
22126
22127
  process.env.MEMORIX_LLM_PROVIDER,
22127
22128
  toml.memory?.llm?.provider,
@@ -22142,16 +22143,18 @@ function getResolvedConfig(options = {}) {
22142
22143
  );
22143
22144
  const openRouterMemoryLlmApiKey = isOpenRouterMemoryLane(memoryLlmProvider, memoryLlmBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
22144
22145
  const isAtlasCloud = memoryLlmProvider === "atlascloud";
22146
+ const isRequesty = memoryLlmProvider === "requesty";
22147
+ const usesPresetKeyOnly = isAtlasCloud || isRequesty;
22145
22148
  const memoryLlmApiKey = first(
22146
22149
  process.env.MEMORIX_LLM_API_KEY,
22147
22150
  process.env.MEMORIX_API_KEY,
22148
22151
  toml.memory?.llm?.api_key,
22149
22152
  yaml.llm?.apiKey,
22150
22153
  legacy.llm?.apiKey,
22151
- // Never borrow another provider's credentials for the Atlas Cloud preset.
22152
- isAtlasCloud ? process.env.ATLASCLOUD_API_KEY : process.env.OPENAI_API_KEY,
22153
- isAtlasCloud ? void 0 : process.env.ANTHROPIC_API_KEY,
22154
- isAtlasCloud ? void 0 : openRouterMemoryLlmApiKey
22154
+ // Never borrow another provider's credentials for the Atlas Cloud or Requesty presets.
22155
+ isAtlasCloud ? process.env.ATLASCLOUD_API_KEY : isRequesty ? process.env.REQUESTY_API_KEY : process.env.OPENAI_API_KEY,
22156
+ usesPresetKeyOnly ? void 0 : process.env.ANTHROPIC_API_KEY,
22157
+ usesPresetKeyOnly ? void 0 : openRouterMemoryLlmApiKey
22155
22158
  );
22156
22159
  const resolved = {
22157
22160
  agent: {
@@ -22200,7 +22203,7 @@ function getResolvedConfig(options = {}) {
22200
22203
  provider: first(process.env.MEMORIX_EMBEDDING, toml.embedding?.provider, yaml.embedding?.provider, legacy.embedding, "off"),
22201
22204
  model: first(process.env.MEMORIX_EMBEDDING_MODEL, toml.embedding?.model, yaml.embedding?.model, legacy.embeddingApi?.model),
22202
22205
  baseUrl: embeddingBaseUrl,
22203
- apiKey: first(process.env.MEMORIX_EMBEDDING_API_KEY, toml.embedding?.api_key, yaml.embedding?.apiKey, legacy.embeddingApi?.apiKey, openRouterEmbeddingApiKey),
22206
+ apiKey: first(process.env.MEMORIX_EMBEDDING_API_KEY, toml.embedding?.api_key, yaml.embedding?.apiKey, legacy.embeddingApi?.apiKey, openRouterEmbeddingApiKey, requestyEmbeddingApiKey),
22204
22207
  dimensions: firstNumber(parseNumber(process.env.MEMORIX_EMBEDDING_DIMENSIONS), toml.embedding?.dimensions, yaml.embedding?.dimensions, legacy.embeddingApi?.dimensions)
22205
22208
  },
22206
22209
  rerank: resolveRerankLane({
@@ -22331,6 +22334,7 @@ function getEnvSourceNames() {
22331
22334
  "MEMORIX_CODEGRAPH_EXTERNAL_COMMAND",
22332
22335
  "MEMORIX_CODEGRAPH_EXTERNAL_TIMEOUT_MS",
22333
22336
  "OPENROUTER_API_KEY",
22337
+ "REQUESTY_API_KEY",
22334
22338
  "ATLASCLOUD_API_KEY"
22335
22339
  ].filter((name) => process.env[name]);
22336
22340
  }
@@ -22343,6 +22347,15 @@ function isOpenRouterUrl(value) {
22343
22347
  return /(^|\.)openrouter\.ai(?::|\/|$)/i.test(value);
22344
22348
  }
22345
22349
  }
22350
+ function isRequestyUrl(value) {
22351
+ if (!value) return false;
22352
+ try {
22353
+ const url2 = new URL(value);
22354
+ return url2.hostname.toLowerCase() === "requesty.ai" || url2.hostname.toLowerCase().endsWith(".requesty.ai");
22355
+ } catch {
22356
+ return /(^|\.)requesty\.ai(?::|\/|$)/i.test(value);
22357
+ }
22358
+ }
22346
22359
  function isOpenRouterMemoryLane(provider2, baseUrl) {
22347
22360
  return provider2?.trim().toLowerCase() === "openrouter" || isOpenRouterUrl(baseUrl);
22348
22361
  }
@@ -22497,6 +22510,9 @@ function defaultEmbeddingModelFor(baseUrl, explicitModel) {
22497
22510
  if (url2 && (url2.hostname === "openrouter.ai" || url2.hostname.endsWith(".openrouter.ai"))) {
22498
22511
  return "qwen/qwen3-embedding-8b";
22499
22512
  }
22513
+ if (url2 && (url2.hostname === "requesty.ai" || url2.hostname.endsWith(".requesty.ai"))) {
22514
+ return "openai/text-embedding-3-small";
22515
+ }
22500
22516
  } catch {
22501
22517
  }
22502
22518
  return "text-embedding-3-small";
@@ -25311,6 +25327,7 @@ var init_provider2 = __esm({
25311
25327
  openai: { baseUrl: "https://api.openai.com/v1", model: "gpt-4.1-nano" },
25312
25328
  anthropic: { baseUrl: "https://api.anthropic.com/v1", model: "claude-3-5-haiku-latest" },
25313
25329
  openrouter: { baseUrl: "https://openrouter.ai/api/v1", model: "openai/gpt-4.1-nano" },
25330
+ requesty: { baseUrl: "https://router.requesty.ai/v1", model: "openai/gpt-4.1-nano" },
25314
25331
  atlascloud: { baseUrl: "https://api.atlascloud.ai/v1", model: "deepseek-ai/deepseek-v3.2" },
25315
25332
  custom: { baseUrl: "http://localhost:11434/v1", model: "llama3" }
25316
25333
  };
@@ -193093,6 +193110,8 @@ function buildInitTomlConfig(options) {
193093
193110
  lines.push('model = "claude-3-haiku-20240307"');
193094
193111
  } else if (options.llmProvider === "openrouter") {
193095
193112
  lines.push('# model = "openai/gpt-4o-mini"');
193113
+ } else if (options.llmProvider === "requesty") {
193114
+ lines.push('# model = "openai/gpt-4o-mini"');
193096
193115
  } else if (options.llmProvider === "atlascloud") {
193097
193116
  lines.push('model = "deepseek-ai/deepseek-v3.2"');
193098
193117
  }
@@ -193227,6 +193246,7 @@ var init_init = __esm({
193227
193246
  { value: "openai", label: "OpenAI", hint: "gpt-4o-mini" },
193228
193247
  { value: "anthropic", label: "Anthropic", hint: "claude-3-haiku" },
193229
193248
  { value: "openrouter", label: "OpenRouter", hint: "multi-provider" },
193249
+ { value: "requesty", label: "Requesty", hint: "multi-provider" },
193230
193250
  { value: "atlascloud", label: "Atlas Cloud", hint: "DeepSeek V3.2" },
193231
193251
  { value: "custom", label: "Custom", hint: "OpenAI-compatible endpoint" }
193232
193252
  ]
@@ -193287,6 +193307,8 @@ var init_init = __esm({
193287
193307
  envLines.push("MEMORIX_LLM_API_KEY=sk-ant-your-key-here");
193288
193308
  } else if (llmProvider === "openrouter") {
193289
193309
  envLines.push("MEMORIX_LLM_API_KEY=sk-or-your-key-here");
193310
+ } else if (llmProvider === "requesty") {
193311
+ envLines.push("MEMORIX_LLM_API_KEY=rqsty-your-key-here");
193290
193312
  }
193291
193313
  if (llmProvider === "custom") {
193292
193314
  envLines.push("# MEMORIX_LLM_BASE_URL=http://localhost:11434/v1");
@@ -193313,6 +193335,7 @@ var init_init = __esm({
193313
193335
  envLines.push("# OPENAI_API_KEY=sk-...");
193314
193336
  envLines.push("# ANTHROPIC_API_KEY=sk-ant-...");
193315
193337
  envLines.push("# OPENROUTER_API_KEY=sk-or-...");
193338
+ envLines.push("# REQUESTY_API_KEY=rqsty-...");
193316
193339
  envLines.push("");
193317
193340
  mkdirSync3(targetDir, { recursive: true });
193318
193341
  writeFileSync3(targetPath, tomlContent, "utf-8");
@@ -195486,6 +195509,8 @@ function generateOpenCodePlugin() {
195486
195509
  */
195487
195510
  import { spawnSync } from 'node:child_process';
195488
195511
 
195512
+ const HOOK_TIMEOUT_MS = 30_000;
195513
+
195489
195514
  export const MemorixPlugin = async ({ project, client, $, directory, worktree }) => {
195490
195515
  // Generate a stable session ID for this plugin lifetime
195491
195516
  const sessionId = \`opencode-\${Date.now().toString(36)}-\${Math.random().toString(36).slice(2, 8)}\`;
@@ -195511,14 +195536,14 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
195511
195536
  * - spawnSync is simpler: no stream lifecycle, no writer.close() bugs
195512
195537
  * - stdin pipe via input option is reliable cross-platform
195513
195538
  */
195514
- function runHook(payload) {
195515
- payload.session_id = sessionId;
195539
+ function runHook(payload, activeSessionId = sessionId) {
195540
+ payload.session_id = activeSessionId;
195516
195541
  const data = JSON.stringify(payload);
195517
195542
  const eventName = payload.hook_event_name || 'unknown';
195518
195543
  try {
195519
195544
  const result = spawnSync(hookCommand, ['hook'], {
195520
195545
  input: data,
195521
- timeout: 10_000,
195546
+ timeout: HOOK_TIMEOUT_MS,
195522
195547
  encoding: 'utf-8',
195523
195548
  stdio: ['pipe', 'pipe', 'pipe'],
195524
195549
  shell: process.platform === 'win32',
@@ -195563,7 +195588,87 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
195563
195588
  .trim();
195564
195589
  }
195565
195590
 
195591
+ function flushAssistantResponse(activeSessionId = sessionId) {
195592
+ if (!pendingAssistantResponse?.text) return;
195593
+ const deliveryKey = pendingAssistantResponse.id
195594
+ ? \`\${pendingAssistantResponse.id}:\${pendingAssistantResponse.text}\`
195595
+ : pendingAssistantResponse.text;
195596
+ if (deliveryKey === lastDeliveredAssistantKey) return;
195597
+ runHook({
195598
+ agent: 'opencode',
195599
+ hook_event_name: 'message.updated',
195600
+ ai_response: pendingAssistantResponse.text,
195601
+ message_id: pendingAssistantResponse.id,
195602
+ cwd: directory,
195603
+ }, activeSessionId);
195604
+ lastDeliveredAssistantKey = deliveryKey;
195605
+ }
195606
+
195607
+ function eventSessionId(event) {
195608
+ const properties = event?.properties ?? {};
195609
+ const value = properties.sessionID ?? properties.sessionId ?? properties.info?.id;
195610
+ return typeof value === 'string' && value ? value : sessionId;
195611
+ }
195612
+
195613
+ async function handleEvent({ event }) {
195614
+ const properties = event?.properties ?? {};
195615
+ const activeSessionId = eventSessionId(event);
195616
+ switch (event?.type) {
195617
+ case 'session.created':
195618
+ runHook({
195619
+ agent: 'opencode',
195620
+ hook_event_name: 'session.created',
195621
+ cwd: directory,
195622
+ }, activeSessionId);
195623
+ break;
195624
+ case 'session.idle':
195625
+ flushAssistantResponse(activeSessionId);
195626
+ runHook({
195627
+ agent: 'opencode',
195628
+ hook_event_name: 'session.idle',
195629
+ cwd: directory,
195630
+ }, activeSessionId);
195631
+ break;
195632
+ case 'file.edited':
195633
+ runHook({
195634
+ agent: 'opencode',
195635
+ hook_event_name: 'file.edited',
195636
+ file_path: properties.file ?? properties.path ?? '',
195637
+ cwd: directory,
195638
+ }, activeSessionId);
195639
+ break;
195640
+ case 'command.executed':
195641
+ runHook({
195642
+ agent: 'opencode',
195643
+ hook_event_name: 'command.executed',
195644
+ command: properties.name ?? properties.command ?? '',
195645
+ cwd: directory,
195646
+ }, activeSessionId);
195647
+ break;
195648
+ case 'message.updated': {
195649
+ const message = properties.info ?? properties.message ?? properties;
195650
+ if (message?.role !== 'assistant') break;
195651
+ const text = extractMessageText(message);
195652
+ if (!text) break;
195653
+ pendingAssistantResponse = { id: message.id, text };
195654
+ break;
195655
+ }
195656
+ case 'session.compacted':
195657
+ runHook({
195658
+ agent: 'opencode',
195659
+ hook_event_name: 'session.compacted',
195660
+ cwd: directory,
195661
+ }, activeSessionId);
195662
+ break;
195663
+ default:
195664
+ break;
195665
+ }
195666
+ }
195667
+
195566
195668
  return {
195669
+ // OpenCode 1.x delivers lifecycle/file/command/message events through this
195670
+ // generic hook. The named keys below remain for older compatible hosts.
195671
+ event: handleEvent,
195567
195672
  /** Session created \u2014 record session start */
195568
195673
  'session.created': async ({ session }) => {
195569
195674
  runHook({
@@ -195575,21 +195680,7 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
195575
195680
 
195576
195681
  /** Session idle \u2014 record session end */
195577
195682
  'session.idle': async ({ session }) => {
195578
- if (pendingAssistantResponse?.text) {
195579
- const deliveryKey = pendingAssistantResponse.id
195580
- ? \`\${pendingAssistantResponse.id}:\${pendingAssistantResponse.text}\`
195581
- : pendingAssistantResponse.text;
195582
- if (deliveryKey !== lastDeliveredAssistantKey) {
195583
- runHook({
195584
- agent: 'opencode',
195585
- hook_event_name: 'message.updated',
195586
- ai_response: pendingAssistantResponse.text,
195587
- message_id: pendingAssistantResponse.id,
195588
- cwd: directory,
195589
- });
195590
- lastDeliveredAssistantKey = deliveryKey;
195591
- }
195592
- }
195683
+ flushAssistantResponse();
195593
195684
  runHook({
195594
195685
  agent: 'opencode',
195595
195686
  hook_event_name: 'session.idle',
@@ -195697,7 +195788,7 @@ function v2RunHook(payload, directory, sessionId) {
195697
195788
  try {
195698
195789
  const result = spawnSync(v2HookCommand, ['hook'], {
195699
195790
  input: JSON.stringify(nextPayload),
195700
- timeout: 10_000,
195791
+ timeout: HOOK_TIMEOUT_MS,
195701
195792
  encoding: 'utf-8',
195702
195793
  stdio: ['pipe', 'pipe', 'pipe'],
195703
195794
  shell: process.platform === 'win32',
@@ -195821,7 +195912,7 @@ function setup(ctx) {
195821
195912
  return () => controller.abort();
195822
195913
  }
195823
195914
 
195824
- export default { id: 'memorix', setup };
195915
+ export default { id: 'memorix', server: MemorixPlugin, setup };
195825
195916
  `;
195826
195917
  }
195827
195918
  async function installOpenCodeSkills(projectRoot, global2 = false) {
@@ -196647,7 +196738,7 @@ var init_installers = __esm({
196647
196738
  "use strict";
196648
196739
  init_esm_shims();
196649
196740
  init_official_skills();
196650
- OPENCODE_PLUGIN_VERSION = 8;
196741
+ OPENCODE_PLUGIN_VERSION = 9;
196651
196742
  AGENT_SKILL_DIRS = {
196652
196743
  cursor: { project: path31.join(".cursor", "skills"), global: path31.join(".cursor", "skills") },
196653
196744
  windsurf: { project: path31.join(".windsurf", "skills"), global: path31.join(".windsurf", "skills") },
@@ -209174,7 +209265,7 @@ var init_tool_profile = __esm({
209174
209265
  function getMcpServerInfo() {
209175
209266
  return {
209176
209267
  name: "memorix",
209177
- version: true ? "1.9.8" : "1.0.1"
209268
+ version: true ? "1.9.10" : "1.0.1"
209178
209269
  };
209179
209270
  }
209180
209271
  function createMcpDiscoverResult(capabilities, serverInfo) {
@@ -225106,7 +225197,8 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
225106
225197
  "MEMORIX_API_KEY",
225107
225198
  "OPENAI_API_KEY",
225108
225199
  "ANTHROPIC_API_KEY",
225109
- ...resolved.memory.llm.provider === "openrouter" || resolved.memory.llm.baseUrl?.includes("openrouter.ai") ? ["OPENROUTER_API_KEY"] : []
225200
+ ...resolved.memory.llm.provider === "openrouter" || resolved.memory.llm.baseUrl?.includes("openrouter.ai") ? ["OPENROUTER_API_KEY"] : [],
225201
+ ...resolved.memory.llm.provider === "requesty" ? ["REQUESTY_API_KEY"] : []
225110
225202
  ];
225111
225203
  if (llmKey) {
225112
225204
  const src2 = resolveDashboardKeySource({ value: llmKey, envKeys: memoryKeyEnv, configSource });
@@ -265497,7 +265589,7 @@ async function analyzeImage(input) {
265497
265589
  const config2 = getLLMConfig();
265498
265590
  if (!OPENAI_COMPATIBLE_PROVIDERS.has(config2.provider)) {
265499
265591
  throw new Error(
265500
- `Image analysis requires an OpenAI-compatible provider (openai, openrouter, or custom). Current provider "${config2.provider}" uses a different API shape. Set MEMORIX_LLM_PROVIDER=openai or configure an OpenAI-compatible base URL.`
265592
+ `Image analysis requires an OpenAI-compatible provider (openai, openrouter, requesty, or custom). Current provider "${config2.provider}" uses a different API shape. Set MEMORIX_LLM_PROVIDER=openai or configure an OpenAI-compatible base URL.`
265501
265593
  );
265502
265594
  }
265503
265595
  const bytes = decodeBase64ImagePayload(input.base64, MAX_VISION_IMAGE_BYTES);
@@ -265536,7 +265628,7 @@ var init_image_loader = __esm({
265536
265628
  init_provider2();
265537
265629
  init_secret_filter();
265538
265630
  init_image_payload();
265539
- OPENAI_COMPATIBLE_PROVIDERS = /* @__PURE__ */ new Set(["openai", "openrouter", "custom"]);
265631
+ OPENAI_COMPATIBLE_PROVIDERS = /* @__PURE__ */ new Set(["openai", "openrouter", "requesty", "custom"]);
265540
265632
  MAX_VISION_PROMPT_CHARS = 12e3;
265541
265633
  MAX_VISION_DESCRIPTION_CHARS = 12e3;
265542
265634
  MAX_VISION_LABELS = 50;
@@ -289682,6 +289774,7 @@ function ConfigureView({ onBack }) {
289682
289774
  { key: "openai", label: "OpenAI", hint: "gpt-4o-mini" },
289683
289775
  { key: "anthropic", label: "Anthropic", hint: "claude-3-haiku" },
289684
289776
  { key: "openrouter", label: "OpenRouter", hint: "multi-provider" },
289777
+ { key: "requesty", label: "Requesty", hint: "multi-provider" },
289685
289778
  { key: "atlascloud", label: "Atlas Cloud", hint: "DeepSeek V3.2" },
289686
289779
  { key: "custom", label: "Custom endpoint", hint: "OpenAI-compatible" },
289687
289780
  { key: "disable", label: "Disable LLM", hint: "free heuristic mode", color: COLORS.warning },
@@ -289975,6 +290068,7 @@ function ConfigureView({ onBack }) {
289975
290068
  function getDefaultModel(provider2) {
289976
290069
  if (provider2 === "anthropic") return "claude-3-haiku-20240307";
289977
290070
  if (provider2 === "openrouter") return "openai/gpt-4o-mini";
290071
+ if (provider2 === "requesty") return "openai/gpt-4o-mini";
289978
290072
  if (provider2 === "atlascloud") return "deepseek-ai/deepseek-v3.2";
289979
290073
  return "gpt-4o-mini";
289980
290074
  }
package/dist/index.js CHANGED
@@ -3818,6 +3818,7 @@ function getResolvedConfig(options = {}) {
3818
3818
  const legacy = loadFileConfig(homeDir);
3819
3819
  const embeddingBaseUrl = first(process.env.MEMORIX_EMBEDDING_BASE_URL, toml.embedding?.base_url, yaml.embedding?.baseUrl, legacy.embeddingApi?.baseUrl);
3820
3820
  const openRouterEmbeddingApiKey = isOpenRouterUrl(embeddingBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
3821
+ const requestyEmbeddingApiKey = isRequestyUrl(embeddingBaseUrl) ? process.env.REQUESTY_API_KEY : void 0;
3821
3822
  const memoryLlmProvider = first(
3822
3823
  process.env.MEMORIX_LLM_PROVIDER,
3823
3824
  toml.memory?.llm?.provider,
@@ -3838,16 +3839,18 @@ function getResolvedConfig(options = {}) {
3838
3839
  );
3839
3840
  const openRouterMemoryLlmApiKey = isOpenRouterMemoryLane(memoryLlmProvider, memoryLlmBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
3840
3841
  const isAtlasCloud = memoryLlmProvider === "atlascloud";
3842
+ const isRequesty = memoryLlmProvider === "requesty";
3843
+ const usesPresetKeyOnly = isAtlasCloud || isRequesty;
3841
3844
  const memoryLlmApiKey = first(
3842
3845
  process.env.MEMORIX_LLM_API_KEY,
3843
3846
  process.env.MEMORIX_API_KEY,
3844
3847
  toml.memory?.llm?.api_key,
3845
3848
  yaml.llm?.apiKey,
3846
3849
  legacy.llm?.apiKey,
3847
- // Never borrow another provider's credentials for the Atlas Cloud preset.
3848
- isAtlasCloud ? process.env.ATLASCLOUD_API_KEY : process.env.OPENAI_API_KEY,
3849
- isAtlasCloud ? void 0 : process.env.ANTHROPIC_API_KEY,
3850
- isAtlasCloud ? void 0 : openRouterMemoryLlmApiKey
3850
+ // Never borrow another provider's credentials for the Atlas Cloud or Requesty presets.
3851
+ isAtlasCloud ? process.env.ATLASCLOUD_API_KEY : isRequesty ? process.env.REQUESTY_API_KEY : process.env.OPENAI_API_KEY,
3852
+ usesPresetKeyOnly ? void 0 : process.env.ANTHROPIC_API_KEY,
3853
+ usesPresetKeyOnly ? void 0 : openRouterMemoryLlmApiKey
3851
3854
  );
3852
3855
  const resolved = {
3853
3856
  agent: {
@@ -3896,7 +3899,7 @@ function getResolvedConfig(options = {}) {
3896
3899
  provider: first(process.env.MEMORIX_EMBEDDING, toml.embedding?.provider, yaml.embedding?.provider, legacy.embedding, "off"),
3897
3900
  model: first(process.env.MEMORIX_EMBEDDING_MODEL, toml.embedding?.model, yaml.embedding?.model, legacy.embeddingApi?.model),
3898
3901
  baseUrl: embeddingBaseUrl,
3899
- apiKey: first(process.env.MEMORIX_EMBEDDING_API_KEY, toml.embedding?.api_key, yaml.embedding?.apiKey, legacy.embeddingApi?.apiKey, openRouterEmbeddingApiKey),
3902
+ apiKey: first(process.env.MEMORIX_EMBEDDING_API_KEY, toml.embedding?.api_key, yaml.embedding?.apiKey, legacy.embeddingApi?.apiKey, openRouterEmbeddingApiKey, requestyEmbeddingApiKey),
3900
3903
  dimensions: firstNumber(parseNumber(process.env.MEMORIX_EMBEDDING_DIMENSIONS), toml.embedding?.dimensions, yaml.embedding?.dimensions, legacy.embeddingApi?.dimensions)
3901
3904
  },
3902
3905
  rerank: resolveRerankLane({
@@ -4027,6 +4030,7 @@ function getEnvSourceNames() {
4027
4030
  "MEMORIX_CODEGRAPH_EXTERNAL_COMMAND",
4028
4031
  "MEMORIX_CODEGRAPH_EXTERNAL_TIMEOUT_MS",
4029
4032
  "OPENROUTER_API_KEY",
4033
+ "REQUESTY_API_KEY",
4030
4034
  "ATLASCLOUD_API_KEY"
4031
4035
  ].filter((name) => process.env[name]);
4032
4036
  }
@@ -4039,6 +4043,15 @@ function isOpenRouterUrl(value) {
4039
4043
  return /(^|\.)openrouter\.ai(?::|\/|$)/i.test(value);
4040
4044
  }
4041
4045
  }
4046
+ function isRequestyUrl(value) {
4047
+ if (!value) return false;
4048
+ try {
4049
+ const url = new URL(value);
4050
+ return url.hostname.toLowerCase() === "requesty.ai" || url.hostname.toLowerCase().endsWith(".requesty.ai");
4051
+ } catch {
4052
+ return /(^|\.)requesty\.ai(?::|\/|$)/i.test(value);
4053
+ }
4054
+ }
4042
4055
  function isOpenRouterMemoryLane(provider2, baseUrl) {
4043
4056
  return provider2?.trim().toLowerCase() === "openrouter" || isOpenRouterUrl(baseUrl);
4044
4057
  }
@@ -4193,6 +4206,9 @@ function defaultEmbeddingModelFor(baseUrl, explicitModel) {
4193
4206
  if (url && (url.hostname === "openrouter.ai" || url.hostname.endsWith(".openrouter.ai"))) {
4194
4207
  return "qwen/qwen3-embedding-8b";
4195
4208
  }
4209
+ if (url && (url.hostname === "requesty.ai" || url.hostname.endsWith(".requesty.ai"))) {
4210
+ return "openai/text-embedding-3-small";
4211
+ }
4196
4212
  } catch {
4197
4213
  }
4198
4214
  return "text-embedding-3-small";
@@ -7007,6 +7023,7 @@ var init_provider2 = __esm({
7007
7023
  openai: { baseUrl: "https://api.openai.com/v1", model: "gpt-4.1-nano" },
7008
7024
  anthropic: { baseUrl: "https://api.anthropic.com/v1", model: "claude-3-5-haiku-latest" },
7009
7025
  openrouter: { baseUrl: "https://openrouter.ai/api/v1", model: "openai/gpt-4.1-nano" },
7026
+ requesty: { baseUrl: "https://router.requesty.ai/v1", model: "openai/gpt-4.1-nano" },
7010
7027
  atlascloud: { baseUrl: "https://api.atlascloud.ai/v1", model: "deepseek-ai/deepseek-v3.2" },
7011
7028
  custom: { baseUrl: "http://localhost:11434/v1", model: "llama3" }
7012
7029
  };
@@ -160104,7 +160121,7 @@ var init_request_context = __esm({
160104
160121
  function getMcpServerInfo() {
160105
160122
  return {
160106
160123
  name: "memorix",
160107
- version: true ? "1.9.8" : "1.0.1"
160124
+ version: true ? "1.9.10" : "1.0.1"
160108
160125
  };
160109
160126
  }
160110
160127
  function createMcpDiscoverResult(capabilities, serverInfo) {
@@ -167573,7 +167590,8 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
167573
167590
  "MEMORIX_API_KEY",
167574
167591
  "OPENAI_API_KEY",
167575
167592
  "ANTHROPIC_API_KEY",
167576
- ...resolved.memory.llm.provider === "openrouter" || resolved.memory.llm.baseUrl?.includes("openrouter.ai") ? ["OPENROUTER_API_KEY"] : []
167593
+ ...resolved.memory.llm.provider === "openrouter" || resolved.memory.llm.baseUrl?.includes("openrouter.ai") ? ["OPENROUTER_API_KEY"] : [],
167594
+ ...resolved.memory.llm.provider === "requesty" ? ["REQUESTY_API_KEY"] : []
167577
167595
  ];
167578
167596
  if (llmKey) {
167579
167597
  const src = resolveDashboardKeySource({ value: llmKey, envKeys: memoryKeyEnv, configSource });
@@ -169158,7 +169176,7 @@ async function analyzeImage(input) {
169158
169176
  const config = getLLMConfig();
169159
169177
  if (!OPENAI_COMPATIBLE_PROVIDERS.has(config.provider)) {
169160
169178
  throw new Error(
169161
- `Image analysis requires an OpenAI-compatible provider (openai, openrouter, or custom). Current provider "${config.provider}" uses a different API shape. Set MEMORIX_LLM_PROVIDER=openai or configure an OpenAI-compatible base URL.`
169179
+ `Image analysis requires an OpenAI-compatible provider (openai, openrouter, requesty, or custom). Current provider "${config.provider}" uses a different API shape. Set MEMORIX_LLM_PROVIDER=openai or configure an OpenAI-compatible base URL.`
169162
169180
  );
169163
169181
  }
169164
169182
  const bytes = decodeBase64ImagePayload(input.base64, MAX_VISION_IMAGE_BYTES);
@@ -169197,7 +169215,7 @@ var init_image_loader = __esm({
169197
169215
  init_provider2();
169198
169216
  init_secret_filter();
169199
169217
  init_image_payload();
169200
- OPENAI_COMPATIBLE_PROVIDERS = /* @__PURE__ */ new Set(["openai", "openrouter", "custom"]);
169218
+ OPENAI_COMPATIBLE_PROVIDERS = /* @__PURE__ */ new Set(["openai", "openrouter", "requesty", "custom"]);
169201
169219
  MAX_VISION_PROMPT_CHARS = 12e3;
169202
169220
  MAX_VISION_DESCRIPTION_CHARS = 12e3;
169203
169221
  MAX_VISION_LABELS = 50;
@@ -169912,6 +169930,8 @@ function generateOpenCodePlugin() {
169912
169930
  */
169913
169931
  import { spawnSync } from 'node:child_process';
169914
169932
 
169933
+ const HOOK_TIMEOUT_MS = 30_000;
169934
+
169915
169935
  export const MemorixPlugin = async ({ project, client, $, directory, worktree }) => {
169916
169936
  // Generate a stable session ID for this plugin lifetime
169917
169937
  const sessionId = \`opencode-\${Date.now().toString(36)}-\${Math.random().toString(36).slice(2, 8)}\`;
@@ -169937,14 +169957,14 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
169937
169957
  * - spawnSync is simpler: no stream lifecycle, no writer.close() bugs
169938
169958
  * - stdin pipe via input option is reliable cross-platform
169939
169959
  */
169940
- function runHook(payload) {
169941
- payload.session_id = sessionId;
169960
+ function runHook(payload, activeSessionId = sessionId) {
169961
+ payload.session_id = activeSessionId;
169942
169962
  const data = JSON.stringify(payload);
169943
169963
  const eventName = payload.hook_event_name || 'unknown';
169944
169964
  try {
169945
169965
  const result = spawnSync(hookCommand, ['hook'], {
169946
169966
  input: data,
169947
- timeout: 10_000,
169967
+ timeout: HOOK_TIMEOUT_MS,
169948
169968
  encoding: 'utf-8',
169949
169969
  stdio: ['pipe', 'pipe', 'pipe'],
169950
169970
  shell: process.platform === 'win32',
@@ -169989,7 +170009,87 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
169989
170009
  .trim();
169990
170010
  }
169991
170011
 
170012
+ function flushAssistantResponse(activeSessionId = sessionId) {
170013
+ if (!pendingAssistantResponse?.text) return;
170014
+ const deliveryKey = pendingAssistantResponse.id
170015
+ ? \`\${pendingAssistantResponse.id}:\${pendingAssistantResponse.text}\`
170016
+ : pendingAssistantResponse.text;
170017
+ if (deliveryKey === lastDeliveredAssistantKey) return;
170018
+ runHook({
170019
+ agent: 'opencode',
170020
+ hook_event_name: 'message.updated',
170021
+ ai_response: pendingAssistantResponse.text,
170022
+ message_id: pendingAssistantResponse.id,
170023
+ cwd: directory,
170024
+ }, activeSessionId);
170025
+ lastDeliveredAssistantKey = deliveryKey;
170026
+ }
170027
+
170028
+ function eventSessionId(event) {
170029
+ const properties = event?.properties ?? {};
170030
+ const value = properties.sessionID ?? properties.sessionId ?? properties.info?.id;
170031
+ return typeof value === 'string' && value ? value : sessionId;
170032
+ }
170033
+
170034
+ async function handleEvent({ event }) {
170035
+ const properties = event?.properties ?? {};
170036
+ const activeSessionId = eventSessionId(event);
170037
+ switch (event?.type) {
170038
+ case 'session.created':
170039
+ runHook({
170040
+ agent: 'opencode',
170041
+ hook_event_name: 'session.created',
170042
+ cwd: directory,
170043
+ }, activeSessionId);
170044
+ break;
170045
+ case 'session.idle':
170046
+ flushAssistantResponse(activeSessionId);
170047
+ runHook({
170048
+ agent: 'opencode',
170049
+ hook_event_name: 'session.idle',
170050
+ cwd: directory,
170051
+ }, activeSessionId);
170052
+ break;
170053
+ case 'file.edited':
170054
+ runHook({
170055
+ agent: 'opencode',
170056
+ hook_event_name: 'file.edited',
170057
+ file_path: properties.file ?? properties.path ?? '',
170058
+ cwd: directory,
170059
+ }, activeSessionId);
170060
+ break;
170061
+ case 'command.executed':
170062
+ runHook({
170063
+ agent: 'opencode',
170064
+ hook_event_name: 'command.executed',
170065
+ command: properties.name ?? properties.command ?? '',
170066
+ cwd: directory,
170067
+ }, activeSessionId);
170068
+ break;
170069
+ case 'message.updated': {
170070
+ const message = properties.info ?? properties.message ?? properties;
170071
+ if (message?.role !== 'assistant') break;
170072
+ const text = extractMessageText(message);
170073
+ if (!text) break;
170074
+ pendingAssistantResponse = { id: message.id, text };
170075
+ break;
170076
+ }
170077
+ case 'session.compacted':
170078
+ runHook({
170079
+ agent: 'opencode',
170080
+ hook_event_name: 'session.compacted',
170081
+ cwd: directory,
170082
+ }, activeSessionId);
170083
+ break;
170084
+ default:
170085
+ break;
170086
+ }
170087
+ }
170088
+
169992
170089
  return {
170090
+ // OpenCode 1.x delivers lifecycle/file/command/message events through this
170091
+ // generic hook. The named keys below remain for older compatible hosts.
170092
+ event: handleEvent,
169993
170093
  /** Session created \u2014 record session start */
169994
170094
  'session.created': async ({ session }) => {
169995
170095
  runHook({
@@ -170001,21 +170101,7 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
170001
170101
 
170002
170102
  /** Session idle \u2014 record session end */
170003
170103
  'session.idle': async ({ session }) => {
170004
- if (pendingAssistantResponse?.text) {
170005
- const deliveryKey = pendingAssistantResponse.id
170006
- ? \`\${pendingAssistantResponse.id}:\${pendingAssistantResponse.text}\`
170007
- : pendingAssistantResponse.text;
170008
- if (deliveryKey !== lastDeliveredAssistantKey) {
170009
- runHook({
170010
- agent: 'opencode',
170011
- hook_event_name: 'message.updated',
170012
- ai_response: pendingAssistantResponse.text,
170013
- message_id: pendingAssistantResponse.id,
170014
- cwd: directory,
170015
- });
170016
- lastDeliveredAssistantKey = deliveryKey;
170017
- }
170018
- }
170104
+ flushAssistantResponse();
170019
170105
  runHook({
170020
170106
  agent: 'opencode',
170021
170107
  hook_event_name: 'session.idle',
@@ -170123,7 +170209,7 @@ function v2RunHook(payload, directory, sessionId) {
170123
170209
  try {
170124
170210
  const result = spawnSync(v2HookCommand, ['hook'], {
170125
170211
  input: JSON.stringify(nextPayload),
170126
- timeout: 10_000,
170212
+ timeout: HOOK_TIMEOUT_MS,
170127
170213
  encoding: 'utf-8',
170128
170214
  stdio: ['pipe', 'pipe', 'pipe'],
170129
170215
  shell: process.platform === 'win32',
@@ -170247,7 +170333,7 @@ function setup(ctx) {
170247
170333
  return () => controller.abort();
170248
170334
  }
170249
170335
 
170250
- export default { id: 'memorix', setup };
170336
+ export default { id: 'memorix', server: MemorixPlugin, setup };
170251
170337
  `;
170252
170338
  }
170253
170339
  async function installOpenCodeSkills(projectRoot, global2 = false) {
@@ -171073,7 +171159,7 @@ var init_installers = __esm({
171073
171159
  "use strict";
171074
171160
  init_esm_shims();
171075
171161
  init_official_skills();
171076
- OPENCODE_PLUGIN_VERSION = 8;
171162
+ OPENCODE_PLUGIN_VERSION = 9;
171077
171163
  AGENT_SKILL_DIRS = {
171078
171164
  cursor: { project: path34.join(".cursor", "skills"), global: path34.join(".cursor", "skills") },
171079
171165
  windsurf: { project: path34.join(".windsurf", "skills"), global: path34.join(".windsurf", "skills") },
@@ -176597,7 +176683,7 @@ var init_serve_shared = __esm({
176597
176683
 
176598
176684
  // src/cli/version.ts
176599
176685
  function getCliVersion() {
176600
- return true ? "1.9.8" : pkg.version;
176686
+ return true ? "1.9.10" : pkg.version;
176601
176687
  }
176602
176688
  var init_version3 = __esm({
176603
176689
  "src/cli/version.ts"() {