@objectstack/service-settings 6.5.1 → 6.7.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
@@ -141,20 +141,20 @@ var SettingsService = class {
141
141
  // Manifest registry
142
142
  // ---------------------------------------------------------------------
143
143
  /** Register (or replace) a manifest. Idempotent. */
144
- registerManifest(manifest3) {
144
+ registerManifest(manifest5) {
145
145
  const scopes = /* @__PURE__ */ new Map();
146
146
  const encryptedKeys = /* @__PURE__ */ new Set();
147
147
  const defaults = /* @__PURE__ */ new Map();
148
- const defaultScope = manifest3.scope ?? "tenant";
149
- for (const spec of manifest3.specifiers) {
148
+ const defaultScope = manifest5.scope ?? "tenant";
149
+ for (const spec of manifest5.specifiers) {
150
150
  if (!spec.key || LAYOUT_ONLY_TYPES.has(spec.type)) continue;
151
151
  scopes.set(spec.key, spec.scope ?? defaultScope);
152
152
  if (spec.encrypted || spec.type === "password") encryptedKeys.add(spec.key);
153
153
  if (typeof spec.default !== "undefined") defaults.set(spec.key, spec.default);
154
154
  }
155
- const prev = this.registry.get(manifest3.namespace);
155
+ const prev = this.registry.get(manifest5.namespace);
156
156
  const actions = prev?.actions ?? /* @__PURE__ */ new Map();
157
- this.registry.set(manifest3.namespace, { manifest: manifest3, scopes, encryptedKeys, defaults, actions });
157
+ this.registry.set(manifest5.namespace, { manifest: manifest5, scopes, encryptedKeys, defaults, actions });
158
158
  }
159
159
  /** Look up a manifest, or throw `UnknownNamespaceError`. */
160
160
  getManifest(namespace) {
@@ -1141,11 +1141,512 @@ var storageTestActionHandler = async ({ values }) => {
1141
1141
  return { ok: false, severity: "error", message: `Unknown adapter: ${adapter}` };
1142
1142
  };
1143
1143
 
1144
+ // src/manifests/ai.manifest.ts
1145
+ var manifest3 = {
1146
+ namespace: "ai",
1147
+ version: 1,
1148
+ label: "AI",
1149
+ icon: "Sparkles",
1150
+ description: "LLM provider, model, credentials, and embedder configuration used by the platform AI and knowledge services. Provider SDK packages (e.g. @ai-sdk/openai for chat, @objectstack/embedder-openai for embeddings) must be installed on the host for the chosen provider to be loadable at runtime.",
1151
+ scope: "global",
1152
+ readPermission: "setup.access",
1153
+ writePermission: "setup.write",
1154
+ category: "Infrastructure",
1155
+ order: 30,
1156
+ specifiers: [
1157
+ // ── Provider selection ────────────────────────────────────────
1158
+ {
1159
+ type: "group",
1160
+ id: "provider",
1161
+ label: "Provider",
1162
+ required: false,
1163
+ description: "Choose the LLM backend. Memory mode echoes input \u2014 useful for tests but never for production."
1164
+ },
1165
+ {
1166
+ type: "select",
1167
+ key: "provider",
1168
+ label: "Provider",
1169
+ required: true,
1170
+ default: "memory",
1171
+ options: [
1172
+ { value: "memory", label: "Memory (echo \u2014 testing only)" },
1173
+ { value: "gateway", label: "Vercel AI Gateway" },
1174
+ { value: "openai", label: "OpenAI" },
1175
+ { value: "anthropic", label: "Anthropic" },
1176
+ { value: "google", label: "Google Generative AI" }
1177
+ ]
1178
+ },
1179
+ // ── Vercel AI Gateway ─────────────────────────────────────────
1180
+ {
1181
+ type: "group",
1182
+ id: "gateway",
1183
+ label: "Vercel AI Gateway",
1184
+ required: false,
1185
+ visible: "${data.provider === 'gateway'}",
1186
+ description: "Multi-provider router. The model spec follows `provider/model`, e.g. `openai/gpt-4o`."
1187
+ },
1188
+ {
1189
+ type: "text",
1190
+ key: "gateway_model",
1191
+ label: "Gateway model",
1192
+ required: true,
1193
+ description: "Forwarded as AI_GATEWAY_MODEL. Example: openai/gpt-4o",
1194
+ visible: "${data.provider === 'gateway'}"
1195
+ },
1196
+ {
1197
+ type: "password",
1198
+ key: "gateway_api_key",
1199
+ label: "Gateway API key",
1200
+ required: false,
1201
+ encrypted: true,
1202
+ description: "Optional \u2014 required only if the gateway enforces auth.",
1203
+ visible: "${data.provider === 'gateway'}"
1204
+ },
1205
+ // ── OpenAI ───────────────────────────────────────────────────
1206
+ {
1207
+ type: "group",
1208
+ id: "openai",
1209
+ label: "OpenAI",
1210
+ required: false,
1211
+ visible: "${data.provider === 'openai'}"
1212
+ },
1213
+ {
1214
+ type: "password",
1215
+ key: "openai_api_key",
1216
+ label: "OpenAI API key",
1217
+ required: true,
1218
+ encrypted: true,
1219
+ description: "Forwarded as OPENAI_API_KEY. Stored encrypted at rest.",
1220
+ visible: "${data.provider === 'openai'}"
1221
+ },
1222
+ {
1223
+ type: "text",
1224
+ key: "openai_model",
1225
+ label: "Model",
1226
+ required: false,
1227
+ default: "gpt-4o",
1228
+ description: "Default model id. Per-agent overrides take precedence.",
1229
+ visible: "${data.provider === 'openai'}"
1230
+ },
1231
+ {
1232
+ type: "text",
1233
+ key: "openai_base_url",
1234
+ label: "Base URL",
1235
+ required: false,
1236
+ description: "Override for Azure OpenAI or self-hosted gateways. Leave blank for api.openai.com.",
1237
+ visible: "${data.provider === 'openai'}"
1238
+ },
1239
+ // ── Anthropic ────────────────────────────────────────────────
1240
+ {
1241
+ type: "group",
1242
+ id: "anthropic",
1243
+ label: "Anthropic",
1244
+ required: false,
1245
+ visible: "${data.provider === 'anthropic'}"
1246
+ },
1247
+ {
1248
+ type: "password",
1249
+ key: "anthropic_api_key",
1250
+ label: "Anthropic API key",
1251
+ required: true,
1252
+ encrypted: true,
1253
+ description: "Forwarded as ANTHROPIC_API_KEY. Stored encrypted at rest.",
1254
+ visible: "${data.provider === 'anthropic'}"
1255
+ },
1256
+ {
1257
+ type: "text",
1258
+ key: "anthropic_model",
1259
+ label: "Model",
1260
+ required: false,
1261
+ default: "claude-sonnet-4-20250514",
1262
+ visible: "${data.provider === 'anthropic'}"
1263
+ },
1264
+ // ── Google Generative AI ─────────────────────────────────────
1265
+ {
1266
+ type: "group",
1267
+ id: "google",
1268
+ label: "Google",
1269
+ required: false,
1270
+ visible: "${data.provider === 'google'}"
1271
+ },
1272
+ {
1273
+ type: "password",
1274
+ key: "google_api_key",
1275
+ label: "Google API key",
1276
+ required: true,
1277
+ encrypted: true,
1278
+ description: "Forwarded as GOOGLE_GENERATIVE_AI_API_KEY. Stored encrypted at rest.",
1279
+ visible: "${data.provider === 'google'}"
1280
+ },
1281
+ {
1282
+ type: "text",
1283
+ key: "google_model",
1284
+ label: "Model",
1285
+ required: false,
1286
+ default: "gemini-2.0-flash",
1287
+ visible: "${data.provider === 'google'}"
1288
+ },
1289
+ // ── Generation defaults ──────────────────────────────────────
1290
+ {
1291
+ type: "group",
1292
+ id: "defaults",
1293
+ label: "Generation defaults",
1294
+ required: false,
1295
+ description: "Applied when an agent or chat request does not specify its own value.",
1296
+ visible: "${data.provider !== 'memory'}"
1297
+ },
1298
+ {
1299
+ type: "slider",
1300
+ key: "temperature",
1301
+ label: "Temperature",
1302
+ required: false,
1303
+ default: 0.7,
1304
+ min: 0,
1305
+ max: 2,
1306
+ step: 0.1,
1307
+ description: "0 = deterministic, 2 = highly creative.",
1308
+ visible: "${data.provider !== 'memory'}"
1309
+ },
1310
+ {
1311
+ type: "number",
1312
+ key: "max_tokens",
1313
+ label: "Max output tokens",
1314
+ required: false,
1315
+ default: 4096,
1316
+ min: 1,
1317
+ max: 1048576,
1318
+ description: "Hard cap on tokens generated per response.",
1319
+ visible: "${data.provider !== 'memory'}"
1320
+ },
1321
+ {
1322
+ type: "number",
1323
+ key: "request_timeout_ms",
1324
+ label: "Request timeout (ms)",
1325
+ required: false,
1326
+ default: 6e4,
1327
+ min: 1e3,
1328
+ max: 6e5,
1329
+ visible: "${data.provider !== 'memory'}"
1330
+ },
1331
+ // ── Observability ────────────────────────────────────────────
1332
+ { type: "group", id: "observability", label: "Observability", required: false },
1333
+ {
1334
+ type: "toggle",
1335
+ key: "trace_enabled",
1336
+ label: "Record traces",
1337
+ required: false,
1338
+ default: true,
1339
+ description: "Persist prompt/response traces to sys_ai_trace for debugging and replay."
1340
+ },
1341
+ {
1342
+ type: "toggle",
1343
+ key: "log_prompts",
1344
+ label: "Log full prompts",
1345
+ required: false,
1346
+ default: false,
1347
+ description: "Include rendered prompts (not just metadata) in trace rows. \u26A0 May leak PII \u2014 disable in regulated environments."
1348
+ },
1349
+ // ── Probe ────────────────────────────────────────────────────
1350
+ {
1351
+ type: "action_button",
1352
+ id: "test",
1353
+ label: "Test connection",
1354
+ required: false,
1355
+ icon: "Plug",
1356
+ handler: { kind: "http", method: "POST", url: "/api/settings/ai/test" }
1357
+ },
1358
+ // ════════════════════════════════════════════════════════════════
1359
+ // Embedder — text → vector provider used by knowledge / RAG.
1360
+ // Decoupled from the chat provider above so an organisation can
1361
+ // mix-and-match (e.g. OpenAI for chat + 阿里通义 for embeddings).
1362
+ //
1363
+ // The preset list mirrors @objectstack/embedder-openai's
1364
+ // OPENAI_COMPATIBLE_PRESETS so a UI dropdown maps 1:1 to a
1365
+ // runtime baseUrl. The "none" choice is the explicit opt-out
1366
+ // for instances that disable knowledge / RAG entirely.
1367
+ // ════════════════════════════════════════════════════════════════
1368
+ {
1369
+ type: "group",
1370
+ id: "embedder",
1371
+ label: "Embedder",
1372
+ required: false,
1373
+ description: "Text \u2192 vector provider used by knowledge sources and RAG. Independent from the chat provider above \u2014 mix providers freely (e.g. OpenAI for chat + \u963F\u91CC\u901A\u4E49 for embeddings)."
1374
+ },
1375
+ {
1376
+ type: "select",
1377
+ key: "embedder_provider",
1378
+ label: "Provider",
1379
+ required: false,
1380
+ default: "none",
1381
+ options: [
1382
+ { value: "none", label: "Disabled (no embeddings)" },
1383
+ { value: "openai", label: "OpenAI" },
1384
+ { value: "azure", label: "Azure OpenAI" },
1385
+ { value: "dashscope", label: "\u963F\u91CC\u901A\u4E49 DashScope" },
1386
+ { value: "zhipu", label: "\u667A\u8C31 BigModel" },
1387
+ { value: "siliconflow", label: "\u7845\u57FA\u6D41\u52A8 SiliconFlow" },
1388
+ { value: "doubao", label: "\u706B\u5C71\u5F15\u64CE Doubao" },
1389
+ { value: "minimax", label: "MiniMax" },
1390
+ { value: "ollama", label: "Ollama (local)" },
1391
+ { value: "custom", label: "Custom (OpenAI-compatible)" }
1392
+ ]
1393
+ },
1394
+ {
1395
+ type: "password",
1396
+ key: "embedder_api_key",
1397
+ label: "Embedder API key",
1398
+ required: false,
1399
+ encrypted: true,
1400
+ description: "Bearer token sent as Authorization header. For Ollama any non-empty value works.",
1401
+ visible: "${data.embedder_provider && data.embedder_provider !== 'none'}"
1402
+ },
1403
+ {
1404
+ type: "text",
1405
+ key: "embedder_model",
1406
+ label: "Model",
1407
+ required: false,
1408
+ description: "Examples \u2014 OpenAI: text-embedding-3-small \xB7 \u963F\u91CC\u901A\u4E49: text-embedding-v3 \xB7 \u667A\u8C31: embedding-3 \xB7 \u7845\u57FA\u6D41\u52A8: BAAI/bge-m3 \xB7 Ollama: bge-m3",
1409
+ visible: "${data.embedder_provider && data.embedder_provider !== 'none'}"
1410
+ },
1411
+ {
1412
+ type: "text",
1413
+ key: "embedder_base_url",
1414
+ label: "Base URL",
1415
+ required: false,
1416
+ description: "Endpoint root (without /embeddings). Auto-filled from preset; override for proxies or self-hosted gateways.",
1417
+ visible: "${data.embedder_provider === 'custom' || data.embedder_provider === 'azure'}"
1418
+ },
1419
+ {
1420
+ type: "number",
1421
+ key: "embedder_dimensions",
1422
+ label: "Dimensions",
1423
+ required: false,
1424
+ min: 1,
1425
+ max: 8192,
1426
+ description: "Override output dimensionality (Matryoshka models only \u2014 OpenAI v3, \u667A\u8C31 embedding-3, BGE-m3 dense). Leave blank to use the model default.",
1427
+ visible: "${data.embedder_provider && data.embedder_provider !== 'none'}"
1428
+ },
1429
+ {
1430
+ type: "number",
1431
+ key: "embedder_batch_size",
1432
+ label: "Batch size",
1433
+ required: false,
1434
+ default: 64,
1435
+ min: 1,
1436
+ max: 2048,
1437
+ description: "Chunks per embed() call. Reduce if hitting provider rate / size limits.",
1438
+ visible: "${data.embedder_provider && data.embedder_provider !== 'none'}"
1439
+ },
1440
+ {
1441
+ type: "action_button",
1442
+ id: "test_embedder",
1443
+ label: "Test embedder",
1444
+ required: false,
1445
+ icon: "Plug",
1446
+ handler: { kind: "http", method: "POST", url: "/api/settings/ai/test_embedder" }
1447
+ }
1448
+ ]
1449
+ };
1450
+ var aiSettingsManifest = manifest3;
1451
+ var aiTestActionHandler = async ({ values, payload }) => {
1452
+ const overrides = payload && typeof payload === "object" && payload !== null && "values" in payload ? payload.values ?? {} : {};
1453
+ const merged = { ...values, ...overrides };
1454
+ const provider = String(merged.provider ?? "memory");
1455
+ values = merged;
1456
+ if (provider === "memory") {
1457
+ return {
1458
+ ok: true,
1459
+ severity: "warning",
1460
+ message: "Memory provider is an echo stub \u2014 no external call to validate. Switch to a real provider for production."
1461
+ };
1462
+ }
1463
+ if (provider === "gateway") {
1464
+ if (!values.gateway_model) {
1465
+ return { ok: false, severity: "error", message: "Gateway model is required (e.g. openai/gpt-4o)." };
1466
+ }
1467
+ return {
1468
+ ok: true,
1469
+ severity: "info",
1470
+ message: `Vercel AI Gateway configured (model=${values.gateway_model}). Mount @objectstack/service-ai to exercise live calls.`
1471
+ };
1472
+ }
1473
+ const keyField = `${provider}_api_key`;
1474
+ if (!values[keyField]) {
1475
+ return { ok: false, severity: "error", message: `${provider} API key is required.` };
1476
+ }
1477
+ const modelField = `${provider}_model`;
1478
+ const model = values[modelField] ?? "(default)";
1479
+ return {
1480
+ ok: true,
1481
+ severity: "info",
1482
+ message: `${provider} configured (model=${model}). Mount @objectstack/service-ai to exercise live calls.`
1483
+ };
1484
+ };
1485
+
1486
+ // src/manifests/knowledge.manifest.ts
1487
+ var manifest4 = {
1488
+ namespace: "knowledge",
1489
+ version: 1,
1490
+ label: "Knowledge",
1491
+ icon: "BookOpen",
1492
+ description: "Vector-store backend for RAG / knowledge sources. \u26A0 Switching adapter does NOT migrate existing indices \u2014 documents indexed under the previous adapter become unreachable until re-indexed. The embedder is configured separately under AI.",
1493
+ scope: "global",
1494
+ readPermission: "setup.access",
1495
+ writePermission: "setup.write",
1496
+ category: "Infrastructure",
1497
+ order: 35,
1498
+ specifiers: [
1499
+ // ── Adapter selection ─────────────────────────────────────────
1500
+ {
1501
+ type: "group",
1502
+ id: "adapter",
1503
+ label: "Backend",
1504
+ required: false,
1505
+ description: "Choose where document chunks and their vectors are stored."
1506
+ },
1507
+ {
1508
+ type: "select",
1509
+ key: "adapter",
1510
+ label: "Adapter",
1511
+ required: true,
1512
+ default: "memory",
1513
+ options: [
1514
+ { value: "memory", label: "In-memory (dev / test only)" },
1515
+ { value: "turso", label: "Turso / libSQL (cloud or local)" },
1516
+ { value: "ragflow", label: "RAGFlow (external)" }
1517
+ ]
1518
+ },
1519
+ // ── Turso / libSQL ────────────────────────────────────────────
1520
+ {
1521
+ type: "group",
1522
+ id: "turso",
1523
+ label: "Turso / libSQL",
1524
+ required: false,
1525
+ visible: "${data.adapter === 'turso'}",
1526
+ description: "Works against managed Turso (libsql://\u2026), local file (file:./knowledge.db), or in-memory (:memory:). For per-tenant cloud deployments, leave blank to reuse the tenant's primary libSQL connection."
1527
+ },
1528
+ {
1529
+ type: "text",
1530
+ key: "turso_url",
1531
+ label: "Connection URL",
1532
+ required: false,
1533
+ description: "Examples: libsql://your-tenant.turso.io \xB7 file:./.objectstack/knowledge.db \xB7 :memory:",
1534
+ visible: "${data.adapter === 'turso'}"
1535
+ },
1536
+ {
1537
+ type: "password",
1538
+ key: "turso_auth_token",
1539
+ label: "Auth token",
1540
+ required: false,
1541
+ encrypted: true,
1542
+ description: "Only required for managed Turso URLs. Leave blank for local file / :memory:.",
1543
+ visible: "${data.adapter === 'turso'}"
1544
+ },
1545
+ // ── RAGFlow ───────────────────────────────────────────────────
1546
+ {
1547
+ type: "group",
1548
+ id: "ragflow",
1549
+ label: "RAGFlow",
1550
+ required: false,
1551
+ visible: "${data.adapter === 'ragflow'}",
1552
+ description: "External RAGFlow deployment. See https://ragflow.io for self-host instructions."
1553
+ },
1554
+ {
1555
+ type: "text",
1556
+ key: "ragflow_base_url",
1557
+ label: "Base URL",
1558
+ required: true,
1559
+ description: "Example: http://localhost:9380",
1560
+ visible: "${data.adapter === 'ragflow'}"
1561
+ },
1562
+ {
1563
+ type: "password",
1564
+ key: "ragflow_api_key",
1565
+ label: "API key",
1566
+ required: true,
1567
+ encrypted: true,
1568
+ visible: "${data.adapter === 'ragflow'}"
1569
+ },
1570
+ {
1571
+ type: "text",
1572
+ key: "ragflow_default_dataset",
1573
+ label: "Default dataset id",
1574
+ required: false,
1575
+ description: "Used when a KnowledgeSource does not specify its own RAGFlow dataset.",
1576
+ visible: "${data.adapter === 'ragflow'}"
1577
+ },
1578
+ // ── Indexing defaults ─────────────────────────────────────────
1579
+ {
1580
+ type: "group",
1581
+ id: "indexing",
1582
+ label: "Indexing defaults",
1583
+ required: false,
1584
+ description: "Per-source values on KnowledgeSource.adapterConfig take precedence.",
1585
+ visible: "${data.adapter !== 'memory'}"
1586
+ },
1587
+ {
1588
+ type: "number",
1589
+ key: "chunk_target",
1590
+ label: "Target chunk size (chars)",
1591
+ required: false,
1592
+ default: 800,
1593
+ min: 64,
1594
+ max: 8192,
1595
+ description: "Soft cap on chunk size in characters before token-aware splitting kicks in.",
1596
+ visible: "${data.adapter !== 'memory'}"
1597
+ },
1598
+ {
1599
+ type: "number",
1600
+ key: "chunk_overlap",
1601
+ label: "Chunk overlap (chars)",
1602
+ required: false,
1603
+ default: 80,
1604
+ min: 0,
1605
+ max: 2048,
1606
+ description: "Characters retained from the previous chunk so context survives the boundary.",
1607
+ visible: "${data.adapter !== 'memory'}"
1608
+ },
1609
+ {
1610
+ type: "number",
1611
+ key: "over_fetch",
1612
+ label: "Over-fetch multiplier",
1613
+ required: false,
1614
+ default: 4,
1615
+ min: 1,
1616
+ max: 20,
1617
+ description: "Internal `topK * overFetch` candidates fetched so JS-side metadata filtering still has rows to return.",
1618
+ visible: "${data.adapter === 'turso'}"
1619
+ },
1620
+ // ── Permissions ───────────────────────────────────────────────
1621
+ { type: "group", id: "permissions", label: "Permissions", required: false },
1622
+ {
1623
+ type: "toggle",
1624
+ key: "enforce_rls",
1625
+ label: "Enforce RLS on search",
1626
+ required: false,
1627
+ default: true,
1628
+ description: "Re-check every hit against the caller's record-level permissions via IDataEngine. \u26A0 Disabling skips the platform's unique safeguard against vector-store data leakage \u2014 leave on in production."
1629
+ },
1630
+ // ── Probe ─────────────────────────────────────────────────────
1631
+ {
1632
+ type: "action_button",
1633
+ id: "test",
1634
+ label: "Test connection",
1635
+ required: false,
1636
+ icon: "Plug",
1637
+ handler: { kind: "http", method: "POST", url: "/api/settings/knowledge/test" }
1638
+ }
1639
+ ]
1640
+ };
1641
+ var knowledgeSettingsManifest = manifest4;
1642
+
1144
1643
  // src/manifests/index.ts
1145
1644
  var builtinSettingsManifests = [
1146
1645
  brandingSettingsManifest,
1147
1646
  mailSettingsManifest,
1148
1647
  storageSettingsManifest,
1648
+ aiSettingsManifest,
1649
+ knowledgeSettingsManifest,
1149
1650
  featureFlagsSettingsManifest
1150
1651
  ];
1151
1652
 
@@ -1271,6 +1772,196 @@ var en = {
1271
1772
  actions: {
1272
1773
  test: { label: "Test connection" }
1273
1774
  }
1775
+ },
1776
+ ai: {
1777
+ title: "AI & Embedder",
1778
+ description: "LLM provider, model, credentials, and embedder configuration used by the platform AI and knowledge services.",
1779
+ groups: {
1780
+ provider: {
1781
+ title: "Provider",
1782
+ description: "Choose the LLM backend. Memory mode echoes input \u2014 useful for tests but never for production."
1783
+ },
1784
+ gateway: {
1785
+ title: "Vercel AI Gateway",
1786
+ description: "Multi-provider router. The model spec follows `provider/model`, e.g. `openai/gpt-4o`."
1787
+ },
1788
+ openai: { title: "OpenAI" },
1789
+ anthropic: { title: "Anthropic" },
1790
+ google: { title: "Google" },
1791
+ defaults: {
1792
+ title: "Generation defaults",
1793
+ description: "Applied when an agent or chat request does not specify its own value."
1794
+ },
1795
+ observability: { title: "Observability" },
1796
+ embedder: {
1797
+ title: "Embedder",
1798
+ description: "Text \u2192 vector provider used by knowledge sources and RAG. Independent from the chat provider above."
1799
+ }
1800
+ },
1801
+ keys: {
1802
+ provider: {
1803
+ label: "Provider",
1804
+ options: {
1805
+ memory: "Memory (echo \u2014 testing only)",
1806
+ gateway: "Vercel AI Gateway",
1807
+ openai: "OpenAI",
1808
+ anthropic: "Anthropic",
1809
+ google: "Google Generative AI"
1810
+ }
1811
+ },
1812
+ gateway_model: {
1813
+ label: "Gateway model",
1814
+ help: "Forwarded as AI_GATEWAY_MODEL. Example: openai/gpt-4o"
1815
+ },
1816
+ gateway_api_key: {
1817
+ label: "Gateway API key",
1818
+ help: "Optional \u2014 required only if the gateway enforces auth."
1819
+ },
1820
+ openai_api_key: {
1821
+ label: "OpenAI API key",
1822
+ help: "Forwarded as OPENAI_API_KEY. Stored encrypted at rest."
1823
+ },
1824
+ openai_model: {
1825
+ label: "Model",
1826
+ help: "Default model id. Per-agent overrides take precedence."
1827
+ },
1828
+ openai_base_url: {
1829
+ label: "Base URL",
1830
+ help: "Override for Azure OpenAI or self-hosted gateways. Leave blank for api.openai.com."
1831
+ },
1832
+ anthropic_api_key: {
1833
+ label: "Anthropic API key",
1834
+ help: "Forwarded as ANTHROPIC_API_KEY. Stored encrypted at rest."
1835
+ },
1836
+ anthropic_model: { label: "Model" },
1837
+ google_api_key: {
1838
+ label: "Google API key",
1839
+ help: "Forwarded as GOOGLE_GENERATIVE_AI_API_KEY. Stored encrypted at rest."
1840
+ },
1841
+ google_model: { label: "Model" },
1842
+ temperature: {
1843
+ label: "Temperature",
1844
+ help: "0 = deterministic, 2 = highly creative."
1845
+ },
1846
+ max_tokens: {
1847
+ label: "Max output tokens",
1848
+ help: "Hard cap on tokens generated per response."
1849
+ },
1850
+ request_timeout_ms: { label: "Request timeout (ms)" },
1851
+ trace_enabled: {
1852
+ label: "Record traces",
1853
+ help: "Persist prompt/response traces to sys_ai_trace for debugging and replay."
1854
+ },
1855
+ log_prompts: {
1856
+ label: "Log full prompts",
1857
+ help: "Include rendered prompts (not just metadata) in trace rows. \u26A0 May leak PII \u2014 disable in regulated environments."
1858
+ },
1859
+ embedder_provider: {
1860
+ label: "Provider",
1861
+ options: {
1862
+ none: "Disabled (no embeddings)",
1863
+ openai: "OpenAI",
1864
+ azure: "Azure OpenAI",
1865
+ dashscope: "\u963F\u91CC\u901A\u4E49 DashScope",
1866
+ zhipu: "\u667A\u8C31 BigModel",
1867
+ siliconflow: "\u7845\u57FA\u6D41\u52A8 SiliconFlow",
1868
+ doubao: "\u706B\u5C71\u5F15\u64CE Doubao",
1869
+ minimax: "MiniMax",
1870
+ ollama: "Ollama (local)",
1871
+ custom: "Custom (OpenAI-compatible)"
1872
+ }
1873
+ },
1874
+ embedder_api_key: {
1875
+ label: "Embedder API key",
1876
+ help: "Bearer token sent as Authorization header. For Ollama any non-empty value works."
1877
+ },
1878
+ embedder_model: {
1879
+ label: "Model",
1880
+ help: "Examples \u2014 OpenAI: text-embedding-3-small \xB7 \u963F\u91CC\u901A\u4E49: text-embedding-v3 \xB7 \u667A\u8C31: embedding-3 \xB7 \u7845\u57FA\u6D41\u52A8: BAAI/bge-m3 \xB7 Ollama: bge-m3"
1881
+ },
1882
+ embedder_base_url: {
1883
+ label: "Base URL",
1884
+ help: "Endpoint root (without /embeddings). Auto-filled from preset; override for proxies or self-hosted gateways."
1885
+ },
1886
+ embedder_dimensions: {
1887
+ label: "Dimensions",
1888
+ help: "Override output dimensionality (Matryoshka models only). Leave blank to use the model default."
1889
+ },
1890
+ embedder_batch_size: {
1891
+ label: "Batch size",
1892
+ help: "Chunks per embed() call. Reduce if hitting provider rate / size limits."
1893
+ }
1894
+ },
1895
+ actions: {
1896
+ test: { label: "Test connection" },
1897
+ test_embedder: { label: "Test embedder" }
1898
+ }
1899
+ },
1900
+ knowledge: {
1901
+ title: "Knowledge",
1902
+ description: "Vector-store backend for RAG / knowledge sources. \u26A0 Switching adapter does NOT migrate existing indices.",
1903
+ groups: {
1904
+ adapter: {
1905
+ title: "Backend",
1906
+ description: "Choose where document chunks and their vectors are stored."
1907
+ },
1908
+ turso: {
1909
+ title: "Turso / libSQL",
1910
+ description: "Works against managed Turso, local file, or in-memory."
1911
+ },
1912
+ ragflow: {
1913
+ title: "RAGFlow",
1914
+ description: "External RAGFlow deployment. See https://ragflow.io for self-host instructions."
1915
+ },
1916
+ indexing: {
1917
+ title: "Indexing defaults",
1918
+ description: "Per-source values on KnowledgeSource.adapterConfig take precedence."
1919
+ },
1920
+ permissions: { title: "Permissions" }
1921
+ },
1922
+ keys: {
1923
+ adapter: {
1924
+ label: "Adapter",
1925
+ options: {
1926
+ memory: "In-memory (dev / test only)",
1927
+ turso: "Turso / libSQL (cloud or local)",
1928
+ ragflow: "RAGFlow (external)"
1929
+ }
1930
+ },
1931
+ turso_url: {
1932
+ label: "Connection URL",
1933
+ help: "Examples: libsql://your-tenant.turso.io \xB7 file:./.objectstack/knowledge.db \xB7 :memory:"
1934
+ },
1935
+ turso_auth_token: {
1936
+ label: "Auth token",
1937
+ help: "Only required for managed Turso URLs."
1938
+ },
1939
+ ragflow_base_url: { label: "Base URL", help: "Example: http://localhost:9380" },
1940
+ ragflow_api_key: { label: "API key" },
1941
+ ragflow_default_dataset: {
1942
+ label: "Default dataset id",
1943
+ help: "Used when a KnowledgeSource does not specify its own RAGFlow dataset."
1944
+ },
1945
+ chunk_target: {
1946
+ label: "Target chunk size (chars)",
1947
+ help: "Soft cap on chunk size before token-aware splitting kicks in."
1948
+ },
1949
+ chunk_overlap: {
1950
+ label: "Chunk overlap (chars)",
1951
+ help: "Characters retained from the previous chunk so context survives the boundary."
1952
+ },
1953
+ over_fetch: {
1954
+ label: "Over-fetch multiplier",
1955
+ help: "Internal topK \xD7 overFetch candidates fetched so JS-side metadata filtering still has rows."
1956
+ },
1957
+ enforce_rls: {
1958
+ label: "Enforce RLS on search",
1959
+ help: "Re-check every hit against the caller's record-level permissions. \u26A0 Disabling skips the platform's unique safeguard."
1960
+ }
1961
+ },
1962
+ actions: {
1963
+ test: { label: "Test connection" }
1964
+ }
1274
1965
  }
1275
1966
  }
1276
1967
  };
@@ -1397,6 +2088,103 @@ var zhCN = {
1397
2088
  actions: {
1398
2089
  test: { label: "\u6D4B\u8BD5\u8FDE\u63A5" }
1399
2090
  }
2091
+ },
2092
+ ai: {
2093
+ title: "AI \u4E0E Embedder",
2094
+ description: "\u5E73\u53F0 AI \u4E0E\u77E5\u8BC6\u5E93\u670D\u52A1\u4F7F\u7528\u7684 LLM \u63D0\u4F9B\u5546\u3001\u6A21\u578B\u3001\u51ED\u636E\u4E0E\u5411\u91CF\u5316\u914D\u7F6E\u3002",
2095
+ groups: {
2096
+ provider: { title: "\u63D0\u4F9B\u5546", description: "\u9009\u62E9 LLM \u540E\u7AEF\u3002Memory \u6A21\u5F0F\u4EC5\u539F\u6837\u56DE\u663E\u8F93\u5165,\u4EC5\u7528\u4E8E\u6D4B\u8BD5,\u4E25\u7981\u7528\u4E8E\u751F\u4EA7\u3002" },
2097
+ gateway: { title: "Vercel AI Gateway", description: "\u591A\u63D0\u4F9B\u5546\u8DEF\u7531\u5668\u3002\u6A21\u578B\u89C4\u683C\u9075\u5FAA `provider/model` \u683C\u5F0F,\u4F8B\u5982 `openai/gpt-4o`\u3002" },
2098
+ openai: { title: "OpenAI" },
2099
+ anthropic: { title: "Anthropic" },
2100
+ google: { title: "Google" },
2101
+ defaults: { title: "\u751F\u6210\u9ED8\u8BA4\u503C", description: "\u5F53 Agent \u6216\u804A\u5929\u8BF7\u6C42\u672A\u6307\u5B9A\u65F6\u4F7F\u7528\u3002" },
2102
+ observability: { title: "\u53EF\u89C2\u6D4B\u6027" },
2103
+ embedder: { title: "Embedder", description: "\u77E5\u8BC6\u5E93\u548C RAG \u4F7F\u7528\u7684\u6587\u672C\u2192\u5411\u91CF\u63D0\u4F9B\u5546,\u4E0E\u4E0A\u65B9\u804A\u5929\u63D0\u4F9B\u5546\u76F8\u4E92\u72EC\u7ACB\u3002" }
2104
+ },
2105
+ keys: {
2106
+ provider: {
2107
+ label: "\u63D0\u4F9B\u5546",
2108
+ options: {
2109
+ memory: "Memory(\u56DE\u663E \u2014 \u4EC5\u6D4B\u8BD5)",
2110
+ gateway: "Vercel AI Gateway",
2111
+ openai: "OpenAI",
2112
+ anthropic: "Anthropic",
2113
+ google: "Google Generative AI"
2114
+ }
2115
+ },
2116
+ gateway_model: { label: "Gateway \u6A21\u578B", help: "\u4F5C\u4E3A AI_GATEWAY_MODEL \u8F6C\u53D1\u3002\u793A\u4F8B:openai/gpt-4o" },
2117
+ gateway_api_key: { label: "Gateway API Key", help: "\u53EF\u9009 \u2014\u2014 \u4EC5\u5F53 Gateway \u5F3A\u5236\u9274\u6743\u65F6\u9700\u8981\u3002" },
2118
+ openai_api_key: { label: "OpenAI API Key", help: "\u4F5C\u4E3A OPENAI_API_KEY \u8F6C\u53D1,\u52A0\u5BC6\u5B58\u50A8\u3002" },
2119
+ openai_model: { label: "\u6A21\u578B", help: "\u9ED8\u8BA4\u6A21\u578B ID\u3002Agent \u7EA7\u8986\u76D6\u4F18\u5148\u751F\u6548\u3002" },
2120
+ openai_base_url: { label: "Base URL", help: "\u7528\u4E8E Azure OpenAI \u6216\u81EA\u5EFA\u7F51\u5173\u3002\u7559\u7A7A\u8D70 api.openai.com\u3002" },
2121
+ anthropic_api_key: { label: "Anthropic API Key", help: "\u4F5C\u4E3A ANTHROPIC_API_KEY \u8F6C\u53D1,\u52A0\u5BC6\u5B58\u50A8\u3002" },
2122
+ anthropic_model: { label: "\u6A21\u578B" },
2123
+ google_api_key: { label: "Google API Key", help: "\u4F5C\u4E3A GOOGLE_GENERATIVE_AI_API_KEY \u8F6C\u53D1,\u52A0\u5BC6\u5B58\u50A8\u3002" },
2124
+ google_model: { label: "\u6A21\u578B" },
2125
+ temperature: { label: "\u6E29\u5EA6", help: "0 = \u786E\u5B9A\u6027,2 = \u9AD8\u5EA6\u53D1\u6563\u3002" },
2126
+ max_tokens: { label: "\u6700\u5927\u8F93\u51FA tokens", help: "\u5355\u6B21\u54CD\u5E94\u751F\u6210\u7684\u786C\u4E0A\u9650\u3002" },
2127
+ request_timeout_ms: { label: "\u8BF7\u6C42\u8D85\u65F6(\u6BEB\u79D2)" },
2128
+ trace_enabled: { label: "\u8BB0\u5F55 Trace", help: "\u5C06 prompt/response \u843D\u5165 sys_ai_trace,\u4FBF\u4E8E\u8C03\u8BD5\u4E0E\u56DE\u653E\u3002" },
2129
+ log_prompts: { label: "\u8BB0\u5F55\u5B8C\u6574 Prompt", help: "\u5728 trace \u884C\u4E2D\u5305\u542B\u5B8C\u6574 prompt \u800C\u975E\u4EC5\u5143\u6570\u636E\u3002\u26A0 \u53EF\u80FD\u6CC4\u9732 PII,\u5408\u89C4\u573A\u666F\u8BF7\u5173\u95ED\u3002" },
2130
+ embedder_provider: {
2131
+ label: "\u63D0\u4F9B\u5546",
2132
+ options: {
2133
+ none: "\u7981\u7528(\u4E0D\u505A\u5411\u91CF\u5316)",
2134
+ openai: "OpenAI",
2135
+ azure: "Azure OpenAI",
2136
+ dashscope: "\u963F\u91CC\u901A\u4E49 DashScope",
2137
+ zhipu: "\u667A\u8C31 BigModel",
2138
+ siliconflow: "\u7845\u57FA\u6D41\u52A8 SiliconFlow",
2139
+ doubao: "\u706B\u5C71\u5F15\u64CE Doubao",
2140
+ minimax: "MiniMax",
2141
+ ollama: "Ollama(\u672C\u5730)",
2142
+ custom: "\u81EA\u5B9A\u4E49(OpenAI \u517C\u5BB9)"
2143
+ }
2144
+ },
2145
+ embedder_api_key: { label: "Embedder API Key", help: "\u4F5C\u4E3A Authorization Bearer \u53D1\u9001\u3002Ollama \u4EFB\u610F\u975E\u7A7A\u503C\u5747\u53EF\u3002" },
2146
+ embedder_model: { label: "\u6A21\u578B", help: "\u793A\u4F8B \u2014 OpenAI: text-embedding-3-small \xB7 \u963F\u91CC\u901A\u4E49: text-embedding-v3 \xB7 \u667A\u8C31: embedding-3 \xB7 \u7845\u57FA\u6D41\u52A8: BAAI/bge-m3 \xB7 Ollama: bge-m3" },
2147
+ embedder_base_url: { label: "Base URL", help: "\u7AEF\u70B9\u6839\u8DEF\u5F84(\u4E0D\u542B /embeddings)\u3002\u9884\u8BBE\u4F1A\u81EA\u52A8\u586B\u5145,\u53EF\u8986\u76D6\u4E3A\u4EE3\u7406\u6216\u81EA\u5EFA\u7F51\u5173\u3002" },
2148
+ embedder_dimensions: { label: "\u7EF4\u5EA6", help: "\u8986\u76D6\u8F93\u51FA\u7EF4\u5EA6(\u4EC5 Matryoshka \u6A21\u578B\u652F\u6301)\u3002\u7559\u7A7A\u4F7F\u7528\u6A21\u578B\u9ED8\u8BA4\u503C\u3002" },
2149
+ embedder_batch_size: { label: "\u6279\u91CF\u5927\u5C0F", help: "\u5355\u6B21 embed() \u8C03\u7528\u7684 chunk \u6570\u3002\u547D\u4E2D\u901F\u7387/\u5927\u5C0F\u9650\u5236\u65F6\u8C03\u5C0F\u3002" }
2150
+ },
2151
+ actions: {
2152
+ test: { label: "\u6D4B\u8BD5\u8FDE\u63A5" },
2153
+ test_embedder: { label: "\u6D4B\u8BD5 Embedder" }
2154
+ }
2155
+ },
2156
+ knowledge: {
2157
+ title: "\u77E5\u8BC6\u5E93",
2158
+ description: "RAG / \u77E5\u8BC6\u6E90\u4F7F\u7528\u7684\u5411\u91CF\u5B58\u50A8\u540E\u7AEF\u3002\u26A0 \u5207\u6362\u9002\u914D\u5668\u4E0D\u4F1A\u8FC1\u79FB\u5DF2\u6709\u7D22\u5F15\u3002",
2159
+ groups: {
2160
+ adapter: { title: "\u540E\u7AEF", description: "\u9009\u62E9\u6587\u6863\u5206\u5757\u53CA\u5176\u5411\u91CF\u7684\u5B58\u50A8\u4F4D\u7F6E\u3002" },
2161
+ turso: { title: "Turso / libSQL", description: "\u652F\u6301\u6258\u7BA1 Turso\u3001\u672C\u5730\u6587\u4EF6\u3001\u5185\u5B58\u4E09\u79CD\u6A21\u5F0F\u3002" },
2162
+ ragflow: { title: "RAGFlow", description: "\u5916\u90E8 RAGFlow \u90E8\u7F72\u3002\u81EA\u90E8\u7F72\u6587\u6863\u89C1 https://ragflow.io \u3002" },
2163
+ indexing: { title: "\u7D22\u5F15\u9ED8\u8BA4\u503C", description: "KnowledgeSource.adapterConfig \u4E0A\u7684\u9010\u6E90\u8986\u76D6\u4F18\u5148\u751F\u6548\u3002" },
2164
+ permissions: { title: "\u6743\u9650" }
2165
+ },
2166
+ keys: {
2167
+ adapter: {
2168
+ label: "\u9002\u914D\u5668",
2169
+ options: {
2170
+ memory: "\u5185\u5B58(\u4EC5\u5F00\u53D1/\u6D4B\u8BD5)",
2171
+ turso: "Turso / libSQL(\u4E91\u7AEF\u6216\u672C\u5730)",
2172
+ ragflow: "RAGFlow(\u5916\u90E8)"
2173
+ }
2174
+ },
2175
+ turso_url: { label: "\u8FDE\u63A5 URL", help: "\u793A\u4F8B:libsql://your-tenant.turso.io \xB7 file:./.objectstack/knowledge.db \xB7 :memory:" },
2176
+ turso_auth_token: { label: "Auth Token", help: "\u4EC5\u6258\u7BA1 Turso URL \u9700\u8981\u3002" },
2177
+ ragflow_base_url: { label: "Base URL", help: "\u793A\u4F8B:http://localhost:9380" },
2178
+ ragflow_api_key: { label: "API Key" },
2179
+ ragflow_default_dataset: { label: "\u9ED8\u8BA4 Dataset ID", help: "KnowledgeSource \u672A\u6307\u5B9A\u65F6\u4F7F\u7528\u3002" },
2180
+ chunk_target: { label: "\u76EE\u6807 chunk \u5927\u5C0F(\u5B57\u7B26)", help: "\u5728\u6309 token \u5207\u5206\u4E4B\u524D\u7684\u8F6F\u4E0A\u9650\u3002" },
2181
+ chunk_overlap: { label: "Chunk \u91CD\u53E0(\u5B57\u7B26)", help: "\u4FDD\u7559\u4E0A\u4E00\u4E2A chunk \u672B\u5C3E\u7684\u5B57\u7B26,\u4EE5\u4FDD\u8BC1\u8DE8\u754C\u4E0A\u4E0B\u6587\u3002" },
2182
+ over_fetch: { label: "\u8FC7\u53D6\u500D\u6570", help: "\u5185\u90E8\u6309 topK \xD7 overFetch \u62C9\u53D6\u5019\u9009,\u4EE5\u4FBF JS \u7AEF\u5143\u6570\u636E\u8FC7\u6EE4\u4ECD\u6709\u884C\u53EF\u8FD4\u56DE\u3002" },
2183
+ enforce_rls: { label: "\u641C\u7D22\u65F6\u5F3A\u5236 RLS", help: "\u5BF9\u6BCF\u6761\u547D\u4E2D\u901A\u8FC7 IDataEngine \u518D\u6B21\u6821\u9A8C\u8C03\u7528\u65B9\u7684\u884C\u7EA7\u6743\u9650\u3002\u26A0 \u5173\u95ED\u5C06\u8DF3\u8FC7\u5E73\u53F0\u5BF9\u5411\u91CF\u5B58\u50A8\u6570\u636E\u5916\u6CC4\u7684\u72EC\u6709\u9632\u62A4\u3002" }
2184
+ },
2185
+ actions: {
2186
+ test: { label: "\u6D4B\u8BD5\u8FDE\u63A5" }
2187
+ }
1400
2188
  }
1401
2189
  }
1402
2190
  };
@@ -1546,7 +2334,8 @@ var SettingsServicePlugin = class {
1546
2334
  manifests: opts.manifests ?? builtinSettingsManifests,
1547
2335
  actionHandlers: opts.actionHandlers ?? {
1548
2336
  mail: { test: mailTestActionHandler },
1549
- storage: { test: storageTestActionHandler }
2337
+ storage: { test: storageTestActionHandler },
2338
+ ai: { test: aiTestActionHandler }
1550
2339
  }
1551
2340
  };
1552
2341
  }