@nylorun/harness 0.9.0-beta.1 → 0.11.1-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 (46) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +5 -79
  3. package/dist/build/bind-tool.js +4 -3
  4. package/dist/build/helpers.d.ts +2 -2
  5. package/dist/build/schema.d.ts +11 -16
  6. package/dist/build/schema.js +141 -22
  7. package/dist/errors.d.ts +1 -1
  8. package/dist/index.d.ts +6 -3
  9. package/dist/index.js +2 -0
  10. package/dist/model/adapters.d.ts +48 -5
  11. package/dist/model/adapters.js +142 -17
  12. package/dist/model/normalize.js +49 -6
  13. package/dist/model/prepared.d.ts +16 -0
  14. package/dist/model/prepared.js +11 -0
  15. package/dist/session/event-log.d.ts +4 -3
  16. package/dist/session/input-queue.d.ts +7 -4
  17. package/dist/session/input-queue.js +12 -0
  18. package/dist/session/output-contract.d.ts +6 -0
  19. package/dist/session/output-contract.js +12 -0
  20. package/dist/session/scheduler.js +1 -1
  21. package/dist/session/seed.js +80 -6
  22. package/dist/session/session.d.ts +8 -5
  23. package/dist/session/session.js +38 -2
  24. package/dist/session/state.d.ts +1 -1
  25. package/dist/session/submission-stream.d.ts +5 -4
  26. package/dist/step/canonicalize.js +3 -0
  27. package/dist/step/context-draft.js +1 -7
  28. package/dist/step/model-configuration.js +5 -19
  29. package/dist/step/project.js +31 -5
  30. package/dist/step/resolve.d.ts +1 -0
  31. package/dist/step/resolve.js +1 -0
  32. package/dist/step/run.d.ts +1 -0
  33. package/dist/step/run.js +24 -3
  34. package/dist/step/seal.d.ts +4 -2
  35. package/dist/step/seal.js +63 -12
  36. package/dist/turn/plan-runner.js +38 -3
  37. package/dist/turn/runner.d.ts +6 -4
  38. package/dist/turn/runner.js +6 -4
  39. package/dist/types/middleware.d.ts +1 -1
  40. package/dist/types/model.d.ts +30 -13
  41. package/dist/types/session.d.ts +34 -13
  42. package/dist/types/shared.d.ts +16 -3
  43. package/dist/types/tool.d.ts +54 -17
  44. package/package.json +11 -12
  45. package/dist/utils/digest.d.ts +0 -1
  46. package/dist/utils/digest.js +0 -14
package/CHANGELOG.md CHANGED
@@ -1,9 +1,69 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.1-beta
4
+
5
+ ### Patch Changes
6
+
7
+ - d27242c: Preserve opaque provider continuation metadata through assistant conversation history. Gemini tool calls now retain thought signatures when sending tool results back to the model, including signed empty text and reasoning blocks. Only the originating provider and model receive their signatures.
8
+
9
+ ## 0.11.0-beta
10
+
11
+ ### Minor Changes
12
+
13
+ - 54ab304: Support portable Runtime protocol version 2 while retaining legacy Studio
14
+ manifest support. Move the Studio CLI into Runtime: use `nylorun studio
15
+ --agent-url <url>` instead of `nylo studio`. Applications should install Runtime
16
+ directly and keep Studio as a development dependency.
17
+
18
+ Retain session lists and media across development reloads, and recognize agent
19
+ file changes on Windows. Validate noninteractive creator startup during release
20
+ verification with deferred provider configuration.
21
+
22
+ Include Harness in this release so the creator does not rely on an unpublished
23
+ compatibility pin. Ship and verify all four packages together.
24
+
3
25
  All notable changes to `@nylorun/harness` are documented in this file.
4
26
 
5
27
  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
28
 
29
+ ## Unreleased
30
+
31
+ ## [0.10.0-beta.1] - 2026-09-03
32
+
33
+ ### Added
34
+
35
+ - Portable ordered media input parts with opaque JSON references preserved through sessions,
36
+ transcripts, middleware, model calls, and observations.
37
+ - `preparedModel()` for adapters that need to materialize a provider request while exposing the
38
+ JSON-safe derived call through the new `model.prepared` observation.
39
+ - Direct `inputSchema` and optional `outputSchema` tool contracts. Harness accepts Zod v4,
40
+ synchronous Standard Schema values with JSON Schema conversion, and explicit validator-backed
41
+ `defineSchema()` contracts.
42
+ - Structured input and output validation diagnostics on failed tool results.
43
+ - Per-turn `session.input({ ... }, { outputSchema })` contracts for locally validated terminal JSON
44
+ results, including canonical model-call projection, JSON candidates, immutable final persistence,
45
+ and `output.invalid` tripwires.
46
+
47
+ ### Changed
48
+
49
+ - Bundled provider translators map direct URL image references and fail unsupported media with the
50
+ stable `model.unsupported-content` error rather than dropping or stringifying it.
51
+ - Chat Completions and Responses translators project terminal output schemas without choosing
52
+ provider strictness; Anthropic Messages reports `model.unsupported-output-schema` until an
53
+ application supplies a custom prepared adapter.
54
+ - Tool definitions use `inputSchema` instead of `parameters`. Completed output is validated when an
55
+ `outputSchema` is supplied; the resulting JSON is the same value recorded, observed, and sent to
56
+ the model. Model configuration and `model.requested` observations expose optional output schemas.
57
+
58
+ ### Breaking changes
59
+
60
+ - Replace every tool `parameters` field with `inputSchema`. `outputSchema` is now an optional tool
61
+ contract and completed tool output may be any JSON value, not only a string.
62
+ - Terminal results, final events, and stream projections may now be JSON values. Consumers that
63
+ assumed string output must render or serialize `JsonValue` safely.
64
+ - Custom model adapters receive the `reportPreparedCall` context method. Adapters that materialize
65
+ provider requests should use it (or `preparedModel()`) to emit one JSON-safe derived request.
66
+
7
67
  ## [0.9.0-beta.1] - 2026-09-02
8
68
 
9
69
  ### Added
package/README.md CHANGED
@@ -1,84 +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();
29
-
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.
5
+ For installation, examples, API details, and release notes, see the
6
+ [Nylorun Harness repository](https://github.com/nylorun/harness).
41
7
 
42
- See [Examples](../examples/README.md) for complete agents and [CHANGELOG.md](./CHANGELOG.md) for
43
- release notes.
44
-
45
- ## Model adapter translators
46
-
47
- For OpenAI-compatible endpoints, keep transport and credentials in host code while Harness maps its
48
- canonical model call and candidate:
49
-
50
- ```ts
51
- import { chatCompletionsAdapter } from "@nylorun/harness/model/adapters";
52
-
53
- const adapter = chatCompletionsAdapter(async (body, call, { signal }) => {
54
- const response = await fetch("https://example.com/v1/chat/completions", {
55
- method: "POST",
56
- headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
57
- body: JSON.stringify({ model: "my-model", ...call.model?.config, ...body }),
58
- signal,
59
- });
60
- if (!response.ok) throw new Error(await response.text());
61
- return response.json();
62
- });
63
- ```
64
-
65
- `toResponses` / `fromResponses` / `responsesAdapter` support direct OpenAI Responses transport.
66
- `toMessages` / `fromMessages` / `anthropicAdapter` support direct Anthropic Messages transport;
67
- `anthropicAdapter` requires an explicit `defaultMaxOutputTokens`. These translators support text and
68
- JSON tool loops only. Provider-native continuation state, streaming, images, and cache controls stay
69
- in application integrations.
70
-
71
- ```ts
72
- const responses = responsesAdapter((body, call, { signal }) =>
73
- openai.responses.create({ model: "gpt-5.6", ...call.model?.config, ...body }, { signal }),
74
- );
8
+ > **Experimental beta.** Install with `npm install @nylorun/harness@beta`.
75
9
 
76
- const messages = anthropicAdapter({
77
- defaultMaxOutputTokens: 1_024,
78
- send: (body, call, { signal }) =>
79
- anthropic.messages.create(
80
- { model: "claude-sonnet-4-5", ...call.model?.config, ...body },
81
- { signal },
82
- ),
83
- });
84
- ```
10
+ Repository development: [contributing](../CONTRIBUTING.md). Package publication: [releasing](../RELEASING.md).
@@ -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
  });
@@ -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,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.adapter-invalid-options" | "model.adapter-invalid-response" | "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 { defineSchema } from "./build/schema.js";
8
+ export { preparedModel } from "./model/prepared.js";
9
+ export type { PreparedModelOptions } from "./model/prepared.js";
7
10
  export type { AgentManifest, MiddlewareManifest } from "./types/manifest.js";
8
11
  export type { BoundMiddleware, CapabilityDeclaration, CapabilityItems, CapabilityState, MiddlewareContributions, StepInput, StepMiddleware, StepRequest, StepResponse, } from "./types/middleware.js";
9
- 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";
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";
@@ -1,8 +1,11 @@
1
1
  import type { ModelAdapter, ModelAdapterContext, ModelCall, ModelCandidate } from "../types/model.js";
2
2
  import type { JsonObject } from "../types/shared.js";
3
3
  export type ChatCompletionsMessage = {
4
- readonly role: "system" | "user";
4
+ readonly role: "system";
5
5
  readonly content: string;
6
+ } | {
7
+ readonly role: "user";
8
+ readonly content: string | readonly ChatCompletionsContentPart[];
6
9
  } | {
7
10
  readonly role: "assistant";
8
11
  readonly content: string | null;
@@ -12,6 +15,15 @@ export type ChatCompletionsMessage = {
12
15
  readonly tool_call_id: string;
13
16
  readonly content: string;
14
17
  };
18
+ export type ChatCompletionsContentPart = {
19
+ readonly type: "text";
20
+ readonly text: string;
21
+ } | {
22
+ readonly type: "image_url";
23
+ readonly image_url: Readonly<{
24
+ url: string;
25
+ }>;
26
+ };
15
27
  export interface ChatCompletionsToolCall {
16
28
  readonly id: string;
17
29
  readonly type: "function";
@@ -32,6 +44,13 @@ export interface ChatCompletionsRequest {
32
44
  }>[];
33
45
  readonly temperature?: number;
34
46
  readonly max_completion_tokens?: number;
47
+ readonly response_format?: Readonly<{
48
+ type: "json_schema";
49
+ json_schema: Readonly<{
50
+ name: string;
51
+ schema: JsonObject;
52
+ }>;
53
+ }>;
35
54
  }
36
55
  export interface ResponsesRequest {
37
56
  readonly instructions?: string;
@@ -44,11 +63,18 @@ export interface ResponsesRequest {
44
63
  }>[];
45
64
  readonly temperature?: number;
46
65
  readonly max_output_tokens?: number;
66
+ readonly text?: Readonly<{
67
+ format: Readonly<{
68
+ type: "json_schema";
69
+ name: string;
70
+ schema: JsonObject;
71
+ }>;
72
+ }>;
47
73
  }
48
74
  export type ResponsesInputItem = {
49
75
  readonly type: "message";
50
76
  readonly role: "user" | "assistant";
51
- readonly content: string;
77
+ readonly content: string | readonly ResponsesContentPart[];
52
78
  } | {
53
79
  readonly type: "function_call";
54
80
  readonly call_id: string;
@@ -59,6 +85,13 @@ export type ResponsesInputItem = {
59
85
  readonly call_id: string;
60
86
  readonly output: string;
61
87
  };
88
+ export type ResponsesContentPart = {
89
+ readonly type: "input_text";
90
+ readonly text: string;
91
+ } | {
92
+ readonly type: "input_image";
93
+ readonly image_url: string;
94
+ };
62
95
  export interface MessagesRequest {
63
96
  readonly system?: string;
64
97
  readonly messages: readonly MessagesMessage[];
@@ -72,7 +105,7 @@ export interface MessagesRequest {
72
105
  }
73
106
  export type MessagesMessage = {
74
107
  readonly role: "user";
75
- readonly content: string | readonly MessagesToolResult[];
108
+ readonly content: string | readonly (MessagesToolResult | MessagesUserContentPart)[];
76
109
  } | {
77
110
  readonly role: "assistant";
78
111
  readonly content: readonly MessagesAssistantPart[];
@@ -92,6 +125,16 @@ export interface MessagesToolResult {
92
125
  readonly content: string;
93
126
  readonly is_error?: boolean;
94
127
  }
128
+ export type MessagesUserContentPart = {
129
+ readonly type: "text";
130
+ readonly text: string;
131
+ } | {
132
+ readonly type: "image";
133
+ readonly source: Readonly<{
134
+ type: "url";
135
+ url: string;
136
+ }>;
137
+ };
95
138
  export type AdapterSend<Request> = (request: Request, call: ModelCall, context: ModelAdapterContext) => Promise<unknown>;
96
139
  export interface AnthropicAdapterOptions {
97
140
  readonly defaultMaxOutputTokens: number;
@@ -100,13 +143,13 @@ export interface AnthropicAdapterOptions {
100
143
  /** Translate a Harness call to the OpenAI Chat Completions request shape. */
101
144
  export declare function toChatCompletions(call: ModelCall): ChatCompletionsRequest;
102
145
  /** Translate a Chat Completions response into a Harness candidate. */
103
- export declare function fromChatCompletions(value: unknown): ModelCandidate;
146
+ export declare function fromChatCompletions(value: unknown, call?: Pick<ModelCall, "outputSchema">): ModelCandidate;
104
147
  /** Return a Harness adapter backed by an application-owned Chat Completions send function. */
105
148
  export declare function chatCompletionsAdapter(send: AdapterSend<ChatCompletionsRequest>): ModelAdapter;
106
149
  /** Translate a Harness call to the OpenAI Responses request shape. */
107
150
  export declare function toResponses(call: ModelCall): ResponsesRequest;
108
151
  /** Translate an OpenAI Responses response into a Harness candidate. */
109
- export declare function fromResponses(value: unknown): ModelCandidate;
152
+ export declare function fromResponses(value: unknown, call?: Pick<ModelCall, "outputSchema">): ModelCandidate;
110
153
  /** Return a Harness adapter backed by an application-owned Responses send function. */
111
154
  export declare function responsesAdapter(send: AdapterSend<ResponsesRequest>): ModelAdapter;
112
155
  /** Translate a Harness call to the Anthropic Messages request shape. */