@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.mjs CHANGED
@@ -50,6 +50,17 @@ var ModelLattice = class extends BaseChatModel {
50
50
  this.config = config;
51
51
  this.llm = this.initChatModel(config);
52
52
  }
53
+ /**
54
+ * 返回构造时使用的配置
55
+ *
56
+ * 供调用方(如 models 控制器)读取 apiKey 是否存在、以及展示元数据,
57
+ * 避免反射读取私有字段。
58
+ *
59
+ * @returns 构造时传入的 LLMConfig
60
+ */
61
+ getConfig() {
62
+ return this.config;
63
+ }
53
64
  /**
54
65
  * 返回模型类型
55
66
  */
@@ -189,6 +200,9 @@ var ModelLattice = class extends BaseChatModel {
189
200
  maxRetries: config.maxRetries || 2,
190
201
  streaming: config.streaming,
191
202
  reasoning: { effort: "none" },
203
+ // Responses API and Chat Completions are different paths with different
204
+ // request fields; an endpoint that supports only one rejects the other.
205
+ ...config.useResponsesApi ? { useResponsesApi: true } : {},
192
206
  apiKey: config.apiKey || process.env[config.apiKeyEnvName || "OPENAI_API_KEY"],
193
207
  configuration: {
194
208
  baseURL: config.baseURL
@@ -279,9 +293,74 @@ var ModelLatticeManager = class _ModelLatticeManager extends BaseLatticeManager
279
293
  getLatticeKeys() {
280
294
  return this.keys();
281
295
  }
296
+ // ========== 租户维度 API ==========
297
+ // 键由基类拼装为 models:{tenantId}:{key}。注意 "default" 租户的键与旧版
298
+ // key-only API 完全一致(models:default:{key}),因此对该租户是逐字节兼容的。
299
+ /**
300
+ * 以租户维度注册模型Lattice
301
+ *
302
+ * 同名 key 采用「后写覆盖」语义:先移除再注册,避免基类重复注册时抛错。
303
+ *
304
+ * @param tenantId 租户ID
305
+ * @param key Lattice键名
306
+ * @param config 模型配置
307
+ */
308
+ registerLatticeWithTenant(tenantId2, key5, config) {
309
+ const client = new ModelLattice(config);
310
+ const modelLattice = {
311
+ key: key5,
312
+ client
313
+ };
314
+ if (this.hasWithTenant(tenantId2, key5)) {
315
+ this.removeWithTenant(tenantId2, key5);
316
+ }
317
+ this.registerWithTenant(tenantId2, key5, modelLattice);
318
+ }
319
+ /**
320
+ * 获取指定租户的模型Lattice
321
+ * @param tenantId 租户ID
322
+ * @param key Lattice键名
323
+ */
324
+ getModelLatticeWithTenant(tenantId2, key5) {
325
+ return this.getWithTenant(tenantId2, key5);
326
+ }
327
+ /**
328
+ * 检查指定租户的模型Lattice是否存在
329
+ * @param tenantId 租户ID
330
+ * @param key Lattice键名
331
+ */
332
+ hasLatticeWithTenant(tenantId2, key5) {
333
+ return this.hasWithTenant(tenantId2, key5);
334
+ }
335
+ /**
336
+ * 获取指定租户的所有模型Lattice
337
+ * @param tenantId 租户ID
338
+ */
339
+ getAllLatticesByTenant(tenantId2) {
340
+ return this.getAllByTenant(tenantId2);
341
+ }
342
+ /**
343
+ * 获取指定租户的所有模型键名
344
+ *
345
+ * 只返回键名,不构造 Lattice 对象数组,适合仅需判断存在性的调用方。
346
+ *
347
+ * @param tenantId 租户ID
348
+ */
349
+ getLatticeKeysByTenant(tenantId2) {
350
+ return this.keysByTenant(tenantId2);
351
+ }
352
+ /**
353
+ * 移除指定租户的模型Lattice
354
+ * @param tenantId 租户ID
355
+ * @param key Lattice键名
356
+ */
357
+ removeLatticeWithTenant(tenantId2, key5) {
358
+ return this.removeWithTenant(tenantId2, key5);
359
+ }
282
360
  };
283
361
  var modelLatticeManager = ModelLatticeManager.getInstance();
284
362
  var registerModelLattice = (key5, config) => modelLatticeManager.registerLattice(key5, config);
363
+ var registerModelLatticeForTenant = (tenantId2, key5, config) => modelLatticeManager.registerLatticeWithTenant(tenantId2, key5, config);
285
364
  var getModelLattice = (key5) => modelLatticeManager.getModelLattice(key5);
286
365
 
287
366
  // src/tool_lattice/get_current_date_time/index.ts
@@ -2013,6 +2092,240 @@ var InMemoryMcpServerConfigStore = class {
2013
2092
  }
2014
2093
  };
2015
2094
 
2095
+ // src/store_lattice/InMemoryModelProviderStore.ts
2096
+ var InMemoryModelProviderStore = class {
2097
+ constructor() {
2098
+ this.providers = /* @__PURE__ */ new Map();
2099
+ this.models = /* @__PURE__ */ new Map();
2100
+ }
2101
+ /**
2102
+ * Get composite key for providers
2103
+ */
2104
+ getProviderKey(tenantId2, id) {
2105
+ return `${tenantId2}:${id}`;
2106
+ }
2107
+ /**
2108
+ * Get composite key for snapshot models
2109
+ */
2110
+ getModelKey(tenantId2, providerId, modelId) {
2111
+ return `${tenantId2}:${providerId}:${modelId}`;
2112
+ }
2113
+ /**
2114
+ * Get all provider configurations for a tenant
2115
+ */
2116
+ async listProviders(tenantId2) {
2117
+ return Array.from(this.providers.values()).filter(
2118
+ (provider) => provider.tenantId === tenantId2
2119
+ );
2120
+ }
2121
+ /**
2122
+ * Get a provider configuration by ID
2123
+ */
2124
+ async getProviderById(tenantId2, id) {
2125
+ return this.providers.get(this.getProviderKey(tenantId2, id)) || null;
2126
+ }
2127
+ /**
2128
+ * Get a provider configuration by business name
2129
+ */
2130
+ async getProviderByName(tenantId2, name) {
2131
+ const providers = await this.listProviders(tenantId2);
2132
+ return providers.find((provider) => provider.name === name) || null;
2133
+ }
2134
+ /**
2135
+ * Create a new provider configuration
2136
+ */
2137
+ async createProvider(tenantId2, id, data) {
2138
+ const now = /* @__PURE__ */ new Date();
2139
+ const entry = {
2140
+ id,
2141
+ tenantId: tenantId2,
2142
+ name: data.name,
2143
+ displayName: data.displayName,
2144
+ protocol: data.protocol,
2145
+ apiStyle: data.apiStyle ?? "chat-completions",
2146
+ // Phase 1 pins the LLM provider: the UI does not offer a choice, and some
2147
+ // initChatModel branches hardcode their own baseURL and would silently
2148
+ // ignore the tenant's (see ADR-138 decision 7b).
2149
+ llmProvider: "openai",
2150
+ baseURL: data.baseURL,
2151
+ apiKey: data.apiKey || void 0,
2152
+ apiKeyHint: data.apiKey ? data.apiKey.slice(-4) : void 0,
2153
+ enabled: data.enabled ?? true,
2154
+ status: "unknown",
2155
+ createdAt: now,
2156
+ updatedAt: now
2157
+ };
2158
+ this.providers.set(this.getProviderKey(tenantId2, id), entry);
2159
+ return entry;
2160
+ }
2161
+ /**
2162
+ * Update an existing provider configuration.
2163
+ * An omitted or empty `apiKey` leaves the stored key untouched.
2164
+ */
2165
+ async updateProvider(tenantId2, id, updates) {
2166
+ const key5 = this.getProviderKey(tenantId2, id);
2167
+ const existing = this.providers.get(key5);
2168
+ if (!existing) {
2169
+ return null;
2170
+ }
2171
+ const hasNewApiKey = Boolean(updates.apiKey);
2172
+ const clearsApiKey = updates.clearApiKey === true;
2173
+ const updated = {
2174
+ ...existing,
2175
+ name: updates.name ?? existing.name,
2176
+ displayName: updates.displayName !== void 0 ? updates.displayName : existing.displayName,
2177
+ protocol: updates.protocol ?? existing.protocol,
2178
+ apiStyle: updates.apiStyle ?? existing.apiStyle,
2179
+ baseURL: updates.baseURL ?? existing.baseURL,
2180
+ apiKey: clearsApiKey ? void 0 : hasNewApiKey ? updates.apiKey : existing.apiKey,
2181
+ apiKeyHint: clearsApiKey ? void 0 : hasNewApiKey ? updates.apiKey?.slice(-4) : existing.apiKeyHint,
2182
+ enabled: updates.enabled ?? existing.enabled,
2183
+ updatedAt: /* @__PURE__ */ new Date()
2184
+ };
2185
+ this.providers.set(key5, updated);
2186
+ return updated;
2187
+ }
2188
+ /**
2189
+ * Delete a provider configuration and its model snapshot
2190
+ */
2191
+ async deleteProvider(tenantId2, id) {
2192
+ await this.deleteModelsByProvider(tenantId2, id);
2193
+ return this.providers.delete(this.getProviderKey(tenantId2, id));
2194
+ }
2195
+ /**
2196
+ * Record the outcome of a discovery attempt
2197
+ */
2198
+ async markProviderStatus(tenantId2, id, status, lastError, discoveredAt) {
2199
+ const key5 = this.getProviderKey(tenantId2, id);
2200
+ const existing = this.providers.get(key5);
2201
+ if (!existing) {
2202
+ return;
2203
+ }
2204
+ this.providers.set(key5, {
2205
+ ...existing,
2206
+ status,
2207
+ lastError: status === "ok" ? void 0 : lastError,
2208
+ lastDiscoveredAt: discoveredAt ?? existing.lastDiscoveredAt,
2209
+ updatedAt: /* @__PURE__ */ new Date()
2210
+ });
2211
+ }
2212
+ /**
2213
+ * List snapshot models for a tenant
2214
+ */
2215
+ async listModels(tenantId2, providerId) {
2216
+ return Array.from(this.models.values()).filter(
2217
+ (model) => model.tenantId === tenantId2 && (providerId === void 0 || model.providerId === providerId)
2218
+ );
2219
+ }
2220
+ /**
2221
+ * Get a single snapshot model.
2222
+ * Keyed by provider as well as model id, since two providers in the same
2223
+ * tenant may expose the same upstream model id.
2224
+ */
2225
+ async getModel(tenantId2, providerId, modelId) {
2226
+ return this.models.get(this.getModelKey(tenantId2, providerId, modelId)) || null;
2227
+ }
2228
+ /**
2229
+ * Replace a provider's model snapshot with the result of one discovery.
2230
+ *
2231
+ * Model ids are scoped per provider, so the same upstream id may appear
2232
+ * under several providers in one tenant — each is an independent model.
2233
+ *
2234
+ * Models the upstream no longer offers are removed; models that survive keep
2235
+ * the tenant's enable/disable choice rather than being reset.
2236
+ */
2237
+ async replaceProviderModels(tenantId2, providerId, models) {
2238
+ const previous = await this.listModels(tenantId2, providerId);
2239
+ const previousById = new Map(
2240
+ previous.map((model) => [model.modelId, model])
2241
+ );
2242
+ const incomingIds = new Set(models.map((model) => model.id));
2243
+ for (const model of previous) {
2244
+ if (!incomingIds.has(model.modelId)) {
2245
+ this.models.delete(
2246
+ this.getModelKey(tenantId2, providerId, model.modelId)
2247
+ );
2248
+ }
2249
+ }
2250
+ const now = /* @__PURE__ */ new Date();
2251
+ const upserted = [];
2252
+ for (const model of models) {
2253
+ const existing = previousById.get(model.id);
2254
+ const entry = {
2255
+ id: `${providerId}:${model.id}`,
2256
+ tenantId: tenantId2,
2257
+ providerId,
2258
+ modelId: model.id,
2259
+ displayName: model.displayName,
2260
+ ownedBy: model.ownedBy,
2261
+ upstreamCreatedAt: model.createdAt ? new Date(model.createdAt) : void 0,
2262
+ // An explicit `enabled` from the caller wins; discovery omits it and
2263
+ // the tenant's earlier choice is preserved.
2264
+ enabled: model.enabled ?? existing?.enabled ?? true,
2265
+ // Unlike `enabled`, this is not preserved: a model present in the list
2266
+ // the caller just read is, by definition, no longer stale. Only an
2267
+ // explicit flag marks one.
2268
+ stale: model.stale ?? false,
2269
+ discoveredAt: now
2270
+ };
2271
+ this.models.set(this.getModelKey(tenantId2, providerId, model.id), entry);
2272
+ upserted.push(model.id);
2273
+ }
2274
+ return upserted;
2275
+ }
2276
+ /**
2277
+ * Enable or disable a single snapshot model
2278
+ */
2279
+ async setModelEnabled(tenantId2, providerId, modelId, enabled) {
2280
+ const key5 = this.getModelKey(tenantId2, providerId, modelId);
2281
+ const existing = this.models.get(key5);
2282
+ if (!existing) {
2283
+ return null;
2284
+ }
2285
+ const updated = {
2286
+ ...existing,
2287
+ enabled
2288
+ };
2289
+ this.models.set(key5, updated);
2290
+ return updated;
2291
+ }
2292
+ /**
2293
+ * Delete every snapshot model belonging to a provider
2294
+ */
2295
+ async deleteModelsByProvider(tenantId2, providerId) {
2296
+ let deleted = 0;
2297
+ for (const [key5, model] of Array.from(this.models.entries())) {
2298
+ if (model.tenantId === tenantId2 && model.providerId === providerId) {
2299
+ this.models.delete(key5);
2300
+ deleted += 1;
2301
+ }
2302
+ }
2303
+ return deleted;
2304
+ }
2305
+ /**
2306
+ * Clear all providers and models (useful for testing)
2307
+ */
2308
+ clear() {
2309
+ this.providers.clear();
2310
+ this.models.clear();
2311
+ }
2312
+ /**
2313
+ * Clear providers and models for a specific tenant
2314
+ */
2315
+ clearByTenant(tenantId2) {
2316
+ for (const [key5, provider] of Array.from(this.providers.entries())) {
2317
+ if (provider.tenantId === tenantId2) {
2318
+ this.providers.delete(key5);
2319
+ }
2320
+ }
2321
+ for (const [key5, model] of Array.from(this.models.entries())) {
2322
+ if (model.tenantId === tenantId2) {
2323
+ this.models.delete(key5);
2324
+ }
2325
+ }
2326
+ }
2327
+ };
2328
+
2016
2329
  // src/store_lattice/InMemoryAgentWebAppStore.ts
2017
2330
  import { randomUUID } from "crypto";
2018
2331
  function cloneAppearance(appearance) {
@@ -4501,6 +4814,7 @@ var defaultProjectStore = new InMemoryProjectStore(defaultCapabilityBundleCoordi
4501
4814
  var defaultDatabaseConfigStore = new InMemoryDatabaseConfigStore();
4502
4815
  var defaultMetricsServerConfigStore = new InMemoryMetricsServerConfigStore();
4503
4816
  var defaultMcpServerConfigStore = new InMemoryMcpServerConfigStore();
4817
+ var defaultModelProviderStore = new InMemoryModelProviderStore();
4504
4818
  var defaultAgentWebAppStore = new InMemoryAgentWebAppStore();
4505
4819
  var defaultCapabilityBundleStore = new InMemoryCapabilityBundleStore(defaultProjectStore, defaultCapabilityBundleCoordinator);
4506
4820
  var defaultProjectRoomCoordinator = new InMemoryProjectRoomCoordinator();
@@ -4522,6 +4836,7 @@ storeLatticeManager.registerLattice("default", "project", defaultProjectStore);
4522
4836
  storeLatticeManager.registerLattice("default", "database", defaultDatabaseConfigStore);
4523
4837
  storeLatticeManager.registerLattice("default", "metrics", defaultMetricsServerConfigStore);
4524
4838
  storeLatticeManager.registerLattice("default", "mcp", defaultMcpServerConfigStore);
4839
+ storeLatticeManager.registerLattice("default", "modelProvider", defaultModelProviderStore);
4525
4840
  storeLatticeManager.registerLattice("default", "agentWebApp", defaultAgentWebAppStore);
4526
4841
  storeLatticeManager.registerLattice("default", "capabilityBundle", defaultCapabilityBundleStore);
4527
4842
  storeLatticeManager.registerLattice("default", "projectRoom", defaultProjectRoomStore);
@@ -34372,13 +34687,16 @@ var LatticeEvalProject = class {
34372
34687
  constructor(project, onCaseComplete) {
34373
34688
  this.suites = /* @__PURE__ */ new Map();
34374
34689
  this.project = project;
34375
- const judgeModelKey = resolveJudgeModelKey(this.project.judge_agent_config.modelKey, modelLatticeManager);
34690
+ const tenantId2 = this.project.lattice_server_config.tenant_id || "default";
34691
+ const judgeModelKey = resolveJudgeModelKey(
34692
+ this.project.judge_agent_config.modelKey,
34693
+ modelLatticeManager
34694
+ );
34376
34695
  if (!modelLatticeManager.hasLattice(judgeModelKey)) {
34377
34696
  throw new Error(
34378
34697
  `Judge model "${judgeModelKey}" not found. Register it first or provide a valid modelKey.`
34379
34698
  );
34380
34699
  }
34381
- const tenantId2 = this.project.lattice_server_config.tenant_id || "default";
34382
34700
  this.judgeAgentKey = `${this.project.projectName}_judge_agent`;
34383
34701
  const judgeAgentConfig = {
34384
34702
  key: this.judgeAgentKey,
@@ -38728,6 +39046,47 @@ function clearEncryptionKeyCache() {
38728
39046
  keyValidated = false;
38729
39047
  }
38730
39048
 
39049
+ // src/util/safeUpstreamUrl.ts
39050
+ var UnsafeUpstreamUrlError = class extends Error {
39051
+ constructor(reason, message, statusCode = 400) {
39052
+ super(message);
39053
+ this.reason = reason;
39054
+ this.statusCode = statusCode;
39055
+ this.name = "UnsafeUpstreamUrlError";
39056
+ }
39057
+ };
39058
+ async function assertSafeUpstreamUrl(raw) {
39059
+ let url;
39060
+ try {
39061
+ url = new URL(raw);
39062
+ } catch {
39063
+ throw new UnsafeUpstreamUrlError(
39064
+ "INVALID_BASE_URL",
39065
+ "Base URL is not a valid URL"
39066
+ );
39067
+ }
39068
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
39069
+ throw new UnsafeUpstreamUrlError(
39070
+ "UNSUPPORTED_SCHEME",
39071
+ "Base URL must use http or https"
39072
+ );
39073
+ }
39074
+ if (url.username.length > 0 || url.password.length > 0) {
39075
+ throw new UnsafeUpstreamUrlError(
39076
+ "CREDENTIALS_IN_URL",
39077
+ "Base URL must not embed credentials; configure the API key separately"
39078
+ );
39079
+ }
39080
+ const hostname = url.hostname.replace(/^\[/, "").replace(/\]$/, "");
39081
+ if (hostname.length === 0) {
39082
+ throw new UnsafeUpstreamUrlError(
39083
+ "INVALID_BASE_URL",
39084
+ "Base URL must include a host"
39085
+ );
39086
+ }
39087
+ return { url };
39088
+ }
39089
+
38731
39090
  // src/middlewares/skillMiddleware.ts
38732
39091
  import { createMiddleware as createMiddleware19 } from "langchain";
38733
39092
 
@@ -46143,6 +46502,7 @@ export {
46143
46502
  InMemoryDatabaseConfigStore,
46144
46503
  InMemoryMailboxStore,
46145
46504
  InMemoryMenuStore,
46505
+ InMemoryModelProviderStore,
46146
46506
  InMemoryOpenAuditStore,
46147
46507
  InMemoryProjectBotMembershipStore,
46148
46508
  InMemoryProjectMembershipStore,
@@ -46236,6 +46596,7 @@ export {
46236
46596
  ThreadStatus2 as ThreadStatus,
46237
46597
  TokenCache,
46238
46598
  ToolLatticeManager,
46599
+ UnsafeUpstreamUrlError,
46239
46600
  VectorStoreLatticeManager,
46240
46601
  VectorStoreProviderManager,
46241
46602
  VolumeFilesystem,
@@ -46244,6 +46605,7 @@ export {
46244
46605
  agentLatticeManager,
46245
46606
  appendReconciliationWarning,
46246
46607
  appendUiFence,
46608
+ assertSafeUpstreamUrl,
46247
46609
  buildGrepResultsDict,
46248
46610
  buildInput,
46249
46611
  buildNamedVolumeName,
@@ -46426,6 +46788,7 @@ export {
46426
46788
  registerExistingToolWithTenant,
46427
46789
  registerLoggerLattice,
46428
46790
  registerModelLattice,
46791
+ registerModelLatticeForTenant,
46429
46792
  registerQueueLattice,
46430
46793
  registerSTTModelLattice,
46431
46794
  registerSandboxProviderType,