modelmix 4.5.16 → 4.5.18

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/README.md CHANGED
@@ -343,7 +343,7 @@ const result = await model.json(
343
343
 
344
344
  ### Enhanced descriptors
345
345
 
346
- Descriptions support **descriptor objects** with `description`, `required`, `enum`, and `default`:
346
+ Descriptions support **descriptor objects** with `description`, `required`, `enum`, `default`, and `nullable`:
347
347
 
348
348
  ```javascript
349
349
  const result = await model.json(
@@ -359,11 +359,97 @@ const result = await model.json(
359
359
  | Property | Type | Default | Description |
360
360
  | --- | --- | --- | --- |
361
361
  | `description` | `string` | — | Field description for the model |
362
- | `required` | `boolean` | `true` | If `false`, field is removed from `required` and type becomes nullable |
363
- | `enum` | `array` | — | Allowed values. If includes `null`, type auto-becomes nullable |
364
- | `default` | `any` | — | Default value for the field |
362
+ | `required` | `boolean` | `true` | If `false`, field is removed from `required` and its type becomes nullable |
363
+ | `enum` | `array` | — | Restricts the field to specific values. Including `null` in the array auto-makes the type nullable |
364
+ | `default` | `any` | — | Default value hint for the model |
365
+ | `nullable` | `boolean` | `false` | If `true`, makes the type nullable without removing from `required` |
365
366
 
366
- You can mix strings and descriptor objects freely in the same descriptions parameter.
367
+ You can mix plain strings and descriptor objects freely in the same descriptions parameter:
368
+
369
+ ```javascript
370
+ const result = await model.json(
371
+ { name: 'Martin', age: 22, status: 'active' },
372
+ {
373
+ name: 'Full name', // plain string
374
+ age: { description: 'Age in years', required: false }, // optional field
375
+ status: { description: 'Account status', enum: ['active', 'inactive', 'banned'], default: 'active' }
376
+ }
377
+ );
378
+ ```
379
+
380
+ ### Nested object descriptions
381
+
382
+ Pass a nested object as the description value to describe fields inside a nested object:
383
+
384
+ ```javascript
385
+ const result = await model.json(
386
+ { user: { name: 'Alice', age: 30 } },
387
+ {
388
+ user: { name: 'Full name of the user', age: 'Age in years' }
389
+ }
390
+ );
391
+ ```
392
+
393
+ To describe the object field itself (e.g. mark it optional) **and** its nested fields, use the `description` / `required` descriptor for the parent key, which applies only to the parent, while still passing nested descriptions as its own separate key:
394
+
395
+ ```javascript
396
+ // Mark the parent optional but don't describe its children
397
+ const result = await model.json(
398
+ { user: { name: 'Alice', age: 30 } },
399
+ { user: { description: 'User details', required: false } }
400
+ );
401
+ ```
402
+
403
+ ### Array item descriptions
404
+
405
+ Pass descriptions for the items of an array by wrapping the descriptions in an array:
406
+
407
+ ```javascript
408
+ const result = await model.json(
409
+ { countries: [{ name: 'France', capital: 'Paris' }] },
410
+ { countries: [{ name: 'Country name', capital: 'Capital city in uppercase' }] }
411
+ );
412
+ ```
413
+
414
+ To mark the array field itself optional while keeping item descriptions, use a descriptor on the key:
415
+
416
+ ```javascript
417
+ const result = await model.json(
418
+ { tags: ['admin'] },
419
+ { tags: { description: 'List of user roles', required: false } }
420
+ );
421
+ ```
422
+
423
+ ### Automatic type and format detection
424
+
425
+ `generateJsonSchema` infers types and formats automatically from the example values:
426
+
427
+ | Example value | Inferred schema |
428
+ | --- | --- |
429
+ | `42` | `{ type: 'integer' }` |
430
+ | `19.99` | `{ type: 'number' }` |
431
+ | `true` / `false` | `{ type: 'boolean' }` |
432
+ | `null` | `{ type: 'null' }` |
433
+ | `'hello'` | `{ type: 'string' }` |
434
+ | `'user@example.com'` | `{ type: 'string', format: 'email' }` |
435
+ | `'1990-01-01'` | `{ type: 'string', format: 'date', description: 'Date in format YYYY-MM-DD' }` |
436
+ | `'14:30'` | `{ type: 'string', format: 'time', description: 'Time in format HH:MM' }` |
437
+ | `'09:15:45'` | `{ type: 'string', format: 'time', description: 'Time in format HH:MM:SS' }` |
438
+ | `[{ … }]` | `{ type: 'array', items: { … } }` — schema inferred from the first element |
439
+ | `{ … }` | `{ type: 'object', properties: { … }, required: […] }` |
440
+
441
+ When a field carries an `enum` that includes `null`, or has `required: false` or `nullable: true`, its type is widened to `[type, 'null']`. For example:
442
+
443
+ ```javascript
444
+ // enum with null → type becomes ['string', 'null']
445
+ { description: 'Gender', enum: ['m', 'f', null] }
446
+
447
+ // required: false → removes from required[] and type becomes ['string', 'null']
448
+ { description: 'Nickname', required: false }
449
+
450
+ // nullable: true → type becomes ['string', 'null'] but stays in required[]
451
+ { description: 'Middle name', nullable: true }
452
+ ```
367
453
 
368
454
  ### Array auto-wrap
369
455
 
package/index.js CHANGED
@@ -37,6 +37,8 @@ const MODEL_PRICING = {
37
37
  // OpenAI
38
38
  'gpt-realtime-mini': [0.60, 2.40],
39
39
  'gpt-realtime': [4.00, 16.00],
40
+ 'gpt-5.5-pro': [30.00, 180.00],
41
+ 'gpt-5.5': [5.00, 30.00],
40
42
  'gpt-5.4': [2.50, 15.00],
41
43
  'gpt-5.4-pro': [30, 180.00],
42
44
  'gpt-5.4-mini': [0.75, 4.50],
@@ -319,7 +321,13 @@ class ModelMix {
319
321
  }
320
322
  gpt54pro({ options = {}, config = {} } = {}) {
321
323
  return this.attach('gpt-5.4-pro', new MixOpenAIResponses({ options, config }));
322
- }
324
+ }
325
+ gpt55({ options = {}, config = {} } = {}) {
326
+ return this.attach('gpt-5.5', new MixOpenAIResponses({ options, config }));
327
+ }
328
+ gpt55pro({ options = {}, config = {} } = {}) {
329
+ return this.attach('gpt-5.5-pro', new MixOpenAIResponses({ options, config }));
330
+ }
323
331
  gptRealtime({ options = {}, config = {} } = {}) {
324
332
  return this.attach('gpt-realtime', new MixOpenAIWebSocket({ options, config }));
325
333
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modelmix",
3
- "version": "4.5.16",
3
+ "version": "4.5.18",
4
4
  "description": "🧬 Reliable interface with automatic fallback for AI LLMs.",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -176,7 +176,53 @@ const result = await model.json(
176
176
  );
177
177
  ```
178
178
 
179
- Descriptor properties: `description` (string), `required` (boolean, default true — if false, field becomes nullable), `enum` (array — if includes null, type auto-becomes nullable), `default` (any).
179
+ Descriptor properties:
180
+
181
+ | Property | Type | Notes |
182
+ | --- | --- | --- |
183
+ | `description` | string | Field description for the model |
184
+ | `required` | boolean (default `true`) | `false` → removes from `required[]` **and** makes type nullable |
185
+ | `enum` | array | Restricts allowed values. Including `null` auto-makes the type nullable |
186
+ | `default` | any | Default value hint |
187
+ | `nullable` | boolean (default `false`) | `true` → makes type nullable but keeps field in `required[]` |
188
+
189
+ #### Nested object descriptions
190
+
191
+ Pass a plain object as the description value to annotate fields inside a nested object:
192
+
193
+ ```javascript
194
+ model.json(
195
+ { user: { name: 'Alice', age: 30 } },
196
+ { user: { name: 'Full name', age: 'Age in years' } }
197
+ );
198
+ ```
199
+
200
+ To mark the object itself as optional, use a descriptor (only `description`/`required`/`nullable` keys) — it applies to the parent, not the children:
201
+
202
+ ```javascript
203
+ model.json(
204
+ { user: { name: 'Alice', age: 30 } },
205
+ { user: { description: 'User details', required: false } }
206
+ );
207
+ ```
208
+
209
+ #### Array item descriptions
210
+
211
+ Wrap descriptions in an array to annotate items of an array field:
212
+
213
+ ```javascript
214
+ model.json(
215
+ { countries: [{ name: 'France', capital: 'Paris' }] },
216
+ { countries: [{ name: 'Country name', capital: 'Capital in uppercase' }] }
217
+ );
218
+ ```
219
+
220
+ #### Automatic type and format detection
221
+
222
+ Schema types are inferred from example values: `integer` (whole numbers), `number` (floats), `boolean`, `null`, `string`, and special formats:
223
+ - `'user@example.com'` → `{ type: 'string', format: 'email' }`
224
+ - `'1990-01-01'` → `{ type: 'string', format: 'date' }`
225
+ - `'14:30'` / `'09:15:45'` → `{ type: 'string', format: 'time' }`
180
226
 
181
227
  #### Array auto-wrap
182
228
 
@@ -390,7 +436,7 @@ const model = ModelMix.new({
390
436
  - Store API keys in `.env` and load with `dotenv/config` or `process.loadEnvFile()`. Never hardcode keys.
391
437
  - Chain models for resilience: primary model first, fallbacks after.
392
438
  - When using MCP tools or `addTool()`, set `max_history` to at least 3 — tool call/response pairs consume history slots.
393
- - Use `.json()` for structured output instead of parsing text manually. Use descriptor objects `{ description, required, enum, default }` for richer schema control.
439
+ - Use `.json()` for structured output instead of parsing text manually. Use descriptor objects `{ description, required, enum, default, nullable }` for richer schema control.
394
440
  - Use `.message()` for simple text, `.raw()` when you need tokens/thinking/toolCalls.
395
441
  - For thinking models, append `think` to the method name (e.g. `sonnet45think()`).
396
442
  - Template placeholders use `{key}` syntax in both system prompts and user messages.
@@ -76,6 +76,18 @@ describe('Token Usage Tracking', () => {
76
76
  expect(request.prompt_cache_retention).to.equal('24h');
77
77
  });
78
78
 
79
+ it('should register GPT-5.5 shortcuts with OpenAI Responses provider', function () {
80
+ const model = ModelMix.new()
81
+ .gpt55()
82
+ .gpt55pro();
83
+
84
+ expect(model.models).to.have.length(2);
85
+ expect(model.models[0].key).to.equal('gpt-5.5');
86
+ expect(model.models[1].key).to.equal('gpt-5.5-pro');
87
+ expect(model.models[0].provider).to.be.instanceOf(MixOpenAIResponses);
88
+ expect(model.models[1].provider).to.be.instanceOf(MixOpenAIResponses);
89
+ });
90
+
79
91
  it('should track tokens in OpenAI response', async function () {
80
92
  this.timeout(30000);
81
93