@axiom-lattice/core 6.0.0 → 6.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.js CHANGED
@@ -1662,6 +1662,7 @@ __export(index_exports, {
1662
1662
  InMemoryDatabaseConfigStore: () => InMemoryDatabaseConfigStore,
1663
1663
  InMemoryMailboxStore: () => InMemoryMailboxStore,
1664
1664
  InMemoryMenuStore: () => InMemoryMenuStore,
1665
+ InMemoryModelProviderStore: () => InMemoryModelProviderStore,
1665
1666
  InMemoryOpenAuditStore: () => InMemoryOpenAuditStore,
1666
1667
  InMemoryProjectBotMembershipStore: () => InMemoryProjectBotMembershipStore,
1667
1668
  InMemoryProjectMembershipStore: () => InMemoryProjectMembershipStore,
@@ -1755,6 +1756,7 @@ __export(index_exports, {
1755
1756
  ThreadStatus: () => ThreadStatus2,
1756
1757
  TokenCache: () => TokenCache,
1757
1758
  ToolLatticeManager: () => ToolLatticeManager,
1759
+ UnsafeUpstreamUrlError: () => UnsafeUpstreamUrlError,
1758
1760
  VectorStoreLatticeManager: () => VectorStoreLatticeManager,
1759
1761
  VectorStoreProviderManager: () => VectorStoreProviderManager,
1760
1762
  VolumeFilesystem: () => VolumeFilesystem,
@@ -1763,6 +1765,7 @@ __export(index_exports, {
1763
1765
  agentLatticeManager: () => agentLatticeManager,
1764
1766
  appendReconciliationWarning: () => appendReconciliationWarning,
1765
1767
  appendUiFence: () => appendUiFence,
1768
+ assertSafeUpstreamUrl: () => assertSafeUpstreamUrl,
1766
1769
  buildGrepResultsDict: () => buildGrepResultsDict,
1767
1770
  buildInput: () => buildInput,
1768
1771
  buildNamedVolumeName: () => buildNamedVolumeName,
@@ -1945,6 +1948,7 @@ __export(index_exports, {
1945
1948
  registerExistingToolWithTenant: () => registerExistingToolWithTenant,
1946
1949
  registerLoggerLattice: () => registerLoggerLattice,
1947
1950
  registerModelLattice: () => registerModelLattice,
1951
+ registerModelLatticeForTenant: () => registerModelLatticeForTenant,
1948
1952
  registerQueueLattice: () => registerQueueLattice,
1949
1953
  registerSTTModelLattice: () => registerSTTModelLattice,
1950
1954
  registerSandboxProviderType: () => registerSandboxProviderType,
@@ -2026,6 +2030,17 @@ var ModelLattice = class extends import_chat_models.BaseChatModel {
2026
2030
  this.config = config;
2027
2031
  this.llm = this.initChatModel(config);
2028
2032
  }
2033
+ /**
2034
+ * 返回构造时使用的配置
2035
+ *
2036
+ * 供调用方(如 models 控制器)读取 apiKey 是否存在、以及展示元数据,
2037
+ * 避免反射读取私有字段。
2038
+ *
2039
+ * @returns 构造时传入的 LLMConfig
2040
+ */
2041
+ getConfig() {
2042
+ return this.config;
2043
+ }
2029
2044
  /**
2030
2045
  * 返回模型类型
2031
2046
  */
@@ -2165,6 +2180,9 @@ var ModelLattice = class extends import_chat_models.BaseChatModel {
2165
2180
  maxRetries: config.maxRetries || 2,
2166
2181
  streaming: config.streaming,
2167
2182
  reasoning: { effort: "none" },
2183
+ // Responses API and Chat Completions are different paths with different
2184
+ // request fields; an endpoint that supports only one rejects the other.
2185
+ ...config.useResponsesApi ? { useResponsesApi: true } : {},
2168
2186
  apiKey: config.apiKey || process.env[config.apiKeyEnvName || "OPENAI_API_KEY"],
2169
2187
  configuration: {
2170
2188
  baseURL: config.baseURL
@@ -2255,9 +2273,74 @@ var ModelLatticeManager = class _ModelLatticeManager extends BaseLatticeManager
2255
2273
  getLatticeKeys() {
2256
2274
  return this.keys();
2257
2275
  }
2276
+ // ========== 租户维度 API ==========
2277
+ // 键由基类拼装为 models:{tenantId}:{key}。注意 "default" 租户的键与旧版
2278
+ // key-only API 完全一致(models:default:{key}),因此对该租户是逐字节兼容的。
2279
+ /**
2280
+ * 以租户维度注册模型Lattice
2281
+ *
2282
+ * 同名 key 采用「后写覆盖」语义:先移除再注册,避免基类重复注册时抛错。
2283
+ *
2284
+ * @param tenantId 租户ID
2285
+ * @param key Lattice键名
2286
+ * @param config 模型配置
2287
+ */
2288
+ registerLatticeWithTenant(tenantId2, key5, config) {
2289
+ const client = new ModelLattice(config);
2290
+ const modelLattice = {
2291
+ key: key5,
2292
+ client
2293
+ };
2294
+ if (this.hasWithTenant(tenantId2, key5)) {
2295
+ this.removeWithTenant(tenantId2, key5);
2296
+ }
2297
+ this.registerWithTenant(tenantId2, key5, modelLattice);
2298
+ }
2299
+ /**
2300
+ * 获取指定租户的模型Lattice
2301
+ * @param tenantId 租户ID
2302
+ * @param key Lattice键名
2303
+ */
2304
+ getModelLatticeWithTenant(tenantId2, key5) {
2305
+ return this.getWithTenant(tenantId2, key5);
2306
+ }
2307
+ /**
2308
+ * 检查指定租户的模型Lattice是否存在
2309
+ * @param tenantId 租户ID
2310
+ * @param key Lattice键名
2311
+ */
2312
+ hasLatticeWithTenant(tenantId2, key5) {
2313
+ return this.hasWithTenant(tenantId2, key5);
2314
+ }
2315
+ /**
2316
+ * 获取指定租户的所有模型Lattice
2317
+ * @param tenantId 租户ID
2318
+ */
2319
+ getAllLatticesByTenant(tenantId2) {
2320
+ return this.getAllByTenant(tenantId2);
2321
+ }
2322
+ /**
2323
+ * 获取指定租户的所有模型键名
2324
+ *
2325
+ * 只返回键名,不构造 Lattice 对象数组,适合仅需判断存在性的调用方。
2326
+ *
2327
+ * @param tenantId 租户ID
2328
+ */
2329
+ getLatticeKeysByTenant(tenantId2) {
2330
+ return this.keysByTenant(tenantId2);
2331
+ }
2332
+ /**
2333
+ * 移除指定租户的模型Lattice
2334
+ * @param tenantId 租户ID
2335
+ * @param key Lattice键名
2336
+ */
2337
+ removeLatticeWithTenant(tenantId2, key5) {
2338
+ return this.removeWithTenant(tenantId2, key5);
2339
+ }
2258
2340
  };
2259
2341
  var modelLatticeManager = ModelLatticeManager.getInstance();
2260
2342
  var registerModelLattice = (key5, config) => modelLatticeManager.registerLattice(key5, config);
2343
+ var registerModelLatticeForTenant = (tenantId2, key5, config) => modelLatticeManager.registerLatticeWithTenant(tenantId2, key5, config);
2261
2344
  var getModelLattice = (key5) => modelLatticeManager.getModelLattice(key5);
2262
2345
 
2263
2346
  // src/tool_lattice/get_current_date_time/index.ts
@@ -3993,6 +4076,240 @@ var InMemoryMcpServerConfigStore = class {
3993
4076
  }
3994
4077
  };
3995
4078
 
4079
+ // src/store_lattice/InMemoryModelProviderStore.ts
4080
+ var InMemoryModelProviderStore = class {
4081
+ constructor() {
4082
+ this.providers = /* @__PURE__ */ new Map();
4083
+ this.models = /* @__PURE__ */ new Map();
4084
+ }
4085
+ /**
4086
+ * Get composite key for providers
4087
+ */
4088
+ getProviderKey(tenantId2, id) {
4089
+ return `${tenantId2}:${id}`;
4090
+ }
4091
+ /**
4092
+ * Get composite key for snapshot models
4093
+ */
4094
+ getModelKey(tenantId2, providerId, modelId) {
4095
+ return `${tenantId2}:${providerId}:${modelId}`;
4096
+ }
4097
+ /**
4098
+ * Get all provider configurations for a tenant
4099
+ */
4100
+ async listProviders(tenantId2) {
4101
+ return Array.from(this.providers.values()).filter(
4102
+ (provider) => provider.tenantId === tenantId2
4103
+ );
4104
+ }
4105
+ /**
4106
+ * Get a provider configuration by ID
4107
+ */
4108
+ async getProviderById(tenantId2, id) {
4109
+ return this.providers.get(this.getProviderKey(tenantId2, id)) || null;
4110
+ }
4111
+ /**
4112
+ * Get a provider configuration by business name
4113
+ */
4114
+ async getProviderByName(tenantId2, name) {
4115
+ const providers = await this.listProviders(tenantId2);
4116
+ return providers.find((provider) => provider.name === name) || null;
4117
+ }
4118
+ /**
4119
+ * Create a new provider configuration
4120
+ */
4121
+ async createProvider(tenantId2, id, data) {
4122
+ const now = /* @__PURE__ */ new Date();
4123
+ const entry = {
4124
+ id,
4125
+ tenantId: tenantId2,
4126
+ name: data.name,
4127
+ displayName: data.displayName,
4128
+ protocol: data.protocol,
4129
+ apiStyle: data.apiStyle ?? "chat-completions",
4130
+ // Phase 1 pins the LLM provider: the UI does not offer a choice, and some
4131
+ // initChatModel branches hardcode their own baseURL and would silently
4132
+ // ignore the tenant's (see ADR-138 decision 7b).
4133
+ llmProvider: "openai",
4134
+ baseURL: data.baseURL,
4135
+ apiKey: data.apiKey || void 0,
4136
+ apiKeyHint: data.apiKey ? data.apiKey.slice(-4) : void 0,
4137
+ enabled: data.enabled ?? true,
4138
+ status: "unknown",
4139
+ createdAt: now,
4140
+ updatedAt: now
4141
+ };
4142
+ this.providers.set(this.getProviderKey(tenantId2, id), entry);
4143
+ return entry;
4144
+ }
4145
+ /**
4146
+ * Update an existing provider configuration.
4147
+ * An omitted or empty `apiKey` leaves the stored key untouched.
4148
+ */
4149
+ async updateProvider(tenantId2, id, updates) {
4150
+ const key5 = this.getProviderKey(tenantId2, id);
4151
+ const existing = this.providers.get(key5);
4152
+ if (!existing) {
4153
+ return null;
4154
+ }
4155
+ const hasNewApiKey = Boolean(updates.apiKey);
4156
+ const clearsApiKey = updates.clearApiKey === true;
4157
+ const updated = {
4158
+ ...existing,
4159
+ name: updates.name ?? existing.name,
4160
+ displayName: updates.displayName !== void 0 ? updates.displayName : existing.displayName,
4161
+ protocol: updates.protocol ?? existing.protocol,
4162
+ apiStyle: updates.apiStyle ?? existing.apiStyle,
4163
+ baseURL: updates.baseURL ?? existing.baseURL,
4164
+ apiKey: clearsApiKey ? void 0 : hasNewApiKey ? updates.apiKey : existing.apiKey,
4165
+ apiKeyHint: clearsApiKey ? void 0 : hasNewApiKey ? updates.apiKey?.slice(-4) : existing.apiKeyHint,
4166
+ enabled: updates.enabled ?? existing.enabled,
4167
+ updatedAt: /* @__PURE__ */ new Date()
4168
+ };
4169
+ this.providers.set(key5, updated);
4170
+ return updated;
4171
+ }
4172
+ /**
4173
+ * Delete a provider configuration and its model snapshot
4174
+ */
4175
+ async deleteProvider(tenantId2, id) {
4176
+ await this.deleteModelsByProvider(tenantId2, id);
4177
+ return this.providers.delete(this.getProviderKey(tenantId2, id));
4178
+ }
4179
+ /**
4180
+ * Record the outcome of a discovery attempt
4181
+ */
4182
+ async markProviderStatus(tenantId2, id, status, lastError, discoveredAt) {
4183
+ const key5 = this.getProviderKey(tenantId2, id);
4184
+ const existing = this.providers.get(key5);
4185
+ if (!existing) {
4186
+ return;
4187
+ }
4188
+ this.providers.set(key5, {
4189
+ ...existing,
4190
+ status,
4191
+ lastError: status === "ok" ? void 0 : lastError,
4192
+ lastDiscoveredAt: discoveredAt ?? existing.lastDiscoveredAt,
4193
+ updatedAt: /* @__PURE__ */ new Date()
4194
+ });
4195
+ }
4196
+ /**
4197
+ * List snapshot models for a tenant
4198
+ */
4199
+ async listModels(tenantId2, providerId) {
4200
+ return Array.from(this.models.values()).filter(
4201
+ (model) => model.tenantId === tenantId2 && (providerId === void 0 || model.providerId === providerId)
4202
+ );
4203
+ }
4204
+ /**
4205
+ * Get a single snapshot model.
4206
+ * Keyed by provider as well as model id, since two providers in the same
4207
+ * tenant may expose the same upstream model id.
4208
+ */
4209
+ async getModel(tenantId2, providerId, modelId) {
4210
+ return this.models.get(this.getModelKey(tenantId2, providerId, modelId)) || null;
4211
+ }
4212
+ /**
4213
+ * Replace a provider's model snapshot with the result of one discovery.
4214
+ *
4215
+ * Model ids are scoped per provider, so the same upstream id may appear
4216
+ * under several providers in one tenant — each is an independent model.
4217
+ *
4218
+ * Models the upstream no longer offers are removed; models that survive keep
4219
+ * the tenant's enable/disable choice rather than being reset.
4220
+ */
4221
+ async replaceProviderModels(tenantId2, providerId, models) {
4222
+ const previous = await this.listModels(tenantId2, providerId);
4223
+ const previousById = new Map(
4224
+ previous.map((model) => [model.modelId, model])
4225
+ );
4226
+ const incomingIds = new Set(models.map((model) => model.id));
4227
+ for (const model of previous) {
4228
+ if (!incomingIds.has(model.modelId)) {
4229
+ this.models.delete(
4230
+ this.getModelKey(tenantId2, providerId, model.modelId)
4231
+ );
4232
+ }
4233
+ }
4234
+ const now = /* @__PURE__ */ new Date();
4235
+ const upserted = [];
4236
+ for (const model of models) {
4237
+ const existing = previousById.get(model.id);
4238
+ const entry = {
4239
+ id: `${providerId}:${model.id}`,
4240
+ tenantId: tenantId2,
4241
+ providerId,
4242
+ modelId: model.id,
4243
+ displayName: model.displayName,
4244
+ ownedBy: model.ownedBy,
4245
+ upstreamCreatedAt: model.createdAt ? new Date(model.createdAt) : void 0,
4246
+ // An explicit `enabled` from the caller wins; discovery omits it and
4247
+ // the tenant's earlier choice is preserved.
4248
+ enabled: model.enabled ?? existing?.enabled ?? true,
4249
+ // Unlike `enabled`, this is not preserved: a model present in the list
4250
+ // the caller just read is, by definition, no longer stale. Only an
4251
+ // explicit flag marks one.
4252
+ stale: model.stale ?? false,
4253
+ discoveredAt: now
4254
+ };
4255
+ this.models.set(this.getModelKey(tenantId2, providerId, model.id), entry);
4256
+ upserted.push(model.id);
4257
+ }
4258
+ return upserted;
4259
+ }
4260
+ /**
4261
+ * Enable or disable a single snapshot model
4262
+ */
4263
+ async setModelEnabled(tenantId2, providerId, modelId, enabled) {
4264
+ const key5 = this.getModelKey(tenantId2, providerId, modelId);
4265
+ const existing = this.models.get(key5);
4266
+ if (!existing) {
4267
+ return null;
4268
+ }
4269
+ const updated = {
4270
+ ...existing,
4271
+ enabled
4272
+ };
4273
+ this.models.set(key5, updated);
4274
+ return updated;
4275
+ }
4276
+ /**
4277
+ * Delete every snapshot model belonging to a provider
4278
+ */
4279
+ async deleteModelsByProvider(tenantId2, providerId) {
4280
+ let deleted = 0;
4281
+ for (const [key5, model] of Array.from(this.models.entries())) {
4282
+ if (model.tenantId === tenantId2 && model.providerId === providerId) {
4283
+ this.models.delete(key5);
4284
+ deleted += 1;
4285
+ }
4286
+ }
4287
+ return deleted;
4288
+ }
4289
+ /**
4290
+ * Clear all providers and models (useful for testing)
4291
+ */
4292
+ clear() {
4293
+ this.providers.clear();
4294
+ this.models.clear();
4295
+ }
4296
+ /**
4297
+ * Clear providers and models for a specific tenant
4298
+ */
4299
+ clearByTenant(tenantId2) {
4300
+ for (const [key5, provider] of Array.from(this.providers.entries())) {
4301
+ if (provider.tenantId === tenantId2) {
4302
+ this.providers.delete(key5);
4303
+ }
4304
+ }
4305
+ for (const [key5, model] of Array.from(this.models.entries())) {
4306
+ if (model.tenantId === tenantId2) {
4307
+ this.models.delete(key5);
4308
+ }
4309
+ }
4310
+ }
4311
+ };
4312
+
3996
4313
  // src/store_lattice/InMemoryAgentWebAppStore.ts
3997
4314
  var import_crypto = require("crypto");
3998
4315
  function cloneAppearance(appearance) {
@@ -6473,6 +6790,7 @@ var defaultProjectStore = new InMemoryProjectStore(defaultCapabilityBundleCoordi
6473
6790
  var defaultDatabaseConfigStore = new InMemoryDatabaseConfigStore();
6474
6791
  var defaultMetricsServerConfigStore = new InMemoryMetricsServerConfigStore();
6475
6792
  var defaultMcpServerConfigStore = new InMemoryMcpServerConfigStore();
6793
+ var defaultModelProviderStore = new InMemoryModelProviderStore();
6476
6794
  var defaultAgentWebAppStore = new InMemoryAgentWebAppStore();
6477
6795
  var defaultCapabilityBundleStore = new InMemoryCapabilityBundleStore(defaultProjectStore, defaultCapabilityBundleCoordinator);
6478
6796
  var defaultProjectRoomCoordinator = new InMemoryProjectRoomCoordinator();
@@ -6494,6 +6812,7 @@ storeLatticeManager.registerLattice("default", "project", defaultProjectStore);
6494
6812
  storeLatticeManager.registerLattice("default", "database", defaultDatabaseConfigStore);
6495
6813
  storeLatticeManager.registerLattice("default", "metrics", defaultMetricsServerConfigStore);
6496
6814
  storeLatticeManager.registerLattice("default", "mcp", defaultMcpServerConfigStore);
6815
+ storeLatticeManager.registerLattice("default", "modelProvider", defaultModelProviderStore);
6497
6816
  storeLatticeManager.registerLattice("default", "agentWebApp", defaultAgentWebAppStore);
6498
6817
  storeLatticeManager.registerLattice("default", "capabilityBundle", defaultCapabilityBundleStore);
6499
6818
  storeLatticeManager.registerLattice("default", "projectRoom", defaultProjectRoomStore);
@@ -36334,13 +36653,16 @@ var LatticeEvalProject = class {
36334
36653
  constructor(project, onCaseComplete) {
36335
36654
  this.suites = /* @__PURE__ */ new Map();
36336
36655
  this.project = project;
36337
- const judgeModelKey = resolveJudgeModelKey(this.project.judge_agent_config.modelKey, modelLatticeManager);
36656
+ const tenantId2 = this.project.lattice_server_config.tenant_id || "default";
36657
+ const judgeModelKey = resolveJudgeModelKey(
36658
+ this.project.judge_agent_config.modelKey,
36659
+ modelLatticeManager
36660
+ );
36338
36661
  if (!modelLatticeManager.hasLattice(judgeModelKey)) {
36339
36662
  throw new Error(
36340
36663
  `Judge model "${judgeModelKey}" not found. Register it first or provide a valid modelKey.`
36341
36664
  );
36342
36665
  }
36343
- const tenantId2 = this.project.lattice_server_config.tenant_id || "default";
36344
36666
  this.judgeAgentKey = `${this.project.projectName}_judge_agent`;
36345
36667
  const judgeAgentConfig = {
36346
36668
  key: this.judgeAgentKey,
@@ -40690,6 +41012,47 @@ function clearEncryptionKeyCache() {
40690
41012
  keyValidated = false;
40691
41013
  }
40692
41014
 
41015
+ // src/util/safeUpstreamUrl.ts
41016
+ var UnsafeUpstreamUrlError = class extends Error {
41017
+ constructor(reason, message, statusCode = 400) {
41018
+ super(message);
41019
+ this.reason = reason;
41020
+ this.statusCode = statusCode;
41021
+ this.name = "UnsafeUpstreamUrlError";
41022
+ }
41023
+ };
41024
+ async function assertSafeUpstreamUrl(raw) {
41025
+ let url;
41026
+ try {
41027
+ url = new URL(raw);
41028
+ } catch {
41029
+ throw new UnsafeUpstreamUrlError(
41030
+ "INVALID_BASE_URL",
41031
+ "Base URL is not a valid URL"
41032
+ );
41033
+ }
41034
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
41035
+ throw new UnsafeUpstreamUrlError(
41036
+ "UNSUPPORTED_SCHEME",
41037
+ "Base URL must use http or https"
41038
+ );
41039
+ }
41040
+ if (url.username.length > 0 || url.password.length > 0) {
41041
+ throw new UnsafeUpstreamUrlError(
41042
+ "CREDENTIALS_IN_URL",
41043
+ "Base URL must not embed credentials; configure the API key separately"
41044
+ );
41045
+ }
41046
+ const hostname = url.hostname.replace(/^\[/, "").replace(/\]$/, "");
41047
+ if (hostname.length === 0) {
41048
+ throw new UnsafeUpstreamUrlError(
41049
+ "INVALID_BASE_URL",
41050
+ "Base URL must include a host"
41051
+ );
41052
+ }
41053
+ return { url };
41054
+ }
41055
+
40693
41056
  // src/middlewares/skillMiddleware.ts
40694
41057
  var import_langchain66 = require("langchain");
40695
41058
 
@@ -48103,6 +48466,7 @@ registerBuiltinPlugins();
48103
48466
  InMemoryDatabaseConfigStore,
48104
48467
  InMemoryMailboxStore,
48105
48468
  InMemoryMenuStore,
48469
+ InMemoryModelProviderStore,
48106
48470
  InMemoryOpenAuditStore,
48107
48471
  InMemoryProjectBotMembershipStore,
48108
48472
  InMemoryProjectMembershipStore,
@@ -48196,6 +48560,7 @@ registerBuiltinPlugins();
48196
48560
  ThreadStatus,
48197
48561
  TokenCache,
48198
48562
  ToolLatticeManager,
48563
+ UnsafeUpstreamUrlError,
48199
48564
  VectorStoreLatticeManager,
48200
48565
  VectorStoreProviderManager,
48201
48566
  VolumeFilesystem,
@@ -48204,6 +48569,7 @@ registerBuiltinPlugins();
48204
48569
  agentLatticeManager,
48205
48570
  appendReconciliationWarning,
48206
48571
  appendUiFence,
48572
+ assertSafeUpstreamUrl,
48207
48573
  buildGrepResultsDict,
48208
48574
  buildInput,
48209
48575
  buildNamedVolumeName,
@@ -48386,6 +48752,7 @@ registerBuiltinPlugins();
48386
48752
  registerExistingToolWithTenant,
48387
48753
  registerLoggerLattice,
48388
48754
  registerModelLattice,
48755
+ registerModelLatticeForTenant,
48389
48756
  registerQueueLattice,
48390
48757
  registerSTTModelLattice,
48391
48758
  registerSandboxProviderType,