@steve31415/baselib 3.29.0 → 3.30.0

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.
@@ -13,32 +13,35 @@ function classifyFinish(stopReason) {
13
13
  }
14
14
  }
15
15
  /**
16
- * Keywords `output_config.format` rejects outright with a 400 (verified on
17
- * Opus 5.5, 2026-09-22). `minItems` is also rejected for values other than 0
18
- * or 1, handled separately; `minLength`/`maxLength`, `pattern` and `format`
19
- * are accepted.
16
+ * Keywords Anthropic's constrained decoding rejects outright with a 400 —
17
+ * `output_config.format` (verified on Opus 5.5, 2026-09-22) and strict tools
18
+ * alike (verified on every forced-tool registry model, 2026-10-07). `minItems`
19
+ * is also rejected for values other than 0 or 1, handled separately;
20
+ * `minLength`/`maxLength`, `pattern`, `format` and nullable unions are
21
+ * accepted.
20
22
  */
21
- const UNSUPPORTED_FORMAT_KEYWORDS = [
23
+ const UNSUPPORTED_STRICT_KEYWORDS = [
22
24
  'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf',
23
25
  'maxItems', 'uniqueItems',
24
26
  ];
25
27
  /**
26
- * Transform a portable JSON schema into the dialect Anthropic's structured
27
- * outputs (`output_config.format`) accepts: every object schema must set
28
- * `additionalProperties: false`, and the numeric/array bounds listed above are
29
- * dropped. The model then no longer enforces those bounds — callers validate
30
- * the parsed result as they already must for truncation. Unlike forced
31
- * tool_use, optional properties stay optional. Returns a deep copy — the
32
- * caller's schema object is reused across failover attempts.
28
+ * Transform a portable JSON schema into the dialect Anthropic's constrained
29
+ * decoding accepts (`output_config.format` and `strict: true` tools): every
30
+ * object schema must set `additionalProperties: false`, and the numeric/array
31
+ * bounds listed above are dropped. The model then no longer enforces those
32
+ * bounds — callers validate the parsed result as they already must for
33
+ * truncation. Optional properties stay optional (unlike OpenAI's strict
34
+ * dialect). Returns a deep copy — the caller's schema object is reused across
35
+ * failover attempts.
33
36
  */
34
- function toOutputFormatSchema(node) {
37
+ function toAnthropicStrictSchema(node) {
35
38
  if (Array.isArray(node))
36
- return node.map(toOutputFormatSchema);
39
+ return node.map(toAnthropicStrictSchema);
37
40
  if (node === null || typeof node !== 'object')
38
41
  return node;
39
42
  const out = {};
40
43
  for (const [key, value] of Object.entries(node)) {
41
- if (UNSUPPORTED_FORMAT_KEYWORDS.includes(key))
44
+ if (UNSUPPORTED_STRICT_KEYWORDS.includes(key))
42
45
  continue;
43
46
  if (key === 'minItems' && typeof value === 'number' && value > 1)
44
47
  continue;
@@ -46,10 +49,10 @@ function toOutputFormatSchema(node) {
46
49
  // property that happens to be named `minimum` is kept.
47
50
  if ((key === 'properties' || key === '$defs' || key === 'definitions')
48
51
  && value !== null && typeof value === 'object' && !Array.isArray(value)) {
49
- out[key] = Object.fromEntries(Object.entries(value).map(([k, v]) => [k, toOutputFormatSchema(v)]));
52
+ out[key] = Object.fromEntries(Object.entries(value).map(([k, v]) => [k, toAnthropicStrictSchema(v)]));
50
53
  continue;
51
54
  }
52
- out[key] = toOutputFormatSchema(value);
55
+ out[key] = toAnthropicStrictSchema(value);
53
56
  }
54
57
  const type = out.type;
55
58
  if (type === 'object' || (Array.isArray(type) && type.includes('object'))) {
@@ -87,20 +90,25 @@ export const anthropicProvider = {
87
90
  ? [{ type: 'text', text: request.systemPrompt, cache_control: { type: 'ephemeral' } }]
88
91
  : request.systemPrompt;
89
92
  }
90
- // JSON schema: a forced tool_use call where the model allows it, else
91
- // structured outputs (Opus 5.5 rejects forced tool_choice).
93
+ // JSON schema: a forced, strict tool_use call where the model allows it,
94
+ // else structured outputs (Opus 5.5 rejects forced tool_choice). Both
95
+ // constrain decoding to the schema. A tool without `strict: true` is not
96
+ // enforced: Sonnet 5 returned an array property as a JSON string (digest,
97
+ // 2026-10-06), Opus 4.8 one wrapped in leaked tool-call markup (podcasts,
98
+ // 2026-09-15).
92
99
  const useOutputFormat = request.jsonSchema !== undefined && !request.supportsForcedToolChoice;
93
100
  if (request.jsonSchema && useOutputFormat) {
94
101
  body.output_config = {
95
102
  ...body.output_config,
96
- format: { type: 'json_schema', schema: toOutputFormatSchema(request.jsonSchema.schema) },
103
+ format: { type: 'json_schema', schema: toAnthropicStrictSchema(request.jsonSchema.schema) },
97
104
  };
98
105
  }
99
106
  else if (request.jsonSchema) {
100
107
  body.tools = [{
101
108
  name: request.jsonSchema.name,
102
109
  description: `Generate structured output matching the ${request.jsonSchema.name} schema`,
103
- input_schema: request.jsonSchema.schema,
110
+ input_schema: toAnthropicStrictSchema(request.jsonSchema.schema),
111
+ strict: true,
104
112
  }];
105
113
  body.tool_choice = { type: 'tool', name: request.jsonSchema.name };
106
114
  }
@@ -37,10 +37,10 @@ function makeNullable(propSchema) {
37
37
  * Transform a portable JSON schema into the shape OpenAI's strict Structured
38
38
  * Outputs mode requires: every object must set `additionalProperties: false`
39
39
  * and list ALL of its properties in `required`. Callers author one portable
40
- * schema (which also works on Anthropic's tool_use and Gemini's responseSchema,
41
- * both of which tolerate missing `additionalProperties` and optional fields);
42
- * this keeps the OpenAI-specific dialect local to this adapter, mirroring the
43
- * Gemini adapter's own `stripUnsupportedSchemaFields`. Returns a deep copy — the
40
+ * schema; this keeps the OpenAI-specific dialect local to this adapter,
41
+ * mirroring the Gemini adapter's `stripUnsupportedSchemaFields` and the
42
+ * Anthropic adapter's `toAnthropicStrictSchema` (which closes objects but keeps
43
+ * optional fields optional). Returns a deep copy — the
44
44
  * caller's schema object is never mutated (it is reused across failover attempts).
45
45
  */
46
46
  function toStrictSchema(node) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steve31415/baselib",
3
- "version": "3.29.0",
3
+ "version": "3.30.0",
4
4
  "description": "Plasticine new-world shared platform library: logging, auth, service-to-service auth, db, HTTP, sync, app updates",
5
5
  "type": "module",
6
6
  "license": "MIT",