@nylorun/harness 0.8.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 +10 -0
- package/README.md +41 -0
- package/dist/build/assemble.js +1 -0
- package/dist/build/builder.js +26 -0
- package/dist/build/manifest.js +12 -1
- package/dist/errors.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- 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 +5 -4
- package/dist/types/middleware.d.ts +9 -0
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,15 @@ 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
|
+
|
|
7
16
|
## [0.8.0-beta.1] - 2026-08-31
|
|
8
17
|
|
|
9
18
|
### Breaking changes
|
|
@@ -93,3 +102,4 @@ The project follows [Semantic Versioning](https://semver.org/). Before 1.0, the
|
|
|
93
102
|
[0.6.0-beta.1]: https://github.com/nylorun/harness/tree/main/harness
|
|
94
103
|
[0.7.0-beta.1]: https://github.com/nylorun/harness/tree/main/harness
|
|
95
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
|
@@ -41,3 +41,44 @@ const result = await agent.run().input("Echo hello").completed;
|
|
|
41
41
|
|
|
42
42
|
See [Examples](../examples/README.md) for complete agents and [CHANGELOG.md](./CHANGELOG.md) for
|
|
43
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/assemble.js
CHANGED
|
@@ -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
|
}
|
package/dist/build/builder.js
CHANGED
|
@@ -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;
|
package/dist/build/manifest.js
CHANGED
|
@@ -1,9 +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
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
|
+
}
|
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
|
@@ -4,9 +4,9 @@ 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";
|
|
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";
|
|
8
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";
|
|
9
|
-
export type { BoundMiddleware, CapabilityDeclaration, CapabilityItems, CapabilityState, StepInput, StepMiddleware, StepRequest, StepResponse, } from "./types/middleware.js";
|
|
10
10
|
export type { BuildDiagnostic, ContextItem, DeferredOutcome, JsonObject, JsonPrimitive, JsonValue, ObserveEvent, ObserveModelConfigurationSnapshot, ObserveModelRequested, ObserveSealedCall, ObserveToolSnapshot, Observer, Tripwire, } from "./types/shared.js";
|
|
11
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
12
|
export type { BoundToolSchema, BoundToolDefinition, Interaction, RequiredInteraction, SealedToolCall, ToolContent, ToolDefinition, ToolExecutionContext, ToolExecutionResume, ToolObjectSchema, ToolOwner, ToolOutcome, ToolResult, } from "./types/tool.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,11 +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
7
|
readonly id: string;
|
|
5
8
|
readonly name: string;
|
|
6
|
-
readonly middleware: readonly
|
|
7
|
-
readonly id: string;
|
|
8
|
-
}[];
|
|
9
|
+
readonly middleware: readonly MiddlewareManifest[];
|
|
9
10
|
}
|
|
10
11
|
export type BuildResult<Agent> = {
|
|
11
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": [
|