@orkestrel/tool 0.0.3 → 0.0.4

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.
@@ -673,6 +673,153 @@ export declare function createDatabaseTool(options?: DatabaseToolOptions): ToolI
673
673
  */
674
674
  export declare function createDescribeTool(tools: ToolManagerInterface): ToolInterface;
675
675
 
676
+ /**
677
+ * Wrap one CONCRETE endpoint ({@link import('./types.js').EndpointDefinition}) as an LLM-callable
678
+ * `ToolInterface` — the endpoint half of the "existing API/DB → MCP tool" bridge (the other half,
679
+ * {@link createInferTool}, is a standalone inference utility).
680
+ *
681
+ * @remarks
682
+ * `parameters` is inferred ONCE at construction from `definition.samples` via
683
+ * `@orkestrel/contract`'s `samplesToSchema` (tuned by {@link import('./types.js').EndpointToolOptions}'s
684
+ * `format` / `enum`), wrapping a non-object root as `{ value: <schema> }` via `schemaToObject` —
685
+ * the SAME object-rooted schema is both the ADVERTISED `parameters` and, by default
686
+ * ({@link import('./types.js').EndpointToolOptions.validate} `true`), the ENFORCED contract:
687
+ * `@orkestrel/contract` 0.0.7's `schemaToShape` compiles it ONCE (via `createContract`) into a
688
+ * `ContractInterface` whose `.parse` runs on every call's `args` before `definition.invoke` — a
689
+ * NORMALIZING parse, not a strict type check: a scalar is COERCED to its inferred type where the
690
+ * house parsers coerce (a number to/from a numeric string, a boolean from `'1'`/`'0'`/`'true'`/
691
+ * `'false'`/`1`/`0`), so `definition.invoke` receives the COERCED value (e.g. `7` sent for a
692
+ * string slot arrives as `'7'`), not the raw call value. A call whose `args` fails to parse into
693
+ * a record — a required key missing, or a value not coercible to its slot's type — THROWS a
694
+ * typed `TOOL` {@link import('./errors.js').AgentToolError} carrying the compiled contract's
695
+ * structured `explain` faults, and `definition.invoke` is never called. `format` annotations are
696
+ * NEVER asserted, and a key outside the closed inferred schema is SILENTLY DROPPED rather than
697
+ * rejected (see {@link import('./types.js').EndpointToolOptions.validate}). With
698
+ * `validate: false`, `execute` PASSES THROUGH the model-supplied `args` to `definition.invoke`
699
+ * WITHOUT re-validation — the pre-0.0.7 behavior, preserved as an explicit opt-out. Either way,
700
+ * `invoke`'s return flows back as the tool call's plain result; a throw PROPAGATES uncaught,
701
+ * isolated by the `ToolManagerInterface` (`@orkestrel/agent`) into the canonical error envelope
702
+ * (AGENTS §14) — never caught or re-wrapped here.
703
+ *
704
+ * @param definition - The endpoint's identity, non-empty samples, and local handler (see
705
+ * {@link import('./types.js').EndpointDefinition})
706
+ * @param options - Construction-time inference tuning + the validate opt-out (see
707
+ * {@link import('./types.js').EndpointToolOptions})
708
+ * @returns A `ToolInterface` named `definition.name`
709
+ *
710
+ * @example
711
+ * ```ts
712
+ * import { createEndpointTool } from '@src/core'
713
+ * import { createToolManager } from '@orkestrel/agent'
714
+ *
715
+ * const tool = createEndpointTool({
716
+ * name: 'lookupUser',
717
+ * description: 'Look up a user by id.',
718
+ * samples: [{ id: '1', name: 'Ada' }, { id: '2', name: 'Bob' }],
719
+ * invoke: (args) => ({ id: args.id, name: 'Ada' }),
720
+ * })
721
+ * const tools = createToolManager()
722
+ * tools.add(tool)
723
+ *
724
+ * // conforming args (all required keys present) parse and reach `invoke`
725
+ * const result = await tools.execute({
726
+ * id: 'call-1',
727
+ * name: 'lookupUser',
728
+ * arguments: { id: '1', name: 'Ada' },
729
+ * })
730
+ * // result.value -> { id: '1', name: 'Ada' }
731
+ *
732
+ * // a nonconforming call (id is not coercible to the required string) is rejected before
733
+ * // `invoke` runs
734
+ * const rejected = await tools.execute({
735
+ * id: 'call-2',
736
+ * name: 'lookupUser',
737
+ * arguments: { id: true, name: 'Ada' },
738
+ * })
739
+ * // rejected.error -> the TOOL AgentToolError message
740
+ * ```
741
+ */
742
+ export declare function createEndpointTool(definition: EndpointDefinition, options?: EndpointToolOptions): ToolInterface;
743
+
744
+ /**
745
+ * Build a standalone LLM-callable tool that infers a JSON Schema from example values — the
746
+ * utility half of the "existing API/DB → MCP tool" bridge (the other half,
747
+ * {@link createEndpointTool}, wraps one CONCRETE endpoint).
748
+ *
749
+ * @remarks
750
+ * The universal tool-handler contract (AGENTS §14): validates the call args against
751
+ * {@link import('./shapers.js').inferToolShape} (`samples` non-empty, `format` / `enum` optional
752
+ * booleans, `candidates` an optional array), infers a schema via `@orkestrel/contract`'s
753
+ * `samplesToSchema`, wraps a non-object root as `{ value: <schema> }` via `schemaToObject` (mirrors
754
+ * the tool-parameters convention every other `create*Tool` factory advertises), and RETURNS the
755
+ * resulting parameters record. An empty `samples` array fails `inferToolShape`'s `min: 1` bound —
756
+ * `contract.parse` returns `undefined` and the handler throws a typed `TOOL`
757
+ * {@link import('./errors.js').AgentToolError}.
758
+ *
759
+ * When `candidates` is ABSENT, the return is the bare parameters record — unchanged from before
760
+ * this array existed. When `candidates` is PRESENT (any array, including empty), the handler
761
+ * compiles a SEPARATE per-call contract from the RAW inferred schema (via `@orkestrel/contract`'s
762
+ * `schemaToShape`, NOT the `schemaToObject`-wrapped parameters — a bare-value sample checks a
763
+ * bare-value candidate) and returns `{ parameters, checks }`, one check per candidate at the same
764
+ * index. Every entry has a UNIFORM shape — `{ index, valid, coercible }`, with `faults` added ONLY
765
+ * when `valid` is `false`: `valid` is the STRICT guard verdict (`checker.is(candidate)`), the
766
+ * OPPOSITE of {@link createEndpointTool}'s enforcement, which coerces (`7` becomes `'7'` for a
767
+ * string slot) — here a conformance report answers "does this value conform AS-IS": `7` against a
768
+ * string slot is `valid: false`, full stop. `coercible` answers a SEPARATE question — "would the
769
+ * NORMALIZING parse accept this value", i.e. would {@link createEndpointTool}'s default enforcement
770
+ * admit it (`checker.parse(candidate) !== undefined`) — computed for every candidate regardless of
771
+ * `valid`; by the house parse/guard round-trip guarantee (AGENTS §14), a `valid: true` entry is
772
+ * ALWAYS also `coercible: true`. `@orkestrel/contract` 0.0.7's `explain` mirrors the normalizing
773
+ * `parse`'s leniency, not `is`'s strictness — so a strictly-invalid but coercible candidate (`7`
774
+ * against a string slot) yields `{ valid: false, coercible: true, faults: [] }`: EMPTY faults, since
775
+ * the mismatch the normalizing parse would silently fix is not one `explain` reports. `faults`
776
+ * therefore only ever populates for a NON-coercible mismatch — a wrong type the parse can't coerce
777
+ * (a boolean in a string slot), a missing required key, or an out-of-enum value — where
778
+ * `coercible: false`. `checker.is` / `.parse` / `.explain` are all total over JSON-safe input — a
779
+ * JSON-safe hostile candidate (a `__proto__`-carrying object, deeply nested data) reaches all three
780
+ * and yields a bounded, non-throwing per-candidate verdict; a NON-JSON-safe candidate (e.g. a
781
+ * throwing-getter `Proxy`) never reaches the checker at all — it fails the OUTER `args` parse
782
+ * against {@link import('./shapers.js').inferToolShape} and rejects the WHOLE call with the same
783
+ * `TOOL` {@link import('./errors.js').AgentToolError} a malformed `samples`/`format`/`enum` throws,
784
+ * with no per-candidate verdict produced.
785
+ *
786
+ * @param options - Advertised `name` / `description` overrides (see
787
+ * {@link import('./types.js').InferToolOptions})
788
+ * @returns A `ToolInterface` (named {@link import('./constants.js').INFER_TOOL_NAME} by default)
789
+ *
790
+ * @example
791
+ * ```ts
792
+ * import { createInferTool } from '@src/core'
793
+ * import { createToolManager } from '@orkestrel/agent'
794
+ *
795
+ * const tool = createInferTool()
796
+ * const tools = createToolManager()
797
+ * tools.add(tool)
798
+ *
799
+ * const result = await tools.execute({
800
+ * id: 'call-1',
801
+ * name: 'infer',
802
+ * arguments: { samples: [{ id: 1, name: 'Ada' }, { id: 2, name: 'Bob' }] },
803
+ * })
804
+ * // result.value -> { type: 'object', properties: { id: {...}, name: {...} }, ... }
805
+ *
806
+ * // with candidates, the result is wrapped with per-candidate verdicts
807
+ * const checked = await tools.execute({
808
+ * id: 'call-2',
809
+ * name: 'infer',
810
+ * arguments: {
811
+ * samples: [{ id: 1, name: 'Ada' }],
812
+ * candidates: [{ id: 2, name: 'Bob' }, { id: 'x', name: 'Cy' }],
813
+ * },
814
+ * })
815
+ * // checked.value -> { parameters: {...}, checks: [
816
+ * // { index: 0, valid: true, coercible: true },
817
+ * // { index: 1, valid: false, coercible: false, faults: [...] },
818
+ * // ] }
819
+ * ```
820
+ */
821
+ export declare function createInferTool(options?: InferToolOptions): ToolInterface;
822
+
676
823
  /**
677
824
  * Create the in-memory {@link DefinitionStoreInterface} — a process-lifetime `Map` of database
678
825
  * definitions, the DEFAULT store the upcoming database / relation tools will persist their
@@ -1349,6 +1496,76 @@ export declare const describeToolShape: ObjectShape<{
1349
1496
  name: StringShape;
1350
1497
  }, false>;
1351
1498
 
1499
+ /**
1500
+ * One concrete endpoint {@link import('./factories.js').createEndpointTool} wraps as an
1501
+ * LLM-callable `ToolInterface` — the advertised identity, a non-empty set of example values its
1502
+ * `parameters` are inferred from, and the local handler that runs a call.
1503
+ *
1504
+ * @remarks
1505
+ * `samples` MUST be non-empty — {@link import('./factories.js').createEndpointTool} throws a
1506
+ * typed `TOOL` {@link import('./errors.js').AgentToolError} at CONSTRUCTION when it is empty,
1507
+ * since an empty sample set cannot infer a schema. By DEFAULT ({@link EndpointToolOptions.validate}
1508
+ * `true`) `invoke` receives the PARSED, NORMALIZED args record — a copy of the model-supplied
1509
+ * `args` with each scalar coerced to its inferred type (e.g. a number sent for a string slot
1510
+ * arrives coerced to a string), checked against the same schema advertised as `parameters` — and
1511
+ * a call with a missing required key or a non-coercible value never reaches `invoke` at all (see
1512
+ * {@link EndpointToolOptions.validate}). With
1513
+ * `validate: false`, `invoke` receives the model-supplied `args` VERBATIM (raw passthrough, never
1514
+ * checked against the inferred schema). Either way `invoke`'s return flows back as the tool
1515
+ * call's result; a throw PROPAGATES uncaught, isolated by the `ToolManagerInterface`
1516
+ * (`@orkestrel/agent`) into the canonical error envelope. When `samples` are non-object values,
1517
+ * the advertised schema wraps them under a single required `value` property, so `invoke` receives
1518
+ * an `args` record of the shape `{ value: ... }` — never the bare value.
1519
+ */
1520
+ export declare interface EndpointDefinition {
1521
+ readonly name: string;
1522
+ readonly description: string;
1523
+ readonly samples: readonly unknown[];
1524
+ readonly invoke: EndpointHandler;
1525
+ }
1526
+
1527
+ /**
1528
+ * The handler {@link import('./types.js').EndpointDefinition.invoke} implements — mirrors
1529
+ * `@orkestrel/agent`'s `ToolOptions.execute` signature EXACTLY (same `Readonly<Record<string,
1530
+ * unknown>>` argument, same `Promise<unknown> | unknown` return) so
1531
+ * `execute: (args) => definition.invoke(args)` typechecks with zero assertions in
1532
+ * {@link import('./factories.js').createEndpointTool}.
1533
+ */
1534
+ export declare type EndpointHandler = (args: Readonly<Record<string, unknown>>) => Promise<unknown> | unknown;
1535
+
1536
+ /**
1537
+ * Construction-time tuning for {@link import('./factories.js').createEndpointTool} — the
1538
+ * inferred `parameters` schema's `format` / `enum` constraints, and whether that same schema is
1539
+ * ENFORCED at `execute` time.
1540
+ *
1541
+ * @remarks
1542
+ * `format` / `enum` default to `false`, matching `@orkestrel/contract`'s own
1543
+ * `ValueToSchemaOptions` defaults. `validate` defaults to `true`: the schema
1544
+ * `createEndpointTool` advertises as `parameters` (`samplesToSchema` + `schemaToObject`) is
1545
+ * compiled ONCE at construction (via `@orkestrel/contract` 0.0.7's `schemaToShape`) into a
1546
+ * `ContractInterface` used to `parse` every call's `args` before `invoke` runs — a NORMALIZING
1547
+ * parse: a scalar value is COERCED to its inferred type where the house parsers coerce (a number
1548
+ * to/from a numeric string, a boolean from `'1'`/`'0'`/`'true'`/`'false'`/`1`/`0`), so `invoke`
1549
+ * receives the COERCED values (e.g. `7` sent for a string slot arrives at `invoke` as `'7'`), not
1550
+ * the raw call args. A call whose `args` fails to parse — a required key missing, or a value not
1551
+ * coercible to its slot's type — THROWS a typed `TOOL` {@link import('./errors.js').AgentToolError}
1552
+ * carrying the structured `explain` faults, and `invoke` is never called. Beyond that coercion,
1553
+ * enforcement is STRUCTURAL — required keys, `enum` membership, and numeric bounds — `format`
1554
+ * annotations (`email`, `date-time`, `uuid`, `uri`, ...) are NEVER asserted, mirroring
1555
+ * `@orkestrel/contract`'s own widening-only law for `schemaToShape`: a `format: true`-tuned
1556
+ * endpoint still ACCEPTS a non-conforming string in a format-tagged slot. A key NOT present in
1557
+ * the inferred (closed, `additionalProperties: false`) schema is NEVER a rejection either — it is
1558
+ * SILENTLY DROPPED before `invoke` runs (the same leniency `@orkestrel/contract`'s own `parse`
1559
+ * grants a closed object generally), so `invoke` may see fewer keys than the caller sent. Set
1560
+ * `validate: false` to restore the PRE-0.0.7 behavior exactly — `execute` passes the
1561
+ * model-supplied `args` straight to `invoke` UNCHANGED, unchecked and unstripped.
1562
+ */
1563
+ export declare interface EndpointToolOptions {
1564
+ readonly format?: boolean;
1565
+ readonly enum?: boolean;
1566
+ readonly validate?: boolean;
1567
+ }
1568
+
1352
1569
  /**
1353
1570
  * Expand the relation tool's FLAT dot-path `include` list into a live `@orkestrel/relation`
1354
1571
  * {@link Include} tree — the pure leaf {@link import('./factories.js').createRelationTool} calls
@@ -1408,6 +1625,54 @@ export declare function expandTables(spec: TableSpec): TablesShape;
1408
1625
  /** Flat dot-path relation include list, expanded via {@link import('./helpers.js').expandInclude}. */
1409
1626
  export declare const includeShape: OptionalShape<ArrayShape<StringShape>>;
1410
1627
 
1628
+ export declare const INFER_TOOL_DESCRIPTION: string;
1629
+
1630
+ /**
1631
+ * The name {@link import('./factories.js').createInferTool} advertises by default — the key a
1632
+ * model calls and the `ToolManagerInterface` (`@orkestrel/agent`) registers under.
1633
+ */
1634
+ export declare const INFER_TOOL_NAME = "infer";
1635
+
1636
+ /**
1637
+ * The lean {@link import('@orkestrel/agent').ToolInterface.summary} {@link import('./factories.js').createInferTool}
1638
+ * advertises in place of {@link INFER_TOOL_DESCRIPTION} — a `ToolManagerInterface.definitions()`
1639
+ * (`@orkestrel/agent`) advertises `summary ?? description`, so this one-sentence text stands in
1640
+ * for the full teaching description; the full text stays retrievable via
1641
+ * {@link import('./factories.js').createDescribeTool}.
1642
+ */
1643
+ export declare const INFER_TOOL_SUMMARY = "Infer a JSON Schema (as advertised tool parameters) from one or more example values. Call describe('infer') for the required fields.";
1644
+
1645
+ /**
1646
+ * Options for {@link import('./factories.js').createInferTool} — advertised name/description
1647
+ * overrides only; `format` / `enum` are RUNTIME call arguments (see
1648
+ * {@link import('./shapers.js').inferToolShape}), not construction-time options, since a model
1649
+ * chooses them per call.
1650
+ */
1651
+ export declare interface InferToolOptions {
1652
+ readonly name?: string;
1653
+ readonly description?: string;
1654
+ }
1655
+
1656
+ /**
1657
+ * The shape of {@link import('./factories.js').createInferTool}'s call arguments — one or more
1658
+ * example `samples` to infer a JSON Schema from, plus per-call `format` / `enum` toggles and an
1659
+ * optional `candidates` array to check against the inferred schema.
1660
+ *
1661
+ * @remarks
1662
+ * `samples` requires at least one element (`min: 1`) — an empty array parses to `undefined`,
1663
+ * surfaced by the handler as a typed `TOOL` {@link import('./errors.js').AgentToolError}. When
1664
+ * `candidates` is present (any array, including empty), the handler compiles a contract from the
1665
+ * freshly inferred schema and checks each candidate against it with a STRICT guard (`.is`, no
1666
+ * coercion) — the opposite of {@link import('./factories.js').createEndpointTool}'s NORMALIZING
1667
+ * `.parse` enforcement.
1668
+ */
1669
+ export declare const inferToolShape: ObjectShape<{
1670
+ samples: ArrayShape<JSONShape>;
1671
+ format: OptionalShape<BooleanShape>;
1672
+ enum: OptionalShape<BooleanShape>;
1673
+ candidates: OptionalShape<ArrayShape<JSONShape>>;
1674
+ }, false>;
1675
+
1411
1676
  /**
1412
1677
  * Type guard narrowing an unknown caught value to an {@link AgentToolError}.
1413
1678
  *
@@ -673,6 +673,153 @@ export declare function createDatabaseTool(options?: DatabaseToolOptions): ToolI
673
673
  */
674
674
  export declare function createDescribeTool(tools: ToolManagerInterface): ToolInterface;
675
675
 
676
+ /**
677
+ * Wrap one CONCRETE endpoint ({@link import('./types.js').EndpointDefinition}) as an LLM-callable
678
+ * `ToolInterface` — the endpoint half of the "existing API/DB → MCP tool" bridge (the other half,
679
+ * {@link createInferTool}, is a standalone inference utility).
680
+ *
681
+ * @remarks
682
+ * `parameters` is inferred ONCE at construction from `definition.samples` via
683
+ * `@orkestrel/contract`'s `samplesToSchema` (tuned by {@link import('./types.js').EndpointToolOptions}'s
684
+ * `format` / `enum`), wrapping a non-object root as `{ value: <schema> }` via `schemaToObject` —
685
+ * the SAME object-rooted schema is both the ADVERTISED `parameters` and, by default
686
+ * ({@link import('./types.js').EndpointToolOptions.validate} `true`), the ENFORCED contract:
687
+ * `@orkestrel/contract` 0.0.7's `schemaToShape` compiles it ONCE (via `createContract`) into a
688
+ * `ContractInterface` whose `.parse` runs on every call's `args` before `definition.invoke` — a
689
+ * NORMALIZING parse, not a strict type check: a scalar is COERCED to its inferred type where the
690
+ * house parsers coerce (a number to/from a numeric string, a boolean from `'1'`/`'0'`/`'true'`/
691
+ * `'false'`/`1`/`0`), so `definition.invoke` receives the COERCED value (e.g. `7` sent for a
692
+ * string slot arrives as `'7'`), not the raw call value. A call whose `args` fails to parse into
693
+ * a record — a required key missing, or a value not coercible to its slot's type — THROWS a
694
+ * typed `TOOL` {@link import('./errors.js').AgentToolError} carrying the compiled contract's
695
+ * structured `explain` faults, and `definition.invoke` is never called. `format` annotations are
696
+ * NEVER asserted, and a key outside the closed inferred schema is SILENTLY DROPPED rather than
697
+ * rejected (see {@link import('./types.js').EndpointToolOptions.validate}). With
698
+ * `validate: false`, `execute` PASSES THROUGH the model-supplied `args` to `definition.invoke`
699
+ * WITHOUT re-validation — the pre-0.0.7 behavior, preserved as an explicit opt-out. Either way,
700
+ * `invoke`'s return flows back as the tool call's plain result; a throw PROPAGATES uncaught,
701
+ * isolated by the `ToolManagerInterface` (`@orkestrel/agent`) into the canonical error envelope
702
+ * (AGENTS §14) — never caught or re-wrapped here.
703
+ *
704
+ * @param definition - The endpoint's identity, non-empty samples, and local handler (see
705
+ * {@link import('./types.js').EndpointDefinition})
706
+ * @param options - Construction-time inference tuning + the validate opt-out (see
707
+ * {@link import('./types.js').EndpointToolOptions})
708
+ * @returns A `ToolInterface` named `definition.name`
709
+ *
710
+ * @example
711
+ * ```ts
712
+ * import { createEndpointTool } from '@src/core'
713
+ * import { createToolManager } from '@orkestrel/agent'
714
+ *
715
+ * const tool = createEndpointTool({
716
+ * name: 'lookupUser',
717
+ * description: 'Look up a user by id.',
718
+ * samples: [{ id: '1', name: 'Ada' }, { id: '2', name: 'Bob' }],
719
+ * invoke: (args) => ({ id: args.id, name: 'Ada' }),
720
+ * })
721
+ * const tools = createToolManager()
722
+ * tools.add(tool)
723
+ *
724
+ * // conforming args (all required keys present) parse and reach `invoke`
725
+ * const result = await tools.execute({
726
+ * id: 'call-1',
727
+ * name: 'lookupUser',
728
+ * arguments: { id: '1', name: 'Ada' },
729
+ * })
730
+ * // result.value -> { id: '1', name: 'Ada' }
731
+ *
732
+ * // a nonconforming call (id is not coercible to the required string) is rejected before
733
+ * // `invoke` runs
734
+ * const rejected = await tools.execute({
735
+ * id: 'call-2',
736
+ * name: 'lookupUser',
737
+ * arguments: { id: true, name: 'Ada' },
738
+ * })
739
+ * // rejected.error -> the TOOL AgentToolError message
740
+ * ```
741
+ */
742
+ export declare function createEndpointTool(definition: EndpointDefinition, options?: EndpointToolOptions): ToolInterface;
743
+
744
+ /**
745
+ * Build a standalone LLM-callable tool that infers a JSON Schema from example values — the
746
+ * utility half of the "existing API/DB → MCP tool" bridge (the other half,
747
+ * {@link createEndpointTool}, wraps one CONCRETE endpoint).
748
+ *
749
+ * @remarks
750
+ * The universal tool-handler contract (AGENTS §14): validates the call args against
751
+ * {@link import('./shapers.js').inferToolShape} (`samples` non-empty, `format` / `enum` optional
752
+ * booleans, `candidates` an optional array), infers a schema via `@orkestrel/contract`'s
753
+ * `samplesToSchema`, wraps a non-object root as `{ value: <schema> }` via `schemaToObject` (mirrors
754
+ * the tool-parameters convention every other `create*Tool` factory advertises), and RETURNS the
755
+ * resulting parameters record. An empty `samples` array fails `inferToolShape`'s `min: 1` bound —
756
+ * `contract.parse` returns `undefined` and the handler throws a typed `TOOL`
757
+ * {@link import('./errors.js').AgentToolError}.
758
+ *
759
+ * When `candidates` is ABSENT, the return is the bare parameters record — unchanged from before
760
+ * this array existed. When `candidates` is PRESENT (any array, including empty), the handler
761
+ * compiles a SEPARATE per-call contract from the RAW inferred schema (via `@orkestrel/contract`'s
762
+ * `schemaToShape`, NOT the `schemaToObject`-wrapped parameters — a bare-value sample checks a
763
+ * bare-value candidate) and returns `{ parameters, checks }`, one check per candidate at the same
764
+ * index. Every entry has a UNIFORM shape — `{ index, valid, coercible }`, with `faults` added ONLY
765
+ * when `valid` is `false`: `valid` is the STRICT guard verdict (`checker.is(candidate)`), the
766
+ * OPPOSITE of {@link createEndpointTool}'s enforcement, which coerces (`7` becomes `'7'` for a
767
+ * string slot) — here a conformance report answers "does this value conform AS-IS": `7` against a
768
+ * string slot is `valid: false`, full stop. `coercible` answers a SEPARATE question — "would the
769
+ * NORMALIZING parse accept this value", i.e. would {@link createEndpointTool}'s default enforcement
770
+ * admit it (`checker.parse(candidate) !== undefined`) — computed for every candidate regardless of
771
+ * `valid`; by the house parse/guard round-trip guarantee (AGENTS §14), a `valid: true` entry is
772
+ * ALWAYS also `coercible: true`. `@orkestrel/contract` 0.0.7's `explain` mirrors the normalizing
773
+ * `parse`'s leniency, not `is`'s strictness — so a strictly-invalid but coercible candidate (`7`
774
+ * against a string slot) yields `{ valid: false, coercible: true, faults: [] }`: EMPTY faults, since
775
+ * the mismatch the normalizing parse would silently fix is not one `explain` reports. `faults`
776
+ * therefore only ever populates for a NON-coercible mismatch — a wrong type the parse can't coerce
777
+ * (a boolean in a string slot), a missing required key, or an out-of-enum value — where
778
+ * `coercible: false`. `checker.is` / `.parse` / `.explain` are all total over JSON-safe input — a
779
+ * JSON-safe hostile candidate (a `__proto__`-carrying object, deeply nested data) reaches all three
780
+ * and yields a bounded, non-throwing per-candidate verdict; a NON-JSON-safe candidate (e.g. a
781
+ * throwing-getter `Proxy`) never reaches the checker at all — it fails the OUTER `args` parse
782
+ * against {@link import('./shapers.js').inferToolShape} and rejects the WHOLE call with the same
783
+ * `TOOL` {@link import('./errors.js').AgentToolError} a malformed `samples`/`format`/`enum` throws,
784
+ * with no per-candidate verdict produced.
785
+ *
786
+ * @param options - Advertised `name` / `description` overrides (see
787
+ * {@link import('./types.js').InferToolOptions})
788
+ * @returns A `ToolInterface` (named {@link import('./constants.js').INFER_TOOL_NAME} by default)
789
+ *
790
+ * @example
791
+ * ```ts
792
+ * import { createInferTool } from '@src/core'
793
+ * import { createToolManager } from '@orkestrel/agent'
794
+ *
795
+ * const tool = createInferTool()
796
+ * const tools = createToolManager()
797
+ * tools.add(tool)
798
+ *
799
+ * const result = await tools.execute({
800
+ * id: 'call-1',
801
+ * name: 'infer',
802
+ * arguments: { samples: [{ id: 1, name: 'Ada' }, { id: 2, name: 'Bob' }] },
803
+ * })
804
+ * // result.value -> { type: 'object', properties: { id: {...}, name: {...} }, ... }
805
+ *
806
+ * // with candidates, the result is wrapped with per-candidate verdicts
807
+ * const checked = await tools.execute({
808
+ * id: 'call-2',
809
+ * name: 'infer',
810
+ * arguments: {
811
+ * samples: [{ id: 1, name: 'Ada' }],
812
+ * candidates: [{ id: 2, name: 'Bob' }, { id: 'x', name: 'Cy' }],
813
+ * },
814
+ * })
815
+ * // checked.value -> { parameters: {...}, checks: [
816
+ * // { index: 0, valid: true, coercible: true },
817
+ * // { index: 1, valid: false, coercible: false, faults: [...] },
818
+ * // ] }
819
+ * ```
820
+ */
821
+ export declare function createInferTool(options?: InferToolOptions): ToolInterface;
822
+
676
823
  /**
677
824
  * Create the in-memory {@link DefinitionStoreInterface} — a process-lifetime `Map` of database
678
825
  * definitions, the DEFAULT store the upcoming database / relation tools will persist their
@@ -1349,6 +1496,76 @@ export declare const describeToolShape: ObjectShape<{
1349
1496
  name: StringShape;
1350
1497
  }, false>;
1351
1498
 
1499
+ /**
1500
+ * One concrete endpoint {@link import('./factories.js').createEndpointTool} wraps as an
1501
+ * LLM-callable `ToolInterface` — the advertised identity, a non-empty set of example values its
1502
+ * `parameters` are inferred from, and the local handler that runs a call.
1503
+ *
1504
+ * @remarks
1505
+ * `samples` MUST be non-empty — {@link import('./factories.js').createEndpointTool} throws a
1506
+ * typed `TOOL` {@link import('./errors.js').AgentToolError} at CONSTRUCTION when it is empty,
1507
+ * since an empty sample set cannot infer a schema. By DEFAULT ({@link EndpointToolOptions.validate}
1508
+ * `true`) `invoke` receives the PARSED, NORMALIZED args record — a copy of the model-supplied
1509
+ * `args` with each scalar coerced to its inferred type (e.g. a number sent for a string slot
1510
+ * arrives coerced to a string), checked against the same schema advertised as `parameters` — and
1511
+ * a call with a missing required key or a non-coercible value never reaches `invoke` at all (see
1512
+ * {@link EndpointToolOptions.validate}). With
1513
+ * `validate: false`, `invoke` receives the model-supplied `args` VERBATIM (raw passthrough, never
1514
+ * checked against the inferred schema). Either way `invoke`'s return flows back as the tool
1515
+ * call's result; a throw PROPAGATES uncaught, isolated by the `ToolManagerInterface`
1516
+ * (`@orkestrel/agent`) into the canonical error envelope. When `samples` are non-object values,
1517
+ * the advertised schema wraps them under a single required `value` property, so `invoke` receives
1518
+ * an `args` record of the shape `{ value: ... }` — never the bare value.
1519
+ */
1520
+ export declare interface EndpointDefinition {
1521
+ readonly name: string;
1522
+ readonly description: string;
1523
+ readonly samples: readonly unknown[];
1524
+ readonly invoke: EndpointHandler;
1525
+ }
1526
+
1527
+ /**
1528
+ * The handler {@link import('./types.js').EndpointDefinition.invoke} implements — mirrors
1529
+ * `@orkestrel/agent`'s `ToolOptions.execute` signature EXACTLY (same `Readonly<Record<string,
1530
+ * unknown>>` argument, same `Promise<unknown> | unknown` return) so
1531
+ * `execute: (args) => definition.invoke(args)` typechecks with zero assertions in
1532
+ * {@link import('./factories.js').createEndpointTool}.
1533
+ */
1534
+ export declare type EndpointHandler = (args: Readonly<Record<string, unknown>>) => Promise<unknown> | unknown;
1535
+
1536
+ /**
1537
+ * Construction-time tuning for {@link import('./factories.js').createEndpointTool} — the
1538
+ * inferred `parameters` schema's `format` / `enum` constraints, and whether that same schema is
1539
+ * ENFORCED at `execute` time.
1540
+ *
1541
+ * @remarks
1542
+ * `format` / `enum` default to `false`, matching `@orkestrel/contract`'s own
1543
+ * `ValueToSchemaOptions` defaults. `validate` defaults to `true`: the schema
1544
+ * `createEndpointTool` advertises as `parameters` (`samplesToSchema` + `schemaToObject`) is
1545
+ * compiled ONCE at construction (via `@orkestrel/contract` 0.0.7's `schemaToShape`) into a
1546
+ * `ContractInterface` used to `parse` every call's `args` before `invoke` runs — a NORMALIZING
1547
+ * parse: a scalar value is COERCED to its inferred type where the house parsers coerce (a number
1548
+ * to/from a numeric string, a boolean from `'1'`/`'0'`/`'true'`/`'false'`/`1`/`0`), so `invoke`
1549
+ * receives the COERCED values (e.g. `7` sent for a string slot arrives at `invoke` as `'7'`), not
1550
+ * the raw call args. A call whose `args` fails to parse — a required key missing, or a value not
1551
+ * coercible to its slot's type — THROWS a typed `TOOL` {@link import('./errors.js').AgentToolError}
1552
+ * carrying the structured `explain` faults, and `invoke` is never called. Beyond that coercion,
1553
+ * enforcement is STRUCTURAL — required keys, `enum` membership, and numeric bounds — `format`
1554
+ * annotations (`email`, `date-time`, `uuid`, `uri`, ...) are NEVER asserted, mirroring
1555
+ * `@orkestrel/contract`'s own widening-only law for `schemaToShape`: a `format: true`-tuned
1556
+ * endpoint still ACCEPTS a non-conforming string in a format-tagged slot. A key NOT present in
1557
+ * the inferred (closed, `additionalProperties: false`) schema is NEVER a rejection either — it is
1558
+ * SILENTLY DROPPED before `invoke` runs (the same leniency `@orkestrel/contract`'s own `parse`
1559
+ * grants a closed object generally), so `invoke` may see fewer keys than the caller sent. Set
1560
+ * `validate: false` to restore the PRE-0.0.7 behavior exactly — `execute` passes the
1561
+ * model-supplied `args` straight to `invoke` UNCHANGED, unchecked and unstripped.
1562
+ */
1563
+ export declare interface EndpointToolOptions {
1564
+ readonly format?: boolean;
1565
+ readonly enum?: boolean;
1566
+ readonly validate?: boolean;
1567
+ }
1568
+
1352
1569
  /**
1353
1570
  * Expand the relation tool's FLAT dot-path `include` list into a live `@orkestrel/relation`
1354
1571
  * {@link Include} tree — the pure leaf {@link import('./factories.js').createRelationTool} calls
@@ -1408,6 +1625,54 @@ export declare function expandTables(spec: TableSpec): TablesShape;
1408
1625
  /** Flat dot-path relation include list, expanded via {@link import('./helpers.js').expandInclude}. */
1409
1626
  export declare const includeShape: OptionalShape<ArrayShape<StringShape>>;
1410
1627
 
1628
+ export declare const INFER_TOOL_DESCRIPTION: string;
1629
+
1630
+ /**
1631
+ * The name {@link import('./factories.js').createInferTool} advertises by default — the key a
1632
+ * model calls and the `ToolManagerInterface` (`@orkestrel/agent`) registers under.
1633
+ */
1634
+ export declare const INFER_TOOL_NAME = "infer";
1635
+
1636
+ /**
1637
+ * The lean {@link import('@orkestrel/agent').ToolInterface.summary} {@link import('./factories.js').createInferTool}
1638
+ * advertises in place of {@link INFER_TOOL_DESCRIPTION} — a `ToolManagerInterface.definitions()`
1639
+ * (`@orkestrel/agent`) advertises `summary ?? description`, so this one-sentence text stands in
1640
+ * for the full teaching description; the full text stays retrievable via
1641
+ * {@link import('./factories.js').createDescribeTool}.
1642
+ */
1643
+ export declare const INFER_TOOL_SUMMARY = "Infer a JSON Schema (as advertised tool parameters) from one or more example values. Call describe('infer') for the required fields.";
1644
+
1645
+ /**
1646
+ * Options for {@link import('./factories.js').createInferTool} — advertised name/description
1647
+ * overrides only; `format` / `enum` are RUNTIME call arguments (see
1648
+ * {@link import('./shapers.js').inferToolShape}), not construction-time options, since a model
1649
+ * chooses them per call.
1650
+ */
1651
+ export declare interface InferToolOptions {
1652
+ readonly name?: string;
1653
+ readonly description?: string;
1654
+ }
1655
+
1656
+ /**
1657
+ * The shape of {@link import('./factories.js').createInferTool}'s call arguments — one or more
1658
+ * example `samples` to infer a JSON Schema from, plus per-call `format` / `enum` toggles and an
1659
+ * optional `candidates` array to check against the inferred schema.
1660
+ *
1661
+ * @remarks
1662
+ * `samples` requires at least one element (`min: 1`) — an empty array parses to `undefined`,
1663
+ * surfaced by the handler as a typed `TOOL` {@link import('./errors.js').AgentToolError}. When
1664
+ * `candidates` is present (any array, including empty), the handler compiles a contract from the
1665
+ * freshly inferred schema and checks each candidate against it with a STRICT guard (`.is`, no
1666
+ * coercion) — the opposite of {@link import('./factories.js').createEndpointTool}'s NORMALIZING
1667
+ * `.parse` enforcement.
1668
+ */
1669
+ export declare const inferToolShape: ObjectShape<{
1670
+ samples: ArrayShape<JSONShape>;
1671
+ format: OptionalShape<BooleanShape>;
1672
+ enum: OptionalShape<BooleanShape>;
1673
+ candidates: OptionalShape<ArrayShape<JSONShape>>;
1674
+ }, false>;
1675
+
1411
1676
  /**
1412
1677
  * Type guard narrowing an unknown caught value to an {@link AgentToolError}.
1413
1678
  *