@aeye/core 0.4.0 → 0.5.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.
@@ -47,6 +47,13 @@ A `Prompt` only does real work when the context supplies `execute` and/or `strea
47
47
 
48
48
  `PromptReconfigInput` stats include: `iteration`, `maxIterations`, `toolParseErrors`, `toolCallErrors`, `tools` (names), `toolSuccesses`, and remaining `toolRetries` / `outputRetries` / `forgetRetries`. `PromptReconfig` lets you return `{ config?, maxIterations?, toolRetries?, outputRetries?, forgetRetries? }`.
49
49
 
50
+ Four semantics of that hook are load-bearing and are not visible in the types:
51
+
52
+ - **`PromptReconfig.maxIterations` is RELATIVE, not absolute.** The loop assigns `maxIterations = iterations + value`, so returning `N` means "this round plus `N - 1` more". Return `budget - stats.iteration` to set an absolute ceiling. Returning `0` **breaks out immediately** — and with no result parsed yet the run then throws its own generic `failed without a specified error`, so `0` is not a graceful stop.
53
+ - **`PromptReconfigInput.maxIterations` is the ceiling as it stood BEFORE this call** — whatever the previous round set. A decision that must track a ceiling you are raising in the same call has to use your own value, not this field.
54
+ - **The error/success counters are cumulative for the run**, not per round: `toolSuccesses` / `toolParseErrors` / `toolCallErrors` only ever grow, so "did THIS round make progress?" is a delta you keep yourself. `toolRetries` / `outputRetries` / `forgetRetries` are the opposite — they are the *remaining* allowances and count down.
55
+ - **`reconfig` runs at the END of an iteration**, after tool dispatch and output parsing and before the `dynamic` re-resolve — so anything it changes takes effect on the NEXT model call.
56
+
50
57
  ## Methods
51
58
 
52
59
  ```typescript
@@ -29,10 +29,25 @@ A descriptor (see `src/schema.ts` line ~26 for the fully documented interface) d
29
29
  | `tupleEncoding` | `'object-numeric-keys'` vs `'prefix-items'` vs `'items-union'`. |
30
30
  | `optionalAsNullable` | Emit optionals as `T \| null` (OpenAI strict) vs drop from `required[]`. |
31
31
  | `allowAllOf` / `allowAnyOf` / `allowOneOf` | Combinator support. |
32
+ | `allowRootRef` / `allowDefsRef` | Whether `$ref: '#'` / `$ref: '#/$defs/X'` are permitted. |
33
+ | `anyEncoding` | How `z.any()` / `z.unknown()` is emitted — see below. |
32
34
  | `supportedStringFormats` | `'all'` or a `Set<string>` whitelist. |
33
35
  | `supportsRecursion` | Whether `$ref` self-reference works. |
34
36
  | `maxStrictTools` / `maxStrictOptionalParams` / `maxStrictUnionTypes` | Per-request slot budgets (`undefined` = no documented limit). |
35
37
 
38
+ ### `anyEncoding` — and what each mode requires
39
+
40
+ An open "any JSON value" slot has no single portable spelling, so each dialect picks one. **Each mode needs keywords the same descriptor has to allow**, and `checkDescriptorConsistency` enforces that pairing:
41
+
42
+ | Mode | Shape | Requires |
43
+ |------|------|------|
44
+ | `'recursive-strict'` | `$defs/Any` with array-of-pairs records (OpenAI strict has no open objects) | `allowAnyOf`, `allowDefsRef`, `supportsRecursion` |
45
+ | `'recursive-open'` | `$defs/Any` with `additionalProperties: <self>` | `allowAnyOf`, `allowDefsRef`, `supportsRecursion` |
46
+ | `'flat'` | inline non-recursive `anyOf` over every JSON type | `allowAnyOf` |
47
+ | `'unconstrained'` | the empty schema `{}` (plus any `description`) | nothing |
48
+
49
+ `'unconstrained'` is the only mode available to a dialect that forbids combinators **and** named `$defs` (Google). Per JSON Schema a schema with no assertion keywords validates every instance, which is exactly what "any value" means — so it is lossless as a constraint, and the source schema's `description` still rides along as the human/model-facing signal.
50
+
36
51
  ## Built-in descriptors
37
52
 
38
53
  Seven ship frozen and pre-registered:
@@ -41,9 +56,10 @@ Seven ship frozen and pre-registered:
41
56
  |------|------|-------|
42
57
  | `OPENAI_STRICT` | openai / strict | records→array-of-pairs, tuples→numeric-key objects, `optional → T \| null`, closed objects, restricted format whitelist. |
43
58
  | `ANTHROPIC_STRICT` | anthropic / strict | closed objects, no recursion, no length/range constraints; budgets 20 tools / 24 optional params / 16 unions. |
44
- | `GOOGLE_STRICT` | google / strict | `prefixItems`, `$ref: '#'` recursion, `propertyOrdering`, restricted format whitelist. |
59
+ | `GOOGLE_STRICT` | google / strict | `prefixItems`, `$ref: '#'` recursion, `propertyOrdering`, restricted format whitelist, `z.any()` → `unconstrained` (Gemini forbids `anyOf` and named `$defs`). |
45
60
  | `LENIENT` | lenient / non-strict | No rewrites; everything passes through. Default for unannotated models. |
46
- | `OPENAI_NON_STRICT`, `ANTHROPIC_NON_STRICT`, `GOOGLE_NON_STRICT` | family / non-strict | Aliased to `LENIENT` but tagged with the family for diagnostics. |
61
+ | `OPENAI_NON_STRICT`, `ANTHROPIC_NON_STRICT` | family / non-strict | Aliased to `LENIENT` but tagged with the family for diagnostics. |
62
+ | `GOOGLE_NON_STRICT` | google / non-strict | `LENIENT` **except** `anyEncoding: 'unconstrained'`. Gemini compiles a decoding grammar whenever a tool call is forced, and rejects a self-referencing `$defs/Any` when it does — which has nothing to do with a per-tool strict flag (Google's function-calling API has none), so the encoding belongs to the dialect. |
47
63
 
48
64
  ## Functions
49
65
 
@@ -53,6 +69,7 @@ toJSONSchema(schema: z.ZodType,
53
69
  strictify<S extends z.ZodType>(schema: S, descriptor?: FormatDescriptor): S // default OPENAI_STRICT
54
70
  analyzeSchema(schema): SchemaFeatures
55
71
  registerDescriptor(descriptor: FormatDescriptor): void
72
+ checkDescriptorConsistency(descriptor: FormatDescriptor): string[] // [] when consistent
56
73
  getDescriptor(family: DescriptorFamily, strict: boolean): FormatDescriptor
57
74
  getDescriptorById(id: string | undefined): FormatDescriptor
58
75
  hasDescriptorFamily(family: string | undefined): boolean
@@ -64,6 +81,7 @@ strictPriority(requested: boolean | number | undefined): number
64
81
  - `toJSONSchema` accepts a descriptor, a `ToJSONSchemaOptions` object, or a boolean (back-compat: `true` ⇒ `OPENAI_STRICT`, `false` ⇒ `LENIENT`).
65
82
  - `strictify` rewrites a Zod schema in place per the descriptor (results cached in a WeakMap; same OOM-safe pattern as `analyzeSchema`).
66
83
  - `analyzeSchema` returns `SchemaFeatures`: `{ hasRecursion, optionalParameterCount, unionTypeCount, recordCount, tupleCount }` — cached per schema.
84
+ - `checkDescriptorConsistency` reports settings that ask the emitter for a keyword the same descriptor forbids (today: `anyEncoding` vs `allowAnyOf` / `allowDefsRef` / `supportsRecursion`). `registerDescriptor` runs it and `console.warn`s each problem, because otherwise the first report of a contradiction is a provider HTTP 400.
67
85
 
68
86
  ```typescript
69
87
  import { OPENAI_STRICT, ANTHROPIC_STRICT, getDescriptor, toJSONSchema } from '@aeye/core';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aeye/core",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "@aeye Core - Core primitives for AI components (agents, tools, prompts)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",