@undefineds.co/xpod 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/cloud.json +1 -1
- package/config/main.json +36 -2
- package/dist/api/ai-gateway/auth/GatewayApiKeyAuthenticator.d.ts +3 -0
- package/dist/api/ai-gateway/auth/GatewayApiKeyAuthenticator.js +1 -1
- package/dist/api/ai-gateway/auth/PodGatewayAccessKeyRepository.d.ts +2 -1
- package/dist/api/ai-gateway/auth/PodGatewayAccessKeyRepository.js +101 -32
- package/dist/api/ai-gateway/connect/DeviceCodeProtocol.d.ts +80 -0
- package/dist/api/ai-gateway/connect/DeviceCodeProtocol.js +3 -0
- package/dist/api/ai-gateway/connect/FileSessionImportAdapter.d.ts +38 -0
- package/dist/api/ai-gateway/connect/FileSessionImportAdapter.js +63 -0
- package/dist/api/ai-gateway/connect/LoopbackAuthorizationCallbackReceiver.d.ts +24 -0
- package/dist/api/ai-gateway/connect/LoopbackAuthorizationCallbackReceiver.js +136 -0
- package/dist/api/ai-gateway/connect/OAuthConnectAdapter.d.ts +2 -0
- package/dist/api/ai-gateway/connect/OAuthConnectAdapter.js +2 -1
- package/dist/api/ai-gateway/connect/OAuthIntegrationRegistry.d.ts +17 -13
- package/dist/api/ai-gateway/connect/OAuthIntegrationRegistry.js +52 -19
- package/dist/api/ai-gateway/connect/OpenAiSubscriptionSessionImportAdapter.d.ts +4 -28
- package/dist/api/ai-gateway/connect/OpenAiSubscriptionSessionImportAdapter.js +7 -58
- package/dist/api/ai-gateway/connect/ProviderAuthorizationProfiles.d.ts +4 -0
- package/dist/api/ai-gateway/connect/ProviderAuthorizationProfiles.js +116 -0
- package/dist/api/ai-gateway/connect/SessionImportProfiles.d.ts +10 -0
- package/dist/api/ai-gateway/connect/SessionImportProfiles.js +112 -0
- package/dist/api/ai-gateway/connect/index.d.ts +125 -23
- package/dist/api/ai-gateway/connect/index.js +1277 -260
- package/dist/api/ai-gateway/models/PodModelSelectionRepository.d.ts +4 -1
- package/dist/api/ai-gateway/models/PodModelSelectionRepository.js +154 -19
- package/dist/api/ai-gateway/providers/DeepSeekRuntimeAdapter.js +10 -6
- package/dist/api/ai-gateway/providers/ModelsDevCatalog.d.ts +7 -0
- package/dist/api/ai-gateway/providers/ModelsDevCatalog.js +25 -1
- package/dist/api/ai-gateway/providers/OfferingAuthorization.d.ts +24 -0
- package/dist/api/ai-gateway/providers/OfferingAuthorization.js +37 -0
- package/dist/api/ai-gateway/providers/OpenAiRuntimeAdapter.d.ts +1 -1
- package/dist/api/ai-gateway/providers/OpenAiRuntimeAdapter.js +5 -3
- package/dist/api/ai-gateway/providers/ProviderRegistry.d.ts +6 -3
- package/dist/api/ai-gateway/providers/ProviderRegistry.js +55 -16
- package/dist/api/ai-gateway/providers/ProviderRuntimeAdapter.d.ts +2 -0
- package/dist/api/ai-gateway/providers/ProviderRuntimeAdapter.js +37 -4
- package/dist/api/ai-gateway/providers/ProviderRuntimeRegistry.js +7 -0
- package/dist/api/ai-gateway/quota/SubscriptionQuotaAdapters.js +2 -1
- package/dist/api/ai-gateway/routing/ModelRouter.js +3 -1
- package/dist/api/container/common.js +30 -40
- package/dist/api/container/index.js +6 -2
- package/dist/api/container/routes.js +5 -22
- package/dist/api/handlers/AiClientConfigurationHandler.js +39 -1
- package/dist/api/handlers/AiGatewayManagementHandler.d.ts +3 -0
- package/dist/api/handlers/AiGatewayManagementHandler.js +173 -58
- package/dist/api/service/AiClientConfigurationService.d.ts +4 -15
- package/dist/api/service/AiClientConfigurationService.js +17 -64
- package/dist/cli/commands/start.js +2 -0
- package/dist/components/components.jsonld +1 -0
- package/dist/components/context.jsonld +80 -0
- package/dist/http/InternalPodDataHttpHandler.d.ts +3 -0
- package/dist/http/InternalPodDataHttpHandler.js +13 -2
- package/dist/http/InternalPodDataHttpHandler.jsonld +12 -0
- package/dist/identity/oidc/RememberedClientPromptFactory.d.ts +1 -1
- package/dist/identity/oidc/RememberedClientPromptFactory.js +5 -2
- package/dist/identity/oidc/RememberedConsentHandler.js +1 -1
- package/dist/identity/oidc/ScopedPickWebIdHandler.d.ts +1 -0
- package/dist/identity/oidc/ScopedPickWebIdHandler.js +26 -0
- package/dist/identity/oidc/ScopedPickWebIdHandler.jsonld +4 -0
- package/dist/identity/oidc/SessionBoundIdentityProviderFactory.d.ts +10 -0
- package/dist/identity/oidc/SessionBoundIdentityProviderFactory.js +21 -0
- package/dist/identity/oidc/SessionBoundIdentityProviderFactory.jsonld +172 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -2
- package/dist/runtime/component-parameter-keys.js +18 -2
- package/dist/storage/accessors/MixDataAccessor.js +6 -2
- package/dist/storage/rdf/PostgresRdfEngine.js +10 -2
- package/dist/storage/rdf/RdfContentTypes.js +4 -0
- package/node_modules/@undefineds.co/ai-connections/dist/AiClientConfigurationSection.d.ts +3 -0
- package/node_modules/@undefineds.co/ai-connections/dist/AiClientConfigurationSection.js +13 -16
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsList.js +1 -3
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsMain.d.ts +2 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsMain.js +2 -2
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsPanel.d.ts +4 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsPanel.js +127 -57
- package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.d.ts +6 -4
- package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.js +436 -83
- package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeysSection.d.ts +3 -2
- package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeysSection.js +212 -76
- package/node_modules/@undefineds.co/ai-connections/dist/AiProviderCard.d.ts +6 -4
- package/node_modules/@undefineds.co/ai-connections/dist/AiProviderCard.js +58 -41
- package/node_modules/@undefineds.co/ai-connections/dist/AiQuotaCard.d.ts +4 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiQuotaCard.js +63 -4
- package/node_modules/@undefineds.co/ai-connections/dist/ai-connections-client.d.ts +46 -6
- package/node_modules/@undefineds.co/ai-connections/dist/ai-connections-client.js +143 -14
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/base-adapter.d.ts +19 -3
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/base-adapter.js +45 -7
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/codex.d.ts +7 -1
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/codex.js +305 -40
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/index.cjs +383 -50
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/json-env-adapter.js +3 -2
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/pi.js +12 -3
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/transaction.d.ts +2 -0
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/transaction.js +39 -4
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/types.d.ts +8 -1
- package/node_modules/@undefineds.co/ai-connections/dist/client-visuals.d.ts +2 -0
- package/node_modules/@undefineds.co/ai-connections/dist/client-visuals.js +14 -0
- package/node_modules/@undefineds.co/ai-connections/dist/controller.d.ts +2 -2
- package/node_modules/@undefineds.co/ai-connections/dist/controller.js +216 -57
- package/node_modules/@undefineds.co/ai-connections/dist/index.d.ts +3 -1
- package/node_modules/@undefineds.co/ai-connections/dist/index.js +16 -2
- package/node_modules/@undefineds.co/ai-connections/dist/provider-visuals.js +6 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/execution/ldp-executor.js +27 -6
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/pod-database.js +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/select-query-builder.js +2 -2
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/sparql/builder/update-builder.js +12 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/builder.js +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/handlers/array.js +8 -0
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/handlers/default.js +9 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/handlers/inline.js +3 -3
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/execution/ldp-executor.js +27 -6
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/pod-database.js +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/select-query-builder.js +2 -2
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/sparql/builder/update-builder.js +12 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/builder.js +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/handlers/array.js +8 -0
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/handlers/default.js +9 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/handlers/inline.js +3 -3
- package/node_modules/@undefineds.co/extension-sdk/dist/web.d.ts +31 -0
- package/package.json +2 -2
- package/static/app/assets/{index-BikOyGPu.js → index-BCZVzMpj.js} +44 -44
- package/static/app/assets/inrupt-smoke.js +2 -2
- package/static/app/assets/main.css +1 -1
- package/static/app/assets/main.js +10 -10
- package/static/auth-callback/assets/{AiConfigContext-DrCH6ckh.js → AiConfigContext-DVkh8Kg2.js} +1 -1
- package/static/auth-callback/assets/{AiConfigPage-DVrlSSdA.js → AiConfigPage-HWVWpe-P.js} +1 -1
- package/static/auth-callback/assets/{DocumentProcessingPanel-_Md-v2R2.js → DocumentProcessingPanel-DDq-mH17.js} +1 -1
- package/static/auth-callback/assets/{IndexLifecyclePanel-BbWwe23H.js → IndexLifecyclePanel-Cx4cjrdP.js} +1 -1
- package/static/auth-callback/assets/{IndexSubjectPanel-upH9MjU-.js → IndexSubjectPanel-DzSw8M9j.js} +1 -1
- package/static/auth-callback/assets/{ModelAssignmentsPanel-CVGH4t4g.js → ModelAssignmentsPanel-BsRxGc2p.js} +1 -1
- package/static/auth-callback/assets/ModelsPage-DEe1lo6Q.js +10 -0
- package/static/auth-callback/assets/{NetworkPage-Dcbrb1fd.js → NetworkPage-DBiEh1Oz.js} +1 -1
- package/static/auth-callback/assets/{PaneListHeader-DrY-1wBq.js → PaneListHeader-lEEgjyN9.js} +1 -1
- package/static/auth-callback/assets/{SearchIndexingPanel-Dw25y53v.js → SearchIndexingPanel-CZCUUGt6.js} +1 -1
- package/static/auth-callback/assets/{StatusSubjectPanel-BHlz9rNF.js → StatusSubjectPanel-21qdueMr.js} +1 -1
- package/static/auth-callback/assets/{StatusWorkspace-BH7exQP5.js → StatusWorkspace-R8RKsqVz.js} +1 -1
- package/static/auth-callback/assets/{SystemSettingsPage-BYLKnM9f.js → SystemSettingsPage-BQ4e-4Wy.js} +1 -1
- package/static/auth-callback/assets/{SystemSettingsSubjectPanel-DT34LKku.js → SystemSettingsSubjectPanel-BopXD7SE.js} +1 -1
- package/static/auth-callback/assets/{activity-DdULRcSI.js → activity-DSfmTGfY.js} +1 -1
- package/static/auth-callback/assets/ai-connections-C03UWyPh.js +1 -0
- package/static/auth-callback/assets/{auth-callback-D7l2P25I.js → auth-callback-DleaitAS.js} +91 -91
- package/static/auth-callback/assets/auth-callback-vPSC2d2F.css +1 -0
- package/static/auth-callback/assets/{badge-j4zjFnmg.js → badge-B0aLu1sl.js} +1 -1
- package/static/auth-callback/assets/{download-DGXoks01.js → download-D_bFwdYJ.js} +1 -1
- package/static/auth-callback/assets/{external-link-CvbgXXaI.js → external-link-DRfjTaQu.js} +1 -1
- package/static/auth-callback/assets/{index-LRWvKJ2i.js → index-CG23cLYQ.js} +1 -1
- package/static/auth-callback/assets/{index-BOK3j1dz.js → index-CyV9ejwx.js} +1 -1
- package/static/auth-callback/assets/{index-browser-C1Qpe0g4.js → index-browser-D70paxIg.js} +1 -1
- package/static/auth-callback/assets/{key-round-CmgS5UdQ.js → key-round-BjV_mVYH.js} +1 -1
- package/static/auth-callback/assets/{rotate-ccw-c4CSG6Rq.js → rotate-ccw-CKoOAfse.js} +1 -1
- package/static/auth-callback/assets/{skeleton-ZPMTiRSM.js → skeleton-BZNjZaHx.js} +1 -1
- package/static/auth-callback/assets/{waypoints-xjyBflU5.js → waypoints-DoaJVzZv.js} +1 -1
- package/static/auth-callback/assets/{workspace-layout-B9nj6F5J.js → workspace-layout-CaP46_ZR.js} +1 -1
- package/static/auth-callback/auth-callback.html +2 -2
- package/static/dashboard/assets/{AiConfigContext-BWOOSYNr.js → AiConfigContext-BSrR27yD.js} +1 -1
- package/static/dashboard/assets/{AiConfigPage-CG2kQ9Wy.js → AiConfigPage-D65Z2LET.js} +1 -1
- package/static/dashboard/assets/{DocumentProcessingPanel-B4VXajfj.js → DocumentProcessingPanel-DyTe7Nei.js} +1 -1
- package/static/dashboard/assets/{IndexLifecyclePanel-CWCy0CC8.js → IndexLifecyclePanel-fK8ZGlrL.js} +1 -1
- package/static/dashboard/assets/{IndexSubjectPanel-CSgyQnG_.js → IndexSubjectPanel-CZPGor7v.js} +1 -1
- package/static/dashboard/assets/{ModelAssignmentsPanel-CdGIGidT.js → ModelAssignmentsPanel-CfYn5LMQ.js} +1 -1
- package/static/dashboard/assets/ModelsPage-djlJsghe.js +25 -0
- package/static/dashboard/assets/{NetworkPage-COia9NCH.js → NetworkPage-Coj6GnPl.js} +1 -1
- package/static/dashboard/assets/{PaneListHeader-SZWyr602.js → PaneListHeader-CUuFNSsZ.js} +1 -1
- package/static/dashboard/assets/{SearchIndexingPanel-D6PZtG6k.js → SearchIndexingPanel-CyJLioXS.js} +1 -1
- package/static/dashboard/assets/{StatusSubjectPanel-CxRD4kv0.js → StatusSubjectPanel-Cv26GSVC.js} +1 -1
- package/static/dashboard/assets/{StatusWorkspace-D3PfmY9-.js → StatusWorkspace-CDjFrsmB.js} +1 -1
- package/static/dashboard/assets/{SystemSettingsPage-COg1MmMj.js → SystemSettingsPage-BuNOyNoa.js} +1 -1
- package/static/dashboard/assets/{SystemSettingsSubjectPanel-BSVVB9Ol.js → SystemSettingsSubjectPanel-B2dasdez.js} +1 -1
- package/static/dashboard/assets/{activity-BJJx5cN-.js → activity-D1d2j_4Z.js} +1 -1
- package/static/dashboard/assets/ai-connections-C03UWyPh.js +1 -0
- package/static/dashboard/assets/{badge-elmrFSCj.js → badge-BeAAv6sp.js} +1 -1
- package/static/dashboard/assets/{dashboard-IvQrNmru.js → dashboard-BnGrUGv8.js} +68 -68
- package/static/dashboard/assets/dashboard-vPSC2d2F.css +1 -0
- package/static/dashboard/assets/{download-C3FH2KzG.js → download-DfFGiZRn.js} +1 -1
- package/static/dashboard/assets/{external-link-Bx7F0uKb.js → external-link-IsjU_LC9.js} +1 -1
- package/static/dashboard/assets/{index-liRoRIP1.js → index-BWQTucPe.js} +1 -1
- package/static/dashboard/assets/{index-M4-kWiGz.js → index-Dj75OjnW.js} +1 -1
- package/static/dashboard/assets/{key-round-CXg0bKAj.js → key-round-BJ_9wmQ_.js} +1 -1
- package/static/dashboard/assets/{rotate-ccw-yxJKOBrX.js → rotate-ccw-D3JGiFbR.js} +1 -1
- package/static/dashboard/assets/{skeleton-x9jNGrQT.js → skeleton-CC4nc-z-.js} +1 -1
- package/static/dashboard/assets/{waypoints-CTKeRsGp.js → waypoints-h7sqFqiG.js} +1 -1
- package/static/dashboard/assets/{workspace-layout--YkgkS38.js → workspace-layout-ZAPTOHhs.js} +1 -1
- package/static/dashboard/dashboard.html +2 -2
- package/static/settings/assets/{AiConfigContext-CelxXFcQ.js → AiConfigContext-lGkWpvKP.js} +1 -1
- package/static/settings/assets/{AiConfigPage-BaP-EovM.js → AiConfigPage-BbZWIBZq.js} +1 -1
- package/static/settings/assets/{DocumentProcessingPanel-DzLZluFv.js → DocumentProcessingPanel-DvYdjT-V.js} +1 -1
- package/static/settings/assets/{IndexLifecyclePanel-Bzj1-_oI.js → IndexLifecyclePanel-W5OGQZj7.js} +1 -1
- package/static/settings/assets/{IndexSubjectPanel-B263QLUh.js → IndexSubjectPanel-g_Aa5soq.js} +1 -1
- package/static/settings/assets/{ModelAssignmentsPanel-BAdT4UR0.js → ModelAssignmentsPanel-COlMBKXj.js} +1 -1
- package/static/settings/assets/ModelsPage-mApKi-aw.js +10 -0
- package/static/settings/assets/{NetworkPage-BK6a7l4g.js → NetworkPage-BDT05JUq.js} +1 -1
- package/static/settings/assets/{PaneListHeader-BAS2ICPq.js → PaneListHeader-CByhNpMG.js} +1 -1
- package/static/settings/assets/{SearchIndexingPanel-Cb_p-NgW.js → SearchIndexingPanel-BFAyzXls.js} +1 -1
- package/static/settings/assets/{StatusSubjectPanel-CcjqUua6.js → StatusSubjectPanel-Bz_XZ3VU.js} +1 -1
- package/static/settings/assets/{StatusWorkspace-7Ibty3_k.js → StatusWorkspace-CeVypHcW.js} +1 -1
- package/static/settings/assets/{SystemSettingsPage-DPSz4dg4.js → SystemSettingsPage-Dh7cQIfx.js} +1 -1
- package/static/settings/assets/{SystemSettingsSubjectPanel-Cb4HkKT5.js → SystemSettingsSubjectPanel-CSnpciO0.js} +1 -1
- package/static/settings/assets/{activity-Qhsr5Pxu.js → activity-DXz3EfFN.js} +1 -1
- package/static/settings/assets/ai-connections-C03UWyPh.js +1 -0
- package/static/settings/assets/{badge-B1vf2qCl.js → badge-1ZA144i4.js} +1 -1
- package/static/settings/assets/{download-CYNt0aZ-.js → download-CUHimlYa.js} +1 -1
- package/static/settings/assets/{external-link-ClKb39NT.js → external-link-BYHSVj2Z.js} +1 -1
- package/static/settings/assets/{index-y8ew3MLM.js → index-DKPDMdW5.js} +1 -1
- package/static/settings/assets/{index-browser-DJhvgpNI.js → index-browser-BaEFz0e0.js} +1 -1
- package/static/settings/assets/{index-BgiIDSch.js → index-viSgSvbO.js} +1 -1
- package/static/settings/assets/{key-round-5d-DrJz-.js → key-round-COByPOg8.js} +1 -1
- package/static/settings/assets/{rotate-ccw-CHZY7kS5.js → rotate-ccw-BC2EN6cE.js} +1 -1
- package/static/settings/assets/{settings-D5VRWyx6.js → settings-DLH9XUoT.js} +86 -86
- package/static/settings/assets/settings-vPSC2d2F.css +1 -0
- package/static/settings/assets/{skeleton-FNabNxNE.js → skeleton-B0-FOhKL.js} +1 -1
- package/static/settings/assets/{waypoints-BnL_8ljo.js → waypoints-DGMsvqWo.js} +1 -1
- package/static/settings/assets/{workspace-layout-D5q3F59S.js → workspace-layout-B0SVrJIu.js} +1 -1
- package/static/settings/settings.html +2 -2
- package/static/auth-callback/assets/ModelsPage-BpkVFKbf.js +0 -10
- package/static/auth-callback/assets/ai-connections-Bm52UO7Y.js +0 -1
- package/static/auth-callback/assets/auth-callback-01yz-UDU.css +0 -1
- package/static/dashboard/assets/ModelsPage-Chd5UK0D.js +0 -25
- package/static/dashboard/assets/ai-connections-Bm52UO7Y.js +0 -1
- package/static/dashboard/assets/dashboard-01yz-UDU.css +0 -1
- package/static/settings/assets/ModelsPage-C0NFa1zq.js +0 -10
- package/static/settings/assets/ai-connections-Bm52UO7Y.js +0 -1
- package/static/settings/assets/settings-01yz-UDU.css +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as os from 'node:os';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
-
import { BaseAiClientConfigAdapter, looksLikePreviousXpodValue, normalizeV1Endpoint, parseJsonObject, profileApiKey, stringifyJson, } from './base-adapter.js';
|
|
4
|
+
import { BaseAiClientConfigAdapter, AiClientConfigError, looksLikePreviousXpodValue, contentFingerprint, normalizeV1Endpoint, parseJsonObject, profileApiKey, stringifyJson, } from './base-adapter.js';
|
|
5
5
|
const START = '# >>> xpod-ai-connections managed';
|
|
6
6
|
const END = '# <<< xpod-ai-connections managed';
|
|
7
7
|
export class CodexConfigAdapter extends BaseAiClientConfigAdapter {
|
|
@@ -9,50 +9,137 @@ export class CodexConfigAdapter extends BaseAiClientConfigAdapter {
|
|
|
9
9
|
const codexHome = path.join(options.homeDir ?? os.homedir(), '.codex');
|
|
10
10
|
const configPath = path.join(codexHome, 'config.toml');
|
|
11
11
|
const authPath = path.join(codexHome, 'auth.json');
|
|
12
|
-
super('codex', 'codex', [configPath
|
|
12
|
+
super('codex', 'codex', [configPath], codexHome);
|
|
13
13
|
this.configPath = configPath;
|
|
14
14
|
this.authPath = authPath;
|
|
15
|
+
this.catalogPath = path.join(codexHome, 'xpod-model-catalog.json');
|
|
16
|
+
this.nativeModelsPath = path.join(codexHome, 'models_cache.json');
|
|
15
17
|
}
|
|
16
18
|
normalizeProfile(profile) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
// Refreshing the menu must not select a different model on the user's behalf.
|
|
20
|
+
return profile.model?.trim() ? super.normalizeProfile(profile) : { ...profile, model: undefined };
|
|
21
|
+
}
|
|
22
|
+
async inspect() {
|
|
23
|
+
const inspection = await super.inspect();
|
|
24
|
+
const state = await this.readState();
|
|
25
|
+
if (!state?.files.some((file) => file.path === this.catalogPath))
|
|
26
|
+
return inspection;
|
|
27
|
+
await this.rejectSymlink(this.catalogPath);
|
|
28
|
+
const catalog = await this.readOptional(this.catalogPath);
|
|
29
|
+
if (catalog !== undefined && contentFingerprint(catalog) === state.projectionHashes?.[this.catalogPath]) {
|
|
30
|
+
return { ...inspection, configPaths: [...inspection.configPaths, this.catalogPath] };
|
|
31
|
+
}
|
|
32
|
+
const withoutFingerprint = { ...inspection };
|
|
33
|
+
delete withoutFingerprint.apiKeyFingerprint;
|
|
34
|
+
return { ...withoutFingerprint, projectionMatches: false, configPaths: [...inspection.configPaths, this.catalogPath] };
|
|
35
|
+
}
|
|
36
|
+
async plan(profile) {
|
|
37
|
+
const plan = await super.plan(profile);
|
|
38
|
+
const state = await this.readState();
|
|
39
|
+
const legacyAuth = state?.files.find((file) => file.path === this.authPath);
|
|
40
|
+
if (!legacyAuth)
|
|
41
|
+
return plan;
|
|
42
|
+
await this.rejectSymlink(this.authPath);
|
|
43
|
+
const current = await this.readOptional(this.authPath);
|
|
44
|
+
if (!this.ownsLegacyAuth(current, state?.apiKeyFingerprint))
|
|
45
|
+
return plan;
|
|
46
|
+
let original;
|
|
47
|
+
if (legacyAuth.existed) {
|
|
48
|
+
if (!legacyAuth.backupPath)
|
|
49
|
+
throw new Error('Missing Codex auth.json backup metadata');
|
|
50
|
+
await this.rejectSymlink(legacyAuth.backupPath);
|
|
51
|
+
original = await this.readOptional(legacyAuth.backupPath);
|
|
52
|
+
if (original === undefined)
|
|
53
|
+
throw new Error('Missing Codex auth.json backup; refusing to guess login state');
|
|
19
54
|
}
|
|
20
|
-
|
|
55
|
+
// Migrate auth in the same transaction without keeping it in the new ownership state.
|
|
56
|
+
plan.writes.splice(plan.writes.length - 1, 0, {
|
|
57
|
+
path: this.authPath,
|
|
58
|
+
content: restoreLegacyAuth(current, original, legacyAuth.existed),
|
|
59
|
+
expectedContentHash: contentFingerprint(current),
|
|
60
|
+
});
|
|
61
|
+
return plan;
|
|
62
|
+
}
|
|
63
|
+
ownsLegacyAuth(current, fingerprint) {
|
|
64
|
+
if (!fingerprint || current === undefined)
|
|
65
|
+
return false;
|
|
66
|
+
const auth = parseJsonObject(current, 'Codex auth.json');
|
|
67
|
+
return typeof auth.OPENAI_API_KEY === 'string' && contentFingerprint(auth.OPENAI_API_KEY) === fingerprint;
|
|
21
68
|
}
|
|
22
69
|
async project(profile, current) {
|
|
23
|
-
const
|
|
70
|
+
const baseContent = current.get(this.configPath) ?? '';
|
|
71
|
+
let nativeModels = [];
|
|
72
|
+
if (profile.activeModels?.length) {
|
|
73
|
+
const nativeCatalog = await this.readOptional(this.nativeModelsPath);
|
|
74
|
+
if (nativeCatalog !== undefined)
|
|
75
|
+
nativeModels = readCatalogModels(nativeCatalog, 'Codex native model cache');
|
|
76
|
+
}
|
|
77
|
+
let catalog = codexModelCatalog(profile.activeModels, nativeModels);
|
|
78
|
+
const state = await this.readState();
|
|
79
|
+
const ownsCatalog = state?.files.some((file) => file.path === this.catalogPath);
|
|
80
|
+
if (catalog !== undefined || ownsCatalog) {
|
|
81
|
+
await this.rejectSymlink(this.catalogPath);
|
|
82
|
+
const existingCatalog = await this.readOptional(this.catalogPath);
|
|
83
|
+
current.set(this.catalogPath, existingCatalog);
|
|
84
|
+
catalog ?? (catalog = existingCatalog);
|
|
85
|
+
if (catalog === undefined)
|
|
86
|
+
throw new Error('Codex model catalog is missing; refresh with the current Gateway models');
|
|
87
|
+
}
|
|
88
|
+
const unmanagedConfig = this.removeManagedBlock(baseContent);
|
|
89
|
+
if (hasUnmanagedModelProviderTable(unmanagedConfig)) {
|
|
90
|
+
throw new Error('Codex config.toml already defines [model_providers.xpod] outside managed block');
|
|
91
|
+
}
|
|
92
|
+
const config = stripRootLevelModelKeys(unmanagedConfig).trim();
|
|
24
93
|
const model = profile.model?.trim();
|
|
25
|
-
const
|
|
94
|
+
const rootBlock = [
|
|
26
95
|
START,
|
|
27
96
|
'model_provider = "xpod"',
|
|
28
|
-
...(model ? [`model = ${JSON.stringify(model)}`] :
|
|
97
|
+
...(model ? [`model = ${JSON.stringify(model)}`] : rootLevelLines(baseContent, 'model')),
|
|
98
|
+
...(catalog !== undefined ? [`model_catalog_json = ${JSON.stringify(this.catalogPath)}`] : rootLevelLines(baseContent, 'model_catalog_json')),
|
|
99
|
+
END,
|
|
100
|
+
].join('\n');
|
|
101
|
+
const providerBlock = [
|
|
102
|
+
START,
|
|
29
103
|
'',
|
|
30
104
|
'[model_providers.xpod]',
|
|
31
105
|
'name = "Xpod AI Connection"',
|
|
32
106
|
`base_url = ${JSON.stringify(normalizeV1Endpoint(profile.endpoint))}`,
|
|
33
107
|
'wire_api = "responses"',
|
|
34
|
-
'requires_openai_auth =
|
|
108
|
+
'requires_openai_auth = false',
|
|
109
|
+
`experimental_bearer_token = ${JSON.stringify(profileApiKey(profile))}`,
|
|
35
110
|
END,
|
|
36
111
|
'',
|
|
37
112
|
].join('\n');
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
[this.authPath, stringifyJson(auth)],
|
|
43
|
-
]);
|
|
113
|
+
const projected = new Map([[this.configPath, `${rootBlock}\n\n${config}${config ? '\n\n' : ''}${providerBlock}`]]);
|
|
114
|
+
if (catalog !== undefined)
|
|
115
|
+
projected.set(this.catalogPath, catalog);
|
|
116
|
+
return projected;
|
|
44
117
|
}
|
|
45
118
|
async verifyProjection(profile) {
|
|
46
119
|
try {
|
|
47
120
|
const config = await fs.promises.readFile(this.configPath, 'utf8');
|
|
48
|
-
const auth = parseJsonObject(await fs.promises.readFile(this.authPath, 'utf8'), 'Codex auth.json');
|
|
49
121
|
const modelMatches = profile.model
|
|
50
|
-
? config.
|
|
51
|
-
:
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
122
|
+
? rootLevelLines(config, 'model').some((line) => line.trim() === `model = ${JSON.stringify(profile.model)}`)
|
|
123
|
+
: true;
|
|
124
|
+
const provider = tableLines(config, 'model_providers.xpod');
|
|
125
|
+
const state = await this.readState();
|
|
126
|
+
const ownsCatalog = state?.files.some((file) => file.path === this.catalogPath);
|
|
127
|
+
let catalogMatches = true;
|
|
128
|
+
if (profile.activeModels !== undefined || ownsCatalog) {
|
|
129
|
+
const catalog = await fs.promises.readFile(this.catalogPath, 'utf8');
|
|
130
|
+
// Validate the applied snapshot, not a cache Codex may have refreshed since planning.
|
|
131
|
+
const expectedCatalog = codexModelCatalog(profile.activeModels, readCatalogModels(catalog, 'Codex model catalog'));
|
|
132
|
+
catalogMatches = rootLevelLines(config, 'model_catalog_json')
|
|
133
|
+
.includes(`model_catalog_json = ${JSON.stringify(this.catalogPath)}`) &&
|
|
134
|
+
contentFingerprint(catalog) === state?.projectionHashes?.[this.catalogPath] &&
|
|
135
|
+
(expectedCatalog === undefined || catalog === expectedCatalog);
|
|
136
|
+
}
|
|
137
|
+
const ok = rootLevelLines(config, 'model_provider').some((line) => line.trim() === 'model_provider = "xpod"') &&
|
|
138
|
+
modelMatches && catalogMatches &&
|
|
139
|
+
provider.includes(`base_url = ${JSON.stringify(normalizeV1Endpoint(profile.endpoint))}`) &&
|
|
140
|
+
provider.includes('wire_api = "responses"') &&
|
|
141
|
+
provider.includes('requires_openai_auth = false') &&
|
|
142
|
+
provider.includes(`experimental_bearer_token = ${JSON.stringify(profileApiKey(profile))}`);
|
|
56
143
|
return ok ? { ok: true } : { ok: false, reason: 'Codex projection differs from the requested connection' };
|
|
57
144
|
}
|
|
58
145
|
catch (error) {
|
|
@@ -60,17 +147,18 @@ export class CodexConfigAdapter extends BaseAiClientConfigAdapter {
|
|
|
60
147
|
}
|
|
61
148
|
}
|
|
62
149
|
async restoreFile(filePath, current, original, originallyExisted) {
|
|
63
|
-
if (filePath === this.
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
!looksLikePreviousXpodValue(before.OPENAI_API_KEY)) {
|
|
68
|
-
restored.OPENAI_API_KEY = before.OPENAI_API_KEY;
|
|
150
|
+
if (filePath === this.catalogPath) {
|
|
151
|
+
const state = await this.readState();
|
|
152
|
+
if (current === undefined || contentFingerprint(current) !== state?.projectionHashes?.[this.catalogPath]) {
|
|
153
|
+
throw new Error('Codex model catalog changed since projection was applied; refusing restore to avoid data loss');
|
|
69
154
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
155
|
+
return originallyExisted ? original : null;
|
|
156
|
+
}
|
|
157
|
+
if (filePath === this.authPath) {
|
|
158
|
+
const state = await this.readState();
|
|
159
|
+
if (!this.ownsLegacyAuth(current, state?.apiKeyFingerprint))
|
|
160
|
+
return current ?? null;
|
|
161
|
+
return restoreLegacyAuth(current, original, originallyExisted);
|
|
74
162
|
}
|
|
75
163
|
let restored = this.removeManagedBlock(current ?? '').trim();
|
|
76
164
|
const hasCurrentProvider = hasRootLevelKey(restored, 'model_provider');
|
|
@@ -86,17 +174,51 @@ export class CodexConfigAdapter extends BaseAiClientConfigAdapter {
|
|
|
86
174
|
restored = insertRootLevelLines(restored, originalRoot);
|
|
87
175
|
}
|
|
88
176
|
}
|
|
177
|
+
if (!hasRootLevelKey(restored, 'model_catalog_json') && !originalHasXpodProjection) {
|
|
178
|
+
const originalCatalog = rootLevelLines(originalContent, 'model_catalog_json');
|
|
179
|
+
if (originalCatalog.length > 0)
|
|
180
|
+
restored = insertRootLevelLines(restored, originalCatalog);
|
|
181
|
+
}
|
|
89
182
|
return !originallyExisted && !restored ? null : `${restored}${restored ? '\n' : ''}`;
|
|
90
183
|
}
|
|
184
|
+
async currentApiKeyFingerprint(current) {
|
|
185
|
+
const token = tableLines(current.get(this.configPath) ?? '', 'model_providers.xpod')
|
|
186
|
+
.find((line) => /^experimental_bearer_token\s*=/u.test(line));
|
|
187
|
+
if (token) {
|
|
188
|
+
const value = JSON.parse(token.slice(token.indexOf('=') + 1).trim());
|
|
189
|
+
return typeof value === 'string' ? contentFingerprint(value) : undefined;
|
|
190
|
+
}
|
|
191
|
+
// Old ownership records may still manage the global auth file until migration.
|
|
192
|
+
if (current.has(this.authPath)) {
|
|
193
|
+
const auth = parseJsonObject(current.get(this.authPath), 'Codex auth.json');
|
|
194
|
+
return typeof auth.OPENAI_API_KEY === 'string' ? contentFingerprint(auth.OPENAI_API_KEY) : undefined;
|
|
195
|
+
}
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
91
198
|
removeManagedBlock(content) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
199
|
+
let start = content.indexOf(START);
|
|
200
|
+
while (start >= 0) {
|
|
201
|
+
const end = content.indexOf(END, start);
|
|
202
|
+
if (end < 0)
|
|
203
|
+
throw new Error('Codex xpod managed block is incomplete');
|
|
204
|
+
content = `${content.slice(0, start)}${content.slice(end + END.length)}`;
|
|
205
|
+
start = content.indexOf(START);
|
|
206
|
+
}
|
|
207
|
+
return content;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function tableLines(content, table) {
|
|
211
|
+
const lines = [];
|
|
212
|
+
let selected = false;
|
|
213
|
+
for (const line of content.split('\n')) {
|
|
214
|
+
if (isTomlTableHeader(line)) {
|
|
215
|
+
selected = line.trim() === `[${table}]`;
|
|
216
|
+
}
|
|
217
|
+
else if (selected) {
|
|
218
|
+
lines.push(line.trim());
|
|
219
|
+
}
|
|
99
220
|
}
|
|
221
|
+
return lines;
|
|
100
222
|
}
|
|
101
223
|
/**
|
|
102
224
|
* Remove only the root-level keys owned by the Codex projection. A TOML key
|
|
@@ -149,10 +271,153 @@ function insertRootLevelLines(content, lines) {
|
|
|
149
271
|
function isTomlTableHeader(line) {
|
|
150
272
|
return /^\s*\[\[?[^\]]+\]\]?\s*(?:#.*)?$/u.test(line);
|
|
151
273
|
}
|
|
274
|
+
function hasUnmanagedModelProviderTable(content) {
|
|
275
|
+
for (const line of content.split('\n')) {
|
|
276
|
+
if (!isTomlTableHeader(line) || /^\s*\[\[/.test(line)) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (normalizeTomlTableName(line) === 'model_providers.xpod') {
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
function normalizeTomlTableName(line) {
|
|
286
|
+
const match = /^\s*\[\s*([^\]]+)\s*\]\s*(?:#.*)?$/u.exec(line);
|
|
287
|
+
if (!match)
|
|
288
|
+
return undefined;
|
|
289
|
+
const rawName = match[1].trim();
|
|
290
|
+
const segments = parseTomlTableSegments(rawName);
|
|
291
|
+
return segments.length === 1 ? undefined : normalizeTomlDottedKey(rawName);
|
|
292
|
+
}
|
|
293
|
+
function normalizeTomlDottedKey(content) {
|
|
294
|
+
const parts = parseTomlTableSegments(content);
|
|
295
|
+
return parts.map((part) => removeTomlQuotes(part)).join('.');
|
|
296
|
+
}
|
|
297
|
+
function parseTomlTableSegments(input) {
|
|
298
|
+
const parts = [];
|
|
299
|
+
let current = '';
|
|
300
|
+
let quote = null;
|
|
301
|
+
for (let i = 0; i < input.length; i++) {
|
|
302
|
+
const char = input[i];
|
|
303
|
+
if (quote) {
|
|
304
|
+
if (char === quote) {
|
|
305
|
+
quote = null;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
current += char;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (char === '"' || char === "'") {
|
|
312
|
+
quote = char;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (char === '.') {
|
|
316
|
+
parts.push(current.trim());
|
|
317
|
+
current = '';
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
current += char;
|
|
321
|
+
}
|
|
322
|
+
parts.push(current.trim());
|
|
323
|
+
return parts.map((part) => part.trim()).filter((part) => part.length > 0);
|
|
324
|
+
}
|
|
325
|
+
function removeTomlQuotes(value) {
|
|
326
|
+
if (value.length === 0)
|
|
327
|
+
return value;
|
|
328
|
+
const first = value[0];
|
|
329
|
+
const last = value[value.length - 1];
|
|
330
|
+
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
331
|
+
return value.slice(1, -1);
|
|
332
|
+
}
|
|
333
|
+
return value;
|
|
334
|
+
}
|
|
152
335
|
function isRootModelKey(line) {
|
|
153
336
|
return rootModelKey(line) !== undefined;
|
|
154
337
|
}
|
|
155
338
|
function rootModelKey(line) {
|
|
156
|
-
const match = /^\s*(model_provider|model)\s*=/.exec(line);
|
|
339
|
+
const match = /^\s*(model_provider|model|model_catalog_json)\s*=/.exec(line);
|
|
157
340
|
return match?.[1];
|
|
158
341
|
}
|
|
342
|
+
/** Revert only the two fields the old adapter owned; preserve refreshed OAuth state. */
|
|
343
|
+
function restoreLegacyAuth(current, original, originallyExisted) {
|
|
344
|
+
const restored = parseJsonObject(current, 'Codex auth.json');
|
|
345
|
+
const before = parseJsonObject(original, 'Codex original auth.json');
|
|
346
|
+
if (restored.auth_mode === 'apikey') {
|
|
347
|
+
if (Object.prototype.hasOwnProperty.call(before, 'auth_mode')) {
|
|
348
|
+
restored.auth_mode = before.auth_mode;
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
delete restored.auth_mode;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (Object.prototype.hasOwnProperty.call(before, 'OPENAI_API_KEY') &&
|
|
355
|
+
!looksLikePreviousXpodValue(before.OPENAI_API_KEY)) {
|
|
356
|
+
restored.OPENAI_API_KEY = before.OPENAI_API_KEY;
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
delete restored.OPENAI_API_KEY;
|
|
360
|
+
}
|
|
361
|
+
return !originallyExisted && Object.keys(restored).length === 0 ? null : stringifyJson(restored);
|
|
362
|
+
}
|
|
363
|
+
/** Gateway ids are already routable; provider labels must not rewrite their slugs. */
|
|
364
|
+
function codexModelCatalog(models, nativeModels = []) {
|
|
365
|
+
if (models === undefined)
|
|
366
|
+
return undefined;
|
|
367
|
+
const available = models.filter((model) => model.availability === undefined || model.availability === 'available');
|
|
368
|
+
if (available.length === 0)
|
|
369
|
+
throw new AiClientConfigError('model_catalog_empty');
|
|
370
|
+
const seen = new Set();
|
|
371
|
+
return stringifyJson({ models: available.map((model, index) => {
|
|
372
|
+
const id = model.id.trim();
|
|
373
|
+
if (!id || seen.has(id))
|
|
374
|
+
throw new Error('Codex model catalog requires unique, non-empty Gateway model ids');
|
|
375
|
+
seen.add(id);
|
|
376
|
+
const native = nativeModels.find((entry) => entry.slug === id);
|
|
377
|
+
const nativeModalities = Array.isArray(native?.input_modalities) ? native.input_modalities : ['text'];
|
|
378
|
+
const declaredModalities = model.inputModalities?.filter((value) => value === 'text' || value === 'image');
|
|
379
|
+
const modalities = declaredModalities?.filter((value) => !native || nativeModalities.includes(value)) ?? nativeModalities;
|
|
380
|
+
const contextWindow = Number.isSafeInteger(model.contextWindow) && model.contextWindow > 0 ? model.contextWindow : undefined;
|
|
381
|
+
const reasoning = codexReasoningLevels(model);
|
|
382
|
+
return {
|
|
383
|
+
slug: id,
|
|
384
|
+
description: 'Xpod Gateway model',
|
|
385
|
+
supported_reasoning_levels: reasoning.levels,
|
|
386
|
+
...(reasoning.defaultLevel ? { default_reasoning_level: reasoning.defaultLevel } : {}),
|
|
387
|
+
shell_type: 'shell_command',
|
|
388
|
+
support_verbosity: false,
|
|
389
|
+
supports_parallel_tool_calls: false,
|
|
390
|
+
truncation_policy: { mode: 'tokens', limit: 10000 },
|
|
391
|
+
experimental_supported_tools: [],
|
|
392
|
+
base_instructions: 'You are a coding assistant.',
|
|
393
|
+
...native,
|
|
394
|
+
display_name: model.displayName?.trim() || native?.display_name || id,
|
|
395
|
+
visibility: 'list',
|
|
396
|
+
supported_in_api: true,
|
|
397
|
+
priority: index,
|
|
398
|
+
input_modalities: modalities.length ? [...new Set(modalities)] : ['text'],
|
|
399
|
+
...(contextWindow !== undefined ? { context_window: contextWindow } : {}),
|
|
400
|
+
...(contextWindow !== undefined && typeof native?.max_context_window === 'number'
|
|
401
|
+
? { max_context_window: Math.min(contextWindow, native.max_context_window) } : {}),
|
|
402
|
+
};
|
|
403
|
+
}) });
|
|
404
|
+
}
|
|
405
|
+
function codexReasoningLevels(model) {
|
|
406
|
+
if (model.provider !== 'deepseek' || !model.capabilities?.includes('reasoning'))
|
|
407
|
+
return { levels: [] };
|
|
408
|
+
return {
|
|
409
|
+
levels: [
|
|
410
|
+
{ effort: 'low', description: 'DeepSeek low reasoning depth' },
|
|
411
|
+
{ effort: 'high', description: 'DeepSeek standard reasoning depth' },
|
|
412
|
+
{ effort: 'max', description: 'DeepSeek maximum reasoning depth' },
|
|
413
|
+
],
|
|
414
|
+
defaultLevel: 'high',
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
function readCatalogModels(content, label) {
|
|
418
|
+
const catalog = parseJsonObject(content, label);
|
|
419
|
+
if (!Array.isArray(catalog.models) || catalog.models.some((model) => !model || typeof model !== 'object' || Array.isArray(model) || typeof model.slug !== 'string')) {
|
|
420
|
+
throw new Error(`${label} must contain a models array with string slugs`);
|
|
421
|
+
}
|
|
422
|
+
return catalog.models;
|
|
423
|
+
}
|