@carlonicora/nestjs-neo4jsonapi 2.7.2 → 2.9.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.
Files changed (106) hide show
  1. package/dist/agents/graph.creator/services/graph.creator.service.d.ts +82 -0
  2. package/dist/agents/graph.creator/services/graph.creator.service.d.ts.map +1 -1
  3. package/dist/agents/graph.creator/services/graph.creator.service.js +43 -10
  4. package/dist/agents/graph.creator/services/graph.creator.service.js.map +1 -1
  5. package/dist/agents/index.d.ts +1 -1
  6. package/dist/agents/index.d.ts.map +1 -1
  7. package/dist/agents/index.js +7 -1
  8. package/dist/agents/index.js.map +1 -1
  9. package/dist/config/base.config.d.ts.map +1 -1
  10. package/dist/config/base.config.js +4 -0
  11. package/dist/config/base.config.js.map +1 -1
  12. package/dist/config/interfaces/config.ai.interface.d.ts +5 -0
  13. package/dist/config/interfaces/config.ai.interface.d.ts.map +1 -1
  14. package/dist/config/interfaces/config.prompts.interface.d.ts +15 -0
  15. package/dist/config/interfaces/config.prompts.interface.d.ts.map +1 -1
  16. package/dist/core/llm/index.d.ts +2 -0
  17. package/dist/core/llm/index.d.ts.map +1 -1
  18. package/dist/core/llm/index.js +2 -0
  19. package/dist/core/llm/index.js.map +1 -1
  20. package/dist/core/llm/interfaces/ai-candidate.interface.d.ts +32 -0
  21. package/dist/core/llm/interfaces/ai-candidate.interface.d.ts.map +1 -0
  22. package/dist/core/llm/interfaces/ai-candidate.interface.js +15 -0
  23. package/dist/core/llm/interfaces/ai-candidate.interface.js.map +1 -0
  24. package/dist/core/llm/llm.module.d.ts.map +1 -1
  25. package/dist/core/llm/llm.module.js +6 -1
  26. package/dist/core/llm/llm.module.js.map +1 -1
  27. package/dist/core/llm/services/ai-connection-resolver.service.d.ts +107 -0
  28. package/dist/core/llm/services/ai-connection-resolver.service.d.ts.map +1 -0
  29. package/dist/core/llm/services/ai-connection-resolver.service.js +329 -0
  30. package/dist/core/llm/services/ai-connection-resolver.service.js.map +1 -0
  31. package/dist/core/llm/services/embedder.service.d.ts +21 -0
  32. package/dist/core/llm/services/embedder.service.d.ts.map +1 -1
  33. package/dist/core/llm/services/embedder.service.js +52 -3
  34. package/dist/core/llm/services/embedder.service.js.map +1 -1
  35. package/dist/core/llm/services/llm.service.d.ts +65 -18
  36. package/dist/core/llm/services/llm.service.d.ts.map +1 -1
  37. package/dist/core/llm/services/llm.service.js +214 -70
  38. package/dist/core/llm/services/llm.service.js.map +1 -1
  39. package/dist/core/llm/services/model.service.d.ts +79 -2
  40. package/dist/core/llm/services/model.service.d.ts.map +1 -1
  41. package/dist/core/llm/services/model.service.js +289 -31
  42. package/dist/core/llm/services/model.service.js.map +1 -1
  43. package/dist/core/llm/services/vision.llm.service.d.ts +25 -1
  44. package/dist/core/llm/services/vision.llm.service.d.ts.map +1 -1
  45. package/dist/core/llm/services/vision.llm.service.js +74 -33
  46. package/dist/core/llm/services/vision.llm.service.js.map +1 -1
  47. package/dist/foundations/ai-connection/ai-connection.module.d.ts +5 -0
  48. package/dist/foundations/ai-connection/ai-connection.module.d.ts.map +1 -0
  49. package/dist/foundations/ai-connection/ai-connection.module.js +37 -0
  50. package/dist/foundations/ai-connection/ai-connection.module.js.map +1 -0
  51. package/dist/foundations/ai-connection/controllers/ai-connection.controller.d.ts +32 -0
  52. package/dist/foundations/ai-connection/controllers/ai-connection.controller.d.ts.map +1 -0
  53. package/dist/foundations/ai-connection/controllers/ai-connection.controller.js +162 -0
  54. package/dist/foundations/ai-connection/controllers/ai-connection.controller.js.map +1 -0
  55. package/dist/foundations/ai-connection/dtos/ai-connection.post.dto.d.ts +41 -0
  56. package/dist/foundations/ai-connection/dtos/ai-connection.post.dto.d.ts.map +1 -0
  57. package/dist/foundations/ai-connection/dtos/ai-connection.post.dto.js +189 -0
  58. package/dist/foundations/ai-connection/dtos/ai-connection.post.dto.js.map +1 -0
  59. package/dist/foundations/ai-connection/dtos/ai-connection.put.dto.d.ts +42 -0
  60. package/dist/foundations/ai-connection/dtos/ai-connection.put.dto.d.ts.map +1 -0
  61. package/dist/foundations/ai-connection/dtos/ai-connection.put.dto.js +179 -0
  62. package/dist/foundations/ai-connection/dtos/ai-connection.put.dto.js.map +1 -0
  63. package/dist/foundations/ai-connection/dtos/ai-connection.reorder.dto.d.ts +8 -0
  64. package/dist/foundations/ai-connection/dtos/ai-connection.reorder.dto.d.ts.map +1 -0
  65. package/dist/foundations/ai-connection/dtos/ai-connection.reorder.dto.js +38 -0
  66. package/dist/foundations/ai-connection/dtos/ai-connection.reorder.dto.js.map +1 -0
  67. package/dist/foundations/ai-connection/entities/ai-connection.d.ts +57 -0
  68. package/dist/foundations/ai-connection/entities/ai-connection.d.ts.map +1 -0
  69. package/dist/foundations/ai-connection/entities/ai-connection.js +73 -0
  70. package/dist/foundations/ai-connection/entities/ai-connection.js.map +1 -0
  71. package/dist/foundations/ai-connection/entities/ai-connection.meta.d.ts +3 -0
  72. package/dist/foundations/ai-connection/entities/ai-connection.meta.d.ts.map +1 -0
  73. package/dist/foundations/ai-connection/entities/ai-connection.meta.js +10 -0
  74. package/dist/foundations/ai-connection/entities/ai-connection.meta.js.map +1 -0
  75. package/dist/foundations/ai-connection/index.d.ts +12 -0
  76. package/dist/foundations/ai-connection/index.d.ts.map +1 -0
  77. package/dist/foundations/ai-connection/index.js +35 -0
  78. package/dist/foundations/ai-connection/index.js.map +1 -0
  79. package/dist/foundations/ai-connection/registry/ai-provider.registry.d.ts +54 -0
  80. package/dist/foundations/ai-connection/registry/ai-provider.registry.d.ts.map +1 -0
  81. package/dist/foundations/ai-connection/registry/ai-provider.registry.js +255 -0
  82. package/dist/foundations/ai-connection/registry/ai-provider.registry.js.map +1 -0
  83. package/dist/foundations/ai-connection/repositories/ai-connection.repository.d.ts +52 -0
  84. package/dist/foundations/ai-connection/repositories/ai-connection.repository.d.ts.map +1 -0
  85. package/dist/foundations/ai-connection/repositories/ai-connection.repository.js +85 -0
  86. package/dist/foundations/ai-connection/repositories/ai-connection.repository.js.map +1 -0
  87. package/dist/foundations/ai-connection/services/ai-connection-encryption.service.d.ts +52 -0
  88. package/dist/foundations/ai-connection/services/ai-connection-encryption.service.d.ts.map +1 -0
  89. package/dist/foundations/ai-connection/services/ai-connection-encryption.service.js +148 -0
  90. package/dist/foundations/ai-connection/services/ai-connection-encryption.service.js.map +1 -0
  91. package/dist/foundations/ai-connection/services/ai-connection.service.d.ts +79 -0
  92. package/dist/foundations/ai-connection/services/ai-connection.service.d.ts.map +1 -0
  93. package/dist/foundations/ai-connection/services/ai-connection.service.js +184 -0
  94. package/dist/foundations/ai-connection/services/ai-connection.service.js.map +1 -0
  95. package/dist/foundations/foundations.modules.d.ts.map +1 -1
  96. package/dist/foundations/foundations.modules.js +2 -0
  97. package/dist/foundations/foundations.modules.js.map +1 -1
  98. package/dist/foundations/index.d.ts +1 -0
  99. package/dist/foundations/index.d.ts.map +1 -1
  100. package/dist/foundations/index.js +1 -0
  101. package/dist/foundations/index.js.map +1 -1
  102. package/dist/foundations/tokenusage/services/tokenusage.service.d.ts +29 -0
  103. package/dist/foundations/tokenusage/services/tokenusage.service.d.ts.map +1 -1
  104. package/dist/foundations/tokenusage/services/tokenusage.service.js +15 -3
  105. package/dist/foundations/tokenusage/services/tokenusage.service.js.map +1 -1
  106. package/package.json +1 -1
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AI_CONNECTIONS_CHANGED_EVENT = exports.AI_CONNECTION_TYPES = void 0;
4
+ exports.AI_CONNECTION_TYPES = [
5
+ "ai",
6
+ "aiLite",
7
+ "aiLarge",
8
+ "vision",
9
+ "audio",
10
+ "embedder",
11
+ "transcriber",
12
+ "documentAi",
13
+ ];
14
+ exports.AI_CONNECTIONS_CHANGED_EVENT = "ai-connections.changed";
15
+ //# sourceMappingURL=ai-candidate.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-candidate.interface.js","sourceRoot":"","sources":["../../../../src/core/llm/interfaces/ai-candidate.interface.ts"],"names":[],"mappings":";;;AAAa,QAAA,mBAAmB,GAAG;IACjC,IAAI;IACJ,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,OAAO;IACP,UAAU;IACV,aAAa;IACb,YAAY;CACJ,CAAC;AAGE,QAAA,4BAA4B,GAAG,wBAAwB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"llm.module.d.ts","sourceRoot":"","sources":["../../../src/core/llm/llm.module.ts"],"names":[],"mappings":"AAyBA;;;;;;;;;;;;;GAaG;AACH,qBAQa,SAAS;CAAG"}
1
+ {"version":3,"file":"llm.module.d.ts","sourceRoot":"","sources":["../../../src/core/llm/llm.module.ts"],"names":[],"mappings":"AA4BA;;;;;;;;;;;;;GAaG;AACH,qBAUa,SAAS;CAAG"}
@@ -9,7 +9,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.LLMModule = void 0;
10
10
  const common_1 = require("@nestjs/common");
11
11
  const redis_module_1 = require("../redis/redis.module");
12
+ const ai_connection_module_1 = require("../../foundations/ai-connection/ai-connection.module");
12
13
  const tokenusage_module_1 = require("../../foundations/tokenusage/tokenusage.module");
14
+ const ai_connection_resolver_service_1 = require("./services/ai-connection-resolver.service");
13
15
  const audio_llm_service_1 = require("./services/audio.llm.service");
14
16
  const embedder_service_1 = require("./services/embedder.service");
15
17
  const embedder_token_bucket_service_1 = require("./services/embedder-token-bucket.service");
@@ -22,6 +24,7 @@ const vision_llm_service_1 = require("./services/vision.llm.service");
22
24
  const LLM_SERVICES = [
23
25
  llm_service_1.LLMService,
24
26
  model_service_1.ModelService,
27
+ ai_connection_resolver_service_1.AiConnectionResolverService,
25
28
  embedder_service_1.EmbedderService,
26
29
  embedder_token_bucket_service_1.EmbedderTokenBucketService,
27
30
  vision_llm_service_1.VisionLLMService,
@@ -52,7 +55,9 @@ exports.LLMModule = LLMModule = __decorate([
52
55
  (0, common_1.Module)({
53
56
  // RedisModule provides RedisClientStorageService for EmbedderTokenBucketService's
54
57
  // distributed token bucket. RedisModule is NOT @Global, so it must be imported here.
55
- imports: [tokenusage_module_1.TokenUsageModule, redis_module_1.RedisModule],
58
+ // AiConnectionModule provides the repository + encryption service the
59
+ // AiConnectionResolverService reads its snapshot through.
60
+ imports: [ai_connection_module_1.AiConnectionModule, tokenusage_module_1.TokenUsageModule, redis_module_1.RedisModule],
56
61
  providers: LLM_SERVICES,
57
62
  exports: LLM_SERVICES,
58
63
  })
@@ -1 +1 @@
1
- {"version":3,"file":"llm.module.js","sourceRoot":"","sources":["../../../src/core/llm/llm.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAgD;AAChD,wDAAoD;AACpD,sFAAkF;AAClF,oEAA+D;AAC/D,kEAA8D;AAC9D,4FAAsF;AACtF,oEAA+D;AAC/D,wEAAmE;AACnE,gFAAmE;AACnE,wDAAoD;AACpD,4DAAwD;AACxD,sEAAiE;AAEjE,MAAM,YAAY,GAAG;IACnB,wBAAU;IACV,4BAAY;IACZ,kCAAe;IACf,0DAA0B;IAC1B,qCAAgB;IAChB,uCAAiB;IACjB,mCAAe;IACf,uCAAa;IACb,mCAAe;CAChB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AASI,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,8BAAS;oBAAT,SAAS;IARrB,IAAA,eAAM,GAAE;IACR,IAAA,eAAM,EAAC;QACN,kFAAkF;QAClF,qFAAqF;QACrF,OAAO,EAAE,CAAC,oCAAgB,EAAE,0BAAW,CAAC;QACxC,SAAS,EAAE,YAAY;QACvB,OAAO,EAAE,YAAY;KACtB,CAAC;GACW,SAAS,CAAG"}
1
+ {"version":3,"file":"llm.module.js","sourceRoot":"","sources":["../../../src/core/llm/llm.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAgD;AAChD,wDAAoD;AACpD,+FAA0F;AAC1F,sFAAkF;AAClF,8FAAwF;AACxF,oEAA+D;AAC/D,kEAA8D;AAC9D,4FAAsF;AACtF,oEAA+D;AAC/D,wEAAmE;AACnE,gFAAmE;AACnE,wDAAoD;AACpD,4DAAwD;AACxD,sEAAiE;AAEjE,MAAM,YAAY,GAAG;IACnB,wBAAU;IACV,4BAAY;IACZ,4DAA2B;IAC3B,kCAAe;IACf,0DAA0B;IAC1B,qCAAgB;IAChB,uCAAiB;IACjB,mCAAe;IACf,uCAAa;IACb,mCAAe;CAChB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AAWI,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,8BAAS;oBAAT,SAAS;IAVrB,IAAA,eAAM,GAAE;IACR,IAAA,eAAM,EAAC;QACN,kFAAkF;QAClF,qFAAqF;QACrF,sEAAsE;QACtE,0DAA0D;QAC1D,OAAO,EAAE,CAAC,yCAAkB,EAAE,oCAAgB,EAAE,0BAAW,CAAC;QAC5D,SAAS,EAAE,YAAY;QACvB,OAAO,EAAE,YAAY;KACtB,CAAC;GACW,SAAS,CAAG"}
@@ -0,0 +1,107 @@
1
+ import { OnModuleInit } from "@nestjs/common";
2
+ import { ConfigService } from "@nestjs/config";
3
+ import { ClsService } from "nestjs-cls";
4
+ import { BaseConfigInterface } from "../../../config/interfaces";
5
+ import type { AiConnection } from "../../../foundations/ai-connection/entities/ai-connection";
6
+ import { AiConnectionRepository } from "../../../foundations/ai-connection/repositories/ai-connection.repository";
7
+ import { AiConnectionEncryptionService } from "../../../foundations/ai-connection/services/ai-connection-encryption.service";
8
+ import { AppLoggingService } from "../../logging/services/logging.service";
9
+ import { AiConnectionType, ResolvedAiCandidate } from "../interfaces/ai-candidate.interface";
10
+ /**
11
+ * Normalises one `AiConnection` node into the candidate shape `buildChatModel`
12
+ * (and its embedder/transcriber siblings) already consume.
13
+ *
14
+ * Pure on purpose: the decryptor is injected so the caller decides what a
15
+ * decryption failure means. It THROWS when a stored secret cannot be decrypted —
16
+ * {@link AiConnectionResolverService.refreshNow} catches that and skips the
17
+ * connection, so a single unreadable secret never takes the chain down
18
+ * (spec § 5 "Error handling").
19
+ */
20
+ export declare function toCandidate(connection: AiConnection, decrypt: (value: string) => string): ResolvedAiCandidate;
21
+ /**
22
+ * Resolves the ordered fallback chain for an AI connection type.
23
+ *
24
+ * Holds an in-memory snapshot of every enabled `AiConnection` node (decrypted,
25
+ * grouped into chains by `(connectionType, companyId | null)`, ordered by
26
+ * `position`), refreshed at boot, every 60s, and immediately on any admin write
27
+ * in this process (`AI_CONNECTIONS_CHANGED_EVENT`).
28
+ *
29
+ * The snapshot exists because `ModelService.getLLM()` is synchronous and called
30
+ * directly by several library services: resolution must stay a pure sync lookup
31
+ * with no signature ripple (spec § 2).
32
+ *
33
+ * Failure ALWAYS degrades toward today's `.env` behaviour, never toward "no AI"
34
+ * (spec § 5): an empty table, a failed refresh, an undecryptable secret or an
35
+ * unknown connection type all leave the `.env` candidate in place, and nothing
36
+ * on the `resolve()` hot path throws.
37
+ */
38
+ export declare class AiConnectionResolverService implements OnModuleInit {
39
+ private readonly repository;
40
+ private readonly encryption;
41
+ private readonly cls;
42
+ private readonly configService;
43
+ private readonly logger?;
44
+ /** `${connectionType}|${companyId ?? "global"}` → ordered candidates. */
45
+ private snapshot;
46
+ /** connectionId → epoch ms before which the candidate is skipped. */
47
+ private readonly cooldownUntil;
48
+ private refreshTimer?;
49
+ constructor(repository: AiConnectionRepository, encryption: AiConnectionEncryptionService, cls: ClsService, configService: ConfigService<BaseConfigInterface>, logger?: AppLoggingService);
50
+ /**
51
+ * Loads the first snapshot and starts the periodic refresh. Neither is
52
+ * awaited and neither can reject: boot must never depend on the database
53
+ * holding AI connection rows (spec § 5).
54
+ */
55
+ onModuleInit(): void;
56
+ /**
57
+ * The ordered candidates to try for `type`, best first.
58
+ *
59
+ * Per-company chain when the request's company has one, otherwise the global
60
+ * chain; the `.env` block is ALWAYS appended last. Candidates inside their
61
+ * cooldown window are dropped — unless that empties the list, in which case
62
+ * the full chain is returned anyway (fail-open, spec § 2).
63
+ *
64
+ * Never throws.
65
+ */
66
+ resolve(type: AiConnectionType): ResolvedAiCandidate[];
67
+ /**
68
+ * Parks a connection for `ai.connectionCooldownMinutes` after a transient
69
+ * failure (429 / 5xx / network). The `.env` candidate is tracked under
70
+ * `env:<type>` like any other, so a dead env fallback cannot block recovery.
71
+ */
72
+ markFailure(connectionId: string): void;
73
+ /**
74
+ * Rebuilds the snapshot from the database. Also the handler for admin writes
75
+ * in this process; other processes catch up at the next scheduled refresh.
76
+ *
77
+ * A read failure keeps the PREVIOUS snapshot — a database blip must not wipe
78
+ * working configuration (spec § 5).
79
+ */
80
+ refreshNow(): Promise<void>;
81
+ /**
82
+ * Reads every connection inside a CLS context: the refresh runs outside any
83
+ * HTTP request, and the query plumbing reads CLS (`companyId`) unconditionally.
84
+ */
85
+ private readConnections;
86
+ /**
87
+ * The company of the current request, or undefined when there is none.
88
+ *
89
+ * Optional-call plus a silent catch: the resolver is also used by workers and
90
+ * boot-time code with no CLS context at all, which is an ordinary state and
91
+ * must not log or throw — it simply means "use the global chain".
92
+ */
93
+ private currentCompanyId;
94
+ private chainKey;
95
+ private get aiConfig();
96
+ private get cooldownMinutes();
97
+ /**
98
+ * The `.env` block for a type, mapped field-for-field onto a candidate. This
99
+ * is the final link of every chain and the whole behaviour when the table is
100
+ * empty — with zero `AiConnection` nodes, resolution is byte-for-byte today's.
101
+ */
102
+ private buildEnvCandidate;
103
+ /** Same block mapping the admin API uses for `meta.envDefaults`. */
104
+ private envBlockFor;
105
+ private warn;
106
+ }
107
+ //# sourceMappingURL=ai-connection-resolver.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-connection-resolver.service.d.ts","sourceRoot":"","sources":["../../../../src/core/llm/services/ai-connection-resolver.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,YAAY,EAAY,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,mBAAmB,EAAqB,MAAM,4BAA4B,CAAC;AACpF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2DAA2D,CAAC;AAC9F,OAAO,EAAE,sBAAsB,EAAE,MAAM,0EAA0E,CAAC;AAClH,OAAO,EAAE,6BAA6B,EAAE,MAAM,8EAA8E,CAAC;AAC7H,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAGL,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,sCAAsC,CAAC;AA2B9C;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,mBAAmB,CA6B7G;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBACa,2BAA4B,YAAW,YAAY;IAU5D,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAGlB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAftC,yEAAyE;IACzE,OAAO,CAAC,QAAQ,CAA4C;IAE5D,qEAAqE;IACrE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAE3D,OAAO,CAAC,YAAY,CAAC,CAAiC;gBAGnC,UAAU,EAAE,sBAAsB,EAClC,UAAU,EAAE,6BAA6B,EACzC,GAAG,EAAE,UAAU,EACf,aAAa,EAAE,aAAa,CAAC,mBAAmB,CAAC,EAGrC,MAAM,CAAC,EAAE,iBAAiB;IAGzD;;;;OAIG;IACH,YAAY,IAAI,IAAI;IASpB;;;;;;;;;OASG;IACH,OAAO,CAAC,IAAI,EAAE,gBAAgB,GAAG,mBAAmB,EAAE;IA0BtD;;;;OAIG;IACH,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAKvC;;;;;;OAMG;IAEG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAuCjC;;;OAGG;IACH,OAAO,CAAC,eAAe;IAOvB;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,QAAQ;IAIhB,OAAO,KAAK,QAAQ,GAMnB;IAED,OAAO,KAAK,eAAe,GAM1B;IAED;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IA8BzB,oEAAoE;IACpE,OAAO,CAAC,WAAW;IAuBnB,OAAO,CAAC,IAAI;CAOb"}
@@ -0,0 +1,329 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var AiConnectionResolverService_1;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.AiConnectionResolverService = void 0;
17
+ exports.toCandidate = toCandidate;
18
+ const common_1 = require("@nestjs/common");
19
+ const config_1 = require("@nestjs/config");
20
+ const event_emitter_1 = require("@nestjs/event-emitter");
21
+ const nestjs_cls_1 = require("nestjs-cls");
22
+ const ai_connection_repository_1 = require("../../../foundations/ai-connection/repositories/ai-connection.repository");
23
+ const ai_connection_encryption_service_1 = require("../../../foundations/ai-connection/services/ai-connection-encryption.service");
24
+ const logging_service_1 = require("../../logging/services/logging.service");
25
+ const ai_candidate_interface_1 = require("../interfaces/ai-candidate.interface");
26
+ /** How often the in-memory snapshot is rebuilt from the database. */
27
+ const SNAPSHOT_REFRESH_INTERVAL_MS = 60_000;
28
+ /** Cooldown used when `ai.connectionCooldownMinutes` is missing from config. */
29
+ const DEFAULT_COOLDOWN_MINUTES = 5;
30
+ /** Snapshot key for the chain that applies to every company. */
31
+ const GLOBAL_SCOPE = "global";
32
+ const KNOWN_CONNECTION_TYPES = new Set(ai_candidate_interface_1.AI_CONNECTION_TYPES);
33
+ const asString = (value) => typeof value === "string" && value !== "" ? value : undefined;
34
+ const asNumber = (value) => (typeof value === "number" ? value : undefined);
35
+ const asBoolean = (value) => (typeof value === "boolean" ? value : undefined);
36
+ /**
37
+ * Normalises one `AiConnection` node into the candidate shape `buildChatModel`
38
+ * (and its embedder/transcriber siblings) already consume.
39
+ *
40
+ * Pure on purpose: the decryptor is injected so the caller decides what a
41
+ * decryption failure means. It THROWS when a stored secret cannot be decrypted —
42
+ * {@link AiConnectionResolverService.refreshNow} catches that and skips the
43
+ * connection, so a single unreadable secret never takes the chain down
44
+ * (spec § 5 "Error handling").
45
+ */
46
+ function toCandidate(connection, decrypt) {
47
+ return {
48
+ source: "db",
49
+ connectionId: connection.id,
50
+ connectionType: connection.connectionType,
51
+ provider: connection.provider,
52
+ apiKey: connection.apiKey ? decrypt(connection.apiKey) : "",
53
+ model: connection.model ?? "",
54
+ url: connection.url ?? "",
55
+ region: connection.region,
56
+ instance: connection.instance,
57
+ apiVersion: connection.apiVersion,
58
+ googleCredentialsBase64: connection.googleCredentialsBase64
59
+ ? decrypt(connection.googleCredentialsBase64)
60
+ : undefined,
61
+ allowFallbacks: connection.allowFallbacks,
62
+ reasoningEffort: connection.reasoningEffort,
63
+ maxOutputTokens: connection.maxOutputTokens,
64
+ dimensions: connection.dimensions,
65
+ inputCostPer1MTokens: connection.inputCostPer1MTokens,
66
+ outputCostPer1MTokens: connection.outputCostPer1MTokens,
67
+ cachedInputCostPer1MTokens: connection.cachedInputCostPer1MTokens,
68
+ costPerMinute: connection.costPerMinute,
69
+ costPerPage: connection.costPerPage,
70
+ directUrl: connection.directUrl,
71
+ language: connection.language,
72
+ directFormat: connection.directFormat,
73
+ directProvider: connection.directProvider,
74
+ };
75
+ }
76
+ /**
77
+ * Resolves the ordered fallback chain for an AI connection type.
78
+ *
79
+ * Holds an in-memory snapshot of every enabled `AiConnection` node (decrypted,
80
+ * grouped into chains by `(connectionType, companyId | null)`, ordered by
81
+ * `position`), refreshed at boot, every 60s, and immediately on any admin write
82
+ * in this process (`AI_CONNECTIONS_CHANGED_EVENT`).
83
+ *
84
+ * The snapshot exists because `ModelService.getLLM()` is synchronous and called
85
+ * directly by several library services: resolution must stay a pure sync lookup
86
+ * with no signature ripple (spec § 2).
87
+ *
88
+ * Failure ALWAYS degrades toward today's `.env` behaviour, never toward "no AI"
89
+ * (spec § 5): an empty table, a failed refresh, an undecryptable secret or an
90
+ * unknown connection type all leave the `.env` candidate in place, and nothing
91
+ * on the `resolve()` hot path throws.
92
+ */
93
+ let AiConnectionResolverService = AiConnectionResolverService_1 = class AiConnectionResolverService {
94
+ constructor(repository, encryption, cls, configService, logger) {
95
+ this.repository = repository;
96
+ this.encryption = encryption;
97
+ this.cls = cls;
98
+ this.configService = configService;
99
+ this.logger = logger;
100
+ /** `${connectionType}|${companyId ?? "global"}` → ordered candidates. */
101
+ this.snapshot = new Map();
102
+ /** connectionId → epoch ms before which the candidate is skipped. */
103
+ this.cooldownUntil = new Map();
104
+ }
105
+ /**
106
+ * Loads the first snapshot and starts the periodic refresh. Neither is
107
+ * awaited and neither can reject: boot must never depend on the database
108
+ * holding AI connection rows (spec § 5).
109
+ */
110
+ onModuleInit() {
111
+ void this.refreshNow().catch((error) => this.warn("initial snapshot load failed", error));
112
+ this.refreshTimer = setInterval(() => {
113
+ void this.refreshNow().catch((error) => this.warn("scheduled snapshot refresh failed", error));
114
+ }, SNAPSHOT_REFRESH_INTERVAL_MS);
115
+ // Never hold the process open for a cache refresh.
116
+ this.refreshTimer.unref?.();
117
+ }
118
+ /**
119
+ * The ordered candidates to try for `type`, best first.
120
+ *
121
+ * Per-company chain when the request's company has one, otherwise the global
122
+ * chain; the `.env` block is ALWAYS appended last. Candidates inside their
123
+ * cooldown window are dropped — unless that empties the list, in which case
124
+ * the full chain is returned anyway (fail-open, spec § 2).
125
+ *
126
+ * Never throws.
127
+ */
128
+ resolve(type) {
129
+ const candidates = [];
130
+ try {
131
+ const companyId = this.currentCompanyId();
132
+ if (companyId) {
133
+ const scoped = this.snapshot.get(this.chainKey(type, companyId));
134
+ if (scoped?.length)
135
+ candidates.push(...scoped);
136
+ }
137
+ if (candidates.length === 0) {
138
+ const global = this.snapshot.get(this.chainKey(type, undefined));
139
+ if (global?.length)
140
+ candidates.push(...global);
141
+ }
142
+ }
143
+ catch (error) {
144
+ // A broken CLS/snapshot read degrades to the env candidate alone.
145
+ this.warn(`chain lookup failed for "${type}"`, error);
146
+ candidates.length = 0;
147
+ }
148
+ candidates.push(this.buildEnvCandidate(type));
149
+ const now = Date.now();
150
+ const healthy = candidates.filter((candidate) => (this.cooldownUntil.get(candidate.connectionId) ?? 0) <= now);
151
+ return healthy.length > 0 ? healthy : candidates;
152
+ }
153
+ /**
154
+ * Parks a connection for `ai.connectionCooldownMinutes` after a transient
155
+ * failure (429 / 5xx / network). The `.env` candidate is tracked under
156
+ * `env:<type>` like any other, so a dead env fallback cannot block recovery.
157
+ */
158
+ markFailure(connectionId) {
159
+ if (!connectionId)
160
+ return;
161
+ this.cooldownUntil.set(connectionId, Date.now() + this.cooldownMinutes * 60_000);
162
+ }
163
+ /**
164
+ * Rebuilds the snapshot from the database. Also the handler for admin writes
165
+ * in this process; other processes catch up at the next scheduled refresh.
166
+ *
167
+ * A read failure keeps the PREVIOUS snapshot — a database blip must not wipe
168
+ * working configuration (spec § 5).
169
+ */
170
+ async refreshNow() {
171
+ let connections;
172
+ try {
173
+ connections = await this.readConnections();
174
+ }
175
+ catch (error) {
176
+ this.warn("snapshot refresh failed — keeping the previous snapshot", error);
177
+ return;
178
+ }
179
+ const next = new Map();
180
+ const ordered = [...(connections ?? [])].sort((a, b) => (a?.position ?? 0) - (b?.position ?? 0));
181
+ for (const connection of ordered) {
182
+ if (!connection || connection.enabled === false)
183
+ continue;
184
+ if (!KNOWN_CONNECTION_TYPES.has(connection.connectionType)) {
185
+ this.warn(`skipping connection ${connection.id}: unknown connectionType "${connection.connectionType}"`);
186
+ continue;
187
+ }
188
+ let candidate;
189
+ try {
190
+ candidate = toCandidate(connection, (value) => this.encryption.decrypt(value));
191
+ }
192
+ catch (error) {
193
+ this.warn(`skipping connection ${connection.id}: stored secret could not be decrypted`, error);
194
+ continue;
195
+ }
196
+ const key = this.chainKey(candidate.connectionType, connection.companyId);
197
+ const chain = next.get(key);
198
+ if (chain)
199
+ chain.push(candidate);
200
+ else
201
+ next.set(key, [candidate]);
202
+ }
203
+ this.snapshot = next;
204
+ }
205
+ // --- internals ----------------------------------------------------------
206
+ /**
207
+ * Reads every connection inside a CLS context: the refresh runs outside any
208
+ * HTTP request, and the query plumbing reads CLS (`companyId`) unconditionally.
209
+ */
210
+ readConnections() {
211
+ if (typeof this.cls?.run === "function") {
212
+ return this.cls.run(async () => this.repository.findAllForResolver());
213
+ }
214
+ return this.repository.findAllForResolver();
215
+ }
216
+ /**
217
+ * The company of the current request, or undefined when there is none.
218
+ *
219
+ * Optional-call plus a silent catch: the resolver is also used by workers and
220
+ * boot-time code with no CLS context at all, which is an ordinary state and
221
+ * must not log or throw — it simply means "use the global chain".
222
+ */
223
+ currentCompanyId() {
224
+ try {
225
+ const companyId = this.cls?.get?.("companyId");
226
+ return typeof companyId === "string" && companyId !== "" ? companyId : undefined;
227
+ }
228
+ catch {
229
+ return undefined;
230
+ }
231
+ }
232
+ chainKey(type, companyId) {
233
+ return `${type}|${companyId ?? GLOBAL_SCOPE}`;
234
+ }
235
+ get aiConfig() {
236
+ try {
237
+ return this.configService?.get("ai");
238
+ }
239
+ catch {
240
+ return undefined;
241
+ }
242
+ }
243
+ get cooldownMinutes() {
244
+ // `connectionCooldownMinutes` is added to the `ai` block by the config task;
245
+ // default defensively so this works even when the read returns undefined.
246
+ const ai = this.aiConfig;
247
+ const configured = ai?.connectionCooldownMinutes;
248
+ return typeof configured === "number" && configured > 0 ? configured : DEFAULT_COOLDOWN_MINUTES;
249
+ }
250
+ /**
251
+ * The `.env` block for a type, mapped field-for-field onto a candidate. This
252
+ * is the final link of every chain and the whole behaviour when the table is
253
+ * empty — with zero `AiConnection` nodes, resolution is byte-for-byte today's.
254
+ */
255
+ buildEnvCandidate(type) {
256
+ const block = this.envBlockFor(type);
257
+ return {
258
+ source: "env",
259
+ connectionId: `env:${type}`,
260
+ connectionType: type,
261
+ provider: asString(block.provider) ?? "",
262
+ apiKey: asString(block.apiKey) ?? "",
263
+ model: asString(block.model) ?? "",
264
+ url: asString(block.url) ?? "",
265
+ region: asString(block.region),
266
+ instance: asString(block.instance),
267
+ apiVersion: asString(block.apiVersion),
268
+ googleCredentialsBase64: asString(block.googleCredentialsBase64),
269
+ allowFallbacks: asBoolean(block.allowFallbacks),
270
+ reasoningEffort: asString(block.reasoningEffort),
271
+ maxOutputTokens: asNumber(block.maxOutputTokens),
272
+ dimensions: asNumber(block.dimensions),
273
+ inputCostPer1MTokens: asNumber(block.inputCostPer1MTokens),
274
+ outputCostPer1MTokens: asNumber(block.outputCostPer1MTokens),
275
+ cachedInputCostPer1MTokens: asNumber(block.cachedInputCostPer1MTokens),
276
+ costPerMinute: asNumber(block.costPerMinute),
277
+ costPerPage: asNumber(block.costPerPage),
278
+ directUrl: asString(block.directUrl),
279
+ language: asString(block.language),
280
+ directFormat: asString(block.directFormat),
281
+ directProvider: asString(block.directProvider),
282
+ };
283
+ }
284
+ /** Same block mapping the admin API uses for `meta.envDefaults`. */
285
+ envBlockFor(type) {
286
+ const ai = this.aiConfig;
287
+ if (!ai)
288
+ return {};
289
+ switch (type) {
290
+ case "aiLite":
291
+ return (ai.aiLite ?? {});
292
+ case "aiLarge":
293
+ return (ai.aiLarge ?? {});
294
+ case "vision":
295
+ return (ai.vision ?? {});
296
+ case "audio":
297
+ return (ai.audio ?? {});
298
+ case "embedder":
299
+ return (ai.embedder ?? {});
300
+ case "transcriber":
301
+ return (ai.transcriber ?? {});
302
+ case "documentAi":
303
+ return (ai.documentAi ?? {});
304
+ default:
305
+ return (ai.ai ?? {});
306
+ }
307
+ }
308
+ warn(message, error) {
309
+ const detail = error instanceof Error ? error.message : error !== undefined ? String(error) : undefined;
310
+ this.logger?.warn(detail ? `[AiConnectionResolver] ${message}: ${detail}` : `[AiConnectionResolver] ${message}`, AiConnectionResolverService_1.name);
311
+ }
312
+ };
313
+ exports.AiConnectionResolverService = AiConnectionResolverService;
314
+ __decorate([
315
+ (0, event_emitter_1.OnEvent)(ai_candidate_interface_1.AI_CONNECTIONS_CHANGED_EVENT),
316
+ __metadata("design:type", Function),
317
+ __metadata("design:paramtypes", []),
318
+ __metadata("design:returntype", Promise)
319
+ ], AiConnectionResolverService.prototype, "refreshNow", null);
320
+ exports.AiConnectionResolverService = AiConnectionResolverService = AiConnectionResolverService_1 = __decorate([
321
+ (0, common_1.Injectable)(),
322
+ __param(4, (0, common_1.Optional)()),
323
+ __metadata("design:paramtypes", [ai_connection_repository_1.AiConnectionRepository,
324
+ ai_connection_encryption_service_1.AiConnectionEncryptionService,
325
+ nestjs_cls_1.ClsService,
326
+ config_1.ConfigService,
327
+ logging_service_1.AppLoggingService])
328
+ ], AiConnectionResolverService);
329
+ //# sourceMappingURL=ai-connection-resolver.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-connection-resolver.service.js","sourceRoot":"","sources":["../../../../src/core/llm/services/ai-connection-resolver.service.ts"],"names":[],"mappings":"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"}
@@ -21,6 +21,27 @@ export declare class EmbedderService {
21
21
  private readonly tokenUsageRecorder?;
22
22
  private readonly logger;
23
23
  constructor(modelService: ModelService, config: ConfigService<BaseConfigInterface>, tokenUsageService?: TokenUsageService, tokenUsageRecorder?: TokenUsageRecorderInterface);
24
+ /**
25
+ * Runs ONE embedding invocation, with a single failover retry on a transient
26
+ * provider failure (429/5xx/socket-level — classified by exactly the rule
27
+ * `LLMService` uses).
28
+ *
29
+ * ONE retry only, and only when there is somewhere to go: the failed
30
+ * connection is put into cooldown and the embedder is rebuilt, so
31
+ * `getEmbedder()` resolves the NEXT healthy link of the chain. A chain of one
32
+ * (no DB-backed embedder connection — just the `.env` block) has nowhere to
33
+ * fail over to, so the rejection propagates immediately, exactly as it does
34
+ * today. Everything beyond that single re-resolution belongs to the BullMQ
35
+ * job retry that owns these calls (spec § 2, "Other modalities").
36
+ */
37
+ private withCandidateFailover;
38
+ /**
39
+ * Puts the embedder connection currently in service into its cooldown window
40
+ * and returns it — or undefined when there is nothing to fail over to: no
41
+ * candidate-aware `ModelService`, a chain of one (`.env` only), or a registry
42
+ * that threw. Undefined means "behave exactly as before failover existed".
43
+ */
44
+ private failCurrentEmbedderCandidate;
24
45
  vectoriseText(params: {
25
46
  text: string;
26
47
  attribution?: EmbedderAttribution;
@@ -1 +1 @@
1
- {"version":3,"file":"embedder.service.d.ts","sourceRoot":"","sources":["../../../../src/core/llm/services/embedder.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,OAAO,EAAwB,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AAC3F,OAAO,EAAE,mBAAmB,EAAqB,MAAM,4BAA4B,CAAC;AAEpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,6DAA6D,CAAC;AAChG,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAcD,qBACa,eAAe;IAIxB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAIX,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC;IACL,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC;IAThF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoC;gBAGxC,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,aAAa,CAAC,mBAAmB,CAAC,EAI9B,iBAAiB,CAAC,EAAE,iBAAiB,EACP,kBAAkB,CAAC,EAAE,2BAA2B;IAGvG,aAAa,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,mBAAmB,CAAA;KAAE,GAAG,OAAO,CAAC,GAAG,CAAC;IAWxF,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IA2B5F;;;;;OAKG;YACW,YAAY;IAO1B;;;;OAIG;YACW,WAAW;IAMzB,OAAO,CAAC,kBAAkB;IAW1B;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAuB1B,qEAAqE;IACrE,OAAO,CAAC,gBAAgB;IAaxB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;;;;OAKG;IACH,OAAO,CAAC,QAAQ;IAYhB,0FAA0F;IAC1F,OAAO,CAAC,SAAS,CAA8B;IAE/C;;;;;;;;;OASG;YACW,YAAY;IA4B1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;YACW,qBAAqB;IASnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,OAAO,CAAC,kBAAkB;IAO1B;;;OAGG;IACH,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,WAAW;CAsBpB"}
1
+ {"version":3,"file":"embedder.service.d.ts","sourceRoot":"","sources":["../../../../src/core/llm/services/embedder.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,OAAO,EAAwB,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AAC3F,OAAO,EAAE,mBAAmB,EAAqB,MAAM,4BAA4B,CAAC;AAEpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,6DAA6D,CAAC;AAChG,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAehE;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAcD,qBACa,eAAe;IAIxB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAIX,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC;IACL,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC;IAThF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoC;gBAGxC,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,aAAa,CAAC,mBAAmB,CAAC,EAI9B,iBAAiB,CAAC,EAAE,iBAAiB,EACP,kBAAkB,CAAC,EAAE,2BAA2B;IAG7G;;;;;;;;;;;;OAYG;YACW,qBAAqB;IAgBnC;;;;;OAKG;IACH,OAAO,CAAC,4BAA4B;IAc9B,aAAa,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,mBAAmB,CAAA;KAAE,GAAG,OAAO,CAAC,GAAG,CAAC;IAWxF,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IA6B5F;;;;;OAKG;YACW,YAAY;IAO1B;;;;OAIG;YACW,WAAW;IAOzB,OAAO,CAAC,kBAAkB;IAW1B;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAuB1B,qEAAqE;IACrE,OAAO,CAAC,gBAAgB;IAaxB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;;;;OAKG;IACH,OAAO,CAAC,QAAQ;IAYhB,0FAA0F;IAC1F,OAAO,CAAC,SAAS,CAA8B;IAE/C;;;;;;;;;OASG;YACW,YAAY;IA4B1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;YACW,qBAAqB;IASnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,OAAO,CAAC,kBAAkB;IAO1B;;;OAGG;IACH,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,WAAW;CAsBpB"}
@@ -21,6 +21,7 @@ const tokens_1 = require("../../../common/tokens");
21
21
  const tokenusage_type_1 = require("../../../foundations/tokenusage/enums/tokenusage.type");
22
22
  const tokenusage_service_1 = require("../../../foundations/tokenusage/services/tokenusage.service");
23
23
  const model_service_1 = require("../../llm/services/model.service");
24
+ const llm_service_1 = require("./llm.service");
24
25
  /**
25
26
  * Ceiling for ONE embedding input. The provider hard cap is 8192 tokens
26
27
  * (`400 Invalid 'input[0]': maximum input length is 8192 tokens` from
@@ -39,6 +40,54 @@ let EmbedderService = EmbedderService_1 = class EmbedderService {
39
40
  this.tokenUsageRecorder = tokenUsageRecorder;
40
41
  this.logger = new common_1.Logger(EmbedderService_1.name);
41
42
  }
43
+ /**
44
+ * Runs ONE embedding invocation, with a single failover retry on a transient
45
+ * provider failure (429/5xx/socket-level — classified by exactly the rule
46
+ * `LLMService` uses).
47
+ *
48
+ * ONE retry only, and only when there is somewhere to go: the failed
49
+ * connection is put into cooldown and the embedder is rebuilt, so
50
+ * `getEmbedder()` resolves the NEXT healthy link of the chain. A chain of one
51
+ * (no DB-backed embedder connection — just the `.env` block) has nowhere to
52
+ * fail over to, so the rejection propagates immediately, exactly as it does
53
+ * today. Everything beyond that single re-resolution belongs to the BullMQ
54
+ * job retry that owns these calls (spec § 2, "Other modalities").
55
+ */
56
+ async withCandidateFailover(work) {
57
+ try {
58
+ return await work(this.modelService.getEmbedder());
59
+ }
60
+ catch (error) {
61
+ if (!(0, llm_service_1.isTransientNetworkError)(error))
62
+ throw error;
63
+ const failed = this.failCurrentEmbedderCandidate();
64
+ if (!failed)
65
+ throw error;
66
+ this.logger.warn(`Embedder connection ${failed.connectionId} failed transiently — retrying once on the next candidate: ${String(error)}`);
67
+ return work(this.modelService.getEmbedder());
68
+ }
69
+ }
70
+ /**
71
+ * Puts the embedder connection currently in service into its cooldown window
72
+ * and returns it — or undefined when there is nothing to fail over to: no
73
+ * candidate-aware `ModelService`, a chain of one (`.env` only), or a registry
74
+ * that threw. Undefined means "behave exactly as before failover existed".
75
+ */
76
+ failCurrentEmbedderCandidate() {
77
+ const resolver = this.modelService;
78
+ if (typeof resolver.getCandidatesForType !== "function" || typeof resolver.notifyCandidateFailure !== "function")
79
+ return undefined;
80
+ try {
81
+ const candidates = resolver.getCandidatesForType("embedder") ?? [];
82
+ if (candidates.length < 2)
83
+ return undefined;
84
+ resolver.notifyCandidateFailure(candidates[0]);
85
+ return candidates[0];
86
+ }
87
+ catch {
88
+ return undefined;
89
+ }
90
+ }
42
91
  async vectoriseText(params) {
43
92
  try {
44
93
  const result = await this.embedGuarded(params.text, params.attribution);
@@ -60,7 +109,7 @@ let EmbedderService = EmbedderService_1 = class EmbedderService {
60
109
  // The texts that fit still travel as ONE provider batch, in input order —
61
110
  // batching is why vectoriseTextBatch exists (chunk.repository.ts:405).
62
111
  if (withinBudget.length > 0) {
63
- const vectors = await this.modelService.getEmbedder().embedDocuments(withinBudget.map((index) => texts[index]));
112
+ const vectors = await this.withCandidateFailover((embedder) => embedder.embedDocuments(withinBudget.map((index) => texts[index])));
64
113
  withinBudget.forEach((index, position) => (result[index] = vectors[position]));
65
114
  }
66
115
  for (const index of oversized) {
@@ -84,7 +133,7 @@ let EmbedderService = EmbedderService_1 = class EmbedderService {
84
133
  async embedGuarded(text, attribution) {
85
134
  const slices = this.splitByTokenBudget(text);
86
135
  if (slices.length === 1)
87
- return this.modelService.getEmbedder().embedQuery(text);
136
+ return this.withCandidateFailover((embedder) => embedder.embedQuery(text));
88
137
  this.warnOversizedInput(text, slices.length, attribution);
89
138
  return this.meanPool(await this.embedSlices(slices));
90
139
  }
@@ -96,7 +145,7 @@ let EmbedderService = EmbedderService_1 = class EmbedderService {
96
145
  async embedSlices(slices) {
97
146
  const vectors = [];
98
147
  for (const slice of slices)
99
- vectors.push(await this.modelService.getEmbedder().embedQuery(slice));
148
+ vectors.push(await this.withCandidateFailover((embedder) => embedder.embedQuery(slice)));
100
149
  return vectors;
101
150
  }
102
151
  warnOversizedInput(text, sliceCount, attribution) {