@axiom-lattice/core 8.0.2 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -233,11 +233,6 @@ var ModelLattice = class extends BaseChatModel {
233
233
  }
234
234
  };
235
235
 
236
- // src/model_lattice/ModelLatticeManager.ts
237
- import {
238
- parseModelKey
239
- } from "@axiom-lattice/protocols";
240
-
241
236
  // src/store_lattice/InMemoryThreadStore.ts
242
237
  var InMemoryThreadStore = class {
243
238
  constructor() {
@@ -1834,6 +1829,9 @@ var InMemoryModelProviderStore = class {
1834
1829
  // explicit flag marks one.
1835
1830
  stale: model.stale ?? false,
1836
1831
  extra: carried && Object.keys(carried).length > 0 ? carried : void 0,
1832
+ // Same rule as `extra`: an omitted kind keeps the stored one, and a new
1833
+ // row — which discovery creates without a kind — starts as chat.
1834
+ kind: model.kind ?? existing?.kind ?? "chat",
1837
1835
  isDefault: model.isDefault ?? (namesDefault ? false : existing?.isDefault) ?? false,
1838
1836
  discoveredAt: now
1839
1837
  };
@@ -5899,6 +5897,137 @@ var SimpleMemoryVectorStore = class extends VectorStore {
5899
5897
  }
5900
5898
  };
5901
5899
 
5900
+ // src/embeddings_lattice/EmbeddingsLatticeManager.ts
5901
+ import { OpenAIEmbeddings } from "@langchain/openai";
5902
+
5903
+ // src/model_lattice/buildModelLlmConfig.ts
5904
+ var ALLOWED_LLM_PROVIDERS = /* @__PURE__ */ new Set(["openai"]);
5905
+ var UnsupportedModelProviderError = class extends Error {
5906
+ constructor(llmProvider) {
5907
+ super(`Unsupported LLM provider: ${llmProvider}`);
5908
+ this.llmProvider = llmProvider;
5909
+ this.code = "UNSUPPORTED_PROTOCOL";
5910
+ this.name = "UnsupportedModelProviderError";
5911
+ }
5912
+ };
5913
+ var DEFAULT_MODEL_EXTRA = {
5914
+ disableThinking: true
5915
+ };
5916
+ function buildModelLlmConfig(provider2, model) {
5917
+ if (!ALLOWED_LLM_PROVIDERS.has(provider2.llmProvider)) {
5918
+ throw new UnsupportedModelProviderError(provider2.llmProvider);
5919
+ }
5920
+ const extra = { ...model.extra ?? {} };
5921
+ delete extra.apiKeyEnvName;
5922
+ const disableThinking = extra.disableThinking === true;
5923
+ delete extra.disableThinking;
5924
+ if (disableThinking) {
5925
+ extra.modelKwargs = {
5926
+ enable_thinking: false,
5927
+ thinking: { type: "disabled" },
5928
+ ...extra.modelKwargs ?? {}
5929
+ };
5930
+ }
5931
+ return {
5932
+ ...extra,
5933
+ provider: provider2.llmProvider,
5934
+ model: model.modelId,
5935
+ displayName: model.displayName ?? model.modelId,
5936
+ streaming: true,
5937
+ apiKey: provider2.apiKey,
5938
+ // A provider without a key talks to an upstream that needs no
5939
+ // credentials. Saying so explicitly keeps `initChatModel` from falling
5940
+ // back to `process.env[...]` and handing the gateway's own deployment key
5941
+ // to the tenant's host.
5942
+ noAuth: !provider2.apiKey,
5943
+ baseURL: provider2.baseURL,
5944
+ // Chat Completions and the Responses API are different routes with
5945
+ // different request fields; without this the gateway would post
5946
+ // `messages` to an endpoint expecting `input`.
5947
+ ...provider2.apiStyle === "responses" ? { useResponsesApi: true } : {}
5948
+ };
5949
+ }
5950
+
5951
+ // src/model_lattice/buildModelConfig.ts
5952
+ function numberOrUndefined(value) {
5953
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
5954
+ }
5955
+ function buildModelEmbeddingsConfig(provider2, model) {
5956
+ const extra = { ...model.extra ?? {} };
5957
+ delete extra.apiKeyEnvName;
5958
+ const encodingFormat = extra.encodingFormat === "float" || extra.encodingFormat === "base64" ? extra.encodingFormat : void 0;
5959
+ return {
5960
+ ...extra,
5961
+ model: model.modelId,
5962
+ baseURL: provider2.baseURL,
5963
+ apiKey: provider2.apiKey || "EMPTY",
5964
+ dimensions: numberOrUndefined(extra.dimensions),
5965
+ timeout: numberOrUndefined(extra.timeout),
5966
+ batchSize: numberOrUndefined(extra.batchSize),
5967
+ encodingFormat
5968
+ };
5969
+ }
5970
+ function buildModelSttConfig(provider2, model) {
5971
+ const extra = { ...model.extra ?? {} };
5972
+ delete extra.apiKeyEnvName;
5973
+ return {
5974
+ ...extra,
5975
+ apiMode: extra.apiMode === "chat" ? "chat" : "whisper",
5976
+ model: model.modelId,
5977
+ // Placeholder for the same reason as embeddings: `STTModelLattice` falls
5978
+ // back to `process.env.OPENAI_API_KEY` when this is empty.
5979
+ apiKey: provider2.apiKey || "EMPTY",
5980
+ baseURL: provider2.baseURL
5981
+ };
5982
+ }
5983
+ function buildModelConfig(provider2, model) {
5984
+ switch (model.kind) {
5985
+ case "embedding":
5986
+ return {
5987
+ kind: "embedding",
5988
+ config: buildModelEmbeddingsConfig(provider2, model)
5989
+ };
5990
+ case "asr":
5991
+ return { kind: "asr", config: buildModelSttConfig(provider2, model) };
5992
+ case "chat":
5993
+ default:
5994
+ return { kind: "chat", config: buildModelLlmConfig(provider2, model) };
5995
+ }
5996
+ }
5997
+
5998
+ // src/model_lattice/loadTenantModelRow.ts
5999
+ import {
6000
+ parseModelKey
6001
+ } from "@axiom-lattice/protocols";
6002
+ async function loadTenantModelRow(tenantId2, key5) {
6003
+ try {
6004
+ const { store } = getStoreLattice("default", "modelProvider");
6005
+ const parsed = parseModelKey(key5);
6006
+ if (!parsed) {
6007
+ return void 0;
6008
+ }
6009
+ const provider2 = await store.getProviderById(tenantId2, parsed.providerId);
6010
+ if (!provider2 || !provider2.enabled) {
6011
+ return void 0;
6012
+ }
6013
+ const model = await store.getModel(
6014
+ tenantId2,
6015
+ parsed.providerId,
6016
+ parsed.modelId
6017
+ );
6018
+ if (!model || !model.enabled) {
6019
+ return void 0;
6020
+ }
6021
+ return { provider: provider2, model };
6022
+ } catch (error) {
6023
+ console.error(
6024
+ `[model_lattice] Failed to load model "${key5}" for tenant "${tenantId2}":`,
6025
+ error
6026
+ );
6027
+ return void 0;
6028
+ }
6029
+ }
6030
+
5902
6031
  // src/embeddings_lattice/EmbeddingsLatticeManager.ts
5903
6032
  var EmbeddingsLatticeManager = class _EmbeddingsLatticeManager extends BaseLatticeManager {
5904
6033
  /**
@@ -5931,6 +6060,37 @@ var EmbeddingsLatticeManager = class _EmbeddingsLatticeManager extends BaseLatti
5931
6060
  };
5932
6061
  this.register(key5, embeddingsLattice);
5933
6062
  }
6063
+ /**
6064
+ * Materialise a tenant embedding model that this process has never seen.
6065
+ *
6066
+ * Called by `BaseLatticeManager` on a registry miss. Only `kind: "embedding"`
6067
+ * rows are accepted: a chat or ASR model key must resolve to nothing here, so
6068
+ * a caller asking for an embedding client can never silently get the wrong
6069
+ * kind of model.
6070
+ *
6071
+ * @param tenantId Tenant scope
6072
+ * @param key `${providerId}/${modelId}`
6073
+ */
6074
+ async loadItemFromStore(tenantId2, key5) {
6075
+ const row = await loadTenantModelRow(tenantId2, key5);
6076
+ if (!row || row.model.kind !== "embedding") {
6077
+ return void 0;
6078
+ }
6079
+ const config = buildModelEmbeddingsConfig(row.provider, row.model);
6080
+ return {
6081
+ key: key5,
6082
+ client: new OpenAIEmbeddings({
6083
+ model: config.model,
6084
+ apiKey: config.apiKey,
6085
+ dimensions: config.dimensions,
6086
+ timeout: config.timeout,
6087
+ batchSize: config.batchSize,
6088
+ encodingFormat: config.encodingFormat,
6089
+ configuration: { baseURL: config.baseURL }
6090
+ }),
6091
+ label: row.model.displayName ?? row.model.modelId
6092
+ };
6093
+ }
5934
6094
  /**
5935
6095
  * Get all registered embeddings as { key, label } info list
5936
6096
  */
@@ -5948,6 +6108,53 @@ var EmbeddingsLatticeManager = class _EmbeddingsLatticeManager extends BaseLatti
5948
6108
  }
5949
6109
  return embeddingsLattice;
5950
6110
  }
6111
+ /**
6112
+ * Drop this replica's cached embedding clients for one provider.
6113
+ *
6114
+ * The provider's config (apiKey, baseURL, model `extra`) is baked into the
6115
+ * client at construction, and a resolved entry never re-reads the store. So a
6116
+ * model-provider change reaches this replica only if the entry is evicted
6117
+ * first — the same rule `ModelLatticeManager.evictProviderCache` applies to
6118
+ * chat models, and the same keys the model-provider outbox event covers.
6119
+ *
6120
+ * Unlike the chat manager this deliberately leaves the `"default"` key alone:
6121
+ * here it is a *deployment registration*, not a tenant resolution, so evicting
6122
+ * it would remove the global fallback every tenant relies on.
6123
+ *
6124
+ * @param tenantId Tenant scope
6125
+ * @param providerId Provider whose `${providerId}/` keys are stale
6126
+ */
6127
+ evictProviderCache(tenantId2, providerId) {
6128
+ if (!providerId) {
6129
+ return;
6130
+ }
6131
+ const prefix = `${providerId}/`;
6132
+ for (const key5 of this.keysByTenant(tenantId2)) {
6133
+ if (key5.startsWith(prefix)) {
6134
+ this.removeWithTenant(tenantId2, key5);
6135
+ }
6136
+ }
6137
+ }
6138
+ /**
6139
+ * Resolve an embeddings client for a tenant, loading a miss from the store.
6140
+ *
6141
+ * Mirrors `resolveModelLatticeForTenant`: the tenant's own model first, then
6142
+ * the deployment namespace, which is where boot-time registrations live.
6143
+ * Returns `undefined` rather than throwing, so a caller can fall back.
6144
+ *
6145
+ * @param tenantId Tenant scope
6146
+ * @param key `${providerId}/${modelId}` or a bare deployment key
6147
+ */
6148
+ async getEmbeddingsClientWithTenant(tenantId2, key5) {
6149
+ const own = await this.getOrLoadWithTenant(tenantId2, key5);
6150
+ if (own) {
6151
+ return own.client;
6152
+ }
6153
+ if (tenantId2 !== "default") {
6154
+ return (await this.getOrLoadWithTenant("default", key5))?.client;
6155
+ }
6156
+ return void 0;
6157
+ }
5951
6158
  /**
5952
6159
  * Get all Lattices
5953
6160
  */
@@ -6003,20 +6210,18 @@ var getEmbeddingsClient = (key5) => embeddingsLatticeManager.getEmbeddingsClient
6003
6210
  // src/vectorstore_lattice/VectorStoreProviderManager.ts
6004
6211
  var MemoryVectorStoreProvider = class {
6005
6212
  async create(params) {
6006
- let embeddings;
6007
- try {
6008
- embeddings = embeddingsLatticeManager.getEmbeddingsClient(params.embeddingKey);
6009
- } catch {
6010
- try {
6011
- embeddings = embeddingsLatticeManager.getEmbeddingsClient("default");
6012
- } catch {
6013
- throw new Error(
6014
- `No embedding model registered. Register one first, e.g.:
6015
- import { registerEmbeddingsLattice } from "@axiom-lattice/core";
6016
- import { OpenAIEmbeddings } from "@langchain/openai";
6017
- registerEmbeddingsLattice("default", new OpenAIEmbeddings({ modelName: "text-embedding-3-small" }));`
6018
- );
6019
- }
6213
+ const tenantId2 = params.tenantId ?? "default";
6214
+ const embeddings = await embeddingsLatticeManager.getEmbeddingsClientWithTenant(
6215
+ tenantId2,
6216
+ params.embeddingKey
6217
+ ) ?? await embeddingsLatticeManager.getEmbeddingsClientWithTenant(
6218
+ tenantId2,
6219
+ "default"
6220
+ );
6221
+ if (!embeddings) {
6222
+ throw new Error(
6223
+ `No embedding model "${params.embeddingKey}" registered for tenant "${tenantId2}", and no deployment default. Configure one in the model provider panel.`
6224
+ );
6020
6225
  }
6021
6226
  return new SimpleMemoryVectorStore(embeddings);
6022
6227
  }
@@ -10373,42 +10578,6 @@ var InMemoryMenuStore = class {
10373
10578
  }
10374
10579
  };
10375
10580
 
10376
- // src/model_lattice/buildModelLlmConfig.ts
10377
- var ALLOWED_LLM_PROVIDERS = /* @__PURE__ */ new Set(["openai"]);
10378
- var UnsupportedModelProviderError = class extends Error {
10379
- constructor(llmProvider) {
10380
- super(`Unsupported LLM provider: ${llmProvider}`);
10381
- this.llmProvider = llmProvider;
10382
- this.code = "UNSUPPORTED_PROTOCOL";
10383
- this.name = "UnsupportedModelProviderError";
10384
- }
10385
- };
10386
- function buildModelLlmConfig(provider2, model) {
10387
- if (!ALLOWED_LLM_PROVIDERS.has(provider2.llmProvider)) {
10388
- throw new UnsupportedModelProviderError(provider2.llmProvider);
10389
- }
10390
- const extra = { ...model.extra ?? {} };
10391
- delete extra.apiKeyEnvName;
10392
- return {
10393
- ...extra,
10394
- provider: provider2.llmProvider,
10395
- model: model.modelId,
10396
- displayName: model.displayName ?? model.modelId,
10397
- streaming: true,
10398
- apiKey: provider2.apiKey,
10399
- // A provider without a key talks to an upstream that needs no
10400
- // credentials. Saying so explicitly keeps `initChatModel` from falling
10401
- // back to `process.env[...]` and handing the gateway's own deployment key
10402
- // to the tenant's host.
10403
- noAuth: !provider2.apiKey,
10404
- baseURL: provider2.baseURL,
10405
- // Chat Completions and the Responses API are different routes with
10406
- // different request fields; without this the gateway would post
10407
- // `messages` to an endpoint expecting `input`.
10408
- ...provider2.apiStyle === "responses" ? { useResponsesApi: true } : {}
10409
- };
10410
- }
10411
-
10412
10581
  // src/model_lattice/ModelLatticeManager.ts
10413
10582
  var DEFAULT_MODEL_KEY = "default";
10414
10583
  var TENANT_DEFAULT_MISS_TTL_MS = 3e4;
@@ -10581,38 +10750,26 @@ var ModelLatticeManager = class _ModelLatticeManager extends BaseLatticeManager
10581
10750
  * @param key Lattice键名(`${providerId}/${modelId}`)
10582
10751
  */
10583
10752
  async loadItemFromStore(tenantId2, key5) {
10584
- try {
10585
- const { store } = getStoreLattice("default", "modelProvider");
10586
- if (key5 === DEFAULT_MODEL_KEY) {
10753
+ if (key5 === DEFAULT_MODEL_KEY) {
10754
+ try {
10755
+ const { store } = getStoreLattice("default", "modelProvider");
10587
10756
  return await this.loadTenantDefault(store, tenantId2);
10588
- }
10589
- const parsed = parseModelKey(key5);
10590
- if (!parsed) {
10591
- return void 0;
10592
- }
10593
- const provider2 = await store.getProviderById(tenantId2, parsed.providerId);
10594
- if (!provider2 || !provider2.enabled) {
10595
- return void 0;
10596
- }
10597
- const model = await store.getModel(
10598
- tenantId2,
10599
- parsed.providerId,
10600
- parsed.modelId
10601
- );
10602
- if (!model || !model.enabled) {
10757
+ } catch (error) {
10758
+ console.error(
10759
+ `[ModelLatticeManager] Failed to load model "${key5}" for tenant "${tenantId2}":`,
10760
+ error
10761
+ );
10603
10762
  return void 0;
10604
10763
  }
10605
- return {
10606
- key: key5,
10607
- client: new ModelLattice(buildModelLlmConfig(provider2, model))
10608
- };
10609
- } catch (error) {
10610
- console.error(
10611
- `[ModelLatticeManager] Failed to load model "${key5}" for tenant "${tenantId2}":`,
10612
- error
10613
- );
10764
+ }
10765
+ const row = await loadTenantModelRow(tenantId2, key5);
10766
+ if (!row || row.model.kind !== "chat") {
10614
10767
  return void 0;
10615
10768
  }
10769
+ return {
10770
+ key: key5,
10771
+ client: new ModelLattice(buildModelLlmConfig(row.provider, row.model))
10772
+ };
10616
10773
  }
10617
10774
  /**
10618
10775
  * Forget that a tenant had no default model.
@@ -10644,7 +10801,9 @@ var ModelLatticeManager = class _ModelLatticeManager extends BaseLatticeManager
10644
10801
  return void 0;
10645
10802
  }
10646
10803
  const models = await store.listModels(tenantId2);
10647
- const marked = models.find((model) => model.isDefault === true);
10804
+ const marked = models.find(
10805
+ (model) => model.isDefault === true && model.kind === "chat"
10806
+ );
10648
10807
  if (!marked || !marked.enabled) {
10649
10808
  this.tenantDefaultMissAt.set(tenantId2, Date.now());
10650
10809
  return void 0;
@@ -10685,6 +10844,37 @@ var ModelLatticeManager = class _ModelLatticeManager extends BaseLatticeManager
10685
10844
  removeLatticeWithTenant(tenantId2, key5) {
10686
10845
  return this.removeWithTenant(tenantId2, key5);
10687
10846
  }
10847
+ /**
10848
+ * 驱逐一个 provider 贡献的缓存,外加本租户已解析的 `default` 哨兵。
10849
+ *
10850
+ * 改了配置(apiKey / baseURL / extra)却还在走旧客户端,根因就是
10851
+ * `BaseLatticeManager.registry` 的**正向条目没有 TTL**:命中一次之后
10852
+ * `loadItemFromStore` 再也不会被调用。驱逐之后,下一次解析会从**共享的**
10853
+ * store 重新物化 —— 这正是多副本「正确性不靠副本之间同步、靠共享库」那条不变式。
10854
+ *
10855
+ * 只驱逐、不重注册:`loadItemFromStore` 已经知道怎么按 key 物化,删掉就够,
10856
+ * 而且这里不需要读库。
10857
+ *
10858
+ * 为什么连哨兵一起删:`default` 是本租户默认模型的**解析结果**,同样被永久缓存。
10859
+ * 不删它,租户换了默认模型就得重启才生效(单副本也一样)。删掉只让下一次解析
10860
+ * 多读一次库。
10861
+ *
10862
+ * @param tenantId 租户ID
10863
+ * @param providerId 该 provider 的 `${providerId}/` 前缀会被清掉;省略则只清
10864
+ * 哨兵(例如只换了默认模型)
10865
+ */
10866
+ evictProviderCache(tenantId2, providerId) {
10867
+ if (providerId) {
10868
+ const prefix = `${providerId}/`;
10869
+ for (const key5 of this.keysByTenant(tenantId2)) {
10870
+ if (key5.startsWith(prefix)) {
10871
+ this.removeWithTenant(tenantId2, key5);
10872
+ }
10873
+ }
10874
+ }
10875
+ this.removeWithTenant(tenantId2, DEFAULT_MODEL_KEY);
10876
+ this.forgetTenantDefaultMiss(tenantId2);
10877
+ }
10688
10878
  };
10689
10879
  var modelLatticeManager = ModelLatticeManager.getInstance();
10690
10880
  var registerModelLattice = (key5, config) => modelLatticeManager.registerLattice(key5, config);
@@ -15814,6 +16004,26 @@ var STTModelLatticeManager = class _STTModelLatticeManager extends BaseLatticeMa
15814
16004
  };
15815
16005
  this.register(key5, sttLattice);
15816
16006
  }
16007
+ /**
16008
+ * Materialise a tenant ASR model that this process has never seen.
16009
+ *
16010
+ * Only `kind: "asr"` rows are accepted, mirroring the embedding loader: a
16011
+ * chat key must never resolve to a speech client.
16012
+ */
16013
+ async loadItemFromStore(tenantId2, key5) {
16014
+ const row = await loadTenantModelRow(tenantId2, key5);
16015
+ if (!row || row.model.kind !== "asr") {
16016
+ return void 0;
16017
+ }
16018
+ const config = buildModelSttConfig(row.provider, row.model);
16019
+ const client = new STTModelLattice(key5, config);
16020
+ return {
16021
+ key: key5,
16022
+ client,
16023
+ config,
16024
+ label: row.model.displayName ?? row.model.modelId
16025
+ };
16026
+ }
15817
16027
  /**
15818
16028
  * Get an STT model lattice by key.
15819
16029
  */
@@ -15845,6 +16055,49 @@ var STTModelLatticeManager = class _STTModelLatticeManager extends BaseLatticeMa
15845
16055
  }
15846
16056
  return lattice.client;
15847
16057
  }
16058
+ /**
16059
+ * Resolve an STT client for a tenant, loading a miss from the store.
16060
+ *
16061
+ * The synchronous `getSTTClientWithTenant` stays as it is for
16062
+ * deployment-registered models; this one is for the tenant catalog, where the
16063
+ * model may exist only in the shared store. Same order as every other
16064
+ * resolver: tenant first, then the deployment namespace.
16065
+ *
16066
+ * @param tenantId Tenant scope
16067
+ * @param key `${providerId}/${modelId}` or a bare deployment key
16068
+ */
16069
+ async getSTTClientWithTenantAsync(tenantId2, key5) {
16070
+ const own = await this.getOrLoadWithTenant(tenantId2, key5);
16071
+ if (own) {
16072
+ return own.client;
16073
+ }
16074
+ if (tenantId2 !== "default") {
16075
+ return (await this.getOrLoadWithTenant("default", key5))?.client;
16076
+ }
16077
+ return void 0;
16078
+ }
16079
+ /**
16080
+ * Drop this replica's cached STT clients for one provider.
16081
+ *
16082
+ * Same rule as the chat and embeddings managers: the provider's key and
16083
+ * address are baked into the client at construction, so a model-provider
16084
+ * change only reaches this replica if the cached entry is evicted first. The
16085
+ * deployment's `"default"` registration is left alone.
16086
+ *
16087
+ * @param tenantId Tenant scope
16088
+ * @param providerId Provider whose `${providerId}/` keys are stale
16089
+ */
16090
+ evictProviderCache(tenantId2, providerId) {
16091
+ if (!providerId) {
16092
+ return;
16093
+ }
16094
+ const prefix = `${providerId}/`;
16095
+ for (const key5 of this.keysByTenant(tenantId2)) {
16096
+ if (key5.startsWith(prefix)) {
16097
+ this.removeWithTenant(tenantId2, key5);
16098
+ }
16099
+ }
16100
+ }
15848
16101
  /**
15849
16102
  * Get all registered STT models as info list.
15850
16103
  */
@@ -15883,13 +16136,16 @@ var getSTTClientWithTenant = (tenantId2, key5) => sttModelLatticeManager.getSTTC
15883
16136
 
15884
16137
  // src/deep_agent_new/backends/transcribeAudio.ts
15885
16138
  async function transcribeAudio(options) {
15886
- const { audioBuffer, format } = options;
15887
- if (!sttModelLatticeManager.hasLattice("default")) {
16139
+ const { audioBuffer, format, tenantId: tenantId2 = "default", modelKey = "default" } = options;
16140
+ const client = await sttModelLatticeManager.getSTTClientWithTenantAsync(
16141
+ tenantId2,
16142
+ modelKey
16143
+ );
16144
+ if (!client) {
15888
16145
  throw new Error(
15889
- "No default STT model registered. Use registerSTTModelLattice('default', { ... }) first."
16146
+ `No STT model "${modelKey}" registered for tenant "${tenantId2}". Configure one in the model provider panel, or register a deployment default.`
15890
16147
  );
15891
16148
  }
15892
- const client = sttModelLatticeManager.getSTTClient("default");
15893
16149
  return await client.transcribe(audioBuffer, format);
15894
16150
  }
15895
16151
 
@@ -16064,7 +16320,13 @@ The current backend does not support binary reading, unable to process audio.`;
16064
16320
  ${sizeWarning}`;
16065
16321
  }
16066
16322
  const format = detectAudioFormat(file_path);
16067
- const result = await transcribeAudio({ audioBuffer: buffer2, format });
16323
+ const result = await transcribeAudio({
16324
+ audioBuffer: buffer2,
16325
+ format,
16326
+ // Tenant first; the deployment's `"default"` registration is the
16327
+ // fallback, so existing deployments keep working unchanged.
16328
+ tenantId: runConfig?.tenantId || "default"
16329
+ });
16068
16330
  let output = `[Audio] ${file_path} (${format}, ${(buffer2.length / 1024).toFixed(1)}KB)`;
16069
16331
  if (result.confidence !== void 0) {
16070
16332
  output += `
@@ -31961,6 +32223,28 @@ var VectorStoreLatticeManager = class _VectorStoreLatticeManager extends BaseLat
31961
32223
  const vectorStoreLattice = this.getVectorStoreLattice(key5);
31962
32224
  return vectorStoreLattice.client;
31963
32225
  }
32226
+ /**
32227
+ * Drop every cached vector store belonging to a tenant.
32228
+ *
32229
+ * Stores are registered through the legacy key-only API with a
32230
+ * `${tenantId}:${collection}` key name, so they all live under the `default`
32231
+ * namespace. Matching has to be on the key name, not on the lattice's tenant
32232
+ * prefix — `clearByTenant` would find nothing here.
32233
+ *
32234
+ * The stores are rebuilt lazily on next use. Evicting is required whenever the
32235
+ * embedding client they were built with may have changed: a collection's
32236
+ * `embeddingKey`, or the model provider behind that key.
32237
+ *
32238
+ * @param tenantId Tenant whose vector stores to drop
32239
+ */
32240
+ clearTenantVectorStores(tenantId2) {
32241
+ const prefix = `${tenantId2}:`;
32242
+ for (const lattice of this.getAllLattices()) {
32243
+ if (lattice.key.startsWith(prefix)) {
32244
+ this.removeLattice(lattice.key);
32245
+ }
32246
+ }
32247
+ }
31964
32248
  };
31965
32249
  var vectorStoreLatticeManager = VectorStoreLatticeManager.getInstance();
31966
32250
  var registerVectorStoreLattice = (key5, vectorStore) => vectorStoreLatticeManager.registerLattice(key5, vectorStore);
@@ -32654,6 +32938,26 @@ var SkillLatticeManager = class _SkillLatticeManager extends BaseLatticeManager
32654
32938
  };
32655
32939
  var skillLatticeManager = SkillLatticeManager.getInstance();
32656
32940
 
32941
+ // src/services/collectionSync.ts
32942
+ var COLLECTION_CHANGED_TOPIC = "collection-changed";
32943
+ var outbox;
32944
+ var owner = "unknown";
32945
+ function configureCollectionSync(next) {
32946
+ outbox = next?.outbox;
32947
+ owner = next?.owner ?? "unknown";
32948
+ }
32949
+ async function publishCollectionChanged(tenantId2) {
32950
+ if (!outbox || !tenantId2) return;
32951
+ try {
32952
+ await outbox.append({
32953
+ topic: COLLECTION_CHANGED_TOPIC,
32954
+ payload: { tenantId: tenantId2 },
32955
+ originReplicaId: owner
32956
+ });
32957
+ } catch {
32958
+ }
32959
+ }
32960
+
32657
32961
  // src/collection_lattice/CollectionLatticeManager.ts
32658
32962
  var CollectionLatticeManager = class _CollectionLatticeManager {
32659
32963
  static getInstance() {
@@ -32666,8 +32970,25 @@ var CollectionLatticeManager = class _CollectionLatticeManager {
32666
32970
  const lattice = storeLatticeManager.getStoreLattice("default", "collection");
32667
32971
  return lattice.store;
32668
32972
  }
32973
+ /**
32974
+ * Drop this replica's cached vector stores for the tenant, and tell peers.
32975
+ *
32976
+ * A vector store is constructed with one embedding client, so any collection
32977
+ * write can invalidate it — creating, renaming, changing `embeddingKey` or
32978
+ * deleting. Clearing the whole tenant is deliberate: the cache is small, and
32979
+ * a per-store match would still miss two collections sharing one client.
32980
+ *
32981
+ * Locally this must happen here because the outbox poller skips events this
32982
+ * replica published itself.
32983
+ */
32984
+ invalidateVectorStores(tenantId2) {
32985
+ vectorStoreLatticeManager.clearTenantVectorStores(tenantId2);
32986
+ void publishCollectionChanged(tenantId2);
32987
+ }
32669
32988
  async registerCollection(tenantId2, data) {
32670
- return this.getStore().createCollection(tenantId2, data);
32989
+ const collection = await this.getStore().createCollection(tenantId2, data);
32990
+ this.invalidateVectorStores(tenantId2);
32991
+ return collection;
32671
32992
  }
32672
32993
  async getCollection(tenantId2, name) {
32673
32994
  return this.getStore().getCollectionByName(tenantId2, name);
@@ -32676,10 +32997,18 @@ var CollectionLatticeManager = class _CollectionLatticeManager {
32676
32997
  return this.getStore().getAllCollections(tenantId2);
32677
32998
  }
32678
32999
  async removeCollection(tenantId2, name) {
32679
- return this.getStore().deleteCollection(tenantId2, name);
33000
+ const removed = await this.getStore().deleteCollection(tenantId2, name);
33001
+ if (removed) {
33002
+ this.invalidateVectorStores(tenantId2);
33003
+ }
33004
+ return removed;
32680
33005
  }
32681
33006
  async updateCollection(tenantId2, name, updates) {
32682
- return this.getStore().updateCollection(tenantId2, name, updates);
33007
+ const updated = await this.getStore().updateCollection(tenantId2, name, updates);
33008
+ if (updated) {
33009
+ this.invalidateVectorStores(tenantId2);
33010
+ }
33011
+ return updated;
32683
33012
  }
32684
33013
  };
32685
33014
  var collectionLatticeManager = CollectionLatticeManager.getInstance();
@@ -32703,7 +33032,11 @@ async function getOrCreateCollectionVectorStore(name, embeddingKey, tenantId2, p
32703
33032
  const initialization = (async () => {
32704
33033
  const provider2 = vectorStoreProviderManager.getProvider(providerKey);
32705
33034
  const tableName = buildTableName(tenantId2, name);
32706
- const vectorStore = await provider2.create({ tableName, embeddingKey });
33035
+ const vectorStore = await provider2.create({
33036
+ tableName,
33037
+ embeddingKey,
33038
+ tenantId: tenantId2
33039
+ });
32707
33040
  vectorStoreLatticeManager.registerLattice(storeKey, vectorStore);
32708
33041
  return vectorStore;
32709
33042
  })();
@@ -37104,8 +37437,8 @@ var TaskLifecycleNotifier = class {
37104
37437
  this.tails = /* @__PURE__ */ new Map();
37105
37438
  }
37106
37439
  /** Installs (or clears) the durable outbox used to fan notifications to other replicas. */
37107
- setOutbox(outbox2) {
37108
- this.outbox = outbox2;
37440
+ setOutbox(outbox3) {
37441
+ this.outbox = outbox3;
37109
37442
  }
37110
37443
  /**
37111
37444
  * Registers an async lifecycle listener.
@@ -37211,8 +37544,8 @@ var ProjectTaskAdmissionNotifier = class {
37211
37544
  this.listenerTimeoutMs = timeout;
37212
37545
  }
37213
37546
  /** Installs (or clears) the durable outbox used to fan notifications to other replicas. */
37214
- setOutbox(outbox2) {
37215
- this.outbox = outbox2;
37547
+ setOutbox(outbox3) {
37548
+ this.outbox = outbox3;
37216
37549
  }
37217
37550
  /**
37218
37551
  * Registers a Project task admission listener.
@@ -38059,9 +38392,9 @@ var BELIEF_OWNER_WRITE_ATTEMPTS = 3;
38059
38392
  function isWellFormedImpact(impact) {
38060
38393
  return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(impact.key) && Number.isInteger(impact.after) && impact.after >= 0 && impact.after <= 100 && impact.basis.trim().length > 0 && impact.basis.length <= 1e3 && !/[\r\n]/.test(impact.basis);
38061
38394
  }
38062
- async function applyBeliefImpacts(taskStore, owner2, impacts) {
38063
- if (impacts.length === 0) return { description: owner2.description ?? "" };
38064
- let current = owner2;
38395
+ async function applyBeliefImpacts(taskStore, owner3, impacts) {
38396
+ if (impacts.length === 0) return { description: owner3.description ?? "" };
38397
+ let current = owner3;
38065
38398
  for (let attempt = 0; attempt < BELIEF_OWNER_WRITE_ATTEMPTS; attempt += 1) {
38066
38399
  const parsed = parseTaskBeliefState(current.description ?? "");
38067
38400
  const existingTable = parsed.success;
@@ -38190,14 +38523,14 @@ function sameBeliefOwner(left, right) {
38190
38523
  function sameBeliefOwnerIdentity(left, right) {
38191
38524
  return canonicalJson({ ...left, beliefState: void 0 }) === canonicalJson({ ...right, beliefState: void 0 });
38192
38525
  }
38193
- function canonicalBeliefOwner(owner2) {
38526
+ function canonicalBeliefOwner(owner3) {
38194
38527
  const beliefState = {
38195
- entries: owner2.beliefState.entries.map((entry) => ({
38528
+ entries: owner3.beliefState.entries.map((entry) => ({
38196
38529
  ...entry,
38197
38530
  basis: entry.basis.trim().replace(/\s+/g, " ")
38198
38531
  })).sort((left, right) => left.key < right.key ? -1 : left.key > right.key ? 1 : 0)
38199
38532
  };
38200
- return { ...owner2, beliefState };
38533
+ return { ...owner3, beliefState };
38201
38534
  }
38202
38535
  function eventKeyFor(taskId, result, impacts, beliefOwner) {
38203
38536
  const base = `belief-impact:${taskId}:${buildBeliefCompletionEventKey(taskId, result, impacts)}`;
@@ -39568,8 +39901,8 @@ var TaskLifecycleService = class {
39568
39901
  return { task: existing, parent: parentResult, eventKey, evidence, result, impacts };
39569
39902
  }
39570
39903
  beliefOwnerSnapshot(task, parent) {
39571
- const owner2 = parent?.ownerType === "agent" ? parent : task;
39572
- const parsed = parseTaskBeliefState(owner2.description ?? "");
39904
+ const owner3 = parent?.ownerType === "agent" ? parent : task;
39905
+ const parsed = parseTaskBeliefState(owner3.description ?? "");
39573
39906
  const beliefState = parsed.success ? parsed.state : { entries: [] };
39574
39907
  if (parent?.ownerType === "agent") {
39575
39908
  return {
@@ -41355,8 +41688,8 @@ function createTaskExecutionService(deps) {
41355
41688
  import { randomUUID as randomUUID13 } from "crypto";
41356
41689
  async function runAsLeader(store, name, fn, options = {}) {
41357
41690
  const key5 = `leader:${name}`;
41358
- const owner2 = options.owner ?? getReplicaId();
41359
- const holder = `${owner2}#${randomUUID13()}`;
41691
+ const owner3 = options.owner ?? getReplicaId();
41692
+ const holder = `${owner3}#${randomUUID13()}`;
41360
41693
  const ttlMs = options.ttlMs ?? 6e4;
41361
41694
  if (!await store.tryAcquire(key5, holder, ttlMs)) return { ran: false };
41362
41695
  const heartbeat = setInterval(() => {
@@ -41374,16 +41707,16 @@ async function runAsLeader(store, name, fn, options = {}) {
41374
41707
 
41375
41708
  // src/services/pluginCatalogSync.ts
41376
41709
  var PLUGIN_CATALOG_CHANGED_TOPIC = "plugin-catalog-changed";
41377
- var outbox;
41378
- var owner = "unknown";
41710
+ var outbox2;
41711
+ var owner2 = "unknown";
41379
41712
  function configurePluginCatalogSync(next) {
41380
- outbox = next?.outbox;
41381
- owner = next?.owner ?? "unknown";
41713
+ outbox2 = next?.outbox;
41714
+ owner2 = next?.owner ?? "unknown";
41382
41715
  }
41383
41716
  async function publishPluginCatalogChanged(tenantId2) {
41384
- if (!outbox || !tenantId2) return;
41717
+ if (!outbox2 || !tenantId2) return;
41385
41718
  try {
41386
- await outbox.append({ topic: PLUGIN_CATALOG_CHANGED_TOPIC, payload: { tenantId: tenantId2 }, originReplicaId: owner });
41719
+ await outbox2.append({ topic: PLUGIN_CATALOG_CHANGED_TOPIC, payload: { tenantId: tenantId2 }, originReplicaId: owner2 });
41387
41720
  } catch {
41388
41721
  }
41389
41722
  }
@@ -41569,9 +41902,9 @@ var createLoadSkillsTool = ({ skills, readAll, pluginSkills, pluginSkillOwners }
41569
41902
  if (!skillName) continue;
41570
41903
  if (await isPluginSkillManagedInSandbox(sandbox, skillName)) {
41571
41904
  const enabledDefinition = pluginSkills?.[skillName];
41572
- const owner2 = pluginSkillOwners?.[skillName];
41573
- const registered = owner2 && enabledDefinition ? PluginRegistry.getSkillForPlugin(owner2, skillName) : void 0;
41574
- if (!registered || registered.definition !== enabledDefinition || !owner2 || !await isPluginSkillAuthorizedInSandbox(sandbox, skillName, owner2) || await getPluginSkillManifestVersion(sandbox, skillName) !== enabledDefinition.version) {
41905
+ const owner3 = pluginSkillOwners?.[skillName];
41906
+ const registered = owner3 && enabledDefinition ? PluginRegistry.getSkillForPlugin(owner3, skillName) : void 0;
41907
+ if (!registered || registered.definition !== enabledDefinition || !owner3 || !await isPluginSkillAuthorizedInSandbox(sandbox, skillName, owner3) || await getPluginSkillManifestVersion(sandbox, skillName) !== enabledDefinition.version) {
41575
41908
  continue;
41576
41909
  }
41577
41910
  }
@@ -41597,8 +41930,8 @@ var createLoadSkillsTool = ({ skills, readAll, pluginSkills, pluginSkillOwners }
41597
41930
  const seenNames = new Set(filteredSkills.map((skill) => skill.name));
41598
41931
  try {
41599
41932
  for (const [name, definition] of Object.entries(pluginSkills ?? {})) {
41600
- const owner2 = pluginSkillOwners?.[name];
41601
- const registered = owner2 ? PluginRegistry.getSkillForPlugin(owner2, name) : void 0;
41933
+ const owner3 = pluginSkillOwners?.[name];
41934
+ const registered = owner3 ? PluginRegistry.getSkillForPlugin(owner3, name) : void 0;
41602
41935
  if (!registered || registered.definition !== definition) continue;
41603
41936
  const { meta } = parseSkillFrontmatter(definition.content);
41604
41937
  if (!meta.name || !meta.description || seenNames.has(meta.name)) continue;
@@ -41635,24 +41968,24 @@ import z54 from "zod";
41635
41968
  import { tool as tool48 } from "langchain";
41636
41969
 
41637
41970
  // src/sandbox_plugin/pluginSkillResolution.ts
41638
- async function resolveTenantPluginSkill(tenantId2, owner2, skillName) {
41971
+ async function resolveTenantPluginSkill(tenantId2, owner3, skillName) {
41639
41972
  try {
41640
41973
  const { plugins } = await getSandboxPluginCatalog().ensureLoaded(tenantId2);
41641
- const plugin = plugins.find((candidate) => candidate.meta.type === owner2);
41974
+ const plugin = plugins.find((candidate) => candidate.meta.type === owner3);
41642
41975
  return plugin?.skills?.[skillName];
41643
41976
  } catch {
41644
41977
  return void 0;
41645
41978
  }
41646
41979
  }
41647
- async function resolveEnabledPluginSkill(owner2, skillName, enabledDefinition, tenantId2) {
41648
- if (!owner2 || !enabledDefinition) return void 0;
41649
- const registered = PluginRegistry.getSkillForPlugin(owner2, skillName);
41650
- if (registered && registered.pluginType === owner2 && registered.definition === enabledDefinition) {
41651
- return { pluginType: owner2, definition: enabledDefinition };
41980
+ async function resolveEnabledPluginSkill(owner3, skillName, enabledDefinition, tenantId2) {
41981
+ if (!owner3 || !enabledDefinition) return void 0;
41982
+ const registered = PluginRegistry.getSkillForPlugin(owner3, skillName);
41983
+ if (registered && registered.pluginType === owner3 && registered.definition === enabledDefinition) {
41984
+ return { pluginType: owner3, definition: enabledDefinition };
41652
41985
  }
41653
41986
  if (!tenantId2) return void 0;
41654
- const tenantDefinition = await resolveTenantPluginSkill(tenantId2, owner2, skillName);
41655
- return tenantDefinition ? { pluginType: owner2, definition: tenantDefinition } : void 0;
41987
+ const tenantDefinition = await resolveTenantPluginSkill(tenantId2, owner3, skillName);
41988
+ return tenantDefinition ? { pluginType: owner3, definition: tenantDefinition } : void 0;
41656
41989
  }
41657
41990
 
41658
41991
  // src/tool_lattice/skill/load_skill_content.ts
@@ -41714,10 +42047,10 @@ var createLoadSkillContentTool = (pluginSkills, pluginSkillOwnersOrStaticSelecti
41714
42047
  return result2;
41715
42048
  }
41716
42049
  const enabledDefinition = pluginSkills?.[input.skill_name];
41717
- const owner2 = pluginSkillOwners?.[input.skill_name];
42050
+ const owner3 = pluginSkillOwners?.[input.skill_name];
41718
42051
  const runConfig = _exe_config?.configurable?.runConfig || {};
41719
42052
  const pluginSkill = await resolveEnabledPluginSkill(
41720
- owner2,
42053
+ owner3,
41721
42054
  input.skill_name,
41722
42055
  enabledDefinition,
41723
42056
  runConfig.tenantId
@@ -41824,9 +42157,9 @@ var createLoadSkillResourceTool = (pluginSkills, pluginSkillOwners) => {
41824
42157
  validateRelativePath(input.resource_path, "resource");
41825
42158
  const sandbox = await getSandboxFromExeConfig3(_exe_config);
41826
42159
  const definition = pluginSkills?.[input.skill_name];
41827
- const owner2 = pluginSkillOwners?.[input.skill_name];
42160
+ const owner3 = pluginSkillOwners?.[input.skill_name];
41828
42161
  const tenantId2 = _exe_config?.configurable?.runConfig?.tenantId;
41829
- const resolved = await resolveEnabledPluginSkill(owner2, input.skill_name, definition, tenantId2);
42162
+ const resolved = await resolveEnabledPluginSkill(owner3, input.skill_name, definition, tenantId2);
41830
42163
  if (definition) {
41831
42164
  if (!resolved || !resolved.definition.resources?.[input.resource_path]) {
41832
42165
  return `Resource "${input.resource_path}" not found for skill "${input.skill_name}".`;
@@ -42190,11 +42523,15 @@ var createSearchCollectionTool = (scope = { collectionKeys: [], connectAll: true
42190
42523
  tenantId2
42191
42524
  );
42192
42525
  }
42193
- let embeddingsClient;
42194
- try {
42195
- embeddingsClient = embeddingsLatticeManager.getEmbeddingsClient(embeddingKey);
42196
- } catch {
42197
- embeddingsClient = embeddingsLatticeManager.getEmbeddingsClient("default");
42526
+ const embeddingsClient = await embeddingsLatticeManager.getEmbeddingsClientWithTenant(
42527
+ tenantId2,
42528
+ embeddingKey
42529
+ ) ?? await embeddingsLatticeManager.getEmbeddingsClientWithTenant(
42530
+ tenantId2,
42531
+ "default"
42532
+ );
42533
+ if (!embeddingsClient) {
42534
+ return `No embedding model "${embeddingKey}" is available for this tenant.`;
42198
42535
  }
42199
42536
  const queryVector = await embeddingsClient.embedQuery(query);
42200
42537
  const results = await vectorStore.similaritySearchVectorWithScore(
@@ -50296,6 +50633,7 @@ export {
50296
50633
  BUILTIN_PLUGINS,
50297
50634
  BUILTIN_SKILLS,
50298
50635
  CAPABILITY_TOOL_NAME_CONFLICT,
50636
+ COLLECTION_CHANGED_TOPIC,
50299
50637
  CapabilityBundleMergeError,
50300
50638
  CapabilityBundleMerger,
50301
50639
  CapabilityBundleValidationError,
@@ -50315,6 +50653,7 @@ export {
50315
50653
  DEFAULT_DELAY_MS,
50316
50654
  DEFAULT_HANDLER_TIMEOUT_MS,
50317
50655
  DEFAULT_MAX_ATTEMPTS,
50656
+ DEFAULT_MODEL_EXTRA,
50318
50657
  DEFAULT_RESIDENT_INVOKE_TIMEOUT_MS,
50319
50658
  DaytonaInstance,
50320
50659
  DaytonaProvider,
@@ -50470,7 +50809,10 @@ export {
50470
50809
  buildGrepResultsDict,
50471
50810
  buildHandlerCommand,
50472
50811
  buildInput,
50812
+ buildModelConfig,
50813
+ buildModelEmbeddingsConfig,
50473
50814
  buildModelLlmConfig,
50815
+ buildModelSttConfig,
50474
50816
  buildNamedVolumeName,
50475
50817
  buildRemotePackDirectoryCommand,
50476
50818
  buildSandboxMetadataEnv,
@@ -50491,6 +50833,7 @@ export {
50491
50833
  compileSandboxPlugin,
50492
50834
  compileWorkflow,
50493
50835
  computeSandboxName,
50836
+ configureCollectionSync,
50494
50837
  configureCoordinationLeaseStore,
50495
50838
  configurePluginCatalogSync,
50496
50839
  configureStores,
@@ -50668,6 +51011,7 @@ export {
50668
51011
  projectRoomPlugin,
50669
51012
  projectTaskAdmissionNotifier,
50670
51013
  provisionSandboxOpenClient,
51014
+ publishCollectionChanged,
50671
51015
  publishPluginCatalogChanged,
50672
51016
  queueLatticeManager,
50673
51017
  readLoopBreakerConfig,