@carlonicora/nextjs-jsonapi 3.0.1 → 3.1.1

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/dist/AbstractApiData-DtitRWPz.d.ts +47 -0
  2. package/dist/AbstractApiData-XLhBP5Tl.d.mts +47 -0
  3. package/dist/{BlockNoteEditor-G4XNDV2O.mjs → BlockNoteEditor-FIMPVA7K.mjs} +5 -5
  4. package/dist/{BlockNoteEditor-3IZ7PMFM.js → BlockNoteEditor-GJPTMKS7.js} +20 -20
  5. package/dist/{BlockNoteEditor-3IZ7PMFM.js.map → BlockNoteEditor-GJPTMKS7.js.map} +1 -1
  6. package/dist/{JsonApiRequest-3UWVIIFL.mjs → JsonApiRequest-2OQCZAK6.mjs} +2 -2
  7. package/dist/JsonApiRequest-6TYNBVUL.js +26 -0
  8. package/dist/{JsonApiRequest-QRU3IPIU.js.map → JsonApiRequest-6TYNBVUL.js.map} +1 -1
  9. package/dist/ai-connection.module-28f-hux0.d.mts +255 -0
  10. package/dist/ai-connection.module-Bqwh_DnR.d.ts +255 -0
  11. package/dist/billing/index.js +399 -399
  12. package/dist/billing/index.mjs +4 -4
  13. package/dist/{chunk-TWXKAY34.mjs → chunk-CGJW7ZZM.mjs} +310 -5
  14. package/dist/chunk-CGJW7ZZM.mjs.map +1 -0
  15. package/dist/{chunk-E5YA5Z5X.mjs → chunk-CMGXH5SA.mjs} +2 -1
  16. package/dist/chunk-CMGXH5SA.mjs.map +1 -0
  17. package/dist/{chunk-5GVBUTS4.js → chunk-E5FWGOP5.js} +1454 -794
  18. package/dist/chunk-E5FWGOP5.js.map +1 -0
  19. package/dist/{chunk-JKGEJGSD.js → chunk-H7DOSB7W.js} +9 -9
  20. package/dist/{chunk-JKGEJGSD.js.map → chunk-H7DOSB7W.js.map} +1 -1
  21. package/dist/{chunk-FPO4DLZN.js → chunk-PFAZPP54.js} +2 -1
  22. package/dist/chunk-PFAZPP54.js.map +1 -0
  23. package/dist/{chunk-WCX4CLRJ.mjs → chunk-VUMM7V5G.mjs} +1740 -1080
  24. package/dist/chunk-VUMM7V5G.mjs.map +1 -0
  25. package/dist/{chunk-53B6NPGA.js → chunk-YUXCJ35V.js} +377 -72
  26. package/dist/chunk-YUXCJ35V.js.map +1 -0
  27. package/dist/{chunk-PHEFWL6L.mjs → chunk-ZMYZFQN3.mjs} +3 -3
  28. package/dist/client/index.js +5 -5
  29. package/dist/client/index.mjs +4 -4
  30. package/dist/components/index.d.mts +122 -4
  31. package/dist/components/index.d.ts +122 -4
  32. package/dist/components/index.js +31 -5
  33. package/dist/components/index.js.map +1 -1
  34. package/dist/components/index.mjs +30 -4
  35. package/dist/{config-BIjrg5wd.d.ts → config-B6UB7j69.d.ts} +1 -46
  36. package/dist/{config-BRUjtCd1.d.mts → config-n-ZpPmOL.d.mts} +1 -46
  37. package/dist/contexts/index.js +5 -5
  38. package/dist/contexts/index.mjs +4 -4
  39. package/dist/core/index.d.mts +5 -4
  40. package/dist/core/index.d.ts +5 -4
  41. package/dist/core/index.js +11 -3
  42. package/dist/core/index.js.map +1 -1
  43. package/dist/core/index.mjs +10 -2
  44. package/dist/features/help/index.js +38 -38
  45. package/dist/features/help/index.mjs +4 -4
  46. package/dist/features/tokenusage/index.d.mts +2 -2
  47. package/dist/features/tokenusage/index.d.ts +2 -2
  48. package/dist/features/tokenusage/index.js +102 -102
  49. package/dist/features/tokenusage/index.mjs +4 -4
  50. package/dist/index.d.mts +3 -3
  51. package/dist/index.d.ts +3 -3
  52. package/dist/index.js +12 -4
  53. package/dist/index.js.map +1 -1
  54. package/dist/index.mjs +11 -3
  55. package/dist/server/index.js +12 -12
  56. package/dist/server/index.mjs +2 -2
  57. package/package.json +1 -1
  58. package/src/components/index.ts +1 -0
  59. package/src/components/tables/ContentListTable.tsx +1 -1
  60. package/src/core/index.ts +5 -0
  61. package/src/core/registry/ModuleRegistry.ts +2 -0
  62. package/src/core/utils/translateResponse.ts +6 -0
  63. package/src/features/administration/components/AdminIndexContainer.tsx +9 -2
  64. package/src/features/ai-connection/ai-connection.module.ts +14 -0
  65. package/src/features/ai-connection/components/containers/AiConnectionsContainer.tsx +109 -0
  66. package/src/features/ai-connection/components/forms/AiConnectionDeleter.tsx +58 -0
  67. package/src/features/ai-connection/components/forms/AiConnectionEditor.tsx +329 -0
  68. package/src/features/ai-connection/components/lists/AiConnectionTypeCard.tsx +223 -0
  69. package/src/features/ai-connection/contexts/AiConnectionsContext.tsx +111 -0
  70. package/src/features/ai-connection/data/__tests__/ai-connection.test.ts +202 -0
  71. package/src/features/ai-connection/data/ai-connection.fields.ts +6 -0
  72. package/src/features/ai-connection/data/ai-connection.interface.ts +87 -0
  73. package/src/features/ai-connection/data/ai-connection.service.ts +90 -0
  74. package/src/features/ai-connection/data/ai-connection.ts +244 -0
  75. package/src/features/ai-connection/data/index.ts +4 -0
  76. package/src/features/ai-connection/i18n-keys.ts +66 -0
  77. package/src/features/ai-connection/index.ts +11 -0
  78. package/src/features/index.ts +1 -0
  79. package/dist/AuthComponent-Cik96ElA.d.mts +0 -56
  80. package/dist/AuthComponent-DTTKu3VK.d.ts +0 -56
  81. package/dist/JsonApiRequest-QRU3IPIU.js +0 -26
  82. package/dist/chunk-53B6NPGA.js.map +0 -1
  83. package/dist/chunk-5GVBUTS4.js.map +0 -1
  84. package/dist/chunk-E5YA5Z5X.mjs.map +0 -1
  85. package/dist/chunk-FPO4DLZN.js.map +0 -1
  86. package/dist/chunk-TWXKAY34.mjs.map +0 -1
  87. package/dist/chunk-WCX4CLRJ.mjs.map +0 -1
  88. /package/dist/{BlockNoteEditor-G4XNDV2O.mjs.map → BlockNoteEditor-FIMPVA7K.mjs.map} +0 -0
  89. /package/dist/{JsonApiRequest-3UWVIIFL.mjs.map → JsonApiRequest-2OQCZAK6.mjs.map} +0 -0
  90. /package/dist/{chunk-PHEFWL6L.mjs.map → chunk-ZMYZFQN3.mjs.map} +0 -0
@@ -0,0 +1,202 @@
1
+ import { beforeAll, describe, expect, it } from "vitest";
2
+ import { ApiRequestDataTypeInterface } from "../../../../core/interfaces/ApiRequestDataTypeInterface";
3
+ import { ModuleRegistry } from "../../../../core/registry/ModuleRegistry";
4
+ import { AiConnection } from "../ai-connection";
5
+ import { AiConnectionInput } from "../ai-connection.interface";
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // Setup — the model resolves its JSON:API types through the real
9
+ // ModuleRegistry, so both modules it names must be registered.
10
+ // ---------------------------------------------------------------------------
11
+ const aiConnectionModule: ApiRequestDataTypeInterface = {
12
+ name: "ai-connections",
13
+ model: AiConnection,
14
+ } as any;
15
+
16
+ const companyModule: ApiRequestDataTypeInterface = {
17
+ name: "companies",
18
+ model: class {},
19
+ } as any;
20
+
21
+ const registerIfAbsent = (key: string, module: ApiRequestDataTypeInterface) => {
22
+ try {
23
+ ModuleRegistry.get(key as any);
24
+ } catch {
25
+ ModuleRegistry.register(key, module);
26
+ }
27
+ };
28
+
29
+ beforeAll(() => {
30
+ registerIfAbsent("AiConnection", aiConnectionModule);
31
+ registerIfAbsent("Company", companyModule);
32
+ });
33
+
34
+ function makeHydratedData(attributeOverrides: Record<string, any> = {}) {
35
+ return {
36
+ jsonApi: {
37
+ id: "connection-1",
38
+ type: "ai-connections",
39
+ attributes: {
40
+ name: "Primary OpenRouter",
41
+ connectionType: "ai",
42
+ provider: "openrouter",
43
+ position: 0,
44
+ enabled: true,
45
+ model: "anthropic/claude-sonnet-4",
46
+ url: "https://openrouter.ai/api/v1",
47
+ region: "eu",
48
+ instance: "my-instance",
49
+ apiVersion: "2024-08-01",
50
+ allowFallbacks: true,
51
+ reasoningEffort: "medium",
52
+ maxOutputTokens: 8192,
53
+ dimensions: 1536,
54
+ inputCostPer1MTokens: 3,
55
+ outputCostPer1MTokens: 15,
56
+ cachedInputCostPer1MTokens: 0.3,
57
+ costPerMinute: 0.02,
58
+ costPerPage: 0.05,
59
+ directUrl: "https://example.test/audio",
60
+ language: "en",
61
+ directFormat: "json",
62
+ directProvider: "groq",
63
+ hasApiKey: true,
64
+ hasGoogleCredentials: false,
65
+ companyId: "company-1",
66
+ ...attributeOverrides,
67
+ },
68
+ meta: {},
69
+ relationships: {},
70
+ },
71
+ included: [] as any[],
72
+ };
73
+ }
74
+
75
+ function makeInput(overrides: Partial<AiConnectionInput> = {}): AiConnectionInput {
76
+ return {
77
+ id: "connection-1",
78
+ name: "Primary OpenRouter",
79
+ connectionType: "ai",
80
+ provider: "openrouter",
81
+ position: 0,
82
+ enabled: true,
83
+ model: "anthropic/claude-sonnet-4",
84
+ ...overrides,
85
+ };
86
+ }
87
+
88
+ describe("AiConnection.rehydrate", () => {
89
+ it("maps every serialized attribute, including the secret presence flags", () => {
90
+ const connection = new AiConnection().rehydrate(makeHydratedData());
91
+
92
+ expect(connection.id).toBe("connection-1");
93
+ expect(connection.type).toBe("ai-connections");
94
+ expect(connection.name).toBe("Primary OpenRouter");
95
+ expect(connection.connectionType).toBe("ai");
96
+ expect(connection.provider).toBe("openrouter");
97
+ expect(connection.position).toBe(0);
98
+ expect(connection.enabled).toBe(true);
99
+ expect(connection.model).toBe("anthropic/claude-sonnet-4");
100
+ expect(connection.url).toBe("https://openrouter.ai/api/v1");
101
+ expect(connection.region).toBe("eu");
102
+ expect(connection.instance).toBe("my-instance");
103
+ expect(connection.apiVersion).toBe("2024-08-01");
104
+ expect(connection.allowFallbacks).toBe(true);
105
+ expect(connection.reasoningEffort).toBe("medium");
106
+ expect(connection.maxOutputTokens).toBe(8192);
107
+ expect(connection.dimensions).toBe(1536);
108
+ expect(connection.inputCostPer1MTokens).toBe(3);
109
+ expect(connection.outputCostPer1MTokens).toBe(15);
110
+ expect(connection.cachedInputCostPer1MTokens).toBe(0.3);
111
+ expect(connection.costPerMinute).toBe(0.02);
112
+ expect(connection.costPerPage).toBe(0.05);
113
+ expect(connection.directUrl).toBe("https://example.test/audio");
114
+ expect(connection.language).toBe("en");
115
+ expect(connection.directFormat).toBe("json");
116
+ expect(connection.directProvider).toBe("groq");
117
+ expect(connection.hasApiKey).toBe(true);
118
+ expect(connection.hasGoogleCredentials).toBe(false);
119
+ expect(connection.companyId).toBe("company-1");
120
+ });
121
+
122
+ it("treats a missing companyId as a global connection and defaults the flags", () => {
123
+ const connection = new AiConnection().rehydrate(
124
+ makeHydratedData({ companyId: undefined, hasApiKey: undefined, hasGoogleCredentials: undefined }),
125
+ );
126
+
127
+ expect(connection.companyId).toBeUndefined();
128
+ expect(connection.hasApiKey).toBe(false);
129
+ expect(connection.hasGoogleCredentials).toBe(false);
130
+ });
131
+ });
132
+
133
+ describe("AiConnection.createJsonApi", () => {
134
+ it("serializes the non-secret attributes under the ai-connections type", () => {
135
+ const payload = new AiConnection().createJsonApi(makeInput());
136
+
137
+ expect(payload.data.type).toBe("ai-connections");
138
+ expect(payload.data.id).toBe("connection-1");
139
+ expect(payload.data.attributes.name).toBe("Primary OpenRouter");
140
+ expect(payload.data.attributes.connectionType).toBe("ai");
141
+ expect(payload.data.attributes.provider).toBe("openrouter");
142
+ expect(payload.data.attributes.position).toBe(0);
143
+ expect(payload.data.attributes.enabled).toBe(true);
144
+ expect(payload.data.attributes.model).toBe("anthropic/claude-sonnet-4");
145
+ // Undefined optional attributes must not be emitted at all.
146
+ expect("url" in payload.data.attributes).toBe(false);
147
+ });
148
+
149
+ it("omits the secrets when they are undefined", () => {
150
+ const payload = new AiConnection().createJsonApi(makeInput());
151
+
152
+ expect("apiKey" in payload.data.attributes).toBe(false);
153
+ expect("googleCredentialsBase64" in payload.data.attributes).toBe(false);
154
+ });
155
+
156
+ it('omits the secrets when they are "" — blank means keep the stored value', () => {
157
+ const payload = new AiConnection().createJsonApi(makeInput({ apiKey: "", googleCredentialsBase64: "" }));
158
+
159
+ expect("apiKey" in payload.data.attributes).toBe(false);
160
+ expect("googleCredentialsBase64" in payload.data.attributes).toBe(false);
161
+ });
162
+
163
+ it("includes the secrets when they carry a value", () => {
164
+ const payload = new AiConnection().createJsonApi(
165
+ makeInput({ apiKey: "sk-secret", googleCredentialsBase64: "Z29vZ2xl" }),
166
+ );
167
+
168
+ expect(payload.data.attributes.apiKey).toBe("sk-secret");
169
+ expect(payload.data.attributes.googleCredentialsBase64).toBe("Z29vZ2xl");
170
+ });
171
+
172
+ it("sets the company relationship only when companyId is present", () => {
173
+ const global = new AiConnection().createJsonApi(makeInput());
174
+ expect(global.data.relationships.company).toBeUndefined();
175
+
176
+ const scoped = new AiConnection().createJsonApi(makeInput({ companyId: "company-1" }));
177
+ expect(scoped.data.relationships.company).toEqual({
178
+ data: { type: "companies", id: "company-1" },
179
+ });
180
+ });
181
+
182
+ it("keeps falsy-but-meaningful values", () => {
183
+ const payload = new AiConnection().createJsonApi(makeInput({ enabled: false, position: 0, maxOutputTokens: 0 }));
184
+
185
+ expect(payload.data.attributes.enabled).toBe(false);
186
+ expect(payload.data.attributes.position).toBe(0);
187
+ expect(payload.data.attributes.maxOutputTokens).toBe(0);
188
+ });
189
+ });
190
+
191
+ describe("AiConnection.createReorderJsonApi", () => {
192
+ it("produces the reorder body the backend expects", () => {
193
+ const payload = new AiConnection().createReorderJsonApi({ ids: ["a", "b", "c"] });
194
+
195
+ expect(payload).toEqual({
196
+ data: {
197
+ type: "ai-connections",
198
+ ids: ["a", "b", "c"],
199
+ },
200
+ });
201
+ });
202
+ });
@@ -0,0 +1,6 @@
1
+ export enum AiConnectionFields {
2
+ name = "name",
3
+ provider = "provider",
4
+ model = "model",
5
+ enabled = "enabled",
6
+ }
@@ -0,0 +1,87 @@
1
+ import { ApiDataInterface } from "../../../core";
2
+
3
+ /**
4
+ * One editable field of a provider, as described by the backend provider
5
+ * registry (`AI_PROVIDER_REGISTRY`, served as top-level JSON:API `meta` on the
6
+ * list endpoint). The registry carries structure only — never display text:
7
+ * the editor derives its i18n keys from `field`.
8
+ */
9
+ export interface AiProviderFieldDescriptor {
10
+ field: string;
11
+ kind: "text" | "secret" | "number" | "boolean" | "select";
12
+ required?: boolean;
13
+ options?: string[];
14
+ default?: string | number | boolean;
15
+ }
16
+
17
+ /** `meta.providerRegistry`: connection type → the providers allowed for it. */
18
+ export type AiProviderRegistry = Record<string, { provider: string; fields: AiProviderFieldDescriptor[] }[]>;
19
+
20
+ /**
21
+ * `meta.envDefaults`: connection type → the `.env` block that always closes the
22
+ * chain. Secrets are never included.
23
+ */
24
+ export type AiConnectionEnvDefaults = Record<string, { provider?: string; model?: string; url?: string }>;
25
+
26
+ export interface AiConnectionInterface extends ApiDataInterface {
27
+ get name(): string;
28
+ get connectionType(): string;
29
+ get provider(): string;
30
+ get position(): number;
31
+ get enabled(): boolean;
32
+ get model(): string | undefined;
33
+ get url(): string | undefined;
34
+ get region(): string | undefined;
35
+ get instance(): string | undefined;
36
+ get apiVersion(): string | undefined;
37
+ get allowFallbacks(): boolean | undefined;
38
+ get reasoningEffort(): string | undefined;
39
+ get maxOutputTokens(): number | undefined;
40
+ get dimensions(): number | undefined;
41
+ get inputCostPer1MTokens(): number | undefined;
42
+ get outputCostPer1MTokens(): number | undefined;
43
+ get cachedInputCostPer1MTokens(): number | undefined;
44
+ get costPerMinute(): number | undefined;
45
+ get costPerPage(): number | undefined;
46
+ get directUrl(): string | undefined;
47
+ get language(): string | undefined;
48
+ get directFormat(): string | undefined;
49
+ get directProvider(): string | undefined;
50
+ /** Secrets are never serialised — the API returns presence flags instead. */
51
+ get hasApiKey(): boolean;
52
+ get hasGoogleCredentials(): boolean;
53
+ /** Absent = a global connection; set = scoped to that company. */
54
+ get companyId(): string | undefined;
55
+ }
56
+
57
+ export type AiConnectionInput = {
58
+ id: string;
59
+ name: string;
60
+ connectionType: string;
61
+ provider: string;
62
+ position: number;
63
+ enabled: boolean;
64
+ model?: string;
65
+ url?: string;
66
+ /** Secrets: omit or send "" to keep the stored value on update. */
67
+ apiKey?: string;
68
+ googleCredentialsBase64?: string;
69
+ region?: string;
70
+ instance?: string;
71
+ apiVersion?: string;
72
+ allowFallbacks?: boolean;
73
+ reasoningEffort?: string;
74
+ maxOutputTokens?: number;
75
+ dimensions?: number;
76
+ inputCostPer1MTokens?: number;
77
+ outputCostPer1MTokens?: number;
78
+ cachedInputCostPer1MTokens?: number;
79
+ costPerMinute?: number;
80
+ costPerPage?: number;
81
+ directUrl?: string;
82
+ language?: string;
83
+ directFormat?: string;
84
+ directProvider?: string;
85
+ /** Set on creation only — scope is immutable thereafter. */
86
+ companyId?: string;
87
+ };
@@ -0,0 +1,90 @@
1
+ import { AbstractService, EndpointCreator, HttpMethod, Modules } from "../../../core";
2
+ import { AiConnection } from "./ai-connection";
3
+ import {
4
+ AiConnectionEnvDefaults,
5
+ AiConnectionInput,
6
+ AiConnectionInterface,
7
+ AiProviderRegistry,
8
+ } from "./ai-connection.interface";
9
+
10
+ export class AiConnectionService extends AbstractService {
11
+ /**
12
+ * Full list plus the editor meta in one round-trip: the list endpoint carries
13
+ * the provider registry and the `.env` defaults as top-level JSON:API `meta`.
14
+ */
15
+ static async listConnections(): Promise<{
16
+ connections: AiConnectionInterface[];
17
+ providerRegistry: AiProviderRegistry;
18
+ envDefaults: AiConnectionEnvDefaults;
19
+ }> {
20
+ const endpoint = new EndpointCreator({ endpoint: Modules.AiConnection });
21
+ endpoint.addAdditionalParam("fetchAll", "true");
22
+
23
+ const { data, meta } = await this.callApiWithMeta<AiConnectionInterface[]>({
24
+ type: Modules.AiConnection,
25
+ method: HttpMethod.GET,
26
+ endpoint: endpoint.generate(),
27
+ });
28
+
29
+ return {
30
+ connections: data ?? [],
31
+ providerRegistry: (meta?.providerRegistry ?? {}) as AiProviderRegistry,
32
+ envDefaults: (meta?.envDefaults ?? {}) as AiConnectionEnvDefaults,
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Create a connection. The optional `companyId` fixes its scope forever.
38
+ */
39
+ static async create(params: AiConnectionInput): Promise<AiConnectionInterface> {
40
+ return this.callApi<AiConnectionInterface>({
41
+ type: Modules.AiConnection,
42
+ method: HttpMethod.POST,
43
+ endpoint: new EndpointCreator({ endpoint: Modules.AiConnection }).generate(),
44
+ input: params,
45
+ });
46
+ }
47
+
48
+ /**
49
+ * Update a connection. Blank/omitted secrets keep the stored values.
50
+ */
51
+ static async update(params: AiConnectionInput): Promise<AiConnectionInterface> {
52
+ return this.callApi<AiConnectionInterface>({
53
+ type: Modules.AiConnection,
54
+ method: HttpMethod.PUT,
55
+ endpoint: new EndpointCreator({ endpoint: Modules.AiConnection, id: params.id }).generate(),
56
+ input: params,
57
+ });
58
+ }
59
+
60
+ /**
61
+ * Delete a connection.
62
+ */
63
+ static async delete(params: { id: string }): Promise<void> {
64
+ await this.callApi({
65
+ type: Modules.AiConnection,
66
+ method: HttpMethod.DELETE,
67
+ endpoint: new EndpointCreator({ endpoint: Modules.AiConnection, id: params.id }).generate(),
68
+ });
69
+ }
70
+
71
+ /**
72
+ * Reorder one chain: the ordered id list is renumbered 0..n-1 server-side.
73
+ * Dedicated model method + `overridesJsonApiCreation` — the sanctioned
74
+ * pairing, never a hand-built payload here.
75
+ */
76
+ static async reorder(params: { ids: string[] }): Promise<void> {
77
+ const model = new AiConnection();
78
+
79
+ await this.callApi({
80
+ type: Modules.AiConnection,
81
+ method: HttpMethod.POST,
82
+ endpoint: new EndpointCreator({
83
+ endpoint: Modules.AiConnection,
84
+ childEndpoint: "reorder",
85
+ }).generate(),
86
+ input: model.createReorderJsonApi({ ids: params.ids }),
87
+ overridesJsonApiCreation: true, // OK — the model method provides the structure
88
+ });
89
+ }
90
+ }
@@ -0,0 +1,244 @@
1
+ import { AbstractApiData, JsonApiHydratedDataInterface, Modules } from "../../../core";
2
+ import { AiConnectionInput, AiConnectionInterface } from "./ai-connection.interface";
3
+
4
+ export class AiConnection extends AbstractApiData implements AiConnectionInterface {
5
+ private _name?: string;
6
+ private _connectionType?: string;
7
+ private _provider?: string;
8
+ private _position?: number;
9
+ private _enabled?: boolean;
10
+ private _model?: string;
11
+ private _url?: string;
12
+ private _region?: string;
13
+ private _instance?: string;
14
+ private _apiVersion?: string;
15
+ private _allowFallbacks?: boolean;
16
+ private _reasoningEffort?: string;
17
+ private _maxOutputTokens?: number;
18
+ private _dimensions?: number;
19
+ private _inputCostPer1MTokens?: number;
20
+ private _outputCostPer1MTokens?: number;
21
+ private _cachedInputCostPer1MTokens?: number;
22
+ private _costPerMinute?: number;
23
+ private _costPerPage?: number;
24
+ private _directUrl?: string;
25
+ private _language?: string;
26
+ private _directFormat?: string;
27
+ private _directProvider?: string;
28
+ private _hasApiKey?: boolean;
29
+ private _hasGoogleCredentials?: boolean;
30
+ private _companyId?: string;
31
+
32
+ get name(): string {
33
+ return this._name ?? "";
34
+ }
35
+
36
+ get connectionType(): string {
37
+ return this._connectionType ?? "";
38
+ }
39
+
40
+ get provider(): string {
41
+ return this._provider ?? "";
42
+ }
43
+
44
+ get position(): number {
45
+ return this._position ?? 0;
46
+ }
47
+
48
+ get enabled(): boolean {
49
+ return this._enabled ?? true;
50
+ }
51
+
52
+ get model(): string | undefined {
53
+ return this._model;
54
+ }
55
+
56
+ get url(): string | undefined {
57
+ return this._url;
58
+ }
59
+
60
+ get region(): string | undefined {
61
+ return this._region;
62
+ }
63
+
64
+ get instance(): string | undefined {
65
+ return this._instance;
66
+ }
67
+
68
+ get apiVersion(): string | undefined {
69
+ return this._apiVersion;
70
+ }
71
+
72
+ get allowFallbacks(): boolean | undefined {
73
+ return this._allowFallbacks;
74
+ }
75
+
76
+ get reasoningEffort(): string | undefined {
77
+ return this._reasoningEffort;
78
+ }
79
+
80
+ get maxOutputTokens(): number | undefined {
81
+ return this._maxOutputTokens;
82
+ }
83
+
84
+ get dimensions(): number | undefined {
85
+ return this._dimensions;
86
+ }
87
+
88
+ get inputCostPer1MTokens(): number | undefined {
89
+ return this._inputCostPer1MTokens;
90
+ }
91
+
92
+ get outputCostPer1MTokens(): number | undefined {
93
+ return this._outputCostPer1MTokens;
94
+ }
95
+
96
+ get cachedInputCostPer1MTokens(): number | undefined {
97
+ return this._cachedInputCostPer1MTokens;
98
+ }
99
+
100
+ get costPerMinute(): number | undefined {
101
+ return this._costPerMinute;
102
+ }
103
+
104
+ get costPerPage(): number | undefined {
105
+ return this._costPerPage;
106
+ }
107
+
108
+ get directUrl(): string | undefined {
109
+ return this._directUrl;
110
+ }
111
+
112
+ get language(): string | undefined {
113
+ return this._language;
114
+ }
115
+
116
+ get directFormat(): string | undefined {
117
+ return this._directFormat;
118
+ }
119
+
120
+ get directProvider(): string | undefined {
121
+ return this._directProvider;
122
+ }
123
+
124
+ get hasApiKey(): boolean {
125
+ return this._hasApiKey ?? false;
126
+ }
127
+
128
+ get hasGoogleCredentials(): boolean {
129
+ return this._hasGoogleCredentials ?? false;
130
+ }
131
+
132
+ get companyId(): string | undefined {
133
+ return this._companyId;
134
+ }
135
+
136
+ rehydrate(data: JsonApiHydratedDataInterface): this {
137
+ super.rehydrate(data);
138
+
139
+ const attributes = data.jsonApi.attributes ?? {};
140
+
141
+ this._name = attributes.name;
142
+ this._connectionType = attributes.connectionType;
143
+ this._provider = attributes.provider;
144
+ this._position = attributes.position;
145
+ this._enabled = attributes.enabled;
146
+ this._model = attributes.model;
147
+ this._url = attributes.url;
148
+ this._region = attributes.region;
149
+ this._instance = attributes.instance;
150
+ this._apiVersion = attributes.apiVersion;
151
+ this._allowFallbacks = attributes.allowFallbacks;
152
+ this._reasoningEffort = attributes.reasoningEffort;
153
+ this._maxOutputTokens = attributes.maxOutputTokens;
154
+ this._dimensions = attributes.dimensions;
155
+ this._inputCostPer1MTokens = attributes.inputCostPer1MTokens;
156
+ this._outputCostPer1MTokens = attributes.outputCostPer1MTokens;
157
+ this._cachedInputCostPer1MTokens = attributes.cachedInputCostPer1MTokens;
158
+ this._costPerMinute = attributes.costPerMinute;
159
+ this._costPerPage = attributes.costPerPage;
160
+ this._directUrl = attributes.directUrl;
161
+ this._language = attributes.language;
162
+ this._directFormat = attributes.directFormat;
163
+ this._directProvider = attributes.directProvider;
164
+ this._hasApiKey = attributes.hasApiKey;
165
+ this._hasGoogleCredentials = attributes.hasGoogleCredentials;
166
+ this._companyId = attributes.companyId;
167
+
168
+ return this;
169
+ }
170
+
171
+ createJsonApi(data: AiConnectionInput): any {
172
+ const response: any = {
173
+ data: {
174
+ type: Modules.AiConnection.name,
175
+ id: data.id,
176
+ attributes: {},
177
+ relationships: {},
178
+ },
179
+ included: [],
180
+ };
181
+
182
+ const setIfDefined = (key: keyof AiConnectionInput) => {
183
+ if (data[key] !== undefined) response.data.attributes[key] = data[key];
184
+ };
185
+
186
+ (
187
+ [
188
+ "name",
189
+ "connectionType",
190
+ "provider",
191
+ "position",
192
+ "enabled",
193
+ "model",
194
+ "url",
195
+ "region",
196
+ "instance",
197
+ "apiVersion",
198
+ "allowFallbacks",
199
+ "reasoningEffort",
200
+ "maxOutputTokens",
201
+ "dimensions",
202
+ "inputCostPer1MTokens",
203
+ "outputCostPer1MTokens",
204
+ "cachedInputCostPer1MTokens",
205
+ "costPerMinute",
206
+ "costPerPage",
207
+ "directUrl",
208
+ "language",
209
+ "directFormat",
210
+ "directProvider",
211
+ ] as const
212
+ ).forEach(setIfDefined);
213
+
214
+ // Secrets: an empty string means "keep the stored value" — omit it from the
215
+ // payload entirely so the backend never overwrites what it already holds.
216
+ if (data.apiKey) response.data.attributes.apiKey = data.apiKey;
217
+ if (data.googleCredentialsBase64) {
218
+ response.data.attributes.googleCredentialsBase64 = data.googleCredentialsBase64;
219
+ }
220
+
221
+ // Scope is chosen at creation and immutable thereafter.
222
+ if (data.companyId) {
223
+ response.data.relationships.company = {
224
+ data: { type: Modules.Company.name, id: data.companyId },
225
+ };
226
+ }
227
+
228
+ return response;
229
+ }
230
+
231
+ /**
232
+ * Body for POST /ai-connections/reorder. A dedicated model method so the
233
+ * service never constructs a JSON:API payload itself — the sanctioned pairing
234
+ * for `overridesJsonApiCreation`.
235
+ */
236
+ createReorderJsonApi(params: { ids: string[] }): any {
237
+ return {
238
+ data: {
239
+ type: Modules.AiConnection.name,
240
+ ids: params.ids,
241
+ },
242
+ };
243
+ }
244
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./ai-connection";
2
+ export * from "./ai-connection.fields";
3
+ export * from "./ai-connection.interface";
4
+ export * from "./ai-connection.service";