@webiny/api-core 6.5.0-beta.1 → 6.5.0-beta.11

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 (90) hide show
  1. package/ApiCoreFeature.js +4 -0
  2. package/ApiCoreFeature.js.map +1 -1
  3. package/createApiCore.d.ts +1 -1
  4. package/createApiCore.js +3 -1
  5. package/createApiCore.js.map +1 -1
  6. package/features/ai/Ai.js +16 -6
  7. package/features/ai/Ai.js.map +1 -1
  8. package/features/ai/AiModelRegistry.d.ts +3 -1
  9. package/features/ai/AiModelRegistry.js +16 -7
  10. package/features/ai/AiModelRegistry.js.map +1 -1
  11. package/features/ai/DEVELOPERS.md +69 -12
  12. package/features/ai/OpenRouterSdkFactory.d.ts +10 -0
  13. package/features/ai/OpenRouterSdkFactory.js +112 -0
  14. package/features/ai/OpenRouterSdkFactory.js.map +1 -0
  15. package/features/ai/RemoteAiModelCatalog.d.ts +13 -0
  16. package/features/ai/RemoteAiModelCatalog.js +74 -0
  17. package/features/ai/RemoteAiModelCatalog.js.map +1 -0
  18. package/features/ai/abstractions.d.ts +25 -0
  19. package/features/ai/abstractions.js +2 -1
  20. package/features/ai/abstractions.js.map +1 -1
  21. package/features/ai/feature.js +4 -0
  22. package/features/ai/feature.js.map +1 -1
  23. package/features/ai/index.d.ts +2 -2
  24. package/features/ai/index.js +1 -1
  25. package/features/debugger/Debugger.d.ts +23 -0
  26. package/features/debugger/Debugger.js +134 -0
  27. package/features/debugger/Debugger.js.map +1 -0
  28. package/features/debugger/GraphQLDebuggerTransport.d.ts +26 -0
  29. package/features/debugger/GraphQLDebuggerTransport.js +45 -0
  30. package/features/debugger/GraphQLDebuggerTransport.js.map +1 -0
  31. package/features/debugger/__tests__/matchNamespace.test.d.ts +1 -0
  32. package/features/debugger/__tests__/matchNamespace.test.js +67 -0
  33. package/features/debugger/__tests__/matchNamespace.test.js.map +1 -0
  34. package/features/debugger/__tests__/serialize.test.d.ts +1 -0
  35. package/features/debugger/__tests__/serialize.test.js +220 -0
  36. package/features/debugger/__tests__/serialize.test.js.map +1 -0
  37. package/features/debugger/abstractions.d.ts +100 -0
  38. package/features/debugger/abstractions.js +6 -0
  39. package/features/debugger/abstractions.js.map +1 -0
  40. package/features/debugger/feature.d.ts +5 -0
  41. package/features/debugger/feature.js +15 -0
  42. package/features/debugger/feature.js.map +1 -0
  43. package/features/debugger/index.d.ts +7 -0
  44. package/features/debugger/index.js +7 -0
  45. package/features/debugger/limits.d.ts +18 -0
  46. package/features/debugger/limits.js +11 -0
  47. package/features/debugger/limits.js.map +1 -0
  48. package/features/debugger/matchNamespace.d.ts +21 -0
  49. package/features/debugger/matchNamespace.js +48 -0
  50. package/features/debugger/matchNamespace.js.map +1 -0
  51. package/features/debugger/permissions.d.ts +33 -0
  52. package/features/debugger/permissions.js +12 -0
  53. package/features/debugger/permissions.js.map +1 -0
  54. package/features/debugger/permissionsSchema.d.ts +17 -0
  55. package/features/debugger/permissionsSchema.js +18 -0
  56. package/features/debugger/permissionsSchema.js.map +1 -0
  57. package/features/debugger/plugins.d.ts +4 -0
  58. package/features/debugger/plugins.js +82 -0
  59. package/features/debugger/plugins.js.map +1 -0
  60. package/features/debugger/serialize.d.ts +11 -0
  61. package/features/debugger/serialize.js +121 -0
  62. package/features/debugger/serialize.js.map +1 -0
  63. package/features/httpClient/HttpClient.d.ts +7 -0
  64. package/features/httpClient/HttpClient.js +72 -0
  65. package/features/httpClient/HttpClient.js.map +1 -0
  66. package/features/httpClient/HttpRequestError.d.ts +10 -0
  67. package/features/httpClient/HttpRequestError.js +13 -0
  68. package/features/httpClient/HttpRequestError.js.map +1 -0
  69. package/features/httpClient/HttpStatusError.d.ts +12 -0
  70. package/features/httpClient/HttpStatusError.js +12 -0
  71. package/features/httpClient/HttpStatusError.js.map +1 -0
  72. package/features/httpClient/abstractions.d.ts +32 -0
  73. package/features/httpClient/abstractions.js +5 -0
  74. package/features/httpClient/abstractions.js.map +1 -0
  75. package/features/httpClient/feature.d.ts +4 -0
  76. package/features/httpClient/feature.js +11 -0
  77. package/features/httpClient/feature.js.map +1 -0
  78. package/features/httpClient/index.d.ts +3 -0
  79. package/features/httpClient/index.js +3 -0
  80. package/features/users/shared/loaders.js.map +1 -1
  81. package/graphql/wcp/graphql.js +2 -0
  82. package/graphql/wcp/graphql.js.map +1 -1
  83. package/idp/JwksCache.d.ts +7 -2
  84. package/idp/JwksCache.js +43 -16
  85. package/idp/JwksCache.js.map +1 -1
  86. package/idp/feature.js +1 -2
  87. package/idp/feature.js.map +1 -1
  88. package/legacy/security/plugins/secureHeaders.js +1 -0
  89. package/legacy/security/plugins/secureHeaders.js.map +1 -1
  90. package/package.json +25 -24
package/ApiCoreFeature.js CHANGED
@@ -8,20 +8,24 @@ import { AdminUsersFeature } from "./features/users/AdminUsersFeature.js";
8
8
  import { IdpAuthenticatorFeature } from "./idp/feature.js";
9
9
  import { KeyValueStoreFeature } from "./features/keyValueStore/feature.js";
10
10
  import { BuildParamsFeature } from "./features/buildParams/feature.js";
11
+ import { DebuggerFeature } from "./features/debugger/feature.js";
11
12
  import { EncryptionFeature } from "./features/encryption/feature.js";
12
13
  import { FeatureFlagsFeature } from "./features/featureFlags/feature.js";
13
14
  import { MaskerFeature } from "./features/masker/feature.js";
14
15
  import { AiFeature } from "./features/ai/feature.js";
16
+ import { HttpClientFeature } from "./features/httpClient/feature.js";
15
17
  import { NullWebhookDispatcher } from "./features/webhooks/WebhookDispatcher/NullWebhookDispatcher.js";
16
18
  import { WebhookProviderFeature } from "./features/webhooks/index.js";
17
19
  const ApiCoreFeature = createFeature({
18
20
  name: "ApiCore",
19
21
  register (container, config) {
20
22
  MaskerFeature.register(container);
23
+ HttpClientFeature.register(container);
21
24
  AiFeature.register(container);
22
25
  LoggerFeature.register(container);
23
26
  EventPublisherFeature.register(container);
24
27
  BuildParamsFeature.register(container);
28
+ DebuggerFeature.register(container);
25
29
  EncryptionFeature.register(container);
26
30
  FeatureFlagsFeature.register(container);
27
31
  TenancyFeature.register(container, config.tenancyStorageOperations);
@@ -1 +1 @@
1
- {"version":3,"file":"ApiCoreFeature.js","sources":["../src/ApiCoreFeature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/api\";\nimport { Container } from \"@webiny/di\";\nimport { EventPublisherFeature } from \"~/features/eventPublisher/feature.js\";\nimport { LoggerFeature } from \"~/features/logger/feature.js\";\nimport { SecurityFeature } from \"~/features/security/SecurityFeature.js\";\nimport { SystemFeature } from \"~/features/system/SystemFeature.js\";\nimport { TenancyFeature } from \"./features/tenancy/TenancyFeature.js\";\nimport { AdminUsersFeature } from \"~/features/users/AdminUsersFeature.js\";\nimport type { ApiCoreStorageOperations } from \"~/types/core.js\";\nimport { IdpAuthenticatorFeature } from \"~/idp/feature.js\";\nimport { KeyValueStoreFeature } from \"~/features/keyValueStore/feature.js\";\nimport { BuildParamsFeature } from \"~/features/buildParams/feature.js\";\nimport { EncryptionFeature } from \"~/features/encryption/feature.js\";\nimport { FeatureFlagsFeature } from \"~/features/featureFlags/feature.js\";\nimport { MaskerFeature } from \"~/features/masker/feature.js\";\nimport { AiFeature } from \"~/features/ai/feature.js\";\nimport { NullWebhookDispatcher } from \"./features/webhooks/WebhookDispatcher/NullWebhookDispatcher.js\";\nimport { WebhookProviderFeature } from \"~/features/webhooks/index.js\";\n\nexport const ApiCoreFeature = createFeature({\n name: \"ApiCore\",\n register(container: Container, config: ApiCoreStorageOperations) {\n // Register features\n MaskerFeature.register(container);\n AiFeature.register(container);\n LoggerFeature.register(container);\n EventPublisherFeature.register(container);\n BuildParamsFeature.register(container);\n EncryptionFeature.register(container);\n FeatureFlagsFeature.register(container);\n TenancyFeature.register(container, config.tenancyStorageOperations);\n SecurityFeature.register(container, config.securityStorageOperations);\n AdminUsersFeature.register(container, config.usersStorageOperations);\n KeyValueStoreFeature.register(container, config.keyValueStorageOperations);\n SystemFeature.register(container);\n IdpAuthenticatorFeature.register(container);\n container.register(NullWebhookDispatcher).inSingletonScope();\n WebhookProviderFeature.register(container);\n }\n});\n"],"names":["ApiCoreFeature","createFeature","container","config","MaskerFeature","AiFeature","LoggerFeature","EventPublisherFeature","BuildParamsFeature","EncryptionFeature","FeatureFlagsFeature","TenancyFeature","SecurityFeature","AdminUsersFeature","KeyValueStoreFeature","SystemFeature","IdpAuthenticatorFeature","NullWebhookDispatcher","WebhookProviderFeature"],"mappings":";;;;;;;;;;;;;;;;AAmBO,MAAMA,iBAAiBC,cAAc;IACxC,MAAM;IACN,UAASC,SAAoB,EAAEC,MAAgC;QAE3DC,cAAc,QAAQ,CAACF;QACvBG,UAAU,QAAQ,CAACH;QACnBI,cAAc,QAAQ,CAACJ;QACvBK,sBAAsB,QAAQ,CAACL;QAC/BM,mBAAmB,QAAQ,CAACN;QAC5BO,kBAAkB,QAAQ,CAACP;QAC3BQ,oBAAoB,QAAQ,CAACR;QAC7BS,eAAe,QAAQ,CAACT,WAAWC,OAAO,wBAAwB;QAClES,gBAAgB,QAAQ,CAACV,WAAWC,OAAO,yBAAyB;QACpEU,kBAAkB,QAAQ,CAACX,WAAWC,OAAO,sBAAsB;QACnEW,qBAAqB,QAAQ,CAACZ,WAAWC,OAAO,yBAAyB;QACzEY,cAAc,QAAQ,CAACb;QACvBc,wBAAwB,QAAQ,CAACd;QACjCA,UAAU,QAAQ,CAACe,uBAAuB,gBAAgB;QAC1DC,uBAAuB,QAAQ,CAAChB;IACpC;AACJ"}
1
+ {"version":3,"file":"ApiCoreFeature.js","sources":["../src/ApiCoreFeature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/api\";\nimport { Container } from \"@webiny/di\";\nimport { EventPublisherFeature } from \"~/features/eventPublisher/feature.js\";\nimport { LoggerFeature } from \"~/features/logger/feature.js\";\nimport { SecurityFeature } from \"~/features/security/SecurityFeature.js\";\nimport { SystemFeature } from \"~/features/system/SystemFeature.js\";\nimport { TenancyFeature } from \"./features/tenancy/TenancyFeature.js\";\nimport { AdminUsersFeature } from \"~/features/users/AdminUsersFeature.js\";\nimport type { ApiCoreStorageOperations } from \"~/types/core.js\";\nimport { IdpAuthenticatorFeature } from \"~/idp/feature.js\";\nimport { KeyValueStoreFeature } from \"~/features/keyValueStore/feature.js\";\nimport { BuildParamsFeature } from \"~/features/buildParams/feature.js\";\nimport { DebuggerFeature } from \"~/features/debugger/feature.js\";\nimport { EncryptionFeature } from \"~/features/encryption/feature.js\";\nimport { FeatureFlagsFeature } from \"~/features/featureFlags/feature.js\";\nimport { MaskerFeature } from \"~/features/masker/feature.js\";\nimport { AiFeature } from \"~/features/ai/feature.js\";\nimport { HttpClientFeature } from \"~/features/httpClient/feature.js\";\nimport { NullWebhookDispatcher } from \"./features/webhooks/WebhookDispatcher/NullWebhookDispatcher.js\";\nimport { WebhookProviderFeature } from \"~/features/webhooks/index.js\";\n\nexport const ApiCoreFeature = createFeature({\n name: \"ApiCore\",\n register(container: Container, config: ApiCoreStorageOperations) {\n // Register features\n MaskerFeature.register(container);\n HttpClientFeature.register(container);\n AiFeature.register(container);\n LoggerFeature.register(container);\n EventPublisherFeature.register(container);\n BuildParamsFeature.register(container);\n DebuggerFeature.register(container);\n EncryptionFeature.register(container);\n FeatureFlagsFeature.register(container);\n TenancyFeature.register(container, config.tenancyStorageOperations);\n SecurityFeature.register(container, config.securityStorageOperations);\n AdminUsersFeature.register(container, config.usersStorageOperations);\n KeyValueStoreFeature.register(container, config.keyValueStorageOperations);\n SystemFeature.register(container);\n IdpAuthenticatorFeature.register(container);\n container.register(NullWebhookDispatcher).inSingletonScope();\n WebhookProviderFeature.register(container);\n }\n});\n"],"names":["ApiCoreFeature","createFeature","container","config","MaskerFeature","HttpClientFeature","AiFeature","LoggerFeature","EventPublisherFeature","BuildParamsFeature","DebuggerFeature","EncryptionFeature","FeatureFlagsFeature","TenancyFeature","SecurityFeature","AdminUsersFeature","KeyValueStoreFeature","SystemFeature","IdpAuthenticatorFeature","NullWebhookDispatcher","WebhookProviderFeature"],"mappings":";;;;;;;;;;;;;;;;;;AAqBO,MAAMA,iBAAiBC,cAAc;IACxC,MAAM;IACN,UAASC,SAAoB,EAAEC,MAAgC;QAE3DC,cAAc,QAAQ,CAACF;QACvBG,kBAAkB,QAAQ,CAACH;QAC3BI,UAAU,QAAQ,CAACJ;QACnBK,cAAc,QAAQ,CAACL;QACvBM,sBAAsB,QAAQ,CAACN;QAC/BO,mBAAmB,QAAQ,CAACP;QAC5BQ,gBAAgB,QAAQ,CAACR;QACzBS,kBAAkB,QAAQ,CAACT;QAC3BU,oBAAoB,QAAQ,CAACV;QAC7BW,eAAe,QAAQ,CAACX,WAAWC,OAAO,wBAAwB;QAClEW,gBAAgB,QAAQ,CAACZ,WAAWC,OAAO,yBAAyB;QACpEY,kBAAkB,QAAQ,CAACb,WAAWC,OAAO,sBAAsB;QACnEa,qBAAqB,QAAQ,CAACd,WAAWC,OAAO,yBAAyB;QACzEc,cAAc,QAAQ,CAACf;QACvBgB,wBAAwB,QAAQ,CAAChB;QACjCA,UAAU,QAAQ,CAACiB,uBAAuB,gBAAgB;QAC1DC,uBAAuB,QAAQ,CAAClB;IACpC;AACJ"}
@@ -4,4 +4,4 @@ export interface ApiCoreConfig {
4
4
  storageOperations: ApiCoreStorageOperations;
5
5
  testProjectLicense?: DecryptedWcpProjectLicense;
6
6
  }
7
- export declare const createApiCore: (config: ApiCoreConfig) => (import("@webiny/api/index.js").ContextPlugin<import("~/types/core.js").ApiCoreContext>[] | import("@webiny/api/index.js").ContextPlugin<import("~/types/core.js").ApiCoreContext> | import("@webiny/handler-graphql/index.js").GraphQLSchemaPlugin<import("@webiny/api/types.js").Context> | import("@webiny/handler/index.js").RegisterExtensionPlugin<import("@webiny/handler/types.js").Context>)[];
7
+ export declare const createApiCore: (config: ApiCoreConfig) => (import("@webiny/api/index.js").ContextPlugin<import("~/types/core.js").ApiCoreContext>[] | import("@webiny/api/index.js").ContextPlugin<import("~/types/core.js").ApiCoreContext> | import("@webiny/handler-graphql/index.js").GraphQLSchemaPlugin<import("@webiny/api/types.js").Context> | import("@webiny/plugins/types.js").PluginCollection | import("@webiny/plugins/types.js").PluginFactory | import("@webiny/handler/index.js").RegisterExtensionPlugin<import("@webiny/handler/types.js").Context> | import("@webiny/plugins/types.js").Plugin<Record<string, any>>)[];
package/createApiCore.js CHANGED
@@ -5,6 +5,7 @@ import { createSecurityContext } from "./legacy/security/createSecurityContext.j
5
5
  import { createAdminUsersContext } from "./legacy/users/createAdminUsersContext.js";
6
6
  import { createTenancyContext } from "./legacy/tenancy/createTenancyContext.js";
7
7
  import { createSystemGraphQL } from "./graphql/system/createSystemGraphQL.js";
8
+ import { createDebuggerPlugins } from "./features/debugger/index.js";
8
9
  const createApiCore = (config)=>{
9
10
  const plugin = createRegisterExtensionPlugin((context)=>{
10
11
  ApiCoreFeature.register(context.container, config.storageOperations);
@@ -18,7 +19,8 @@ const createApiCore = (config)=>{
18
19
  createTenancyContext(),
19
20
  createSecurityContext(),
20
21
  createAdminUsersContext(),
21
- createSystemGraphQL()
22
+ createSystemGraphQL(),
23
+ ...createDebuggerPlugins()
22
24
  ];
23
25
  };
24
26
  export { createApiCore };
@@ -1 +1 @@
1
- {"version":3,"file":"createApiCore.js","sources":["../src/createApiCore.ts"],"sourcesContent":["import { createRegisterExtensionPlugin } from \"@webiny/handler/plugins/RegisterExtensionPlugin.js\";\nimport { ApiCoreFeature } from \"./ApiCoreFeature.js\";\nimport type { DecryptedWcpProjectLicense } from \"@webiny/wcp/types.js\";\nimport { createWcpContext } from \"~/legacy/wcp/context.js\";\nimport { createSecurityContext } from \"~/legacy/security/createSecurityContext.js\";\nimport { createAdminUsersContext } from \"~/legacy/users/createAdminUsersContext.js\";\nimport { createTenancyContext } from \"~/legacy/tenancy/createTenancyContext.js\";\nimport { createSystemGraphQL } from \"~/graphql/system/createSystemGraphQL.js\";\nimport type { ApiCoreStorageOperations } from \"~/types/core.js\";\n\nexport interface ApiCoreConfig {\n storageOperations: ApiCoreStorageOperations;\n testProjectLicense?: DecryptedWcpProjectLicense;\n}\n\nexport const createApiCore = (config: ApiCoreConfig) => {\n const plugin = createRegisterExtensionPlugin(context => {\n ApiCoreFeature.register(context.container, config.storageOperations);\n });\n plugin.name = \"apiCore.extension\";\n\n return [\n plugin,\n createWcpContext({ testProjectLicense: config.testProjectLicense }),\n createTenancyContext(),\n createSecurityContext(),\n createAdminUsersContext(),\n createSystemGraphQL()\n ];\n};\n"],"names":["createApiCore","config","plugin","createRegisterExtensionPlugin","context","ApiCoreFeature","createWcpContext","createTenancyContext","createSecurityContext","createAdminUsersContext","createSystemGraphQL"],"mappings":";;;;;;;AAeO,MAAMA,gBAAgB,CAACC;IAC1B,MAAMC,SAASC,8BAA8BC,CAAAA;QACzCC,eAAe,QAAQ,CAACD,QAAQ,SAAS,EAAEH,OAAO,iBAAiB;IACvE;IACAC,OAAO,IAAI,GAAG;IAEd,OAAO;QACHA;QACAI,iBAAiB;YAAE,oBAAoBL,OAAO,kBAAkB;QAAC;QACjEM;QACAC;QACAC;QACAC;KACH;AACL"}
1
+ {"version":3,"file":"createApiCore.js","sources":["../src/createApiCore.ts"],"sourcesContent":["import { createRegisterExtensionPlugin } from \"@webiny/handler/plugins/RegisterExtensionPlugin.js\";\nimport { ApiCoreFeature } from \"./ApiCoreFeature.js\";\nimport type { DecryptedWcpProjectLicense } from \"@webiny/wcp/types.js\";\nimport { createWcpContext } from \"~/legacy/wcp/context.js\";\nimport { createSecurityContext } from \"~/legacy/security/createSecurityContext.js\";\nimport { createAdminUsersContext } from \"~/legacy/users/createAdminUsersContext.js\";\nimport { createTenancyContext } from \"~/legacy/tenancy/createTenancyContext.js\";\nimport { createSystemGraphQL } from \"~/graphql/system/createSystemGraphQL.js\";\nimport type { ApiCoreStorageOperations } from \"~/types/core.js\";\nimport { createDebuggerPlugins } from \"~/features/debugger/index.js\";\n\nexport interface ApiCoreConfig {\n storageOperations: ApiCoreStorageOperations;\n testProjectLicense?: DecryptedWcpProjectLicense;\n}\n\nexport const createApiCore = (config: ApiCoreConfig) => {\n const plugin = createRegisterExtensionPlugin(context => {\n ApiCoreFeature.register(context.container, config.storageOperations);\n });\n plugin.name = \"apiCore.extension\";\n\n return [\n plugin,\n createWcpContext({ testProjectLicense: config.testProjectLicense }),\n createTenancyContext(),\n createSecurityContext(),\n createAdminUsersContext(),\n createSystemGraphQL(),\n ...createDebuggerPlugins()\n ];\n};\n"],"names":["createApiCore","config","plugin","createRegisterExtensionPlugin","context","ApiCoreFeature","createWcpContext","createTenancyContext","createSecurityContext","createAdminUsersContext","createSystemGraphQL","createDebuggerPlugins"],"mappings":";;;;;;;;AAgBO,MAAMA,gBAAgB,CAACC;IAC1B,MAAMC,SAASC,8BAA8BC,CAAAA;QACzCC,eAAe,QAAQ,CAACD,QAAQ,SAAS,EAAEH,OAAO,iBAAiB;IACvE;IACAC,OAAO,IAAI,GAAG;IAEd,OAAO;QACHA;QACAI,iBAAiB;YAAE,oBAAoBL,OAAO,kBAAkB;QAAC;QACjEM;QACAC;QACAC;QACAC;WACGC;KACN;AACL"}
package/features/ai/Ai.js CHANGED
@@ -4,6 +4,11 @@ import { Ai, AiConnectionFactory, AiModelRegistry, AiSdkFactory } from "./abstra
4
4
  import { mdbid } from "@webiny/utils/mdbid.js";
5
5
  import { EventPublisher } from "../eventPublisher/index.js";
6
6
  import { AiAfterGenerateTextEvent, AiBeforeGenerateTextEvent, AiBeforeStreamTextEvent, AiGenerateTextErrorEvent } from "./events.js";
7
+ const withSupportedOptions = (options, model)=>{
8
+ if (void 0 === options.temperature || model.supports?.temperature === true) return options;
9
+ const { temperature: _temperature, ...rest } = options;
10
+ return rest;
11
+ };
7
12
  class AiImpl {
8
13
  constructor(sdkFactories, connectionFactories, eventPublisher, modelRegistry){
9
14
  this.sdkFactories = sdkFactories;
@@ -14,8 +19,9 @@ class AiImpl {
14
19
  this.resolvedConnections = null;
15
20
  }
16
21
  async generateText(params) {
17
- const { model, connection, ...rest } = params;
18
- const resolvedModel = await this.resolveLanguageModel(model, connection);
22
+ const { model, connection, ...options } = params;
23
+ const { languageModel: resolvedModel, model: catalogModel } = await this.resolveLanguageModel(model, connection);
24
+ const rest = withSupportedOptions(options, catalogModel);
19
25
  const requestId = mdbid();
20
26
  await this.eventPublisher.publish(new AiBeforeGenerateTextEvent({
21
27
  requestId,
@@ -48,8 +54,9 @@ class AiImpl {
48
54
  }
49
55
  }
50
56
  async streamText(params) {
51
- const { model, connection, ...rest } = params;
52
- const resolvedModel = await this.resolveLanguageModel(model, connection);
57
+ const { model, connection, ...options } = params;
58
+ const { languageModel: resolvedModel, model: catalogModel } = await this.resolveLanguageModel(model, connection);
59
+ const rest = withSupportedOptions(options, catalogModel);
53
60
  await this.eventPublisher.publish(new AiBeforeStreamTextEvent({
54
61
  params
55
62
  }));
@@ -82,11 +89,14 @@ class AiImpl {
82
89
  const providerId = modelId.slice(0, slashIndex);
83
90
  const rawModelId = modelId.slice(slashIndex + 1);
84
91
  const models = await this.modelRegistry.listModels();
85
- const found = models.some((m)=>m.providerId === providerId && m.modelId === rawModelId);
92
+ const found = models.find((m)=>m.providerId === providerId && m.modelId === rawModelId);
86
93
  if (!found) throw new Error(`Model "${modelId}" is not available. Use listModels() to see available models.`);
87
94
  const conn = await this.resolveConnection(providerId, connection);
88
95
  const sdk = await this.getSdk(conn);
89
- return sdk.languageModel(rawModelId);
96
+ return {
97
+ languageModel: sdk.languageModel(rawModelId),
98
+ model: found
99
+ };
90
100
  }
91
101
  async getConnections() {
92
102
  if (!this.resolvedConnections) this.resolvedConnections = await Promise.all(this.connectionFactories.map((f)=>f.execute()));
@@ -1 +1 @@
1
- {"version":3,"file":"features/ai/Ai.js","sources":["../../../src/features/ai/Ai.ts"],"sourcesContent":["import { createImplementation } from \"@webiny/feature/api\";\nimport { generateText } from \"ai\";\nimport { streamText } from \"ai\";\nimport { Ai as AiAbstraction } from \"./abstractions.js\";\nimport { AiSdkFactory } from \"./abstractions.js\";\nimport { AiConnectionFactory } from \"./abstractions.js\";\nimport { AiModelRegistry } from \"./abstractions.js\";\nimport type { AiGenerateTextParams, AiModel } from \"./abstractions.js\";\nimport type { AiStreamTextParams } from \"./abstractions.js\";\nimport type { IAiSdk } from \"./abstractions.js\";\nimport type { IAiConnection } from \"./abstractions.js\";\nimport type { IAiConnectionInline } from \"./abstractions.js\";\nimport type { LanguageModel } from \"ai\";\nimport { mdbid } from \"@webiny/utils/mdbid.js\";\nimport { EventPublisher } from \"~/features/eventPublisher/index.js\";\nimport {\n AiBeforeGenerateTextEvent,\n AiAfterGenerateTextEvent,\n AiGenerateTextErrorEvent,\n AiBeforeStreamTextEvent\n} from \"./events.js\";\n\nclass AiImpl implements AiAbstraction.Interface {\n private sdkCache = new Map<string, IAiSdk>();\n private resolvedConnections: IAiConnection[] | null = null;\n\n constructor(\n private readonly sdkFactories: AiSdkFactory.Interface[],\n private readonly connectionFactories: AiConnectionFactory.Interface[],\n private readonly eventPublisher: EventPublisher.Interface,\n private readonly modelRegistry: AiModelRegistry.Interface\n ) {}\n\n async generateText(params: AiGenerateTextParams): ReturnType<typeof generateText> {\n const { model, connection, ...rest } = params;\n const resolvedModel = await this.resolveLanguageModel(model, connection);\n const requestId = mdbid();\n\n await this.eventPublisher.publish(new AiBeforeGenerateTextEvent({ requestId, params }));\n\n const start = performance.now();\n\n try {\n const result = await generateText({ model: resolvedModel, ...rest } as Parameters<\n typeof generateText\n >[0]);\n\n const duration = performance.now() - start;\n\n await this.eventPublisher.publish(\n new AiAfterGenerateTextEvent({ requestId, params, result, duration })\n );\n\n return result;\n } catch (err) {\n const duration = performance.now() - start;\n const error = err instanceof Error ? err : new Error(String(err));\n\n await this.eventPublisher.publish(\n new AiGenerateTextErrorEvent({ requestId, params, error, duration })\n );\n\n throw err;\n }\n }\n\n async streamText(params: AiStreamTextParams): Promise<ReturnType<typeof streamText>> {\n const { model, connection, ...rest } = params;\n const resolvedModel = await this.resolveLanguageModel(model, connection);\n\n await this.eventPublisher.publish(new AiBeforeStreamTextEvent({ params }));\n\n // Cast required: spreading the discriminated Prompt union loses its narrowing.\n return streamText({ model: resolvedModel, ...rest } as Parameters<typeof streamText>[0]);\n }\n\n listModels(): Promise<AiModel[]> {\n return this.modelRegistry.listModels();\n }\n\n async listModelsByConnections(): Promise<AiModel[]> {\n const [connections, models] = await Promise.all([\n this.getConnections(),\n this.modelRegistry.listModels()\n ]);\n const connectedProviderIds = new Set(connections.map(c => c.sdkName));\n return models.filter(m => connectedProviderIds.has(m.providerId));\n }\n\n async listModelsByConnection(connection: string | IAiConnectionInline): Promise<AiModel[]> {\n const [conn, models] = await Promise.all([\n this.resolveConnection(undefined, connection),\n this.modelRegistry.listModels()\n ]);\n return models.filter(m => m.providerId === conn.sdkName);\n }\n\n private async resolveLanguageModel(\n modelId: string,\n connection?: string | IAiConnectionInline\n ): Promise<LanguageModel> {\n const slashIndex = modelId.indexOf(\"/\");\n if (slashIndex === -1) {\n throw new Error(\n `Invalid model ID \"${modelId}\". Expected format: \"<providerId>/<modelId>\" (e.g. \"openai/gpt-4o\").`\n );\n }\n\n const providerId = modelId.slice(0, slashIndex);\n const rawModelId = modelId.slice(slashIndex + 1);\n\n const models = await this.modelRegistry.listModels();\n const found = models.some(m => m.providerId === providerId && m.modelId === rawModelId);\n if (!found) {\n throw new Error(\n `Model \"${modelId}\" is not available. Use listModels() to see available models.`\n );\n }\n\n const conn = await this.resolveConnection(providerId, connection);\n const sdk = await this.getSdk(conn);\n return sdk.languageModel(rawModelId);\n }\n\n private async getConnections(): Promise<IAiConnection[]> {\n if (!this.resolvedConnections) {\n this.resolvedConnections = await Promise.all(\n this.connectionFactories.map(f => f.execute())\n );\n }\n return this.resolvedConnections;\n }\n\n private async resolveConnection(\n providerId: string | undefined,\n connection?: string | IAiConnectionInline\n ): Promise<IAiConnectionInline> {\n if (typeof connection === \"object\") {\n return connection;\n }\n\n const connections = await this.getConnections();\n\n if (typeof connection === \"string\") {\n const found = connections.find(c => c.id === connection);\n if (!found) {\n const known = connections.map(c => `\"${c.id}\"`).join(\", \");\n throw new Error(\n `Unknown AI connection \"${connection}\". Registered connections: ${known}.`\n );\n }\n return found;\n }\n\n const found = connections.find(c => c.sdkName === providerId);\n if (!found) {\n const known = connections.map(c => `\"${c.id}\" (${c.sdkName})`).join(\", \");\n throw new Error(\n `No AI connection found for provider \"${providerId}\". Registered connections: ${known}.`\n );\n }\n return found;\n }\n\n private async getSdk(connection: IAiConnectionInline): Promise<IAiSdk> {\n const cacheKey =\n \"id\" in connection\n ? (connection as IAiConnection).id\n : `${connection.sdkName}:${connection.apiKey ?? \"__env__\"}`;\n\n const cached = this.sdkCache.get(cacheKey);\n if (cached) {\n return cached;\n }\n\n const factory = this.sdkFactories.find(f => f.id === connection.sdkName);\n if (!factory) {\n const known = this.sdkFactories.map(f => `\"${f.id}\"`).join(\", \");\n throw new Error(\n `No AI SDK factory found for \"${connection.sdkName}\". Registered factories: ${known}.`\n );\n }\n\n const sdk = await factory.execute(connection.apiKey);\n this.sdkCache.set(cacheKey, sdk);\n return sdk;\n }\n}\n\nexport const Ai = createImplementation({\n abstraction: AiAbstraction,\n implementation: AiImpl,\n dependencies: [\n [AiSdkFactory, { multiple: true }],\n [AiConnectionFactory, { multiple: true }],\n EventPublisher,\n AiModelRegistry\n ]\n});\n"],"names":["AiImpl","sdkFactories","connectionFactories","eventPublisher","modelRegistry","Map","params","model","connection","rest","resolvedModel","requestId","mdbid","AiBeforeGenerateTextEvent","start","performance","result","generateText","duration","AiAfterGenerateTextEvent","err","error","Error","String","AiGenerateTextErrorEvent","AiBeforeStreamTextEvent","streamText","connections","models","Promise","connectedProviderIds","Set","c","m","conn","undefined","modelId","slashIndex","providerId","rawModelId","found","sdk","f","known","cacheKey","cached","factory","Ai","createImplementation","AiAbstraction","AiSdkFactory","AiConnectionFactory","EventPublisher","AiModelRegistry"],"mappings":"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"}
1
+ {"version":3,"file":"features/ai/Ai.js","sources":["../../../src/features/ai/Ai.ts"],"sourcesContent":["import { createImplementation } from \"@webiny/feature/api\";\nimport { generateText } from \"ai\";\nimport { streamText } from \"ai\";\nimport { Ai as AiAbstraction } from \"./abstractions.js\";\nimport { AiSdkFactory } from \"./abstractions.js\";\nimport { AiConnectionFactory } from \"./abstractions.js\";\nimport { AiModelRegistry } from \"./abstractions.js\";\nimport type { AiGenerateTextParams, AiModel } from \"./abstractions.js\";\nimport type { AiStreamTextParams } from \"./abstractions.js\";\nimport type { IAiSdk } from \"./abstractions.js\";\nimport type { IAiConnection } from \"./abstractions.js\";\nimport type { IAiConnectionInline } from \"./abstractions.js\";\nimport type { LanguageModel } from \"ai\";\nimport { mdbid } from \"@webiny/utils/mdbid.js\";\nimport { EventPublisher } from \"~/features/eventPublisher/index.js\";\nimport {\n AiBeforeGenerateTextEvent,\n AiAfterGenerateTextEvent,\n AiGenerateTextErrorEvent,\n AiBeforeStreamTextEvent\n} from \"./events.js\";\n\n/**\n * Drops call options the model doesn't accept. Several current models reject `temperature` outright\n * (a 400, not a warning), so it is only sent when the catalog says the model supports it. No\n * information counts as unsupported: leaving it out costs some determinism, sending it can fail the\n * whole request.\n */\nconst withSupportedOptions = <T extends { temperature?: number }>(\n options: T,\n model: AiModel\n): T => {\n if (options.temperature === undefined || model.supports?.temperature === true) {\n return options;\n }\n const { temperature: _temperature, ...rest } = options;\n return rest as T;\n};\n\nclass AiImpl implements AiAbstraction.Interface {\n private sdkCache = new Map<string, IAiSdk>();\n private resolvedConnections: IAiConnection[] | null = null;\n\n constructor(\n private readonly sdkFactories: AiSdkFactory.Interface[],\n private readonly connectionFactories: AiConnectionFactory.Interface[],\n private readonly eventPublisher: EventPublisher.Interface,\n private readonly modelRegistry: AiModelRegistry.Interface\n ) {}\n\n async generateText(params: AiGenerateTextParams): ReturnType<typeof generateText> {\n const { model, connection, ...options } = params;\n const { languageModel: resolvedModel, model: catalogModel } =\n await this.resolveLanguageModel(model, connection);\n const rest = withSupportedOptions(options, catalogModel);\n const requestId = mdbid();\n\n await this.eventPublisher.publish(new AiBeforeGenerateTextEvent({ requestId, params }));\n\n const start = performance.now();\n\n try {\n const result = await generateText({ model: resolvedModel, ...rest } as Parameters<\n typeof generateText\n >[0]);\n\n const duration = performance.now() - start;\n\n await this.eventPublisher.publish(\n new AiAfterGenerateTextEvent({ requestId, params, result, duration })\n );\n\n return result;\n } catch (err) {\n const duration = performance.now() - start;\n const error = err instanceof Error ? err : new Error(String(err));\n\n await this.eventPublisher.publish(\n new AiGenerateTextErrorEvent({ requestId, params, error, duration })\n );\n\n throw err;\n }\n }\n\n async streamText(params: AiStreamTextParams): Promise<ReturnType<typeof streamText>> {\n const { model, connection, ...options } = params;\n const { languageModel: resolvedModel, model: catalogModel } =\n await this.resolveLanguageModel(model, connection);\n const rest = withSupportedOptions(options, catalogModel);\n\n await this.eventPublisher.publish(new AiBeforeStreamTextEvent({ params }));\n\n // Cast required: spreading the discriminated Prompt union loses its narrowing.\n return streamText({ model: resolvedModel, ...rest } as Parameters<typeof streamText>[0]);\n }\n\n listModels(): Promise<AiModel[]> {\n return this.modelRegistry.listModels();\n }\n\n async listModelsByConnections(): Promise<AiModel[]> {\n const [connections, models] = await Promise.all([\n this.getConnections(),\n this.modelRegistry.listModels()\n ]);\n const connectedProviderIds = new Set(connections.map(c => c.sdkName));\n return models.filter(m => connectedProviderIds.has(m.providerId));\n }\n\n async listModelsByConnection(connection: string | IAiConnectionInline): Promise<AiModel[]> {\n const [conn, models] = await Promise.all([\n this.resolveConnection(undefined, connection),\n this.modelRegistry.listModels()\n ]);\n return models.filter(m => m.providerId === conn.sdkName);\n }\n\n private async resolveLanguageModel(\n modelId: string,\n connection?: string | IAiConnectionInline\n ): Promise<{ languageModel: LanguageModel; model: AiModel }> {\n const slashIndex = modelId.indexOf(\"/\");\n if (slashIndex === -1) {\n throw new Error(\n `Invalid model ID \"${modelId}\". Expected format: \"<providerId>/<modelId>\" (e.g. \"openai/gpt-4o\").`\n );\n }\n\n const providerId = modelId.slice(0, slashIndex);\n const rawModelId = modelId.slice(slashIndex + 1);\n\n const models = await this.modelRegistry.listModels();\n const found = models.find(m => m.providerId === providerId && m.modelId === rawModelId);\n if (!found) {\n throw new Error(\n `Model \"${modelId}\" is not available. Use listModels() to see available models.`\n );\n }\n\n const conn = await this.resolveConnection(providerId, connection);\n const sdk = await this.getSdk(conn);\n return { languageModel: sdk.languageModel(rawModelId), model: found };\n }\n\n private async getConnections(): Promise<IAiConnection[]> {\n if (!this.resolvedConnections) {\n this.resolvedConnections = await Promise.all(\n this.connectionFactories.map(f => f.execute())\n );\n }\n return this.resolvedConnections;\n }\n\n private async resolveConnection(\n providerId: string | undefined,\n connection?: string | IAiConnectionInline\n ): Promise<IAiConnectionInline> {\n if (typeof connection === \"object\") {\n return connection;\n }\n\n const connections = await this.getConnections();\n\n if (typeof connection === \"string\") {\n const found = connections.find(c => c.id === connection);\n if (!found) {\n const known = connections.map(c => `\"${c.id}\"`).join(\", \");\n throw new Error(\n `Unknown AI connection \"${connection}\". Registered connections: ${known}.`\n );\n }\n return found;\n }\n\n const found = connections.find(c => c.sdkName === providerId);\n if (!found) {\n const known = connections.map(c => `\"${c.id}\" (${c.sdkName})`).join(\", \");\n throw new Error(\n `No AI connection found for provider \"${providerId}\". Registered connections: ${known}.`\n );\n }\n return found;\n }\n\n private async getSdk(connection: IAiConnectionInline): Promise<IAiSdk> {\n const cacheKey =\n \"id\" in connection\n ? (connection as IAiConnection).id\n : `${connection.sdkName}:${connection.apiKey ?? \"__env__\"}`;\n\n const cached = this.sdkCache.get(cacheKey);\n if (cached) {\n return cached;\n }\n\n const factory = this.sdkFactories.find(f => f.id === connection.sdkName);\n if (!factory) {\n const known = this.sdkFactories.map(f => `\"${f.id}\"`).join(\", \");\n throw new Error(\n `No AI SDK factory found for \"${connection.sdkName}\". Registered factories: ${known}.`\n );\n }\n\n const sdk = await factory.execute(connection.apiKey);\n this.sdkCache.set(cacheKey, sdk);\n return sdk;\n }\n}\n\nexport const Ai = createImplementation({\n abstraction: AiAbstraction,\n implementation: AiImpl,\n dependencies: [\n [AiSdkFactory, { multiple: true }],\n [AiConnectionFactory, { multiple: true }],\n EventPublisher,\n AiModelRegistry\n ]\n});\n"],"names":["withSupportedOptions","options","model","undefined","_temperature","rest","AiImpl","sdkFactories","connectionFactories","eventPublisher","modelRegistry","Map","params","connection","resolvedModel","catalogModel","requestId","mdbid","AiBeforeGenerateTextEvent","start","performance","result","generateText","duration","AiAfterGenerateTextEvent","err","error","Error","String","AiGenerateTextErrorEvent","AiBeforeStreamTextEvent","streamText","connections","models","Promise","connectedProviderIds","Set","c","m","conn","modelId","slashIndex","providerId","rawModelId","found","sdk","f","known","cacheKey","cached","factory","Ai","createImplementation","AiAbstraction","AiSdkFactory","AiConnectionFactory","EventPublisher","AiModelRegistry"],"mappings":"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"}
@@ -1,9 +1,11 @@
1
1
  import { AiModelRegistry as AiModelRegistryAbstraction } from "./abstractions.js";
2
2
  import { AiSdkFactory } from "./abstractions.js";
3
+ import { AiModelCatalog } from "./abstractions.js";
3
4
  import type { AiModel } from "./abstractions.js";
4
5
  declare class AiModelRegistryImpl implements AiModelRegistryAbstraction.Interface {
5
6
  private readonly sdkFactories;
6
- constructor(sdkFactories: AiSdkFactory.Interface[]);
7
+ private readonly catalog;
8
+ constructor(sdkFactories: AiSdkFactory.Interface[], catalog: AiModelCatalog.Interface);
7
9
  listModels(): Promise<AiModel[]>;
8
10
  }
9
11
  export declare const AiModelRegistry: typeof AiModelRegistryImpl & {
@@ -1,17 +1,25 @@
1
- import { AiModelRegistry, AiSdkFactory } from "./abstractions.js";
1
+ import { AiModelCatalog, AiModelRegistry, AiSdkFactory } from "./abstractions.js";
2
2
  class AiModelRegistryImpl {
3
- constructor(sdkFactories){
3
+ constructor(sdkFactories, catalog){
4
4
  this.sdkFactories = sdkFactories;
5
+ this.catalog = catalog;
5
6
  }
6
7
  async listModels() {
7
- return this.sdkFactories.flatMap((factory)=>factory.models.map((m)=>({
8
+ const providers = await this.catalog.listProviders();
9
+ return this.sdkFactories.flatMap((factory)=>{
10
+ const provider = providers?.find((p)=>p.id === factory.id);
11
+ const providerName = provider?.name ?? factory.name;
12
+ const models = provider?.models ?? factory.models;
13
+ return models.map((m)=>({
8
14
  providerId: factory.id,
9
- providerName: factory.name,
15
+ providerName,
10
16
  modelId: m.id,
11
17
  modelName: m.name,
12
18
  deprecated: m.deprecated,
13
- endOfLife: m.endOfLife
14
- })));
19
+ endOfLife: m.endOfLife,
20
+ supports: m.supports
21
+ }));
22
+ });
15
23
  }
16
24
  }
17
25
  const AiModelRegistry_AiModelRegistry = AiModelRegistry.createImplementation({
@@ -22,7 +30,8 @@ const AiModelRegistry_AiModelRegistry = AiModelRegistry.createImplementation({
22
30
  {
23
31
  multiple: true
24
32
  }
25
- ]
33
+ ],
34
+ AiModelCatalog
26
35
  ]
27
36
  });
28
37
  export { AiModelRegistry_AiModelRegistry as AiModelRegistry };
@@ -1 +1 @@
1
- {"version":3,"file":"features/ai/AiModelRegistry.js","sources":["../../../src/features/ai/AiModelRegistry.ts"],"sourcesContent":["import { AiModelRegistry as AiModelRegistryAbstraction } from \"./abstractions.js\";\nimport { AiSdkFactory } from \"./abstractions.js\";\nimport type { AiModel } from \"./abstractions.js\";\n\nclass AiModelRegistryImpl implements AiModelRegistryAbstraction.Interface {\n constructor(private readonly sdkFactories: AiSdkFactory.Interface[]) {}\n\n async listModels(): Promise<AiModel[]> {\n return this.sdkFactories.flatMap(factory =>\n factory.models.map(m => ({\n providerId: factory.id,\n providerName: factory.name,\n modelId: m.id,\n modelName: m.name,\n deprecated: m.deprecated,\n endOfLife: m.endOfLife\n }))\n );\n }\n}\n\nexport const AiModelRegistry = AiModelRegistryAbstraction.createImplementation({\n implementation: AiModelRegistryImpl,\n dependencies: [[AiSdkFactory, { multiple: true }]]\n});\n"],"names":["AiModelRegistryImpl","sdkFactories","factory","m","AiModelRegistry","AiModelRegistryAbstraction","AiSdkFactory"],"mappings":";AAIA,MAAMA;IACF,YAA6BC,YAAsC,CAAE;aAAxCA,YAAY,GAAZA;IAAyC;IAEtE,MAAM,aAAiC;QACnC,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,CAACC,CAAAA,UAC7BA,QAAQ,MAAM,CAAC,GAAG,CAACC,CAAAA,IAAM;oBACrB,YAAYD,QAAQ,EAAE;oBACtB,cAAcA,QAAQ,IAAI;oBAC1B,SAASC,EAAE,EAAE;oBACb,WAAWA,EAAE,IAAI;oBACjB,YAAYA,EAAE,UAAU;oBACxB,WAAWA,EAAE,SAAS;gBAC1B;IAER;AACJ;AAEO,MAAMC,kCAAkBC,gBAAAA,oBAA+C,CAAC;IAC3E,gBAAgBL;IAChB,cAAc;QAAC;YAACM;YAAc;gBAAE,UAAU;YAAK;SAAE;KAAC;AACtD"}
1
+ {"version":3,"file":"features/ai/AiModelRegistry.js","sources":["../../../src/features/ai/AiModelRegistry.ts"],"sourcesContent":["import { AiModelRegistry as AiModelRegistryAbstraction } from \"./abstractions.js\";\nimport { AiSdkFactory } from \"./abstractions.js\";\nimport { AiModelCatalog } from \"./abstractions.js\";\nimport type { AiModel } from \"./abstractions.js\";\n\nclass AiModelRegistryImpl implements AiModelRegistryAbstraction.Interface {\n constructor(\n private readonly sdkFactories: AiSdkFactory.Interface[],\n private readonly catalog: AiModelCatalog.Interface\n ) {}\n\n async listModels(): Promise<AiModel[]> {\n const providers = await this.catalog.listProviders();\n\n /*\n * A provider is only offered when an SDK factory can serve it. The catalog decides its\n * models; a factory the catalog doesn't know (a custom SDK), or an unreachable catalog,\n * falls back to the factory's own list.\n */\n return this.sdkFactories.flatMap(factory => {\n const provider = providers?.find(p => p.id === factory.id);\n const providerName = provider?.name ?? factory.name;\n const models = provider?.models ?? factory.models;\n\n return models.map(m => ({\n providerId: factory.id,\n providerName,\n modelId: m.id,\n modelName: m.name,\n deprecated: m.deprecated,\n endOfLife: m.endOfLife,\n supports: m.supports\n }));\n });\n }\n}\n\nexport const AiModelRegistry = AiModelRegistryAbstraction.createImplementation({\n implementation: AiModelRegistryImpl,\n dependencies: [[AiSdkFactory, { multiple: true }], AiModelCatalog]\n});\n"],"names":["AiModelRegistryImpl","sdkFactories","catalog","providers","factory","provider","p","providerName","models","m","AiModelRegistry","AiModelRegistryAbstraction","AiSdkFactory","AiModelCatalog"],"mappings":";AAKA,MAAMA;IACF,YACqBC,YAAsC,EACtCC,OAAiC,CACpD;aAFmBD,YAAY,GAAZA;aACAC,OAAO,GAAPA;IAClB;IAEH,MAAM,aAAiC;QACnC,MAAMC,YAAY,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa;QAOlD,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,CAACC,CAAAA;YAC7B,MAAMC,WAAWF,WAAW,KAAKG,CAAAA,IAAKA,EAAE,EAAE,KAAKF,QAAQ,EAAE;YACzD,MAAMG,eAAeF,UAAU,QAAQD,QAAQ,IAAI;YACnD,MAAMI,SAASH,UAAU,UAAUD,QAAQ,MAAM;YAEjD,OAAOI,OAAO,GAAG,CAACC,CAAAA,IAAM;oBACpB,YAAYL,QAAQ,EAAE;oBACtBG;oBACA,SAASE,EAAE,EAAE;oBACb,WAAWA,EAAE,IAAI;oBACjB,YAAYA,EAAE,UAAU;oBACxB,WAAWA,EAAE,SAAS;oBACtB,UAAUA,EAAE,QAAQ;gBACxB;QACJ;IACJ;AACJ;AAEO,MAAMC,kCAAkBC,gBAAAA,oBAA+C,CAAC;IAC3E,gBAAgBX;IAChB,cAAc;QAAC;YAACY;YAAc;gBAAE,UAAU;YAAK;SAAE;QAAEC;KAAe;AACtE"}
@@ -1,10 +1,49 @@
1
1
  # AI Models
2
2
 
3
- This directory contains the AI SDK factory implementations that define which models are available in Webiny's AI Powerups.
3
+ This directory contains the AI SDK factory implementations that serve the models available in Webiny's AI Powerups.
4
+
5
+ ## Where the model list comes from
6
+
7
+ `AiModelRegistry` takes the published catalog at `https://api.webiny.com/ai/models` (`RemoteAiModelCatalog`, overridable through `WCP_API_URL`) and intersects it with the registered SDK factories:
8
+
9
+ - A catalog provider with no SDK factory is not offered. The SDK factory is the last gate.
10
+ - For a provider that has a factory, the catalog's models and names replace the factory's own list.
11
+ - A factory the catalog doesn't know (a custom SDK) keeps its own `models`.
12
+ - If the catalog can't be loaded (timeout, error status, invalid body), every factory falls back to its own `models`.
13
+
14
+ The catalog is cached for an hour per process. A failed load is retried after a minute. The first AI call on a cold Lambda waits for the fetch. If the catalog doesn't answer within 3 seconds, the fetch is aborted and the factories' own lists are used.
15
+
16
+ So adding or deprecating a model for an existing provider is a catalog change and needs no release. The `*_MODELS` arrays below are the offline fallback; keep them roughly in sync.
17
+
18
+ The catalog response looks like this. Dates are ISO strings. `supports` is kept (`tools`, `vision`, `temperature`), and fields the schema doesn't know (`api`, other `supports` keys, ...) are dropped:
19
+
20
+ ```json
21
+ {
22
+ "providers": [
23
+ {
24
+ "id": "openai",
25
+ "name": "OpenAI",
26
+ "models": [
27
+ {
28
+ "id": "o3",
29
+ "name": "o3",
30
+ "deprecated": "2026-06-11",
31
+ "endOfLife": "2026-12-11",
32
+ "supports": { "tools": true, "vision": true, "temperature": false }
33
+ }
34
+ ]
35
+ }
36
+ ]
37
+ }
38
+ ```
39
+
40
+ A response that doesn't match the schema counts as a failed load, so one bad entry drops the whole catalog and every factory falls back to its own list.
41
+
42
+ `Ai.generateText()` and `Ai.streamText()` send `temperature` only to a model whose `supports.temperature` is `true`. A model that says `false`, or says nothing (no `supports`, or a factory's fallback list), gets the call without it. Several current models reject `temperature` with a 400, so callers can always pass it and leave the decision to `Ai`.
4
43
 
5
44
  ## Selection criteria
6
45
 
7
- Every model listed in a factory **must** support:
46
+ Every model in the catalog or in a factory **must** support:
8
47
 
9
48
  - **Tool/function calling** — AI Powerups relies on tools for structured output (content generation, entry comparison, image enrichment, etc.). Models without tool support will break these features at runtime.
10
49
  - **Chat/text generation** — only conversational models are listed; embedding, image-generation, TTS, and other specialized models are excluded.
@@ -21,7 +60,7 @@ Each model entry in `IAiSdkModel` supports two optional `Date` fields:
21
60
 
22
61
  Set these fields when the provider announces deprecation or end-of-life dates. The UI can use them to warn users about upcoming shutdowns or steer them toward replacements.
23
62
 
24
- When a model reaches its `endOfLife` date, remove it from the factory in the next release — keeping it would cause runtime failures.
63
+ When a model reaches its `endOfLife` date, remove it from the catalog, which takes effect within an hour. Remove it from the factory in the next release too, or deployments that can't reach the catalog will keep offering a model that no longer works.
25
64
 
26
65
  ## Providers
27
66
 
@@ -100,27 +139,45 @@ Env var fallback: `WEBINY_API_GOOGLE_API_KEY`.
100
139
 
101
140
  Source: https://ai.google.dev/gemini-api/docs/deprecations
102
141
 
142
+ ### OpenRouter (`OpenRouterSdkFactory.ts`)
143
+
144
+ OpenRouter is a gateway: one key reaches models from many vendors. Its model ids carry the vendor, so a stored model looks like `openrouter/anthropic/claude-sonnet-5.5`. `Ai` splits on the first slash, so the provider is `openrouter` and the rest goes to OpenRouter unchanged.
145
+
146
+ The factory list is a short pick of each vendor's current models that support tool calling, not OpenRouter's whole catalog (a few hundred models, many without tools). Pick from `https://openrouter.ai/api/v1/models`, keeping entries whose `supported_parameters` includes `tools` and skipping the `:free` and `:batch` variants. OpenRouter publishes retirement dates as `expiration_date`; leave out models that have one coming up.
147
+
148
+ `openrouter/auto` is OpenRouter's Auto Router: it picks the model per request, so the vendor that answers can change between calls. It's stored as `openrouter/openrouter/auto`. The catalog marks it as not accepting `temperature`, because some of the models it can pick reject it.
149
+
150
+ Uses `@openrouter/ai-sdk-provider` rather than `@ai-sdk/openai` with a different base URL, so non-OpenAI models routed through it don't get OpenAI-only request options.
151
+
152
+ Env var fallback: `WEBINY_API_OPENROUTER_API_KEY`.
153
+
154
+ Source: https://openrouter.ai/models
155
+
103
156
  ## Adding a new model
104
157
 
105
158
  1. Verify the model supports tool/function calling.
106
- 2. Add an entry to the `*_MODELS` array in the corresponding factory file. The `id` must match the provider's API model ID exactly.
107
- 3. If the model has known deprecation or end-of-life dates, set `deprecated` and/or `endOfLife` as `new Date("YYYY-MM-DD")`.
108
- 4. Update the table in this file.
159
+ 2. Add it to the catalog at `https://api.webiny.com/ai/models`. That is what users see.
160
+ 3. Add an entry to the `*_MODELS` array in the corresponding factory file. The `id` must match the provider's API model ID exactly.
161
+ 4. If the model has known deprecation or end-of-life dates, set `deprecated` and/or `endOfLife`: ISO strings (`"YYYY-MM-DD"`) in the catalog, `new Date("YYYY-MM-DD")` in the factory.
162
+ 5. Update the table in this file.
109
163
 
110
164
  ## Adding a new provider
111
165
 
112
- 1. Create a new `<Provider>SdkFactory.ts` implementing `AiSdkFactory`.
166
+ A new provider needs a release, because the catalog can only offer providers that have an SDK factory.
167
+
168
+ 1. Create a new `<Provider>SdkFactory.ts` implementing `AiSdkFactory`. Its `id` is the key the catalog matches on.
113
169
  2. Add the `@ai-sdk/<provider>` dependency via `yarn add` in `packages/api-core`.
114
170
  3. Register the factory in `feature.ts`.
115
- 4. Update this file.
171
+ 4. Add the provider to the catalog under the same `id`. Until you do, the factory's own `models` are used.
172
+ 5. Update this file.
116
173
 
117
174
  ## Updating models
118
175
 
119
- Check the provider docs periodically and update the lists:
176
+ Check the provider docs periodically and update the catalog first, then the factory lists:
120
177
 
121
178
  - **New models**: add to the top of the active section.
122
179
  - **Deprecated models**: set `deprecated` and `endOfLife` dates, move to the bottom of the array.
123
- - **End-of-life reached**: remove from the factory. Keeping a dead model causes runtime failures.
124
- - **Never remove a model before its end-of-life date** — users may have presets referencing it, and removing it breaks `ProvidersHandler` validation on save.
180
+ - **End-of-life reached**: remove from the catalog and the factory. Keeping a dead model causes runtime failures.
181
+ - **Never remove a model before its end-of-life date** — users may have presets referencing it, and removing it breaks `ProvidersHandler` validation on save. This matters more for the catalog than for the factory: `Ai` rejects any model the registry doesn't list, so dropping a model from the catalog breaks calls that use it in every deployment within the hour.
125
182
 
126
- Last updated: September 2026.
183
+ Last updated: October 2026.
@@ -0,0 +1,10 @@
1
+ import { AiSdkFactory as AiSdkFactoryAbstraction } from "./abstractions.js";
2
+ import type { IAiSdk, IAiSdkModel } from "./abstractions.js";
3
+ declare class OpenRouterSdkFactoryImpl implements AiSdkFactoryAbstraction.Interface {
4
+ readonly id = "openrouter";
5
+ readonly name = "OpenRouter";
6
+ readonly models: IAiSdkModel[];
7
+ execute(apiKey?: string): Promise<IAiSdk>;
8
+ }
9
+ export declare const OpenRouterSdkFactory: import("@webiny/di").Implementation<typeof OpenRouterSdkFactoryImpl>;
10
+ export {};
@@ -0,0 +1,112 @@
1
+ import { createImplementation } from "@webiny/feature/api";
2
+ import { AiSdkFactory } from "./abstractions.js";
3
+ const OPENROUTER_MODELS = [
4
+ {
5
+ id: "openrouter/auto",
6
+ name: "OpenRouter: Auto Router"
7
+ },
8
+ {
9
+ id: "mistralai/mistral-large-4-0",
10
+ name: "Mistral: Mistral Large 4"
11
+ },
12
+ {
13
+ id: "openai/gpt-6.1-sol",
14
+ name: "OpenAI: GPT-6.1 Sol"
15
+ },
16
+ {
17
+ id: "anthropic/claude-sonnet-5.5",
18
+ name: "Anthropic: Claude Sonnet 5.5"
19
+ },
20
+ {
21
+ id: "qwen/qwen3.8-max-prime",
22
+ name: "Qwen: Qwen3.8 Max Prime"
23
+ },
24
+ {
25
+ id: "z-ai/glm-5.3-prime",
26
+ name: "Z.ai: GLM 5.3 Prime"
27
+ },
28
+ {
29
+ id: "openai/gpt-6-sol",
30
+ name: "OpenAI: GPT-6 Sol"
31
+ },
32
+ {
33
+ id: "openai/gpt-6-luna",
34
+ name: "OpenAI: GPT-6 Luna"
35
+ },
36
+ {
37
+ id: "anthropic/claude-opus-5.5",
38
+ name: "Anthropic: Claude Opus 5.5"
39
+ },
40
+ {
41
+ id: "x-ai/grok-4.7",
42
+ name: "SpaceXAI: Grok 4.7"
43
+ },
44
+ {
45
+ id: "deepseek/deepseek-v4.1-flash",
46
+ name: "DeepSeek: DeepSeek V4.1 Flash"
47
+ },
48
+ {
49
+ id: "openai/gpt-6-astra",
50
+ name: "OpenAI: GPT-6 Astra"
51
+ },
52
+ {
53
+ id: "google/gemini-3.8-flash",
54
+ name: "Google: Gemini 3.8 Flash"
55
+ },
56
+ {
57
+ id: "anthropic/claude-fable-5.1",
58
+ name: "Anthropic: Claude Fable 5.1"
59
+ },
60
+ {
61
+ id: "anthropic/claude-opus-5",
62
+ name: "Anthropic: Claude Opus 5"
63
+ },
64
+ {
65
+ id: "google/gemini-3.5-flash-lite",
66
+ name: "Google: Gemini 3.5 Flash Lite"
67
+ },
68
+ {
69
+ id: "moonshotai/kimi-k3",
70
+ name: "MoonshotAI: Kimi K3"
71
+ },
72
+ {
73
+ id: "openai/gpt-5.6-terra",
74
+ name: "OpenAI: GPT-5.6 Terra"
75
+ },
76
+ {
77
+ id: "anthropic/claude-sonnet-5",
78
+ name: "Anthropic: Claude Sonnet 5"
79
+ },
80
+ {
81
+ id: "openai/gpt-5.4-mini",
82
+ name: "OpenAI: GPT-5.4 Mini"
83
+ },
84
+ {
85
+ id: "anthropic/claude-haiku-4.5",
86
+ name: "Anthropic: Claude Haiku 4.5"
87
+ }
88
+ ];
89
+ class OpenRouterSdkFactoryImpl {
90
+ async execute(apiKey) {
91
+ const { createOpenRouter } = await import("@openrouter/ai-sdk-provider");
92
+ const provider = createOpenRouter({
93
+ apiKey: apiKey ?? process.env.WEBINY_API_OPENROUTER_API_KEY
94
+ });
95
+ return {
96
+ languageModel: (modelId)=>provider.chat(modelId)
97
+ };
98
+ }
99
+ constructor(){
100
+ this.id = "openrouter";
101
+ this.name = "OpenRouter";
102
+ this.models = OPENROUTER_MODELS;
103
+ }
104
+ }
105
+ const OpenRouterSdkFactory = createImplementation({
106
+ abstraction: AiSdkFactory,
107
+ implementation: OpenRouterSdkFactoryImpl,
108
+ dependencies: []
109
+ });
110
+ export { OpenRouterSdkFactory };
111
+
112
+ //# sourceMappingURL=OpenRouterSdkFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features/ai/OpenRouterSdkFactory.js","sources":["../../../src/features/ai/OpenRouterSdkFactory.ts"],"sourcesContent":["import { createImplementation } from \"@webiny/feature/api\";\nimport { AiSdkFactory as AiSdkFactoryAbstraction } from \"./abstractions.js\";\nimport type { IAiSdk, IAiSdkModel } from \"./abstractions.js\";\n\n/*\n * OpenRouter model ids carry their own vendor prefix, so the composite Webiny id has two slashes:\n * \"openrouter/anthropic/claude-sonnet-5.5\". `Ai` splits on the first slash only, which leaves the\n * OpenRouter id intact.\n */\nconst OPENROUTER_MODELS: IAiSdkModel[] = [\n // OpenRouter picks the model per request, so which vendor answers can change between calls.\n { id: \"openrouter/auto\", name: \"OpenRouter: Auto Router\" },\n { id: \"mistralai/mistral-large-4-0\", name: \"Mistral: Mistral Large 4\" },\n { id: \"openai/gpt-6.1-sol\", name: \"OpenAI: GPT-6.1 Sol\" },\n { id: \"anthropic/claude-sonnet-5.5\", name: \"Anthropic: Claude Sonnet 5.5\" },\n { id: \"qwen/qwen3.8-max-prime\", name: \"Qwen: Qwen3.8 Max Prime\" },\n { id: \"z-ai/glm-5.3-prime\", name: \"Z.ai: GLM 5.3 Prime\" },\n { id: \"openai/gpt-6-sol\", name: \"OpenAI: GPT-6 Sol\" },\n { id: \"openai/gpt-6-luna\", name: \"OpenAI: GPT-6 Luna\" },\n { id: \"anthropic/claude-opus-5.5\", name: \"Anthropic: Claude Opus 5.5\" },\n { id: \"x-ai/grok-4.7\", name: \"SpaceXAI: Grok 4.7\" },\n { id: \"deepseek/deepseek-v4.1-flash\", name: \"DeepSeek: DeepSeek V4.1 Flash\" },\n { id: \"openai/gpt-6-astra\", name: \"OpenAI: GPT-6 Astra\" },\n { id: \"google/gemini-3.8-flash\", name: \"Google: Gemini 3.8 Flash\" },\n { id: \"anthropic/claude-fable-5.1\", name: \"Anthropic: Claude Fable 5.1\" },\n { id: \"anthropic/claude-opus-5\", name: \"Anthropic: Claude Opus 5\" },\n { id: \"google/gemini-3.5-flash-lite\", name: \"Google: Gemini 3.5 Flash Lite\" },\n { id: \"moonshotai/kimi-k3\", name: \"MoonshotAI: Kimi K3\" },\n { id: \"openai/gpt-5.6-terra\", name: \"OpenAI: GPT-5.6 Terra\" },\n { id: \"anthropic/claude-sonnet-5\", name: \"Anthropic: Claude Sonnet 5\" },\n { id: \"openai/gpt-5.4-mini\", name: \"OpenAI: GPT-5.4 Mini\" },\n { id: \"anthropic/claude-haiku-4.5\", name: \"Anthropic: Claude Haiku 4.5\" }\n];\n\nclass OpenRouterSdkFactoryImpl implements AiSdkFactoryAbstraction.Interface {\n readonly id = \"openrouter\";\n readonly name = \"OpenRouter\";\n readonly models = OPENROUTER_MODELS;\n\n async execute(apiKey?: string): Promise<IAiSdk> {\n const { createOpenRouter } = await import(\"@openrouter/ai-sdk-provider\");\n const provider = createOpenRouter({\n apiKey: apiKey ?? process.env.WEBINY_API_OPENROUTER_API_KEY\n });\n return {\n languageModel: modelId => provider.chat(modelId)\n };\n }\n}\n\nexport const OpenRouterSdkFactory = createImplementation({\n abstraction: AiSdkFactoryAbstraction,\n implementation: OpenRouterSdkFactoryImpl,\n dependencies: []\n});\n"],"names":["OPENROUTER_MODELS","OpenRouterSdkFactoryImpl","apiKey","createOpenRouter","provider","process","modelId","OpenRouterSdkFactory","createImplementation","AiSdkFactoryAbstraction"],"mappings":";;AASA,MAAMA,oBAAmC;IAErC;QAAE,IAAI;QAAmB,MAAM;IAA0B;IACzD;QAAE,IAAI;QAA+B,MAAM;IAA2B;IACtE;QAAE,IAAI;QAAsB,MAAM;IAAsB;IACxD;QAAE,IAAI;QAA+B,MAAM;IAA+B;IAC1E;QAAE,IAAI;QAA0B,MAAM;IAA0B;IAChE;QAAE,IAAI;QAAsB,MAAM;IAAsB;IACxD;QAAE,IAAI;QAAoB,MAAM;IAAoB;IACpD;QAAE,IAAI;QAAqB,MAAM;IAAqB;IACtD;QAAE,IAAI;QAA6B,MAAM;IAA6B;IACtE;QAAE,IAAI;QAAiB,MAAM;IAAqB;IAClD;QAAE,IAAI;QAAgC,MAAM;IAAgC;IAC5E;QAAE,IAAI;QAAsB,MAAM;IAAsB;IACxD;QAAE,IAAI;QAA2B,MAAM;IAA2B;IAClE;QAAE,IAAI;QAA8B,MAAM;IAA8B;IACxE;QAAE,IAAI;QAA2B,MAAM;IAA2B;IAClE;QAAE,IAAI;QAAgC,MAAM;IAAgC;IAC5E;QAAE,IAAI;QAAsB,MAAM;IAAsB;IACxD;QAAE,IAAI;QAAwB,MAAM;IAAwB;IAC5D;QAAE,IAAI;QAA6B,MAAM;IAA6B;IACtE;QAAE,IAAI;QAAuB,MAAM;IAAuB;IAC1D;QAAE,IAAI;QAA8B,MAAM;IAA8B;CAC3E;AAED,MAAMC;IAKF,MAAM,QAAQC,MAAe,EAAmB;QAC5C,MAAM,EAAEC,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC;QAC1C,MAAMC,WAAWD,iBAAiB;YAC9B,QAAQD,UAAUG,QAAQ,GAAG,CAAC,6BAA6B;QAC/D;QACA,OAAO;YACH,eAAeC,CAAAA,UAAWF,SAAS,IAAI,CAACE;QAC5C;IACJ;;aAZS,EAAE,GAAG;aACL,IAAI,GAAG;aACP,MAAM,GAAGN;;AAWtB;AAEO,MAAMO,uBAAuBC,qBAAqB;IACrD,aAAaC;IACb,gBAAgBR;IAChB,cAAc,EAAE;AACpB"}
@@ -0,0 +1,13 @@
1
+ import { AiModelCatalog as AiModelCatalogAbstraction } from "./abstractions.js";
2
+ import type { AiCatalogProvider } from "./abstractions.js";
3
+ import { Logger } from "../../features/logger/index.js";
4
+ import { HttpClient } from "../../features/httpClient/index.js";
5
+ declare class RemoteAiModelCatalogImpl implements AiModelCatalogAbstraction.Interface {
6
+ private readonly httpClient;
7
+ private readonly logger;
8
+ constructor(httpClient: HttpClient.Interface, logger: Logger.Interface);
9
+ listProviders(): Promise<AiCatalogProvider[] | undefined>;
10
+ private fetchProviders;
11
+ }
12
+ export declare const RemoteAiModelCatalog: import("@webiny/di").Implementation<typeof RemoteAiModelCatalogImpl>;
13
+ export {};
@@ -0,0 +1,74 @@
1
+ import { z } from "zod";
2
+ import { createImplementation } from "@webiny/feature/api";
3
+ import { getWcpApiUrl } from "@webiny/wcp";
4
+ import { AiModelCatalog } from "./abstractions.js";
5
+ import { Logger } from "../logger/index.js";
6
+ import { HttpClient } from "../httpClient/index.js";
7
+ const FETCH_TIMEOUT_MS = 3000;
8
+ const SUCCESS_TTL_MS = 3600000;
9
+ const FAILURE_TTL_MS = 60000;
10
+ const optionalDate = z.coerce.date().optional();
11
+ const catalogSchema = z.object({
12
+ providers: z.array(z.object({
13
+ id: z.string(),
14
+ name: z.string(),
15
+ models: z.array(z.object({
16
+ id: z.string(),
17
+ name: z.string(),
18
+ deprecated: optionalDate,
19
+ endOfLife: optionalDate,
20
+ supports: z.object({
21
+ tools: z.boolean().optional(),
22
+ vision: z.boolean().optional(),
23
+ temperature: z.boolean().optional()
24
+ }).optional()
25
+ }))
26
+ }))
27
+ });
28
+ let cached;
29
+ class RemoteAiModelCatalogImpl {
30
+ constructor(httpClient, logger){
31
+ this.httpClient = httpClient;
32
+ this.logger = logger;
33
+ }
34
+ listProviders() {
35
+ if (cached && cached.expiresAt > Date.now()) return cached.providers;
36
+ const entry = {
37
+ expiresAt: Date.now() + SUCCESS_TTL_MS,
38
+ providers: this.fetchProviders().then((providers)=>{
39
+ if (!providers) entry.expiresAt = Date.now() + FAILURE_TTL_MS;
40
+ return providers;
41
+ })
42
+ };
43
+ cached = entry;
44
+ return entry.providers;
45
+ }
46
+ async fetchProviders() {
47
+ const url = getWcpApiUrl("/ai/models");
48
+ const response = await this.httpClient.requestJson({
49
+ url,
50
+ timeoutMs: FETCH_TIMEOUT_MS
51
+ });
52
+ if (response.isFail()) return void this.logger.warn({
53
+ url,
54
+ error: response.error
55
+ }, "Failed to load AI models.");
56
+ const result = catalogSchema.safeParse(response.value);
57
+ if (!result.success) return void this.logger.warn({
58
+ url,
59
+ error: result.error
60
+ }, "Received invalid AI models.");
61
+ return result.data.providers;
62
+ }
63
+ }
64
+ const RemoteAiModelCatalog = createImplementation({
65
+ abstraction: AiModelCatalog,
66
+ implementation: RemoteAiModelCatalogImpl,
67
+ dependencies: [
68
+ HttpClient,
69
+ Logger
70
+ ]
71
+ });
72
+ export { RemoteAiModelCatalog };
73
+
74
+ //# sourceMappingURL=RemoteAiModelCatalog.js.map