@nylorun/harness 0.8.0-beta.1 → 0.11.0-beta

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 (51) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +5 -38
  3. package/dist/build/assemble.js +1 -0
  4. package/dist/build/bind-tool.js +4 -3
  5. package/dist/build/builder.js +26 -0
  6. package/dist/build/helpers.d.ts +2 -2
  7. package/dist/build/manifest.js +12 -1
  8. package/dist/build/schema.d.ts +11 -16
  9. package/dist/build/schema.js +141 -22
  10. package/dist/errors.d.ts +1 -1
  11. package/dist/index.d.ts +8 -5
  12. package/dist/index.js +2 -0
  13. package/dist/model/adapters.d.ts +160 -0
  14. package/dist/model/adapters.js +545 -0
  15. package/dist/{model-normalize.d.ts → model/normalize.d.ts} +2 -2
  16. package/dist/{model-normalize.js → model/normalize.js} +35 -4
  17. package/dist/model/prepared.d.ts +16 -0
  18. package/dist/model/prepared.js +11 -0
  19. package/dist/session/event-log.d.ts +4 -3
  20. package/dist/session/input-queue.d.ts +7 -4
  21. package/dist/session/input-queue.js +12 -0
  22. package/dist/session/output-contract.d.ts +6 -0
  23. package/dist/session/output-contract.js +12 -0
  24. package/dist/session/scheduler.js +1 -1
  25. package/dist/session/seed.js +79 -5
  26. package/dist/session/session.d.ts +8 -5
  27. package/dist/session/session.js +38 -2
  28. package/dist/session/state.d.ts +1 -1
  29. package/dist/session/submission-stream.d.ts +5 -4
  30. package/dist/step/context-draft.js +1 -7
  31. package/dist/step/model-configuration.js +6 -20
  32. package/dist/step/project.js +25 -3
  33. package/dist/step/resolve.d.ts +1 -0
  34. package/dist/step/resolve.js +1 -0
  35. package/dist/step/run.d.ts +1 -0
  36. package/dist/step/run.js +25 -4
  37. package/dist/step/seal.d.ts +4 -2
  38. package/dist/step/seal.js +64 -13
  39. package/dist/step/step-context.js +1 -1
  40. package/dist/turn/plan-runner.js +38 -3
  41. package/dist/turn/runner.d.ts +6 -4
  42. package/dist/turn/runner.js +6 -4
  43. package/dist/types/manifest.d.ts +5 -4
  44. package/dist/types/middleware.d.ts +10 -1
  45. package/dist/types/model.d.ts +21 -13
  46. package/dist/types/session.d.ts +34 -13
  47. package/dist/types/shared.d.ts +16 -3
  48. package/dist/types/tool.d.ts +54 -17
  49. package/package.json +15 -12
  50. package/dist/utils/digest.d.ts +0 -1
  51. package/dist/utils/digest.js +0 -14
package/CHANGELOG.md CHANGED
@@ -1,9 +1,72 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.0-beta
4
+
5
+ ### Minor Changes
6
+
7
+ - 54ab304: Support portable Runtime protocol version 2 while retaining legacy Studio
8
+ manifest support. Move the Studio CLI into Runtime: use `nylorun studio
9
+ --agent-url <url>` instead of `nylo studio`. Applications should install Runtime
10
+ directly and keep Studio as a development dependency.
11
+
12
+ Retain session lists and media across development reloads, and recognize agent
13
+ file changes on Windows. Validate noninteractive creator startup during release
14
+ verification with deferred provider configuration.
15
+
16
+ Include Harness in this release so the creator does not rely on an unpublished
17
+ compatibility pin. Ship and verify all four packages together.
18
+
3
19
  All notable changes to `@nylorun/harness` are documented in this file.
4
20
 
5
21
  The project follows [Semantic Versioning](https://semver.org/). Before 1.0, the public API is experimental: breaking changes may occur in minor releases, while patch releases are reserved for compatible fixes.
6
22
 
23
+ ## Unreleased
24
+
25
+ ## [0.10.0-beta.1] - 2026-09-03
26
+
27
+ ### Added
28
+
29
+ - Portable ordered media input parts with opaque JSON references preserved through sessions,
30
+ transcripts, middleware, model calls, and observations.
31
+ - `preparedModel()` for adapters that need to materialize a provider request while exposing the
32
+ JSON-safe derived call through the new `model.prepared` observation.
33
+ - Direct `inputSchema` and optional `outputSchema` tool contracts. Harness accepts Zod v4,
34
+ synchronous Standard Schema values with JSON Schema conversion, and explicit validator-backed
35
+ `defineSchema()` contracts.
36
+ - Structured input and output validation diagnostics on failed tool results.
37
+ - Per-turn `session.input({ ... }, { outputSchema })` contracts for locally validated terminal JSON
38
+ results, including canonical model-call projection, JSON candidates, immutable final persistence,
39
+ and `output.invalid` tripwires.
40
+
41
+ ### Changed
42
+
43
+ - Bundled provider translators map direct URL image references and fail unsupported media with the
44
+ stable `model.unsupported-content` error rather than dropping or stringifying it.
45
+ - Chat Completions and Responses translators project terminal output schemas without choosing
46
+ provider strictness; Anthropic Messages reports `model.unsupported-output-schema` until an
47
+ application supplies a custom prepared adapter.
48
+ - Tool definitions use `inputSchema` instead of `parameters`. Completed output is validated when an
49
+ `outputSchema` is supplied; the resulting JSON is the same value recorded, observed, and sent to
50
+ the model. Model configuration and `model.requested` observations expose optional output schemas.
51
+
52
+ ### Breaking changes
53
+
54
+ - Replace every tool `parameters` field with `inputSchema`. `outputSchema` is now an optional tool
55
+ contract and completed tool output may be any JSON value, not only a string.
56
+ - Terminal results, final events, and stream projections may now be JSON values. Consumers that
57
+ assumed string output must render or serialize `JsonValue` safely.
58
+ - Custom model adapters receive the `reportPreparedCall` context method. Adapters that materialize
59
+ provider requests should use it (or `preparedModel()`) to emit one JSON-safe derived request.
60
+
61
+ ## [0.9.0-beta.1] - 2026-09-02
62
+
63
+ ### Added
64
+
65
+ - Public provider translator helpers under `@nylorun/harness/model/adapters` for OpenAI-compatible
66
+ Chat Completions, OpenAI Responses, and Anthropic Messages model loops.
67
+ - `MiddlewareManifest` and declared middleware contributions in `AgentManifest`, including static
68
+ instructions, tool metadata, and model controls for host tooling such as Studio.
69
+
7
70
  ## [0.8.0-beta.1] - 2026-08-31
8
71
 
9
72
  ### Breaking changes
@@ -93,3 +156,4 @@ The project follows [Semantic Versioning](https://semver.org/). Before 1.0, the
93
156
  [0.6.0-beta.1]: https://github.com/nylorun/harness/tree/main/harness
94
157
  [0.7.0-beta.1]: https://github.com/nylorun/harness/tree/main/harness
95
158
  [0.8.0-beta.1]: https://github.com/nylorun/harness/tree/main/harness
159
+ [0.9.0-beta.1]: https://github.com/nylorun/harness/tree/main/harness
package/README.md CHANGED
@@ -1,43 +1,10 @@
1
1
  # `@nylorun/harness`
2
2
 
3
- `@nylorun/harness` is a small, provider-neutral TypeScript loop for model calls and tools. Compose
4
- an agent from a model adapter and named capabilities; keep provider clients, stores, HTTP, and
5
- credentials in ordinary application code.
3
+ Nylorun's TypeScript agent runtime.
6
4
 
7
- > **Experimental beta.** Install with `npm install @nylorun/harness@beta`.
8
-
9
- ```ts
10
- import { Agent, model, tool } from "@nylorun/harness";
11
- import { z } from "zod";
12
-
13
- const adapter = model(async (call) => provider.complete(call));
14
- const echo = tool({
15
- name: "echo",
16
- description: "Echo text",
17
- parameters: z.object({ text: z.string() }),
18
- execute: async ({ text }) => ({ kind: "completed", output: text }),
19
- });
20
-
21
- const agent = Agent({
22
- id: "echo",
23
- name: "Echo",
24
- instructions: "Use echo when asked.",
25
- })
26
- .use({ id: "echo", tools: [echo] })
27
- .with(adapter)
28
- .build();
5
+ For installation, examples, API details, and release notes, see the
6
+ [Nylorun Harness repository](https://github.com/nylorun/harness).
29
7
 
30
- const result = await agent.run().input("Echo hello").completed;
31
- ```
32
-
33
- ## Design boundaries
34
-
35
- - **Capabilities** declare model-visible tools, instructions, context, and candidate review through
36
- `.use()`.
37
- - **Services** are dependencies you construct and pass to capabilities, such as a store, MCP client,
38
- or container runner.
39
- - **Host code** owns transport, persistence, credentials, and background work.
40
- - **Harness core** owns the loop, validation, scheduling, and recording invariants.
8
+ > **Experimental beta.** Install with `npm install @nylorun/harness@beta`.
41
9
 
42
- See [Examples](../examples/README.md) for complete agents and [CHANGELOG.md](./CHANGELOG.md) for
43
- release notes.
10
+ Repository development: [contributing](../CONTRIBUTING.md). Package publication: [releasing](../RELEASING.md).
@@ -27,6 +27,7 @@ export function assembleAgent(middleware, invoke, identity) {
27
27
  id: item.id,
28
28
  handle: item.handle,
29
29
  ...(item.state === undefined ? {} : { state: item.state }),
30
+ ...(item.contributions === undefined ? {} : { contributions: item.contributions }),
30
31
  }));
31
32
  }
32
33
  }
@@ -1,16 +1,17 @@
1
1
  import { HarnessError } from "../errors.js";
2
- import { normalizedSchemaFor } from "./schema.js";
2
+ import { normalizedSchemasFor } from "./schema.js";
3
3
  /** Prepares an executable Tool and records its middleware slot provenance. */
4
4
  export function bindTool(item, owner) {
5
5
  if (!item.name)
6
6
  throw new HarnessError("tool.invalid-name", "Tool name must not be empty");
7
7
  if (typeof item.execute !== "function")
8
8
  throw new HarnessError("tool.invalid", `Tool '${item.name}' must provide execute()`);
9
- const parameters = normalizedSchemaFor(item);
9
+ const schemas = normalizedSchemasFor(item);
10
10
  return Object.freeze({
11
11
  name: item.name,
12
12
  ...(item.description ? { description: item.description } : {}),
13
- parameters: parameters,
13
+ inputSchema: schemas.inputSchema,
14
+ ...(schemas.outputSchema === undefined ? {} : { outputSchema: schemas.outputSchema }),
14
15
  execute: item.execute.bind(item),
15
16
  owner: Object.freeze({ ...owner }),
16
17
  });
@@ -99,6 +99,7 @@ function compileDeclaration(declaration) {
99
99
  const tools = copyItems(declaration.tools, declaration.id);
100
100
  const instructions = copyItems(declaration.instructions, declaration.id);
101
101
  const model = declaration.model;
102
+ const contributions = snapshotContributions(instructions?.items, tools?.items, model);
102
103
  const handle = async (request, next) => {
103
104
  if (tools)
104
105
  request.configuration.tools.set(tools.slot, tools.items);
@@ -114,8 +115,33 @@ function compileDeclaration(declaration) {
114
115
  ...(declaration.state === undefined
115
116
  ? {}
116
117
  : { state: declaration.state }),
118
+ ...(contributions === undefined ? {} : { contributions }),
117
119
  };
118
120
  }
121
+ function snapshotContributions(instructions, tools, model) {
122
+ const snapInstructions = instructions === undefined ? undefined : Object.freeze([...instructions]);
123
+ const snapTools = tools === undefined
124
+ ? undefined
125
+ : Object.freeze(tools.map((tool) => Object.freeze({
126
+ name: tool.name,
127
+ ...(tool.description === undefined ? {} : { description: tool.description }),
128
+ })));
129
+ const snapModel = model === undefined ? undefined : snapshotModel(model);
130
+ if (snapInstructions === undefined && snapTools === undefined && snapModel === undefined) {
131
+ return undefined;
132
+ }
133
+ return Object.freeze({
134
+ ...(snapInstructions === undefined ? {} : { instructions: snapInstructions }),
135
+ ...(snapTools === undefined ? {} : { tools: snapTools }),
136
+ ...(snapModel === undefined ? {} : { model: snapModel }),
137
+ });
138
+ }
139
+ function snapshotModel(model) {
140
+ return Object.freeze({
141
+ ...(model.id === undefined ? {} : { id: model.id }),
142
+ ...(model.controls === undefined ? {} : { controls: Object.freeze({ ...model.controls }) }),
143
+ });
144
+ }
119
145
  function copyItems(value, defaultSlot) {
120
146
  if (value === undefined)
121
147
  return undefined;
@@ -1,6 +1,6 @@
1
1
  import type { ModelAdapter } from "../types/model.js";
2
2
  import type { StepMiddleware } from "../types/middleware.js";
3
- import type { ToolDefinition, ToolObjectSchema } from "../types/tool.js";
4
- export declare const tool: <Parameters extends ToolObjectSchema, State = never>(value: ToolDefinition<Parameters, State>) => ToolDefinition<Parameters, State>;
3
+ import type { ToolDefinition, ToolInputSchema, ToolOutputSchema } from "../types/tool.js";
4
+ export declare const tool: <InputSchema extends ToolInputSchema, State = never, OutputSchema extends ToolOutputSchema | undefined = undefined>(value: ToolDefinition<InputSchema, State, OutputSchema>) => ToolDefinition<InputSchema, State, OutputSchema>;
5
5
  export declare const model: <T extends ModelAdapter>(value: T) => T;
6
6
  export declare const middleware: <T extends StepMiddleware>(value: T) => T;
@@ -1,9 +1,20 @@
1
1
  import { deepFreeze } from "../utils/immutable.js";
2
2
  export function createManifest(input) {
3
- const middleware = input.middleware.map(({ id }) => ({ id }));
3
+ const middleware = input.middleware.map((item) => projectMiddleware(item));
4
4
  return deepFreeze({
5
5
  id: input.id,
6
6
  name: input.name,
7
7
  middleware,
8
8
  });
9
9
  }
10
+ function projectMiddleware(item) {
11
+ const contributions = item.contributions;
12
+ return {
13
+ id: item.id,
14
+ ...(contributions?.instructions === undefined
15
+ ? {}
16
+ : { instructions: contributions.instructions }),
17
+ ...(contributions?.tools === undefined ? {} : { tools: contributions.tools }),
18
+ ...(contributions?.model === undefined ? {} : { model: contributions.model }),
19
+ };
20
+ }
@@ -1,19 +1,14 @@
1
- import type { output } from "zod";
2
- import type { ToolDefinition, ToolObjectSchema } from "../types/tool.js";
3
- export type SchemaValidation<T> = {
4
- readonly ok: true;
5
- readonly value: T;
6
- } | {
7
- readonly ok: false;
8
- readonly issues: readonly string[];
9
- };
10
- export interface NormalizedToolSchema<T> {
11
- readonly jsonSchema: import("../types/shared.js").JsonObject;
12
- validate(value: unknown): SchemaValidation<T>;
1
+ import type { BoundToolSchema, ToolDefinition, ToolSchema, ToolSchemaSource } from "../types/tool.js";
2
+ export type { SchemaValidation } from "../types/tool.js";
3
+ interface NormalizedToolSchemas {
4
+ readonly inputSchema: BoundToolSchema<unknown>;
5
+ readonly outputSchema?: BoundToolSchema<unknown>;
13
6
  }
7
+ /** Creates a portable, explicitly validated schema source from raw JSON Schema. */
8
+ export declare function defineSchema<T>(value: ToolSchema<T>): ToolSchema<T>;
14
9
  /** Eagerly prepares a definition authored through Harness's tool() helper. */
15
10
  export declare function prepareTool<T extends ToolDefinition>(definition: T): T;
16
- /** Returns a cached normalized schema, preparing raw definitions on first bind. */
17
- export declare function normalizedSchemaFor(definition: ToolDefinition): NormalizedToolSchema<unknown>;
18
- /** Converts a synchronous Zod object schema into Harness's immutable runtime representation. */
19
- export declare function normalizeSchema<Parameters extends ToolObjectSchema>(parameters: Parameters): NormalizedToolSchema<output<Parameters>>;
11
+ /** Returns cached normalized contracts, preparing raw definitions on first bind. */
12
+ export declare function normalizedSchemasFor(definition: ToolDefinition): NormalizedToolSchemas;
13
+ /** Converts an accepted tool schema source into Harness's immutable runtime representation. */
14
+ export declare function normalizeSchema<T>(source: ToolSchemaSource<T>, role: "input" | "output"): BoundToolSchema<T>;
@@ -2,56 +2,162 @@ import { z } from "zod";
2
2
  import { HarnessError } from "../errors.js";
3
3
  import { copyJsonObject } from "../utils/immutable.js";
4
4
  const preparedSchemas = new WeakMap();
5
+ /** Creates a portable, explicitly validated schema source from raw JSON Schema. */
6
+ export function defineSchema(value) {
7
+ if (!value || typeof value !== "object" || typeof value.validate !== "function")
8
+ throw new HarnessError("tool.invalid-schema", "Tool schema must provide validate()");
9
+ return Object.freeze({
10
+ jsonSchema: copyJsonObject(value.jsonSchema, "tool schema jsonSchema"),
11
+ validate: value.validate.bind(value),
12
+ });
13
+ }
5
14
  /** Eagerly prepares a definition authored through Harness's tool() helper. */
6
15
  export function prepareTool(definition) {
7
- normalizedSchemaFor(definition);
16
+ normalizedSchemasFor(definition);
8
17
  return definition;
9
18
  }
10
- /** Returns a cached normalized schema, preparing raw definitions on first bind. */
11
- export function normalizedSchemaFor(definition) {
19
+ /** Returns cached normalized contracts, preparing raw definitions on first bind. */
20
+ export function normalizedSchemasFor(definition) {
12
21
  const cached = preparedSchemas.get(definition);
13
22
  if (cached)
14
23
  return cached;
15
- const normalized = normalizeSchema(definition.parameters);
24
+ const normalized = Object.freeze({
25
+ inputSchema: normalizeSchema(definition.inputSchema, "input"),
26
+ ...(definition.outputSchema === undefined
27
+ ? {}
28
+ : { outputSchema: normalizeSchema(definition.outputSchema, "output") }),
29
+ });
16
30
  preparedSchemas.set(definition, normalized);
17
31
  return normalized;
18
32
  }
19
- /** Converts a synchronous Zod object schema into Harness's immutable runtime representation. */
20
- export function normalizeSchema(parameters) {
21
- if (!(parameters instanceof z.ZodObject)) {
22
- throw new HarnessError("tool.invalid-schema", "Tool parameters schema must be a Zod object schema");
23
- }
24
- if (hasDeclaredAsyncWork(parameters)) {
25
- throw new HarnessError("tool.invalid-schema", "Tool parameters schema must validate synchronously");
26
- }
33
+ /** Converts an accepted tool schema source into Harness's immutable runtime representation. */
34
+ export function normalizeSchema(source, role) {
35
+ const normalized = isZodSchema(source)
36
+ ? normalizeZodSchema(source, role)
37
+ : isStandardSchema(source)
38
+ ? normalizeStandardSchema(source, role)
39
+ : isToolSchema(source)
40
+ ? normalizeExplicitSchema(source)
41
+ : undefined;
42
+ if (!normalized)
43
+ throw new HarnessError("tool.invalid-schema", "Tool schema must be a Zod schema, synchronous Standard Schema with JSON Schema conversion, or defineSchema() contract");
44
+ if (role === "input" && normalized.jsonSchema.type !== "object")
45
+ throw new HarnessError("tool.invalid-schema", "Tool inputSchema JSON Schema root type must be object");
46
+ return normalized;
47
+ }
48
+ function normalizeZodSchema(source, role) {
49
+ if (hasDeclaredAsyncWork(source))
50
+ throw new HarnessError("tool.invalid-schema", "Tool schema must validate synchronously");
27
51
  let jsonSchema;
28
52
  try {
29
- jsonSchema = copyJsonObject(z.toJSONSchema(parameters, { target: "draft-07" }), "tool.parameters.jsonSchema");
53
+ jsonSchema = copyJsonObject(z.toJSONSchema(source, { target: "draft-07" }), `tool.${role}Schema.jsonSchema`);
30
54
  }
31
55
  catch (error) {
32
- throw new HarnessError("tool.invalid-schema", `Tool parameters schema must convert to JSON Schema: ${message(error)}`, { cause: error });
33
- }
34
- if (jsonSchema.type !== "object") {
35
- throw new HarnessError("tool.invalid-schema", "Tool JSON Schema root type must be object");
56
+ throw new HarnessError("tool.invalid-schema", `Tool ${role}Schema must convert to JSON Schema: ${message(error)}`, { cause: error });
36
57
  }
37
58
  return Object.freeze({
38
59
  jsonSchema,
39
60
  validate(value) {
40
61
  try {
41
- const result = parameters.safeParse(value);
62
+ const result = source.safeParse(value);
42
63
  if (result.success)
43
64
  return { ok: true, value: result.data };
44
65
  return {
45
66
  ok: false,
46
- issues: result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`),
67
+ issues: Object.freeze(result.error.issues.map((entry) => issue(entry.path.filter(isPathSegment), entry.code, entry.message))),
47
68
  };
48
69
  }
49
70
  catch (error) {
50
- return { ok: false, issues: [synchronousIssue(error)] };
71
+ return { ok: false, issues: [issue([], "schema_error", synchronousIssue(error))] };
51
72
  }
52
73
  },
53
74
  });
54
75
  }
76
+ function normalizeStandardSchema(source, role) {
77
+ let jsonSchema;
78
+ try {
79
+ jsonSchema = copyJsonObject(role === "input"
80
+ ? source["~standard"].jsonSchema.input()
81
+ : source["~standard"].jsonSchema.output(), `tool.${role}Schema.jsonSchema`);
82
+ }
83
+ catch (error) {
84
+ throw new HarnessError("tool.invalid-schema", `Tool ${role}Schema must convert to JSON Schema: ${message(error)}`, { cause: error });
85
+ }
86
+ return Object.freeze({
87
+ jsonSchema,
88
+ validate(value) {
89
+ try {
90
+ const result = source["~standard"].validate(value);
91
+ if (isPromiseLike(result))
92
+ return {
93
+ ok: false,
94
+ issues: [issue([], "async_validation", "Schema validation must be synchronous")],
95
+ };
96
+ if (result && typeof result === "object" && "issues" in result && result.issues)
97
+ return { ok: false, issues: Object.freeze(result.issues.map(standardIssue)) };
98
+ if (result && typeof result === "object" && "value" in result)
99
+ return { ok: true, value: result.value };
100
+ return {
101
+ ok: false,
102
+ issues: [issue([], "schema_error", "Standard Schema returned an invalid result")],
103
+ };
104
+ }
105
+ catch (error) {
106
+ return { ok: false, issues: [issue([], "schema_error", synchronousIssue(error))] };
107
+ }
108
+ },
109
+ });
110
+ }
111
+ function normalizeExplicitSchema(source) {
112
+ return Object.freeze({
113
+ jsonSchema: copyJsonObject(source.jsonSchema, "tool schema jsonSchema"),
114
+ validate(value) {
115
+ try {
116
+ const result = source.validate(value);
117
+ if (isPromiseLike(result))
118
+ return {
119
+ ok: false,
120
+ issues: [issue([], "async_validation", "Schema validation must be synchronous")],
121
+ };
122
+ if (!result || typeof result !== "object" || typeof result.ok !== "boolean")
123
+ return {
124
+ ok: false,
125
+ issues: [issue([], "schema_error", "Tool schema returned an invalid result")],
126
+ };
127
+ if (result.ok)
128
+ return { ok: true, value: result.value };
129
+ return { ok: false, issues: Object.freeze(result.issues.map(normalizeIssue)) };
130
+ }
131
+ catch (error) {
132
+ return { ok: false, issues: [issue([], "schema_error", synchronousIssue(error))] };
133
+ }
134
+ },
135
+ });
136
+ }
137
+ function isZodSchema(value) {
138
+ return value instanceof z.ZodType;
139
+ }
140
+ function isStandardSchema(value) {
141
+ if (!value || typeof value !== "object" || !("~standard" in value))
142
+ return false;
143
+ const standard = value["~standard"];
144
+ if (!standard || typeof standard !== "object")
145
+ return false;
146
+ const record = standard;
147
+ if (typeof record.validate !== "function" ||
148
+ !record.jsonSchema ||
149
+ typeof record.jsonSchema !== "object")
150
+ return false;
151
+ const jsonSchema = record.jsonSchema;
152
+ return typeof jsonSchema.input === "function" && typeof jsonSchema.output === "function";
153
+ }
154
+ function isToolSchema(value) {
155
+ return (!!value &&
156
+ typeof value === "object" &&
157
+ "jsonSchema" in value &&
158
+ "validate" in value &&
159
+ typeof value.validate === "function");
160
+ }
55
161
  /** Zod exposes declared checks in its stable v4 definition graph; reject async checks before a run. */
56
162
  function hasDeclaredAsyncWork(schema) {
57
163
  return visitDefinition(schema._zod?.def, new Set());
@@ -64,8 +170,6 @@ function visitDefinition(value, seen) {
64
170
  if (seen.has(value))
65
171
  return false;
66
172
  seen.add(value);
67
- // Nested Zod schemas carry their own definition graph. Inspect it directly so public helpers
68
- // such as `parseAsync` do not make every otherwise-synchronous schema look asynchronous.
69
173
  const nestedDefinition = value._zod?.def;
70
174
  if (nestedDefinition !== undefined && nestedDefinition !== value)
71
175
  return visitDefinition(nestedDefinition, seen);
@@ -75,6 +179,21 @@ function visitDefinition(value, seen) {
75
179
  }
76
180
  return false;
77
181
  }
182
+ function standardIssue(value) {
183
+ return issue(value.path?.filter(isPathSegment) ?? [], value.code ?? "invalid", value.message ?? "Invalid value");
184
+ }
185
+ function normalizeIssue(value) {
186
+ return issue(value.path?.filter(isPathSegment) ?? [], value.code ?? "invalid", value.message ?? "Invalid value");
187
+ }
188
+ function issue(path, code, text) {
189
+ return Object.freeze({ path: Object.freeze([...path]), code, message: text });
190
+ }
191
+ function isPathSegment(value) {
192
+ return typeof value === "string" || typeof value === "number";
193
+ }
194
+ function isPromiseLike(value) {
195
+ return (!!value && typeof value === "object" && typeof value.then === "function");
196
+ }
78
197
  function synchronousIssue(error) {
79
198
  const reason = message(error);
80
199
  return reason.includes("Promise during synchronous parse")
package/dist/errors.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Machine-readable error raised by Harness-owned code. */
2
- export type HarnessErrorCode = "agent.build-failed" | "agent.lifecycle-sealed" | "capability.state.create-failed" | "capability.state.undeclared" | "context.invalid-item" | "context.invalid-item-type" | "context.invalid-order" | "context.invalid-reason" | "context.invalid-slot" | "interaction.invalid" | "interaction.missing-resume" | "interaction.uncorrelated-resume" | "json.invalid-data" | "json.invalid-object" | "middleware.next-after-return" | "middleware.next-called-twice" | "middleware.request-mutators-revoked" | "model.candidate-missing" | "model.invalid-candidate" | "model.invalid-directive" | "configuration.duplicate-tool-name" | "configuration.invalid" | "configuration.invalid-instructions" | "configuration.invalid-order" | "configuration.invalid-reason" | "configuration.invalid-slot" | "configuration.invalid-tools" | "configuration.model-selection-conflict" | "response.invalid-replacement" | "session.invalid-seed" | "session.record-failed" | "session.stale-result" | "tool.invalid" | "tool.invalid-arguments" | "tool.invalid-name" | "tool.invalid-schema" | "tool.invalid-tool-result";
2
+ export type HarnessErrorCode = "agent.build-failed" | "agent.lifecycle-sealed" | "capability.state.create-failed" | "capability.state.undeclared" | "context.invalid-item" | "context.invalid-item-type" | "context.invalid-order" | "context.invalid-reason" | "context.invalid-slot" | "interaction.invalid" | "interaction.missing-resume" | "interaction.uncorrelated-resume" | "input.invalid-content" | "output.invalid" | "output.invalid-schema" | "json.invalid-data" | "json.invalid-object" | "middleware.next-after-return" | "middleware.next-called-twice" | "middleware.request-mutators-revoked" | "model.candidate-missing" | "model.adapter-invalid-options" | "model.adapter-invalid-response" | "model.invalid-candidate" | "model.invalid-directive" | "model.unsupported-content" | "model.unsupported-output-schema" | "configuration.duplicate-tool-name" | "configuration.invalid" | "configuration.invalid-instructions" | "configuration.invalid-order" | "configuration.invalid-reason" | "configuration.invalid-slot" | "configuration.invalid-tools" | "configuration.model-selection-conflict" | "response.invalid-replacement" | "session.invalid-seed" | "session.record-failed" | "session.stale-result" | "tool.invalid" | "tool.invalid-arguments" | "tool.invalid-output" | "tool.invalid-name" | "tool.invalid-schema" | "tool.invalid-tool-result";
3
3
  export type HarnessErrorDetails = Readonly<Record<string, string | number | boolean>>;
4
4
  export interface HarnessErrorOptions {
5
5
  readonly cause?: unknown;
package/dist/index.d.ts CHANGED
@@ -4,9 +4,12 @@ export { HarnessError, isHarnessError } from "./errors.js";
4
4
  export type { HarnessErrorCode, HarnessErrorDetails, HarnessErrorOptions } from "./errors.js";
5
5
  export { BuiltAgent } from "./build/agent.js";
6
6
  export { middleware, model, tool } from "./build/helpers.js";
7
- export type { AgentManifest } from "./types/manifest.js";
8
- export type { ModelCandidate, ModelControls, ModelDirective, ModelEvidence, ModelFinishReason, ModelAdapter, ModelAdapterContext, ContextContributor, ContextMutationOptions, ContextSnapshot, ModelCall, ModelCallTool, ModelOutputBlock, PromptContentPart, PromptItem, ModelConfigurationContributor, ModelConfigurationInstruction, ModelConfigurationMutationOptions, ModelConfigurationSnapshot, ModelConfigurationTool, ModelRequest, ModelToolCall, ModelUsage, } from "./types/model.js";
9
- export type { BoundMiddleware, CapabilityDeclaration, CapabilityItems, CapabilityState, StepInput, StepMiddleware, StepRequest, StepResponse, } from "./types/middleware.js";
7
+ export { defineSchema } from "./build/schema.js";
8
+ export { preparedModel } from "./model/prepared.js";
9
+ export type { PreparedModelOptions } from "./model/prepared.js";
10
+ export type { AgentManifest, MiddlewareManifest } from "./types/manifest.js";
11
+ export type { BoundMiddleware, CapabilityDeclaration, CapabilityItems, CapabilityState, MiddlewareContributions, StepInput, StepMiddleware, StepRequest, StepResponse, } from "./types/middleware.js";
12
+ export type { ModelCandidate, ModelControls, ModelDirective, ModelEvidence, ModelFinishReason, ModelAdapter, ModelAdapterContext, ModelPreparedCall, ContextContributor, ContextMutationOptions, ContextSnapshot, ModelCall, ModelCallTool, ModelOutputBlock, PromptContentPart, PromptItem, ModelConfigurationContributor, ModelConfigurationInstruction, ModelConfigurationMutationOptions, ModelConfigurationSnapshot, ModelConfigurationTool, ModelRequest, ModelToolCall, ModelUsage, } from "./types/model.js";
10
13
  export type { BuildDiagnostic, ContextItem, DeferredOutcome, JsonObject, JsonPrimitive, JsonValue, ObserveEvent, ObserveModelConfigurationSnapshot, ObserveModelRequested, ObserveSealedCall, ObserveToolSnapshot, Observer, Tripwire, } from "./types/shared.js";
11
- export type { ActiveExecutionRecord, ActiveInteractionExecutionRecord, ActiveModelExecutionRecord, ActiveToolCallRecord, ActiveToolsExecutionRecord, InteractionReply, InputCompletion, InputEvent, InputHandle, InputOptions, MessageInput, Session, SessionInput, SessionIdentity, SessionEvent, SessionOptions, SessionRecord, SessionRecorder, SessionRunOptions, SessionSeed, SeededSessionOptions, SessionSnapshot, TranscriptEntry, } from "./types/session.js";
12
- export type { BoundToolSchema, BoundToolDefinition, Interaction, RequiredInteraction, SealedToolCall, ToolContent, ToolDefinition, ToolExecutionContext, ToolExecutionResume, ToolObjectSchema, ToolOwner, ToolOutcome, ToolResult, } from "./types/tool.js";
14
+ export type { ActiveExecutionRecord, ActiveInteractionExecutionRecord, ActiveModelExecutionRecord, ActiveToolCallRecord, ActiveToolsExecutionRecord, InteractionReply, InputCompletion, InputEvent, InputHandle, InputOptions, OutputInputOptions, MessageInput, Session, SessionInput, SessionIdentity, SessionEvent, SessionOptions, SessionRecord, SessionRecorder, SessionRunOptions, SessionSeed, SeededSessionOptions, SessionSnapshot, TranscriptEntry, UserContentPart, } from "./types/session.js";
15
+ export type { BoundToolSchema, BoundToolDefinition, Interaction, RequiredInteraction, SealedToolCall, ToolContent, ToolDefinition, ToolExecutionContext, ToolExecutionResume, ToolInputSchema, ToolOutputSchema, ToolSchema, ToolSchemaSource, StandardToolSchema, StandardSchemaIssue, SchemaIssue, SchemaValidation, SchemaOutput, ToolOwner, ToolOutcome, ToolResult, ToolValidationFailureDetails, } from "./types/tool.js";
package/dist/index.js CHANGED
@@ -2,3 +2,5 @@ export { Agent, AgentBuilder, AgentBuildError, AgentLifecycleError, BoundAgentBu
2
2
  export { HarnessError, isHarnessError } from "./errors.js";
3
3
  export { BuiltAgent } from "./build/agent.js";
4
4
  export { middleware, model, tool } from "./build/helpers.js";
5
+ export { defineSchema } from "./build/schema.js";
6
+ export { preparedModel } from "./model/prepared.js";