@nylorun/harness 0.7.0-beta.1 → 0.9.0-beta.1
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.
- package/CHANGELOG.md +28 -0
- package/README.md +48 -2
- package/dist/build/agent.d.ts +2 -0
- package/dist/build/agent.js +4 -0
- package/dist/build/assemble.d.ts +4 -1
- package/dist/build/assemble.js +13 -2
- package/dist/build/builder.d.ts +28 -10
- package/dist/build/builder.js +85 -29
- package/dist/build/manifest.d.ts +2 -0
- package/dist/build/manifest.js +14 -1
- package/dist/errors.d.ts +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1 -1
- package/dist/model/adapters.d.ts +117 -0
- package/dist/model/adapters.js +431 -0
- package/dist/{model-normalize.d.ts → model/normalize.d.ts} +2 -2
- package/dist/{model-normalize.js → model/normalize.js} +3 -3
- package/dist/session/seed.js +1 -1
- package/dist/step/model-configuration.js +1 -1
- package/dist/step/run.js +1 -1
- package/dist/step/seal.js +1 -1
- package/dist/step/step-context.js +1 -1
- package/dist/types/manifest.d.ts +7 -4
- package/dist/types/middleware.d.ts +9 -0
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,32 @@ All notable changes to `@nylorun/harness` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
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
6
|
|
|
7
|
+
## [0.9.0-beta.1] - 2026-09-02
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Public provider translator helpers under `@nylorun/harness/model/adapters` for OpenAI-compatible
|
|
12
|
+
Chat Completions, OpenAI Responses, and Anthropic Messages model loops.
|
|
13
|
+
- `MiddlewareManifest` and declared middleware contributions in `AgentManifest`, including static
|
|
14
|
+
instructions, tool metadata, and model controls for host tooling such as Studio.
|
|
15
|
+
|
|
16
|
+
## [0.8.0-beta.1] - 2026-08-31
|
|
17
|
+
|
|
18
|
+
### Breaking changes
|
|
19
|
+
|
|
20
|
+
- `Agent()` now takes identity options instead of a model adapter. Migrate
|
|
21
|
+
`Agent(adapter).use(...).build()` to
|
|
22
|
+
`Agent({ id, name, instructions }).use(...).with(adapter).build()`.
|
|
23
|
+
- `.build()` exists only on `BoundAgentBuilder`, the type returned by a single `.with(onModelCall)`.
|
|
24
|
+
`AgentBuilder` has `.use()` and `.with()` only.
|
|
25
|
+
- `AgentManifest` now includes `id` and `name`. Built agents expose the same fields as `agent.id`
|
|
26
|
+
and `agent.name`.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- Optional constructor `instructions` compile as reserved `agent` middleware. A string is
|
|
31
|
+
normalized to one instruction. Capability-specific instructions still go through `.use()`.
|
|
32
|
+
|
|
7
33
|
## [0.7.0-beta.1] - 2026-08-30
|
|
8
34
|
|
|
9
35
|
### Breaking changes
|
|
@@ -75,3 +101,5 @@ The project follows [Semantic Versioning](https://semver.org/). Before 1.0, the
|
|
|
75
101
|
[0.5.0-beta.1]: https://github.com/nylorun/harness/tree/main/harness
|
|
76
102
|
[0.6.0-beta.1]: https://github.com/nylorun/harness/tree/main/harness
|
|
77
103
|
[0.7.0-beta.1]: https://github.com/nylorun/harness/tree/main/harness
|
|
104
|
+
[0.8.0-beta.1]: https://github.com/nylorun/harness/tree/main/harness
|
|
105
|
+
[0.9.0-beta.1]: https://github.com/nylorun/harness/tree/main/harness
|
package/README.md
CHANGED
|
@@ -18,8 +18,13 @@ const echo = tool({
|
|
|
18
18
|
execute: async ({ text }) => ({ kind: "completed", output: text }),
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
const agent = Agent(
|
|
22
|
-
|
|
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)
|
|
23
28
|
.build();
|
|
24
29
|
|
|
25
30
|
const result = await agent.run().input("Echo hello").completed;
|
|
@@ -36,3 +41,44 @@ const result = await agent.run().input("Echo hello").completed;
|
|
|
36
41
|
|
|
37
42
|
See [Examples](../examples/README.md) for complete agents and [CHANGELOG.md](./CHANGELOG.md) for
|
|
38
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
|
+
);
|
|
75
|
+
|
|
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
|
+
```
|
package/dist/build/agent.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export declare class BuiltAgent {
|
|
|
10
10
|
#private;
|
|
11
11
|
readonly middleware: readonly BoundMiddleware[];
|
|
12
12
|
readonly manifest: AgentManifest;
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly name: string;
|
|
13
15
|
private constructor();
|
|
14
16
|
run(options?: SessionRunOptions): Session;
|
|
15
17
|
}
|
package/dist/build/agent.js
CHANGED
|
@@ -5,10 +5,14 @@ let createBoundAgent;
|
|
|
5
5
|
export class BuiltAgent {
|
|
6
6
|
middleware;
|
|
7
7
|
manifest;
|
|
8
|
+
id;
|
|
9
|
+
name;
|
|
8
10
|
#loopAgent;
|
|
9
11
|
constructor(middleware, invoke, manifest) {
|
|
10
12
|
this.middleware = middleware;
|
|
11
13
|
this.manifest = manifest;
|
|
14
|
+
this.id = manifest.id;
|
|
15
|
+
this.name = manifest.name;
|
|
12
16
|
this.#loopAgent = Object.freeze({
|
|
13
17
|
middleware,
|
|
14
18
|
invoke,
|
package/dist/build/assemble.d.ts
CHANGED
|
@@ -2,4 +2,7 @@ import type { BuildResult } from "../types/manifest.js";
|
|
|
2
2
|
import type { BoundMiddleware } from "../types/middleware.js";
|
|
3
3
|
import type { ModelAdapter } from "../types/model.js";
|
|
4
4
|
import { type BuiltAgent } from "./agent.js";
|
|
5
|
-
export declare function assembleAgent(middleware: readonly BoundMiddleware[], invoke: ModelAdapter
|
|
5
|
+
export declare function assembleAgent(middleware: readonly BoundMiddleware[], invoke: ModelAdapter, identity: Readonly<{
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
}>): BuildResult<BuiltAgent>;
|
package/dist/build/assemble.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { bindAgent } from "./agent.js";
|
|
2
2
|
import { createManifest } from "./manifest.js";
|
|
3
3
|
const diagnostic = (code, message, extra = {}) => Object.freeze({ code, message, ...extra });
|
|
4
|
-
export function assembleAgent(middleware, invoke) {
|
|
4
|
+
export function assembleAgent(middleware, invoke, identity) {
|
|
5
5
|
const diagnostics = [];
|
|
6
|
+
if (typeof identity.id !== "string" || identity.id.length === 0) {
|
|
7
|
+
diagnostics.push(diagnostic("agent.invalid-id", "Agent id must be a non-empty string"));
|
|
8
|
+
}
|
|
9
|
+
if (typeof identity.name !== "string" || identity.name.length === 0) {
|
|
10
|
+
diagnostics.push(diagnostic("agent.invalid-name", "Agent name must be a non-empty string"));
|
|
11
|
+
}
|
|
6
12
|
if (typeof invoke !== "function") {
|
|
7
13
|
diagnostics.push(diagnostic("harness.invalid-model", "A model invoke function is required"));
|
|
8
14
|
}
|
|
@@ -21,13 +27,18 @@ export function assembleAgent(middleware, invoke) {
|
|
|
21
27
|
id: item.id,
|
|
22
28
|
handle: item.handle,
|
|
23
29
|
...(item.state === undefined ? {} : { state: item.state }),
|
|
30
|
+
...(item.contributions === undefined ? {} : { contributions: item.contributions }),
|
|
24
31
|
}));
|
|
25
32
|
}
|
|
26
33
|
}
|
|
27
34
|
if (diagnostics.length)
|
|
28
35
|
return Object.freeze({ ok: false, diagnostics: Object.freeze(diagnostics) });
|
|
29
36
|
const frozenMiddleware = Object.freeze(frozen);
|
|
30
|
-
const manifest = createManifest({
|
|
37
|
+
const manifest = createManifest({
|
|
38
|
+
id: identity.id,
|
|
39
|
+
name: identity.name,
|
|
40
|
+
middleware: frozenMiddleware,
|
|
41
|
+
});
|
|
31
42
|
const agent = bindAgent(frozenMiddleware, invoke, manifest);
|
|
32
43
|
return Object.freeze({ ok: true, agent, manifest });
|
|
33
44
|
}
|
package/dist/build/builder.d.ts
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
|
-
import type { CapabilityDeclaration, StepMiddleware } from "../types/middleware.js";
|
|
1
|
+
import type { BoundMiddleware, CapabilityDeclaration, StepMiddleware } from "../types/middleware.js";
|
|
2
2
|
import type { ModelAdapter } from "../types/model.js";
|
|
3
3
|
import type { BuildDiagnostic } from "../types/shared.js";
|
|
4
4
|
import { HarnessError } from "../errors.js";
|
|
5
5
|
import type { BuiltAgent } from "./agent.js";
|
|
6
|
+
export interface AgentOptions {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly instructions?: string | readonly string[];
|
|
10
|
+
}
|
|
11
|
+
interface BuilderState {
|
|
12
|
+
readonly id: string;
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly middleware: BoundMiddleware[];
|
|
15
|
+
invoke?: ModelAdapter;
|
|
16
|
+
bound: boolean;
|
|
17
|
+
sealed: boolean;
|
|
18
|
+
agent?: BuiltAgent;
|
|
19
|
+
error?: AgentBuildError;
|
|
20
|
+
middlewareSeq: number;
|
|
21
|
+
}
|
|
6
22
|
export declare class AgentBuildError extends HarnessError {
|
|
7
23
|
readonly diagnostics: readonly BuildDiagnostic[];
|
|
8
24
|
constructor(diagnostics: readonly BuildDiagnostic[]);
|
|
@@ -10,19 +26,21 @@ export declare class AgentBuildError extends HarnessError {
|
|
|
10
26
|
export declare class AgentLifecycleError extends HarnessError {
|
|
11
27
|
constructor(message: string);
|
|
12
28
|
}
|
|
13
|
-
export declare function Agent(
|
|
29
|
+
export declare function Agent(options: AgentOptions): AgentBuilder;
|
|
14
30
|
export declare class AgentBuilder {
|
|
15
|
-
private readonly
|
|
16
|
-
|
|
17
|
-
private sealed;
|
|
18
|
-
private agent?;
|
|
19
|
-
private error?;
|
|
20
|
-
private middlewareSeq;
|
|
21
|
-
constructor(invoke: ModelAdapter);
|
|
31
|
+
private readonly state;
|
|
32
|
+
constructor(state: BuilderState);
|
|
22
33
|
use(middleware: StepMiddleware): this;
|
|
23
34
|
use(id: string, middleware: StepMiddleware): this;
|
|
24
35
|
use<State>(declaration: CapabilityDeclaration<State>): this;
|
|
25
|
-
|
|
36
|
+
with(onModelCall: ModelAdapter): BoundAgentBuilder;
|
|
26
37
|
private nextMiddlewareId;
|
|
27
38
|
private push;
|
|
39
|
+
private assertOpen;
|
|
40
|
+
}
|
|
41
|
+
export declare class BoundAgentBuilder {
|
|
42
|
+
private readonly state;
|
|
43
|
+
constructor(state: BuilderState);
|
|
44
|
+
build(): BuiltAgent;
|
|
28
45
|
}
|
|
46
|
+
export {};
|
package/dist/build/builder.js
CHANGED
|
@@ -14,18 +14,13 @@ export class AgentLifecycleError extends HarnessError {
|
|
|
14
14
|
this.name = "AgentLifecycleError";
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
-
export function Agent(
|
|
18
|
-
return new AgentBuilder(
|
|
17
|
+
export function Agent(options) {
|
|
18
|
+
return new AgentBuilder(createState(options));
|
|
19
19
|
}
|
|
20
20
|
export class AgentBuilder {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
agent;
|
|
25
|
-
error;
|
|
26
|
-
middlewareSeq = 0;
|
|
27
|
-
constructor(invoke) {
|
|
28
|
-
this.invoke = invoke;
|
|
21
|
+
state;
|
|
22
|
+
constructor(state) {
|
|
23
|
+
this.state = state;
|
|
29
24
|
}
|
|
30
25
|
use(idOrMiddleware, middleware) {
|
|
31
26
|
if (typeof idOrMiddleware === "function") {
|
|
@@ -35,40 +30,76 @@ export class AgentBuilder {
|
|
|
35
30
|
return this.push(compileDeclaration(idOrMiddleware));
|
|
36
31
|
return this.push({ id: idOrMiddleware, handle: middleware });
|
|
37
32
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
this.sealed = true;
|
|
44
|
-
const result = assembleAgent(this.middleware, this.invoke);
|
|
45
|
-
if (!result.ok) {
|
|
46
|
-
this.error = new AgentBuildError(result.diagnostics);
|
|
47
|
-
throw this.error;
|
|
48
|
-
}
|
|
49
|
-
this.agent = result.agent;
|
|
50
|
-
return this.agent;
|
|
33
|
+
with(onModelCall) {
|
|
34
|
+
this.assertOpen("with()");
|
|
35
|
+
this.state.bound = true;
|
|
36
|
+
this.state.invoke = onModelCall;
|
|
37
|
+
return new BoundAgentBuilder(this.state);
|
|
51
38
|
}
|
|
52
39
|
nextMiddlewareId() {
|
|
53
|
-
const taken = new Set(this.middleware.map((item) => item.id));
|
|
40
|
+
const taken = new Set(this.state.middleware.map((item) => item.id));
|
|
54
41
|
let id;
|
|
55
42
|
do {
|
|
56
|
-
this.middlewareSeq += 1;
|
|
57
|
-
id = `middleware-${this.middlewareSeq}`;
|
|
43
|
+
this.state.middlewareSeq += 1;
|
|
44
|
+
id = `middleware-${this.state.middlewareSeq}`;
|
|
58
45
|
} while (taken.has(id));
|
|
59
46
|
return id;
|
|
60
47
|
}
|
|
61
48
|
push(entry) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
this.middleware.push(entry);
|
|
49
|
+
this.assertOpen("build()");
|
|
50
|
+
this.state.middleware.push(entry);
|
|
65
51
|
return this;
|
|
66
52
|
}
|
|
53
|
+
assertOpen(after) {
|
|
54
|
+
if (this.state.bound)
|
|
55
|
+
throw new AgentLifecycleError("AgentBuilder cannot be changed after with()");
|
|
56
|
+
if (this.state.sealed)
|
|
57
|
+
throw new AgentLifecycleError(`AgentBuilder cannot be changed after ${after}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export class BoundAgentBuilder {
|
|
61
|
+
state;
|
|
62
|
+
constructor(state) {
|
|
63
|
+
this.state = state;
|
|
64
|
+
}
|
|
65
|
+
build() {
|
|
66
|
+
if (this.state.agent)
|
|
67
|
+
return this.state.agent;
|
|
68
|
+
if (this.state.error)
|
|
69
|
+
throw this.state.error;
|
|
70
|
+
this.state.sealed = true;
|
|
71
|
+
const result = assembleAgent(this.state.middleware, this.state.invoke, {
|
|
72
|
+
id: this.state.id,
|
|
73
|
+
name: this.state.name,
|
|
74
|
+
});
|
|
75
|
+
if (!result.ok) {
|
|
76
|
+
this.state.error = new AgentBuildError(result.diagnostics);
|
|
77
|
+
throw this.state.error;
|
|
78
|
+
}
|
|
79
|
+
this.state.agent = result.agent;
|
|
80
|
+
return this.state.agent;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function createState(options) {
|
|
84
|
+
const middleware = [];
|
|
85
|
+
if (options.instructions !== undefined) {
|
|
86
|
+
const instructions = typeof options.instructions === "string" ? [options.instructions] : options.instructions;
|
|
87
|
+
middleware.push(compileDeclaration({ id: "agent", instructions }));
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
id: options.id,
|
|
91
|
+
name: options.name,
|
|
92
|
+
middleware,
|
|
93
|
+
bound: false,
|
|
94
|
+
sealed: false,
|
|
95
|
+
middlewareSeq: 0,
|
|
96
|
+
};
|
|
67
97
|
}
|
|
68
98
|
function compileDeclaration(declaration) {
|
|
69
99
|
const tools = copyItems(declaration.tools, declaration.id);
|
|
70
100
|
const instructions = copyItems(declaration.instructions, declaration.id);
|
|
71
101
|
const model = declaration.model;
|
|
102
|
+
const contributions = snapshotContributions(instructions?.items, tools?.items, model);
|
|
72
103
|
const handle = async (request, next) => {
|
|
73
104
|
if (tools)
|
|
74
105
|
request.configuration.tools.set(tools.slot, tools.items);
|
|
@@ -84,8 +115,33 @@ function compileDeclaration(declaration) {
|
|
|
84
115
|
...(declaration.state === undefined
|
|
85
116
|
? {}
|
|
86
117
|
: { state: declaration.state }),
|
|
118
|
+
...(contributions === undefined ? {} : { contributions }),
|
|
87
119
|
};
|
|
88
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
|
+
}
|
|
89
145
|
function copyItems(value, defaultSlot) {
|
|
90
146
|
if (value === undefined)
|
|
91
147
|
return undefined;
|
package/dist/build/manifest.d.ts
CHANGED
package/dist/build/manifest.js
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import { deepFreeze } from "../utils/immutable.js";
|
|
2
2
|
export function createManifest(input) {
|
|
3
|
-
const middleware = input.middleware.map((
|
|
3
|
+
const middleware = input.middleware.map((item) => projectMiddleware(item));
|
|
4
4
|
return deepFreeze({
|
|
5
|
+
id: input.id,
|
|
6
|
+
name: input.name,
|
|
5
7
|
middleware,
|
|
6
8
|
});
|
|
7
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
|
+
}
|
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" | "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";
|
|
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
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
export { Agent, AgentBuilder, AgentBuildError, AgentLifecycleError } from "./build/builder.js";
|
|
1
|
+
export { Agent, AgentBuilder, AgentBuildError, AgentLifecycleError, BoundAgentBuilder, } from "./build/builder.js";
|
|
2
|
+
export type { AgentOptions } from "./build/builder.js";
|
|
2
3
|
export { HarnessError, isHarnessError } from "./errors.js";
|
|
3
4
|
export type { HarnessErrorCode, HarnessErrorDetails, HarnessErrorOptions } from "./errors.js";
|
|
4
5
|
export { BuiltAgent } from "./build/agent.js";
|
|
5
6
|
export { middleware, model, tool } from "./build/helpers.js";
|
|
6
|
-
export type { AgentManifest } from "./types/manifest.js";
|
|
7
|
+
export type { AgentManifest, MiddlewareManifest } from "./types/manifest.js";
|
|
8
|
+
export type { BoundMiddleware, CapabilityDeclaration, CapabilityItems, CapabilityState, MiddlewareContributions, StepInput, StepMiddleware, StepRequest, StepResponse, } from "./types/middleware.js";
|
|
7
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";
|
|
8
|
-
export type { BoundMiddleware, CapabilityDeclaration, CapabilityItems, CapabilityState, StepInput, StepMiddleware, StepRequest, StepResponse, } from "./types/middleware.js";
|
|
9
10
|
export type { BuildDiagnostic, ContextItem, DeferredOutcome, JsonObject, JsonPrimitive, JsonValue, ObserveEvent, ObserveModelConfigurationSnapshot, ObserveModelRequested, ObserveSealedCall, ObserveToolSnapshot, Observer, Tripwire, } from "./types/shared.js";
|
|
10
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";
|
|
11
12
|
export type { BoundToolSchema, BoundToolDefinition, Interaction, RequiredInteraction, SealedToolCall, ToolContent, ToolDefinition, ToolExecutionContext, ToolExecutionResume, ToolObjectSchema, ToolOwner, ToolOutcome, ToolResult, } from "./types/tool.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { Agent, AgentBuilder, AgentBuildError, AgentLifecycleError } from "./build/builder.js";
|
|
1
|
+
export { Agent, AgentBuilder, AgentBuildError, AgentLifecycleError, BoundAgentBuilder, } from "./build/builder.js";
|
|
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";
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { ModelAdapter, ModelAdapterContext, ModelCall, ModelCandidate } from "../types/model.js";
|
|
2
|
+
import type { JsonObject } from "../types/shared.js";
|
|
3
|
+
export type ChatCompletionsMessage = {
|
|
4
|
+
readonly role: "system" | "user";
|
|
5
|
+
readonly content: string;
|
|
6
|
+
} | {
|
|
7
|
+
readonly role: "assistant";
|
|
8
|
+
readonly content: string | null;
|
|
9
|
+
readonly tool_calls?: readonly ChatCompletionsToolCall[];
|
|
10
|
+
} | {
|
|
11
|
+
readonly role: "tool";
|
|
12
|
+
readonly tool_call_id: string;
|
|
13
|
+
readonly content: string;
|
|
14
|
+
};
|
|
15
|
+
export interface ChatCompletionsToolCall {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
readonly type: "function";
|
|
18
|
+
readonly function: Readonly<{
|
|
19
|
+
name: string;
|
|
20
|
+
arguments: string;
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
23
|
+
export interface ChatCompletionsRequest {
|
|
24
|
+
readonly messages: readonly ChatCompletionsMessage[];
|
|
25
|
+
readonly tools?: readonly Readonly<{
|
|
26
|
+
type: "function";
|
|
27
|
+
function: Readonly<{
|
|
28
|
+
name: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
parameters: JsonObject;
|
|
31
|
+
}>;
|
|
32
|
+
}>[];
|
|
33
|
+
readonly temperature?: number;
|
|
34
|
+
readonly max_completion_tokens?: number;
|
|
35
|
+
}
|
|
36
|
+
export interface ResponsesRequest {
|
|
37
|
+
readonly instructions?: string;
|
|
38
|
+
readonly input: readonly ResponsesInputItem[];
|
|
39
|
+
readonly tools?: readonly Readonly<{
|
|
40
|
+
type: "function";
|
|
41
|
+
name: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
parameters: JsonObject;
|
|
44
|
+
}>[];
|
|
45
|
+
readonly temperature?: number;
|
|
46
|
+
readonly max_output_tokens?: number;
|
|
47
|
+
}
|
|
48
|
+
export type ResponsesInputItem = {
|
|
49
|
+
readonly type: "message";
|
|
50
|
+
readonly role: "user" | "assistant";
|
|
51
|
+
readonly content: string;
|
|
52
|
+
} | {
|
|
53
|
+
readonly type: "function_call";
|
|
54
|
+
readonly call_id: string;
|
|
55
|
+
readonly name: string;
|
|
56
|
+
readonly arguments: string;
|
|
57
|
+
} | {
|
|
58
|
+
readonly type: "function_call_output";
|
|
59
|
+
readonly call_id: string;
|
|
60
|
+
readonly output: string;
|
|
61
|
+
};
|
|
62
|
+
export interface MessagesRequest {
|
|
63
|
+
readonly system?: string;
|
|
64
|
+
readonly messages: readonly MessagesMessage[];
|
|
65
|
+
readonly tools?: readonly Readonly<{
|
|
66
|
+
name: string;
|
|
67
|
+
description?: string;
|
|
68
|
+
input_schema: JsonObject;
|
|
69
|
+
}>[];
|
|
70
|
+
readonly temperature?: number;
|
|
71
|
+
readonly max_tokens: number;
|
|
72
|
+
}
|
|
73
|
+
export type MessagesMessage = {
|
|
74
|
+
readonly role: "user";
|
|
75
|
+
readonly content: string | readonly MessagesToolResult[];
|
|
76
|
+
} | {
|
|
77
|
+
readonly role: "assistant";
|
|
78
|
+
readonly content: readonly MessagesAssistantPart[];
|
|
79
|
+
};
|
|
80
|
+
export type MessagesAssistantPart = {
|
|
81
|
+
readonly type: "text";
|
|
82
|
+
readonly text: string;
|
|
83
|
+
} | {
|
|
84
|
+
readonly type: "tool_use";
|
|
85
|
+
readonly id: string;
|
|
86
|
+
readonly name: string;
|
|
87
|
+
readonly input: JsonObject;
|
|
88
|
+
};
|
|
89
|
+
export interface MessagesToolResult {
|
|
90
|
+
readonly type: "tool_result";
|
|
91
|
+
readonly tool_use_id: string;
|
|
92
|
+
readonly content: string;
|
|
93
|
+
readonly is_error?: boolean;
|
|
94
|
+
}
|
|
95
|
+
export type AdapterSend<Request> = (request: Request, call: ModelCall, context: ModelAdapterContext) => Promise<unknown>;
|
|
96
|
+
export interface AnthropicAdapterOptions {
|
|
97
|
+
readonly defaultMaxOutputTokens: number;
|
|
98
|
+
readonly send: AdapterSend<MessagesRequest>;
|
|
99
|
+
}
|
|
100
|
+
/** Translate a Harness call to the OpenAI Chat Completions request shape. */
|
|
101
|
+
export declare function toChatCompletions(call: ModelCall): ChatCompletionsRequest;
|
|
102
|
+
/** Translate a Chat Completions response into a Harness candidate. */
|
|
103
|
+
export declare function fromChatCompletions(value: unknown): ModelCandidate;
|
|
104
|
+
/** Return a Harness adapter backed by an application-owned Chat Completions send function. */
|
|
105
|
+
export declare function chatCompletionsAdapter(send: AdapterSend<ChatCompletionsRequest>): ModelAdapter;
|
|
106
|
+
/** Translate a Harness call to the OpenAI Responses request shape. */
|
|
107
|
+
export declare function toResponses(call: ModelCall): ResponsesRequest;
|
|
108
|
+
/** Translate an OpenAI Responses response into a Harness candidate. */
|
|
109
|
+
export declare function fromResponses(value: unknown): ModelCandidate;
|
|
110
|
+
/** Return a Harness adapter backed by an application-owned Responses send function. */
|
|
111
|
+
export declare function responsesAdapter(send: AdapterSend<ResponsesRequest>): ModelAdapter;
|
|
112
|
+
/** Translate a Harness call to the Anthropic Messages request shape. */
|
|
113
|
+
export declare function toMessages(call: ModelCall, defaultMaxOutputTokens: number): MessagesRequest;
|
|
114
|
+
/** Translate an Anthropic Messages response into a Harness candidate. */
|
|
115
|
+
export declare function fromMessages(value: unknown): ModelCandidate;
|
|
116
|
+
/** Return a Harness adapter backed by an application-owned Anthropic Messages send function. */
|
|
117
|
+
export declare function anthropicAdapter(options: AnthropicAdapterOptions): ModelAdapter;
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { HarnessError } from "../errors.js";
|
|
2
|
+
import { copyJsonObject } from "../utils/immutable.js";
|
|
3
|
+
/** Translate a Harness call to the OpenAI Chat Completions request shape. */
|
|
4
|
+
export function toChatCompletions(call) {
|
|
5
|
+
const messages = call.prompt.map((item) => {
|
|
6
|
+
if (item.kind === "instructions")
|
|
7
|
+
return { role: "system", content: textOf(item) };
|
|
8
|
+
if (item.kind === "tool-result")
|
|
9
|
+
return { role: "tool", tool_call_id: item.toolCallId, content: textOf(item) };
|
|
10
|
+
if (item.kind === "message" && item.role === "assistant") {
|
|
11
|
+
const toolCalls = toolCallsOf(item.content).map((part) => ({
|
|
12
|
+
id: part.id,
|
|
13
|
+
type: "function",
|
|
14
|
+
function: { name: part.name, arguments: JSON.stringify(part.args) },
|
|
15
|
+
}));
|
|
16
|
+
const text = textOf(item);
|
|
17
|
+
return {
|
|
18
|
+
role: "assistant",
|
|
19
|
+
content: text === "" ? null : text,
|
|
20
|
+
...(toolCalls.length === 0 ? {} : { tool_calls: toolCalls }),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return { role: "user", content: textOf(item) };
|
|
24
|
+
});
|
|
25
|
+
return {
|
|
26
|
+
messages,
|
|
27
|
+
...(call.tools.length === 0
|
|
28
|
+
? {}
|
|
29
|
+
: {
|
|
30
|
+
tools: call.tools.map((tool) => ({
|
|
31
|
+
type: "function",
|
|
32
|
+
function: {
|
|
33
|
+
name: tool.name,
|
|
34
|
+
...(tool.description === undefined ? {} : { description: tool.description }),
|
|
35
|
+
parameters: tool.inputSchema,
|
|
36
|
+
},
|
|
37
|
+
})),
|
|
38
|
+
}),
|
|
39
|
+
...chatControls(call),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Translate a Chat Completions response into a Harness candidate. */
|
|
43
|
+
export function fromChatCompletions(value) {
|
|
44
|
+
const response = record(value, "response");
|
|
45
|
+
const choices = array(response.choices, "response.choices");
|
|
46
|
+
if (choices.length === 0)
|
|
47
|
+
throw invalidResponse("response.choices must contain a choice", "response.choices");
|
|
48
|
+
const choice = record(choices[0], "response.choices[0]");
|
|
49
|
+
const message = record(choice.message, "response.choices[0].message");
|
|
50
|
+
const output = [];
|
|
51
|
+
if (typeof message.content === "string" && message.content !== "")
|
|
52
|
+
output.push({ type: "text", text: message.content });
|
|
53
|
+
if (typeof message.reasoning_content === "string" && message.reasoning_content !== "")
|
|
54
|
+
output.push({ type: "reasoning", text: message.reasoning_content });
|
|
55
|
+
for (const [index, raw] of optionalArray(message.tool_calls, "response.choices[0].message.tool_calls").entries()) {
|
|
56
|
+
const call = record(raw, `response.choices[0].message.tool_calls[${index}]`);
|
|
57
|
+
const fn = record(call.function, `response.choices[0].message.tool_calls[${index}].function`);
|
|
58
|
+
output.push({
|
|
59
|
+
type: "tool-call",
|
|
60
|
+
id: string(call.id, `response.choices[0].message.tool_calls[${index}].id`),
|
|
61
|
+
name: string(fn.name, `response.choices[0].message.tool_calls[${index}].function.name`),
|
|
62
|
+
...argumentsOf(fn.arguments, `response.choices[0].message.tool_calls[${index}].function.arguments`),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return candidate({
|
|
66
|
+
output,
|
|
67
|
+
finishReason: chatFinishReason(choice.finish_reason, output),
|
|
68
|
+
usage: chatUsage(response.usage),
|
|
69
|
+
evidence: evidence(response),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/** Return a Harness adapter backed by an application-owned Chat Completions send function. */
|
|
73
|
+
export function chatCompletionsAdapter(send) {
|
|
74
|
+
return async (call, context) => fromChatCompletions(await send(toChatCompletions(call), call, context));
|
|
75
|
+
}
|
|
76
|
+
/** Translate a Harness call to the OpenAI Responses request shape. */
|
|
77
|
+
export function toResponses(call) {
|
|
78
|
+
const instructions = call.prompt.filter((item) => item.kind === "instructions").map(textOf);
|
|
79
|
+
const input = call.prompt.flatMap((item) => {
|
|
80
|
+
if (item.kind === "instructions")
|
|
81
|
+
return [];
|
|
82
|
+
if (item.kind === "tool-result")
|
|
83
|
+
return [{ type: "function_call_output", call_id: item.toolCallId, output: textOf(item) }];
|
|
84
|
+
if (item.kind === "message" && item.role === "assistant") {
|
|
85
|
+
const text = textOf(item);
|
|
86
|
+
return [
|
|
87
|
+
...(text === ""
|
|
88
|
+
? []
|
|
89
|
+
: [{ type: "message", role: "assistant", content: text }]),
|
|
90
|
+
...toolCallsOf(item.content).map((part) => ({
|
|
91
|
+
type: "function_call",
|
|
92
|
+
call_id: part.id,
|
|
93
|
+
name: part.name,
|
|
94
|
+
arguments: JSON.stringify(part.args),
|
|
95
|
+
})),
|
|
96
|
+
];
|
|
97
|
+
}
|
|
98
|
+
return [{ type: "message", role: "user", content: textOf(item) }];
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
...(instructions.length === 0 ? {} : { instructions: instructions.join("\n\n") }),
|
|
102
|
+
input,
|
|
103
|
+
...(call.tools.length === 0
|
|
104
|
+
? {}
|
|
105
|
+
: {
|
|
106
|
+
tools: call.tools.map((tool) => ({
|
|
107
|
+
type: "function",
|
|
108
|
+
name: tool.name,
|
|
109
|
+
...(tool.description === undefined ? {} : { description: tool.description }),
|
|
110
|
+
parameters: tool.inputSchema,
|
|
111
|
+
})),
|
|
112
|
+
}),
|
|
113
|
+
...responsesControls(call),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/** Translate an OpenAI Responses response into a Harness candidate. */
|
|
117
|
+
export function fromResponses(value) {
|
|
118
|
+
const response = record(value, "response");
|
|
119
|
+
if (response.error !== undefined && response.error !== null)
|
|
120
|
+
throw invalidResponse("response.error is present", "response.error");
|
|
121
|
+
const output = [];
|
|
122
|
+
for (const [index, raw] of array(response.output, "response.output").entries()) {
|
|
123
|
+
const item = record(raw, `response.output[${index}]`);
|
|
124
|
+
if (item.type === "function_call") {
|
|
125
|
+
output.push({
|
|
126
|
+
type: "tool-call",
|
|
127
|
+
id: string(item.call_id, `response.output[${index}].call_id`),
|
|
128
|
+
name: string(item.name, `response.output[${index}].name`),
|
|
129
|
+
...argumentsOf(item.arguments, `response.output[${index}].arguments`),
|
|
130
|
+
});
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (item.type === "message") {
|
|
134
|
+
for (const [partIndex, rawPart] of optionalArray(item.content, `response.output[${index}].content`).entries()) {
|
|
135
|
+
const part = record(rawPart, `response.output[${index}].content[${partIndex}]`);
|
|
136
|
+
if (part.type === "output_text" && typeof part.text === "string")
|
|
137
|
+
output.push({ type: "text", text: part.text });
|
|
138
|
+
}
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (item.type === "reasoning") {
|
|
142
|
+
const summary = optionalArray(item.summary, `response.output[${index}].summary`)
|
|
143
|
+
.map((rawPart, partIndex) => record(rawPart, `response.output[${index}].summary[${partIndex}]`))
|
|
144
|
+
.filter((part) => part.type === "summary_text" && typeof part.text === "string")
|
|
145
|
+
.map((part) => part.text)
|
|
146
|
+
.join("\n");
|
|
147
|
+
if (summary !== "")
|
|
148
|
+
output.push({ type: "reasoning", text: summary });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return candidate({
|
|
152
|
+
output,
|
|
153
|
+
finishReason: responsesFinishReason(response, output),
|
|
154
|
+
usage: responsesUsage(response.usage),
|
|
155
|
+
evidence: evidence(response),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/** Return a Harness adapter backed by an application-owned Responses send function. */
|
|
159
|
+
export function responsesAdapter(send) {
|
|
160
|
+
return async (call, context) => fromResponses(await send(toResponses(call), call, context));
|
|
161
|
+
}
|
|
162
|
+
/** Translate a Harness call to the Anthropic Messages request shape. */
|
|
163
|
+
export function toMessages(call, defaultMaxOutputTokens) {
|
|
164
|
+
checkedMaxOutputTokens(defaultMaxOutputTokens);
|
|
165
|
+
const instructions = call.prompt.filter((item) => item.kind === "instructions").map(textOf);
|
|
166
|
+
const messages = call.prompt.flatMap((item) => {
|
|
167
|
+
if (item.kind === "instructions")
|
|
168
|
+
return [];
|
|
169
|
+
if (item.kind === "tool-result")
|
|
170
|
+
return [
|
|
171
|
+
{
|
|
172
|
+
role: "user",
|
|
173
|
+
content: [
|
|
174
|
+
{
|
|
175
|
+
type: "tool_result",
|
|
176
|
+
tool_use_id: item.toolCallId,
|
|
177
|
+
content: textOf(item),
|
|
178
|
+
...(item.status === "completed" ? {} : { is_error: true }),
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
];
|
|
183
|
+
if (item.kind === "message" && item.role === "assistant")
|
|
184
|
+
return [
|
|
185
|
+
{
|
|
186
|
+
role: "assistant",
|
|
187
|
+
content: item.content.map((part) => part.type === "text"
|
|
188
|
+
? { type: "text", text: part.text }
|
|
189
|
+
: { type: "tool_use", id: part.id, name: part.name, input: part.args }),
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
return [{ role: "user", content: textOf(item) }];
|
|
193
|
+
});
|
|
194
|
+
return {
|
|
195
|
+
...(instructions.length === 0 ? {} : { system: instructions.join("\n\n") }),
|
|
196
|
+
messages,
|
|
197
|
+
...(call.tools.length === 0
|
|
198
|
+
? {}
|
|
199
|
+
: {
|
|
200
|
+
tools: call.tools.map((tool) => ({
|
|
201
|
+
name: tool.name,
|
|
202
|
+
...(tool.description === undefined ? {} : { description: tool.description }),
|
|
203
|
+
input_schema: tool.inputSchema,
|
|
204
|
+
})),
|
|
205
|
+
}),
|
|
206
|
+
...(call.model?.controls?.temperature === undefined
|
|
207
|
+
? {}
|
|
208
|
+
: { temperature: call.model.controls.temperature }),
|
|
209
|
+
max_tokens: call.model?.controls?.maxOutputTokens ?? defaultMaxOutputTokens,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/** Translate an Anthropic Messages response into a Harness candidate. */
|
|
213
|
+
export function fromMessages(value) {
|
|
214
|
+
const response = record(value, "response");
|
|
215
|
+
const output = [];
|
|
216
|
+
for (const [index, raw] of array(response.content, "response.content").entries()) {
|
|
217
|
+
const part = record(raw, `response.content[${index}]`);
|
|
218
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
219
|
+
output.push({ type: "text", text: part.text });
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (part.type === "thinking" && typeof part.thinking === "string") {
|
|
223
|
+
output.push({ type: "reasoning", text: part.thinking });
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (part.type === "tool_use") {
|
|
227
|
+
output.push({
|
|
228
|
+
type: "tool-call",
|
|
229
|
+
id: string(part.id, `response.content[${index}].id`),
|
|
230
|
+
name: string(part.name, `response.content[${index}].name`),
|
|
231
|
+
args: jsonObject(part.input, `response.content[${index}].input`),
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return candidate({
|
|
236
|
+
output,
|
|
237
|
+
finishReason: messagesFinishReason(response.stop_reason, output),
|
|
238
|
+
usage: messagesUsage(response.usage),
|
|
239
|
+
evidence: evidence(response),
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
/** Return a Harness adapter backed by an application-owned Anthropic Messages send function. */
|
|
243
|
+
export function anthropicAdapter(options) {
|
|
244
|
+
checkedMaxOutputTokens(options.defaultMaxOutputTokens);
|
|
245
|
+
return async (call, context) => fromMessages(await options.send(toMessages(call, options.defaultMaxOutputTokens), call, context));
|
|
246
|
+
}
|
|
247
|
+
function textOf(item) {
|
|
248
|
+
return item.content
|
|
249
|
+
.filter((part) => part.type === "text")
|
|
250
|
+
.map((part) => part.text)
|
|
251
|
+
.join("");
|
|
252
|
+
}
|
|
253
|
+
function toolCallsOf(parts) {
|
|
254
|
+
return parts.filter((part) => part.type === "tool-call");
|
|
255
|
+
}
|
|
256
|
+
function chatControls(call) {
|
|
257
|
+
return {
|
|
258
|
+
...(call.model?.controls?.temperature === undefined
|
|
259
|
+
? {}
|
|
260
|
+
: { temperature: call.model.controls.temperature }),
|
|
261
|
+
...(call.model?.controls?.maxOutputTokens === undefined
|
|
262
|
+
? {}
|
|
263
|
+
: { max_completion_tokens: call.model.controls.maxOutputTokens }),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function responsesControls(call) {
|
|
267
|
+
return {
|
|
268
|
+
...(call.model?.controls?.temperature === undefined
|
|
269
|
+
? {}
|
|
270
|
+
: { temperature: call.model.controls.temperature }),
|
|
271
|
+
...(call.model?.controls?.maxOutputTokens === undefined
|
|
272
|
+
? {}
|
|
273
|
+
: { max_output_tokens: call.model.controls.maxOutputTokens }),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function argumentsOf(value, path) {
|
|
277
|
+
const raw = string(value, path);
|
|
278
|
+
try {
|
|
279
|
+
return { args: jsonObject(JSON.parse(raw), path), raw };
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
throw invalidResponse(`${path} must be a JSON object`, path, error);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function jsonObject(value, path) {
|
|
286
|
+
try {
|
|
287
|
+
return copyJsonObject(value, path);
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
throw invalidResponse(`${path} must be a JSON object`, path, error);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function candidate(value) {
|
|
294
|
+
return {
|
|
295
|
+
output: value.output,
|
|
296
|
+
...(value.finishReason === undefined ? {} : { finishReason: value.finishReason }),
|
|
297
|
+
...(value.usage === undefined ? {} : { usage: value.usage }),
|
|
298
|
+
...(value.evidence === undefined ? {} : { evidence: value.evidence }),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function chatFinishReason(value, output) {
|
|
302
|
+
if (value === "tool_calls" || value === "function_call")
|
|
303
|
+
return "tool-calls";
|
|
304
|
+
if (value === "length")
|
|
305
|
+
return "length";
|
|
306
|
+
if (value === "content_filter")
|
|
307
|
+
return "content-filter";
|
|
308
|
+
if (value === "stop" || value === null || value === undefined)
|
|
309
|
+
return hasToolCall(output) ? "tool-calls" : "stop";
|
|
310
|
+
return "other";
|
|
311
|
+
}
|
|
312
|
+
function responsesFinishReason(response, output) {
|
|
313
|
+
if (response.status === "incomplete") {
|
|
314
|
+
const details = response.incomplete_details === undefined
|
|
315
|
+
? undefined
|
|
316
|
+
: record(response.incomplete_details, "response.incomplete_details");
|
|
317
|
+
return details?.reason === "max_output_tokens" ? "length" : "other";
|
|
318
|
+
}
|
|
319
|
+
if (response.status === "failed" || response.status === "cancelled")
|
|
320
|
+
throw invalidResponse(`response.status is ${String(response.status)}`, "response.status");
|
|
321
|
+
return hasToolCall(output) ? "tool-calls" : "stop";
|
|
322
|
+
}
|
|
323
|
+
function messagesFinishReason(value, output) {
|
|
324
|
+
if (value === "tool_use")
|
|
325
|
+
return "tool-calls";
|
|
326
|
+
if (value === "max_tokens")
|
|
327
|
+
return "length";
|
|
328
|
+
if (value === "end_turn" || value === "stop_sequence" || value === undefined || value === null)
|
|
329
|
+
return hasToolCall(output) ? "tool-calls" : "stop";
|
|
330
|
+
return "other";
|
|
331
|
+
}
|
|
332
|
+
function chatUsage(value) {
|
|
333
|
+
const usage = optionalRecord(value, "response.usage");
|
|
334
|
+
if (usage === undefined)
|
|
335
|
+
return undefined;
|
|
336
|
+
return usageOf({
|
|
337
|
+
inputTokens: usage.prompt_tokens,
|
|
338
|
+
outputTokens: usage.completion_tokens,
|
|
339
|
+
totalTokens: usage.total_tokens,
|
|
340
|
+
cachedTokens: optionalRecord(usage.prompt_tokens_details, "response.usage.prompt_tokens_details")?.cached_tokens,
|
|
341
|
+
reasoningTokens: optionalRecord(usage.completion_tokens_details, "response.usage.completion_tokens_details")?.reasoning_tokens,
|
|
342
|
+
}, "response.usage");
|
|
343
|
+
}
|
|
344
|
+
function responsesUsage(value) {
|
|
345
|
+
const usage = optionalRecord(value, "response.usage");
|
|
346
|
+
if (usage === undefined)
|
|
347
|
+
return undefined;
|
|
348
|
+
return usageOf({
|
|
349
|
+
inputTokens: usage.input_tokens,
|
|
350
|
+
outputTokens: usage.output_tokens,
|
|
351
|
+
totalTokens: usage.total_tokens,
|
|
352
|
+
cachedTokens: optionalRecord(usage.input_tokens_details, "response.usage.input_tokens_details")?.cached_tokens,
|
|
353
|
+
reasoningTokens: optionalRecord(usage.output_tokens_details, "response.usage.output_tokens_details")?.reasoning_tokens,
|
|
354
|
+
}, "response.usage");
|
|
355
|
+
}
|
|
356
|
+
function messagesUsage(value) {
|
|
357
|
+
const usage = optionalRecord(value, "response.usage");
|
|
358
|
+
if (usage === undefined)
|
|
359
|
+
return undefined;
|
|
360
|
+
return usageOf({
|
|
361
|
+
inputTokens: usage.input_tokens,
|
|
362
|
+
outputTokens: usage.output_tokens,
|
|
363
|
+
cachedTokens: usage.cache_read_input_tokens,
|
|
364
|
+
}, "response.usage");
|
|
365
|
+
}
|
|
366
|
+
function usageOf(value, path) {
|
|
367
|
+
const fields = Object.entries(value).flatMap(([key, raw]) => {
|
|
368
|
+
if (raw === undefined)
|
|
369
|
+
return [];
|
|
370
|
+
if (!isNonNegativeInteger(raw))
|
|
371
|
+
throw invalidResponse(`${path}.${key} must be a non-negative integer`, `${path}.${key}`);
|
|
372
|
+
return [[key, raw]];
|
|
373
|
+
});
|
|
374
|
+
return Object.fromEntries(fields);
|
|
375
|
+
}
|
|
376
|
+
function evidence(response) {
|
|
377
|
+
const requestId = optionalString(response.id, "response.id");
|
|
378
|
+
const resolvedModel = optionalString(response.model, "response.model");
|
|
379
|
+
return requestId === undefined && resolvedModel === undefined
|
|
380
|
+
? undefined
|
|
381
|
+
: {
|
|
382
|
+
...(requestId === undefined ? {} : { requestId }),
|
|
383
|
+
...(resolvedModel === undefined ? {} : { resolvedModel }),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function record(value, path) {
|
|
387
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
388
|
+
throw invalidResponse(`${path} must be an object`, path);
|
|
389
|
+
return value;
|
|
390
|
+
}
|
|
391
|
+
function optionalRecord(value, path) {
|
|
392
|
+
return value === undefined || value === null ? undefined : record(value, path);
|
|
393
|
+
}
|
|
394
|
+
function array(value, path) {
|
|
395
|
+
if (!Array.isArray(value))
|
|
396
|
+
throw invalidResponse(`${path} must be an array`, path);
|
|
397
|
+
return value;
|
|
398
|
+
}
|
|
399
|
+
function optionalArray(value, path) {
|
|
400
|
+
if (value === undefined || value === null)
|
|
401
|
+
return [];
|
|
402
|
+
if (!Array.isArray(value))
|
|
403
|
+
throw invalidResponse(`${path} must be an array`, path);
|
|
404
|
+
return value;
|
|
405
|
+
}
|
|
406
|
+
function string(value, path) {
|
|
407
|
+
if (typeof value !== "string")
|
|
408
|
+
throw invalidResponse(`${path} must be a string`, path);
|
|
409
|
+
return value;
|
|
410
|
+
}
|
|
411
|
+
function optionalString(value, path) {
|
|
412
|
+
if (value === undefined || value === null)
|
|
413
|
+
return undefined;
|
|
414
|
+
return string(value, path);
|
|
415
|
+
}
|
|
416
|
+
function checkedMaxOutputTokens(value) {
|
|
417
|
+
if (!isNonNegativeInteger(value))
|
|
418
|
+
throw new HarnessError("model.adapter-invalid-options", "Anthropic defaultMaxOutputTokens must be a non-negative integer", { details: { path: "defaultMaxOutputTokens" } });
|
|
419
|
+
}
|
|
420
|
+
function isNonNegativeInteger(value) {
|
|
421
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
422
|
+
}
|
|
423
|
+
function hasToolCall(output) {
|
|
424
|
+
return output.some((part) => part.type === "tool-call");
|
|
425
|
+
}
|
|
426
|
+
function invalidResponse(message, path, cause) {
|
|
427
|
+
return new HarnessError("model.adapter-invalid-response", message, {
|
|
428
|
+
...(cause === undefined ? {} : { cause }),
|
|
429
|
+
details: { path },
|
|
430
|
+
});
|
|
431
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { HarnessError } from "
|
|
2
|
-
import type { ModelCandidate, ModelDirective, ModelOutputBlock } from "
|
|
1
|
+
import { HarnessError } from "../errors.js";
|
|
2
|
+
import type { ModelCandidate, ModelDirective, ModelOutputBlock } from "../types/model.js";
|
|
3
3
|
export declare function normalizeDirective(value: unknown): ModelDirective | HarnessError;
|
|
4
4
|
export declare function sameDirective(left: ModelDirective, right: ModelDirective): boolean;
|
|
5
5
|
export declare function textFromOutput(output: readonly ModelOutputBlock[]): string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { HarnessError, isHarnessError } from "
|
|
2
|
-
import { digest } from "
|
|
3
|
-
import { copyJsonObject } from "
|
|
1
|
+
import { HarnessError, isHarnessError } from "../errors.js";
|
|
2
|
+
import { digest } from "../utils/digest.js";
|
|
3
|
+
import { copyJsonObject } from "../utils/immutable.js";
|
|
4
4
|
const FINISH_REASONS = new Set([
|
|
5
5
|
"stop",
|
|
6
6
|
"length",
|
package/dist/session/seed.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HarnessError } from "../errors.js";
|
|
2
|
-
import { normalizeCandidate } from "../model
|
|
2
|
+
import { normalizeCandidate } from "../model/normalize.js";
|
|
3
3
|
import { assertJson, copyJson, copyJsonObject } from "../utils/immutable.js";
|
|
4
4
|
export function normalizeSessionSeed(seed) {
|
|
5
5
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { bindTool } from "../build/bind-tool.js";
|
|
2
2
|
import { HarnessError, isHarnessError } from "../errors.js";
|
|
3
|
-
import { normalizeDirective, sameDirective } from "../model
|
|
3
|
+
import { normalizeDirective, sameDirective } from "../model/normalize.js";
|
|
4
4
|
import { digest } from "../utils/digest.js";
|
|
5
5
|
import { copyJson } from "../utils/immutable.js";
|
|
6
6
|
import { checkedReason, slotOwner, SlotDraft } from "./slot-assembly.js";
|
package/dist/step/run.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { normalizeCandidate } from "../model
|
|
1
|
+
import { normalizeCandidate } from "../model/normalize.js";
|
|
2
2
|
import { HarnessError, isHarnessError } from "../errors.js";
|
|
3
3
|
import { copyJson } from "../utils/immutable.js";
|
|
4
4
|
import { runMiddleware } from "./compose.js";
|
package/dist/step/seal.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { textFromOutput } from "../model
|
|
1
|
+
import { textFromOutput } from "../model/normalize.js";
|
|
2
2
|
import { createId } from "../utils/ids.js";
|
|
3
3
|
import { HarnessError, isHarnessError } from "../errors.js";
|
|
4
4
|
import { assertJson, copyJson } from "../utils/immutable.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HarnessError, isHarnessError } from "../errors.js";
|
|
2
2
|
import { callsFromCanonical, candidateFromCanonical, canonicalizeOutput, identityKey, } from "./canonicalize.js";
|
|
3
|
-
import { normalizeCandidate } from "../model
|
|
3
|
+
import { normalizeCandidate } from "../model/normalize.js";
|
|
4
4
|
import { ContextDraft } from "./context-draft.js";
|
|
5
5
|
import { ModelConfigurationDraft } from "./model-configuration.js";
|
|
6
6
|
const branded = new WeakSet();
|
package/dist/types/manifest.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import type { BoundMiddleware } from "./middleware.js";
|
|
1
|
+
import type { BoundMiddleware, MiddlewareContributions } from "./middleware.js";
|
|
2
2
|
import type { BuildDiagnostic } from "./shared.js";
|
|
3
|
+
export interface MiddlewareManifest extends MiddlewareContributions {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
}
|
|
3
6
|
export interface AgentManifest {
|
|
4
|
-
readonly
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly middleware: readonly MiddlewareManifest[];
|
|
7
10
|
}
|
|
8
11
|
export type BuildResult<Agent> = {
|
|
9
12
|
readonly ok: true;
|
|
@@ -78,9 +78,18 @@ export interface CapabilityDeclaration<State = never> {
|
|
|
78
78
|
readonly state?: CapabilityState<State>;
|
|
79
79
|
readonly middleware?: StepMiddleware<State>;
|
|
80
80
|
}
|
|
81
|
+
export interface MiddlewareContributions {
|
|
82
|
+
readonly instructions?: readonly string[];
|
|
83
|
+
readonly tools?: readonly {
|
|
84
|
+
readonly name: string;
|
|
85
|
+
readonly description?: string;
|
|
86
|
+
}[];
|
|
87
|
+
readonly model?: Pick<ModelDirective, "id" | "controls">;
|
|
88
|
+
}
|
|
81
89
|
export interface BoundMiddleware {
|
|
82
90
|
readonly id: string;
|
|
83
91
|
readonly handle: StepMiddleware;
|
|
84
92
|
readonly state?: CapabilityState<unknown>;
|
|
93
|
+
readonly contributions?: MiddlewareContributions;
|
|
85
94
|
}
|
|
86
95
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nylorun/harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0-beta.1",
|
|
4
4
|
"description": "A provider-neutral, in-memory agent loop for TypeScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
".": {
|
|
28
28
|
"types": "./dist/index.d.ts",
|
|
29
29
|
"import": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./model/adapters": {
|
|
32
|
+
"types": "./dist/model/adapters.d.ts",
|
|
33
|
+
"import": "./dist/model/adapters.js"
|
|
30
34
|
}
|
|
31
35
|
},
|
|
32
36
|
"files": [
|