create-nextblock 0.11.1 → 0.11.2

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 (58) hide show
  1. package/package.json +1 -1
  2. package/templates/nextblock-template/app/actions/interactions.test.ts +301 -0
  3. package/templates/nextblock-template/app/actions/interactions.ts +372 -0
  4. package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +4 -4
  5. package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +2 -2
  6. package/templates/nextblock-template/app/api/ai/global-agent/route.ts +56 -57
  7. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +1 -1
  8. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +837 -0
  9. package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +6 -0
  10. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +4 -0
  11. package/templates/nextblock-template/app/cms/components/ConnectGitHubButton.tsx +7 -2
  12. package/templates/nextblock-template/app/cms/components/github-connect-actions.ts +4 -0
  13. package/templates/nextblock-template/app/cms/interactions/InteractionsModerationClient.tsx +408 -0
  14. package/templates/nextblock-template/app/cms/interactions/page.tsx +51 -0
  15. package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +4 -3
  16. package/templates/nextblock-template/app/cms/settings/cortex-ai/StoredCortexAiSettingsClient.tsx +1 -1
  17. package/templates/nextblock-template/app/cms/settings/cortex-ai/actions.ts +3 -5
  18. package/templates/nextblock-template/app/cms/settings/cortex-ai/page.tsx +1 -1
  19. package/templates/nextblock-template/app/page.tsx +2 -2
  20. package/templates/nextblock-template/app/product/[slug]/page.tsx +2 -0
  21. package/templates/nextblock-template/components/AppShell.tsx +1 -1
  22. package/templates/nextblock-template/components/PostCommentsSection.tsx +369 -0
  23. package/templates/nextblock-template/components/ProductReviewsSection.tsx +419 -0
  24. package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +2 -0
  25. package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +62 -19
  26. package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +19 -19
  27. package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +4 -4
  28. package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +2 -0
  29. package/templates/nextblock-template/lib/setup/actions.ts +3 -1
  30. package/templates/nextblock-template/lib/setup/migrations-bundle.ts +30 -0
  31. package/templates/nextblock-template/lib/updates/check-upstream.ts +38 -4
  32. package/templates/nextblock-template/package.json +2 -1
  33. package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +2 -4
  34. package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +1 -1
  35. package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +1 -1
  36. package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +1 -1
  37. package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
  38. package/templates/nextblock-template/lib/ai-block-generation.ts +0 -339
  39. package/templates/nextblock-template/lib/ai-client.ts +0 -247
  40. package/templates/nextblock-template/lib/ai-config.ts +0 -98
  41. package/templates/nextblock-template/lib/ai-cortex-widget-builder.ts +0 -125
  42. package/templates/nextblock-template/lib/ai-global-agent-custom-block-tools.ts +0 -363
  43. package/templates/nextblock-template/lib/ai-global-agent-db-tools.test.ts +0 -405
  44. package/templates/nextblock-template/lib/ai-global-agent-db-tools.ts +0 -1228
  45. package/templates/nextblock-template/lib/ai-global-agent-ecommerce.ts +0 -5
  46. package/templates/nextblock-template/lib/ai-global-agent-tools-stats.test.ts +0 -223
  47. package/templates/nextblock-template/lib/ai-global-agent-tools.test.ts +0 -2183
  48. package/templates/nextblock-template/lib/ai-global-agent-tools.ts +0 -4807
  49. package/templates/nextblock-template/lib/ai-key-crypto.test.ts +0 -70
  50. package/templates/nextblock-template/lib/ai-key-crypto.ts +0 -132
  51. package/templates/nextblock-template/lib/ai-model-catalog.test.ts +0 -49
  52. package/templates/nextblock-template/lib/ai-model-catalog.ts +0 -41
  53. package/templates/nextblock-template/lib/ai-model-registry.test.ts +0 -231
  54. package/templates/nextblock-template/lib/ai-model-registry.ts +0 -522
  55. package/templates/nextblock-template/lib/cortex-widget-registry.test.ts +0 -199
  56. package/templates/nextblock-template/lib/cortex-widget-registry.ts +0 -88
  57. package/templates/nextblock-template/lib/cortex-widget-schema.test.tsx +0 -237
  58. package/templates/nextblock-template/lib/cortex-widget-schema.ts +0 -393
@@ -1,70 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import {
4
- decryptOpenRouterApiKey,
5
- encryptOpenRouterApiKey,
6
- getMaskedOpenRouterKey,
7
- getOpenRouterKeyEnvelopeStatus,
8
- } from './ai-key-crypto';
9
-
10
- describe('Cortex AI OpenRouter key crypto', () => {
11
- const apiKey = 'sk-or-v1-test-secret-1234567890';
12
- const encryptionSecret = 'local-test-encryption-secret';
13
-
14
- it('encrypts and decrypts an OpenRouter key', () => {
15
- const encryptedKey = encryptOpenRouterApiKey({
16
- apiKey,
17
- encryptionSecret,
18
- now: new Date('2026-04-27T12:00:00.000Z'),
19
- });
20
-
21
- expect(decryptOpenRouterApiKey({ encryptedKey, encryptionSecret })).toBe(apiKey);
22
- });
23
-
24
- it('does not store the raw key in the encrypted payload', () => {
25
- const encryptedKey = encryptOpenRouterApiKey({
26
- apiKey,
27
- encryptionSecret,
28
- });
29
-
30
- expect(JSON.stringify(encryptedKey)).not.toContain(apiKey);
31
- expect(encryptedKey.last4).toBe('7890');
32
- });
33
-
34
- it('fails safely when the encryption secret is wrong or missing', () => {
35
- const encryptedKey = encryptOpenRouterApiKey({
36
- apiKey,
37
- encryptionSecret,
38
- });
39
-
40
- expect(() =>
41
- decryptOpenRouterApiKey({
42
- encryptedKey,
43
- encryptionSecret: 'wrong-secret',
44
- })
45
- ).toThrow('Failed to decrypt stored OpenRouter key.');
46
-
47
- expect(() =>
48
- encryptOpenRouterApiKey({
49
- apiKey,
50
- encryptionSecret: '',
51
- })
52
- ).toThrow('CORTEX_AI_ENCRYPTION_KEY is required');
53
- });
54
-
55
- it('reports masked stored key status', () => {
56
- const encryptedKey = encryptOpenRouterApiKey({
57
- apiKey,
58
- encryptionSecret,
59
- now: new Date('2026-04-27T12:00:00.000Z'),
60
- });
61
-
62
- expect(getMaskedOpenRouterKey(encryptedKey.last4)).toBe('**** 7890');
63
- expect(getOpenRouterKeyEnvelopeStatus(encryptedKey)).toEqual({
64
- hasStoredKey: true,
65
- last4: '7890',
66
- maskedKey: '**** 7890',
67
- updatedAt: '2026-04-27T12:00:00.000Z',
68
- });
69
- });
70
- });
@@ -1,132 +0,0 @@
1
- import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'crypto';
2
-
3
- export const CORTEX_AI_KEY_ALGORITHM = 'aes-256-gcm';
4
- export const CORTEX_AI_KEY_ENVELOPE_VERSION = 1;
5
-
6
- export type EncryptedOpenRouterKeyEnvelope = {
7
- algorithm: typeof CORTEX_AI_KEY_ALGORITHM;
8
- authTag: string;
9
- ciphertext: string;
10
- iv: string;
11
- last4: string;
12
- updatedAt: string;
13
- version: typeof CORTEX_AI_KEY_ENVELOPE_VERSION;
14
- };
15
-
16
- function normalizeSecret(secret: string) {
17
- const normalizedSecret = secret.trim();
18
-
19
- if (!normalizedSecret) {
20
- throw new Error('CORTEX_AI_ENCRYPTION_KEY is required to manage stored OpenRouter keys.');
21
- }
22
-
23
- return createHash('sha256').update(normalizedSecret).digest();
24
- }
25
-
26
- function assertEnvelope(value: unknown): EncryptedOpenRouterKeyEnvelope {
27
- if (!value || typeof value !== 'object') {
28
- throw new Error('Invalid encrypted OpenRouter key payload.');
29
- }
30
-
31
- const envelope = value as Partial<EncryptedOpenRouterKeyEnvelope>;
32
-
33
- if (
34
- envelope.algorithm !== CORTEX_AI_KEY_ALGORITHM ||
35
- envelope.version !== CORTEX_AI_KEY_ENVELOPE_VERSION ||
36
- typeof envelope.authTag !== 'string' ||
37
- typeof envelope.ciphertext !== 'string' ||
38
- typeof envelope.iv !== 'string'
39
- ) {
40
- throw new Error('Invalid encrypted OpenRouter key payload.');
41
- }
42
-
43
- return {
44
- algorithm: envelope.algorithm,
45
- authTag: envelope.authTag,
46
- ciphertext: envelope.ciphertext,
47
- iv: envelope.iv,
48
- last4: typeof envelope.last4 === 'string' ? envelope.last4 : '',
49
- updatedAt: typeof envelope.updatedAt === 'string' ? envelope.updatedAt : '',
50
- version: envelope.version,
51
- };
52
- }
53
-
54
- export function getMaskedOpenRouterKey(last4?: string | null) {
55
- const normalizedLast4 = (last4 || '').trim();
56
- return normalizedLast4 ? `**** ${normalizedLast4}` : 'Stored key';
57
- }
58
-
59
- export function encryptOpenRouterApiKey(params: {
60
- apiKey: string;
61
- encryptionSecret: string;
62
- now?: Date;
63
- }): EncryptedOpenRouterKeyEnvelope {
64
- const apiKey = params.apiKey.trim();
65
-
66
- if (!apiKey) {
67
- throw new Error('OpenRouter API key is required.');
68
- }
69
-
70
- const key = normalizeSecret(params.encryptionSecret);
71
- const iv = randomBytes(12);
72
- const cipher = createCipheriv(CORTEX_AI_KEY_ALGORITHM, key, iv);
73
- const ciphertext = Buffer.concat([
74
- cipher.update(apiKey, 'utf8'),
75
- cipher.final(),
76
- ]);
77
- const authTag = cipher.getAuthTag();
78
-
79
- return {
80
- algorithm: CORTEX_AI_KEY_ALGORITHM,
81
- authTag: authTag.toString('base64'),
82
- ciphertext: ciphertext.toString('base64'),
83
- iv: iv.toString('base64'),
84
- last4: apiKey.slice(-4),
85
- updatedAt: (params.now || new Date()).toISOString(),
86
- version: CORTEX_AI_KEY_ENVELOPE_VERSION,
87
- };
88
- }
89
-
90
- export function decryptOpenRouterApiKey(params: {
91
- encryptedKey: unknown;
92
- encryptionSecret: string;
93
- }) {
94
- const envelope = assertEnvelope(params.encryptedKey);
95
- const key = normalizeSecret(params.encryptionSecret);
96
-
97
- try {
98
- const decipher = createDecipheriv(
99
- CORTEX_AI_KEY_ALGORITHM,
100
- key,
101
- Buffer.from(envelope.iv, 'base64')
102
- );
103
- decipher.setAuthTag(Buffer.from(envelope.authTag, 'base64'));
104
-
105
- return Buffer.concat([
106
- decipher.update(Buffer.from(envelope.ciphertext, 'base64')),
107
- decipher.final(),
108
- ]).toString('utf8');
109
- } catch {
110
- throw new Error('Failed to decrypt stored OpenRouter key.');
111
- }
112
- }
113
-
114
- export function getOpenRouterKeyEnvelopeStatus(value: unknown) {
115
- try {
116
- const envelope = assertEnvelope(value);
117
-
118
- return {
119
- hasStoredKey: true,
120
- last4: envelope.last4 || null,
121
- maskedKey: getMaskedOpenRouterKey(envelope.last4),
122
- updatedAt: envelope.updatedAt || null,
123
- };
124
- } catch {
125
- return {
126
- hasStoredKey: false,
127
- last4: null,
128
- maskedKey: null,
129
- updatedAt: null,
130
- };
131
- }
132
- }
@@ -1,49 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
-
3
- import { listCortexAiCompatibleOpenRouterModels } from './ai-model-catalog';
4
-
5
- describe('Cortex AI OpenRouter model catalog', () => {
6
- it('fetches and filters compatible OpenRouter models', async () => {
7
- const fetch = vi.fn(async () =>
8
- new Response(
9
- JSON.stringify({
10
- data: [
11
- {
12
- architecture: { output_modalities: ['text'] },
13
- context_length: 128000,
14
- id: 'openai/gpt-5.5',
15
- name: 'OpenAI: GPT-5.5',
16
- pricing: { completion: '0.00003', prompt: '0.000005' },
17
- supported_parameters: ['tools', 'structured_outputs', 'temperature'],
18
- },
19
- {
20
- architecture: { output_modalities: ['text'] },
21
- id: 'text/no-structured-output',
22
- name: 'No Structured Output',
23
- pricing: { completion: '0', prompt: '0' },
24
- supported_parameters: ['tools'],
25
- },
26
- ],
27
- }),
28
- {
29
- headers: { 'Content-Type': 'application/json' },
30
- status: 200,
31
- }
32
- )
33
- );
34
-
35
- const models = await listCortexAiCompatibleOpenRouterModels({
36
- fetch,
37
- now: new Date('2026-04-29T12:00:00.000Z'),
38
- });
39
-
40
- expect(fetch).toHaveBeenCalledWith(
41
- expect.stringContaining('/models?supported_parameters=tools%2Cstructured_outputs'),
42
- expect.objectContaining({
43
- cache: 'no-store',
44
- headers: { Accept: 'application/json' },
45
- })
46
- );
47
- expect(models.map((model) => model.id)).toEqual(['openai/gpt-5.5']);
48
- });
49
- });
@@ -1,41 +0,0 @@
1
- import {
2
- CORTEX_AI_OPENROUTER_BASE_URL,
3
- CORTEX_AI_REQUIRED_MODEL_PARAMETERS,
4
- filterCortexAiCompatibleOpenRouterModels,
5
- type CortexAiCompatibleOpenRouterModel,
6
- } from './ai-model-registry';
7
-
8
- const SERVER_ONLY_ERROR_MESSAGE =
9
- 'Cortex AI OpenRouter model catalog can only be imported from server-side code.';
10
-
11
- if (typeof window !== 'undefined') {
12
- throw new Error(SERVER_ONLY_ERROR_MESSAGE);
13
- }
14
-
15
- type FetchFunction = typeof globalThis.fetch;
16
-
17
- function buildOpenRouterModelsUrl() {
18
- const url = new URL(`${CORTEX_AI_OPENROUTER_BASE_URL}/models`);
19
- url.searchParams.set('supported_parameters', CORTEX_AI_REQUIRED_MODEL_PARAMETERS.join(','));
20
- url.searchParams.set('output_modalities', 'text');
21
- return url.toString();
22
- }
23
-
24
- export async function listCortexAiCompatibleOpenRouterModels(params?: {
25
- fetch?: FetchFunction;
26
- now?: Date;
27
- }): Promise<CortexAiCompatibleOpenRouterModel[]> {
28
- const fetchImpl = params?.fetch || globalThis.fetch;
29
- const response = await fetchImpl(buildOpenRouterModelsUrl(), {
30
- cache: 'no-store',
31
- headers: {
32
- Accept: 'application/json',
33
- },
34
- });
35
-
36
- if (!response.ok) {
37
- throw new Error(`Failed to load OpenRouter models: ${response.status} ${response.statusText}`);
38
- }
39
-
40
- return filterCortexAiCompatibleOpenRouterModels(await response.json(), params?.now);
41
- }
@@ -1,231 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import {
4
- buildCortexAiRoutingPolicy,
5
- buildCortexAiModelFallbackChain,
6
- CORTEX_AI_FREE_MODEL_FALLBACK_REGISTRY,
7
- CORTEX_AI_OPENROUTER_FREE_ROUTER_MODEL,
8
- CortexAiRoutingError,
9
- filterCortexAiCompatibleOpenRouterModels,
10
- isOpenRouterRecoverableRoutingError,
11
- isOpenRouterRateLimitError,
12
- omitUnsupportedCortexAiModelOptions,
13
- runWithCortexAiModelFallback,
14
- safeParseCortexAiModelSelection,
15
- } from './ai-model-registry';
16
-
17
- describe('Cortex AI OpenRouter routing', () => {
18
- it('builds a free-model fallback chain with preferred overrides', () => {
19
- expect(buildCortexAiModelFallbackChain()).toEqual([
20
- ...CORTEX_AI_FREE_MODEL_FALLBACK_REGISTRY,
21
- ]);
22
-
23
- expect(
24
- buildCortexAiModelFallbackChain({
25
- modelId: 'openai/gpt-oss-120b:free',
26
- })
27
- ).toEqual([
28
- 'openai/gpt-oss-120b:free',
29
- ...CORTEX_AI_FREE_MODEL_FALLBACK_REGISTRY.filter(
30
- (modelId) => modelId !== 'openai/gpt-oss-120b:free'
31
- ),
32
- ]);
33
- });
34
-
35
- it('detects OpenRouter rate limit errors across common error shapes', () => {
36
- expect(isOpenRouterRateLimitError({ statusCode: 429 })).toBe(true);
37
- expect(isOpenRouterRateLimitError({ response: { status: 429 } })).toBe(true);
38
- expect(isOpenRouterRateLimitError({ cause: { status: 429 } })).toBe(true);
39
- expect(isOpenRouterRateLimitError({ statusCode: 500 })).toBe(false);
40
- });
41
-
42
- it('detects recoverable OpenRouter routing errors for unavailable free models', () => {
43
- expect(
44
- isOpenRouterRecoverableRoutingError(
45
- new Error('No endpoints found that can handle the requested parameters.')
46
- )
47
- ).toBe(true);
48
- expect(
49
- isOpenRouterRecoverableRoutingError(
50
- new Error('Model is no longer available as a free model.')
51
- )
52
- ).toBe(true);
53
- expect(isOpenRouterRecoverableRoutingError({ statusCode: 401 })).toBe(false);
54
- });
55
-
56
- it('retries alternate free models after a 429', async () => {
57
- const tried: string[] = [];
58
- const result = await runWithCortexAiModelFallback({
59
- modelIds: [
60
- CORTEX_AI_OPENROUTER_FREE_ROUTER_MODEL,
61
- 'nvidia/nemotron-3-super-120b-a12b:free',
62
- ],
63
- execute: async (modelId) => {
64
- tried.push(modelId);
65
-
66
- if (modelId === CORTEX_AI_OPENROUTER_FREE_ROUTER_MODEL) {
67
- throw { statusCode: 429 };
68
- }
69
-
70
- return `ok:${modelId}`;
71
- },
72
- });
73
-
74
- expect(tried).toEqual([
75
- CORTEX_AI_OPENROUTER_FREE_ROUTER_MODEL,
76
- 'nvidia/nemotron-3-super-120b-a12b:free',
77
- ]);
78
- expect(result.modelId).toBe('nvidia/nemotron-3-super-120b-a12b:free');
79
- expect(result.result).toBe('ok:nvidia/nemotron-3-super-120b-a12b:free');
80
- expect(result.attempts.map((attempt) => attempt.status)).toEqual([
81
- 'rate_limited',
82
- 'success',
83
- ]);
84
- });
85
-
86
- it('stops retrying on non-recoverable failures', async () => {
87
- await expect(
88
- runWithCortexAiModelFallback({
89
- modelIds: [
90
- CORTEX_AI_OPENROUTER_FREE_ROUTER_MODEL,
91
- 'nvidia/nemotron-3-super-120b-a12b:free',
92
- ],
93
- execute: async () => {
94
- throw { statusCode: 401 };
95
- },
96
- })
97
- ).rejects.toBeInstanceOf(CortexAiRoutingError);
98
- });
99
-
100
- it('keeps env-key routing locked to the configured free models', () => {
101
- const policy = buildCortexAiRoutingPolicy({
102
- credentialSource: 'env',
103
- requestedModelId: 'openai/gpt-5.5',
104
- });
105
-
106
- expect(policy.modelIds).toEqual([...CORTEX_AI_FREE_MODEL_FALLBACK_REGISTRY]);
107
- expect(policy.ignoredRequestedModelId).toBe('openai/gpt-5.5');
108
- expect(policy.modelSelection).toBeNull();
109
- });
110
-
111
- it('uses the stored BYOK model before free fallbacks', () => {
112
- const policy = buildCortexAiRoutingPolicy({
113
- credentialSource: 'stored',
114
- requestedModelId: 'anthropic/claude-sonnet-4.5',
115
- selectedModel: {
116
- contextLength: 128000,
117
- modelId: 'openai/gpt-5.5',
118
- name: 'OpenAI: GPT-5.5',
119
- pricing: { completion: '0.00003', prompt: '0.000005' },
120
- supportedParameters: ['tools', 'structured_outputs'],
121
- updatedAt: '2026-04-29T12:00:00.000Z',
122
- },
123
- });
124
-
125
- expect(policy.modelIds).toEqual([
126
- 'openai/gpt-5.5',
127
- ...CORTEX_AI_FREE_MODEL_FALLBACK_REGISTRY,
128
- ]);
129
- expect(policy.ignoredRequestedModelId).toBe('anthropic/claude-sonnet-4.5');
130
- });
131
-
132
- it('keeps stored BYOK without a selection on the free registry', () => {
133
- const policy = buildCortexAiRoutingPolicy({
134
- credentialSource: 'stored',
135
- requestedModelId: 'anthropic/claude-sonnet-4.5',
136
- });
137
-
138
- expect(policy.modelIds).toEqual([...CORTEX_AI_FREE_MODEL_FALLBACK_REGISTRY]);
139
- expect(policy.ignoredRequestedModelId).toBe('anthropic/claude-sonnet-4.5');
140
- });
141
-
142
- it('filters OpenRouter catalog models to Cortex AI-compatible text models', () => {
143
- const filtered = filterCortexAiCompatibleOpenRouterModels(
144
- {
145
- data: [
146
- {
147
- architecture: { output_modalities: ['text'] },
148
- context_length: 128000,
149
- id: 'openai/gpt-5.5',
150
- name: 'OpenAI: GPT-5.5',
151
- pricing: { completion: '0.00003', prompt: '0.000005' },
152
- supported_parameters: ['tools', 'structured_outputs', 'temperature'],
153
- },
154
- {
155
- architecture: { output_modalities: ['image'] },
156
- id: 'image/model',
157
- name: 'Image Model',
158
- supported_parameters: ['tools', 'structured_outputs'],
159
- },
160
- {
161
- architecture: { output_modalities: ['text'] },
162
- id: 'text/no-tools',
163
- name: 'No Tools',
164
- supported_parameters: ['structured_outputs'],
165
- },
166
- {
167
- architecture: { output_modalities: ['text'] },
168
- expiration_date: '2026-04-01T00:00:00.000Z',
169
- id: 'expired/model',
170
- name: 'Expired',
171
- supported_parameters: ['tools', 'structured_outputs'],
172
- },
173
- ],
174
- },
175
- new Date('2026-04-29T12:00:00.000Z')
176
- );
177
-
178
- expect(filtered.map((model) => model.id)).toEqual(['openai/gpt-5.5']);
179
- });
180
-
181
- it('parses stored model selections defensively', () => {
182
- expect(
183
- safeParseCortexAiModelSelection({
184
- contextLength: 128000,
185
- modelId: 'openai/gpt-5.5',
186
- name: 'OpenAI: GPT-5.5',
187
- pricing: { completion: '0.00003', prompt: '0.000005' },
188
- supportedParameters: ['tools', 'structured_outputs'],
189
- updatedAt: '2026-04-29T12:00:00.000Z',
190
- })
191
- ).toMatchObject({
192
- modelId: 'openai/gpt-5.5',
193
- name: 'OpenAI: GPT-5.5',
194
- });
195
-
196
- expect(
197
- safeParseCortexAiModelSelection({
198
- modelId: 'openai/gpt-5.5',
199
- name: 'OpenAI: GPT-5.5',
200
- supportedParameters: ['tools'],
201
- updatedAt: '2026-04-29T12:00:00.000Z',
202
- })
203
- ).toBeNull();
204
- });
205
-
206
- it('strips unsupported optional parameters for the selected paid model only', () => {
207
- const options = omitUnsupportedCortexAiModelOptions(
208
- {
209
- maxRetries: 0,
210
- prompt: 'Hi',
211
- temperature: 0.2,
212
- },
213
- {
214
- modelId: 'openai/gpt-5.5',
215
- modelSelection: {
216
- contextLength: 128000,
217
- modelId: 'openai/gpt-5.5',
218
- name: 'OpenAI: GPT-5.5',
219
- pricing: {},
220
- supportedParameters: ['tools', 'structured_outputs'],
221
- updatedAt: '2026-04-29T12:00:00.000Z',
222
- },
223
- }
224
- );
225
-
226
- expect(options).toEqual({
227
- maxRetries: 0,
228
- prompt: 'Hi',
229
- });
230
- });
231
- });