@nylorun/harness 0.11.0-beta → 0.12.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/build/agent.d.ts +2 -3
- package/dist/build/agent.js +6 -11
- package/dist/build/assemble.d.ts +1 -2
- package/dist/build/assemble.js +2 -5
- package/dist/build/builder.d.ts +1 -9
- package/dist/build/builder.js +19 -34
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/model/adapters.js +14 -3
- package/dist/model/normalize.js +15 -3
- package/dist/session/scheduler.d.ts +2 -2
- package/dist/session/scheduler.js +5 -15
- package/dist/session/seed.js +2 -2
- package/dist/session/session.d.ts +1 -2
- package/dist/session/session.js +1 -3
- package/dist/step/canonicalize.js +3 -0
- package/dist/step/project.js +6 -2
- package/dist/turn/plan-runner.d.ts +2 -0
- package/dist/turn/plan-runner.js +1 -0
- package/dist/turn/runner.js +1 -0
- package/dist/types/model.d.ts +9 -0
- package/dist/types/session.d.ts +5 -2
- package/dist/types/tool.d.ts +3 -0
- package/dist/utils/observe.d.ts +1 -6
- package/dist/utils/observe.js +12 -28
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.12.0-beta
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 4badb5b: Move model execution to session startup, provide Runtime as a mountable Hono router, and generate Hono-first projects with supervised application and Studio development. Studio now resolves root-relative Runtime endpoints correctly for custom mount paths.
|
|
8
|
+
|
|
9
|
+
## 0.11.1-beta
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- d27242c: Preserve opaque provider continuation metadata through assistant conversation history. Gemini tool calls now retain thought signatures when sending tool results back to the model, including signed empty text and reasoning blocks. Only the originating provider and model receive their signatures.
|
|
14
|
+
|
|
3
15
|
## 0.11.0-beta
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/dist/build/agent.d.ts
CHANGED
|
@@ -7,12 +7,11 @@ export interface LoopAgent {
|
|
|
7
7
|
readonly invoke: ModelAdapter;
|
|
8
8
|
}
|
|
9
9
|
export declare class BuiltAgent {
|
|
10
|
-
#private;
|
|
11
10
|
readonly middleware: readonly BoundMiddleware[];
|
|
12
11
|
readonly manifest: AgentManifest;
|
|
13
12
|
readonly id: string;
|
|
14
13
|
readonly name: string;
|
|
15
14
|
private constructor();
|
|
16
|
-
run(options
|
|
15
|
+
run(options: SessionRunOptions): Session;
|
|
17
16
|
}
|
|
18
|
-
export declare function bindAgent(middleware: readonly BoundMiddleware[],
|
|
17
|
+
export declare function bindAgent(middleware: readonly BoundMiddleware[], manifest: AgentManifest): BuiltAgent;
|
package/dist/build/agent.js
CHANGED
|
@@ -7,21 +7,16 @@ export class BuiltAgent {
|
|
|
7
7
|
manifest;
|
|
8
8
|
id;
|
|
9
9
|
name;
|
|
10
|
-
|
|
11
|
-
constructor(middleware, invoke, manifest) {
|
|
10
|
+
constructor(middleware, manifest) {
|
|
12
11
|
this.middleware = middleware;
|
|
13
12
|
this.manifest = manifest;
|
|
14
13
|
this.id = manifest.id;
|
|
15
14
|
this.name = manifest.name;
|
|
16
|
-
this.#loopAgent = Object.freeze({
|
|
17
|
-
middleware,
|
|
18
|
-
invoke,
|
|
19
|
-
});
|
|
20
15
|
}
|
|
21
16
|
static {
|
|
22
|
-
createBoundAgent = (middleware,
|
|
17
|
+
createBoundAgent = (middleware, manifest) => new BuiltAgent(middleware, manifest);
|
|
23
18
|
}
|
|
24
|
-
run(options
|
|
19
|
+
run(options) {
|
|
25
20
|
const seeded = "seed" in options && options.seed !== undefined;
|
|
26
21
|
if (seeded &&
|
|
27
22
|
(("id" in options && options.id !== undefined) ||
|
|
@@ -29,9 +24,9 @@ export class BuiltAgent {
|
|
|
29
24
|
("context" in options && options.context !== undefined)))
|
|
30
25
|
throw new HarnessError("session.invalid-seed", "Seeded run options cannot include id, userId, or context outside the seed");
|
|
31
26
|
const id = (seeded ? options.seed.id : "id" in options ? options.id : undefined) ?? createId("session");
|
|
32
|
-
return new LiveSession(id, this
|
|
27
|
+
return new LiveSession(id, Object.freeze({ middleware: this.middleware, invoke: options.onModelCall }), options);
|
|
33
28
|
}
|
|
34
29
|
}
|
|
35
|
-
export function bindAgent(middleware,
|
|
36
|
-
return createBoundAgent(middleware,
|
|
30
|
+
export function bindAgent(middleware, manifest) {
|
|
31
|
+
return createBoundAgent(middleware, manifest);
|
|
37
32
|
}
|
package/dist/build/assemble.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { BuildResult } from "../types/manifest.js";
|
|
2
2
|
import type { BoundMiddleware } from "../types/middleware.js";
|
|
3
|
-
import type { ModelAdapter } from "../types/model.js";
|
|
4
3
|
import { type BuiltAgent } from "./agent.js";
|
|
5
|
-
export declare function assembleAgent(middleware: readonly BoundMiddleware[],
|
|
4
|
+
export declare function assembleAgent(middleware: readonly BoundMiddleware[], identity: Readonly<{
|
|
6
5
|
id: string;
|
|
7
6
|
name: string;
|
|
8
7
|
}>): BuildResult<BuiltAgent>;
|
package/dist/build/assemble.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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,
|
|
4
|
+
export function assembleAgent(middleware, identity) {
|
|
5
5
|
const diagnostics = [];
|
|
6
6
|
if (typeof identity.id !== "string" || identity.id.length === 0) {
|
|
7
7
|
diagnostics.push(diagnostic("agent.invalid-id", "Agent id must be a non-empty string"));
|
|
@@ -9,9 +9,6 @@ export function assembleAgent(middleware, invoke, identity) {
|
|
|
9
9
|
if (typeof identity.name !== "string" || identity.name.length === 0) {
|
|
10
10
|
diagnostics.push(diagnostic("agent.invalid-name", "Agent name must be a non-empty string"));
|
|
11
11
|
}
|
|
12
|
-
if (typeof invoke !== "function") {
|
|
13
|
-
diagnostics.push(diagnostic("harness.invalid-model", "A model invoke function is required"));
|
|
14
|
-
}
|
|
15
12
|
const middlewareIds = new Set();
|
|
16
13
|
const frozen = [];
|
|
17
14
|
for (const item of middleware) {
|
|
@@ -39,6 +36,6 @@ export function assembleAgent(middleware, invoke, identity) {
|
|
|
39
36
|
name: identity.name,
|
|
40
37
|
middleware: frozenMiddleware,
|
|
41
38
|
});
|
|
42
|
-
const agent = bindAgent(frozenMiddleware,
|
|
39
|
+
const agent = bindAgent(frozenMiddleware, manifest);
|
|
43
40
|
return Object.freeze({ ok: true, agent, manifest });
|
|
44
41
|
}
|
package/dist/build/builder.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { BoundMiddleware, CapabilityDeclaration, StepMiddleware } from "../types/middleware.js";
|
|
2
|
-
import type { ModelAdapter } from "../types/model.js";
|
|
3
2
|
import type { BuildDiagnostic } from "../types/shared.js";
|
|
4
3
|
import { HarnessError } from "../errors.js";
|
|
5
4
|
import type { BuiltAgent } from "./agent.js";
|
|
@@ -12,8 +11,6 @@ interface BuilderState {
|
|
|
12
11
|
readonly id: string;
|
|
13
12
|
readonly name: string;
|
|
14
13
|
readonly middleware: BoundMiddleware[];
|
|
15
|
-
invoke?: ModelAdapter;
|
|
16
|
-
bound: boolean;
|
|
17
14
|
sealed: boolean;
|
|
18
15
|
agent?: BuiltAgent;
|
|
19
16
|
error?: AgentBuildError;
|
|
@@ -33,14 +30,9 @@ export declare class AgentBuilder {
|
|
|
33
30
|
use(middleware: StepMiddleware): this;
|
|
34
31
|
use(id: string, middleware: StepMiddleware): this;
|
|
35
32
|
use<State>(declaration: CapabilityDeclaration<State>): this;
|
|
36
|
-
|
|
33
|
+
build(): BuiltAgent;
|
|
37
34
|
private nextMiddlewareId;
|
|
38
35
|
private push;
|
|
39
36
|
private assertOpen;
|
|
40
37
|
}
|
|
41
|
-
export declare class BoundAgentBuilder {
|
|
42
|
-
private readonly state;
|
|
43
|
-
constructor(state: BuilderState);
|
|
44
|
-
build(): BuiltAgent;
|
|
45
|
-
}
|
|
46
38
|
export {};
|
package/dist/build/builder.js
CHANGED
|
@@ -30,45 +30,13 @@ export class AgentBuilder {
|
|
|
30
30
|
return this.push(compileDeclaration(idOrMiddleware));
|
|
31
31
|
return this.push({ id: idOrMiddleware, handle: middleware });
|
|
32
32
|
}
|
|
33
|
-
with(onModelCall) {
|
|
34
|
-
this.assertOpen("with()");
|
|
35
|
-
this.state.bound = true;
|
|
36
|
-
this.state.invoke = onModelCall;
|
|
37
|
-
return new BoundAgentBuilder(this.state);
|
|
38
|
-
}
|
|
39
|
-
nextMiddlewareId() {
|
|
40
|
-
const taken = new Set(this.state.middleware.map((item) => item.id));
|
|
41
|
-
let id;
|
|
42
|
-
do {
|
|
43
|
-
this.state.middlewareSeq += 1;
|
|
44
|
-
id = `middleware-${this.state.middlewareSeq}`;
|
|
45
|
-
} while (taken.has(id));
|
|
46
|
-
return id;
|
|
47
|
-
}
|
|
48
|
-
push(entry) {
|
|
49
|
-
this.assertOpen("build()");
|
|
50
|
-
this.state.middleware.push(entry);
|
|
51
|
-
return this;
|
|
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
33
|
build() {
|
|
66
34
|
if (this.state.agent)
|
|
67
35
|
return this.state.agent;
|
|
68
36
|
if (this.state.error)
|
|
69
37
|
throw this.state.error;
|
|
70
38
|
this.state.sealed = true;
|
|
71
|
-
const result = assembleAgent(this.state.middleware,
|
|
39
|
+
const result = assembleAgent(this.state.middleware, {
|
|
72
40
|
id: this.state.id,
|
|
73
41
|
name: this.state.name,
|
|
74
42
|
});
|
|
@@ -79,6 +47,24 @@ export class BoundAgentBuilder {
|
|
|
79
47
|
this.state.agent = result.agent;
|
|
80
48
|
return this.state.agent;
|
|
81
49
|
}
|
|
50
|
+
nextMiddlewareId() {
|
|
51
|
+
const taken = new Set(this.state.middleware.map((item) => item.id));
|
|
52
|
+
let id;
|
|
53
|
+
do {
|
|
54
|
+
this.state.middlewareSeq += 1;
|
|
55
|
+
id = `middleware-${this.state.middlewareSeq}`;
|
|
56
|
+
} while (taken.has(id));
|
|
57
|
+
return id;
|
|
58
|
+
}
|
|
59
|
+
push(entry) {
|
|
60
|
+
this.assertOpen();
|
|
61
|
+
this.state.middleware.push(entry);
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
assertOpen() {
|
|
65
|
+
if (this.state.sealed)
|
|
66
|
+
throw new AgentLifecycleError("AgentBuilder cannot be changed after build()");
|
|
67
|
+
}
|
|
82
68
|
}
|
|
83
69
|
function createState(options) {
|
|
84
70
|
const middleware = [];
|
|
@@ -90,7 +76,6 @@ function createState(options) {
|
|
|
90
76
|
id: options.id,
|
|
91
77
|
name: options.name,
|
|
92
78
|
middleware,
|
|
93
|
-
bound: false,
|
|
94
79
|
sealed: false,
|
|
95
80
|
middlewareSeq: 0,
|
|
96
81
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { Agent, AgentBuilder, AgentBuildError, AgentLifecycleError
|
|
1
|
+
export { Agent, AgentBuilder, AgentBuildError, AgentLifecycleError } from "./build/builder.js";
|
|
2
2
|
export type { AgentOptions } from "./build/builder.js";
|
|
3
3
|
export { HarnessError, isHarnessError } from "./errors.js";
|
|
4
4
|
export type { HarnessErrorCode, HarnessErrorDetails, HarnessErrorOptions } from "./errors.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { Agent, AgentBuilder, AgentBuildError, AgentLifecycleError
|
|
1
|
+
export { Agent, AgentBuilder, AgentBuildError, AgentLifecycleError } 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";
|
package/dist/model/adapters.js
CHANGED
|
@@ -3,7 +3,7 @@ import { copyJsonObject } from "../utils/immutable.js";
|
|
|
3
3
|
import { preparedModel } from "./prepared.js";
|
|
4
4
|
/** Translate a Harness call to the OpenAI Chat Completions request shape. */
|
|
5
5
|
export function toChatCompletions(call) {
|
|
6
|
-
const messages = call.prompt.map((item) => {
|
|
6
|
+
const messages = withoutProviderReasoning(call.prompt).map((item) => {
|
|
7
7
|
if (item.kind === "instructions")
|
|
8
8
|
return { role: "system", content: textOf(item) };
|
|
9
9
|
if (item.kind === "tool-result")
|
|
@@ -93,7 +93,7 @@ export function chatCompletionsAdapter(send) {
|
|
|
93
93
|
/** Translate a Harness call to the OpenAI Responses request shape. */
|
|
94
94
|
export function toResponses(call) {
|
|
95
95
|
const instructions = call.prompt.filter((item) => item.kind === "instructions").map(textOf);
|
|
96
|
-
const input = call.prompt.flatMap((item) => {
|
|
96
|
+
const input = withoutProviderReasoning(call.prompt).flatMap((item) => {
|
|
97
97
|
if (item.kind === "instructions")
|
|
98
98
|
return [];
|
|
99
99
|
if (item.kind === "tool-result")
|
|
@@ -201,7 +201,7 @@ export function toMessages(call, defaultMaxOutputTokens) {
|
|
|
201
201
|
if (call.outputSchema !== undefined)
|
|
202
202
|
throw new HarnessError("model.unsupported-output-schema", "Anthropic Messages output schemas require a custom prepared adapter");
|
|
203
203
|
const instructions = call.prompt.filter((item) => item.kind === "instructions").map(textOf);
|
|
204
|
-
const messages = call.prompt.flatMap((item) => {
|
|
204
|
+
const messages = withoutProviderReasoning(call.prompt).flatMap((item) => {
|
|
205
205
|
if (item.kind === "instructions")
|
|
206
206
|
return [];
|
|
207
207
|
if (item.kind === "tool-result")
|
|
@@ -543,3 +543,14 @@ function invalidResponse(message, path, cause) {
|
|
|
543
543
|
details: { path },
|
|
544
544
|
});
|
|
545
545
|
}
|
|
546
|
+
// These portable translators do not interpret another adapter's signed reasoning.
|
|
547
|
+
function withoutProviderReasoning(prompt) {
|
|
548
|
+
return prompt.flatMap((item) => {
|
|
549
|
+
if (item.kind !== "message" ||
|
|
550
|
+
item.role !== "assistant" ||
|
|
551
|
+
!item.content.some((part) => part.type === "reasoning"))
|
|
552
|
+
return [item];
|
|
553
|
+
const content = item.content.filter((part) => part.type !== "reasoning");
|
|
554
|
+
return content.length === 0 ? [] : [{ ...item, content }];
|
|
555
|
+
});
|
|
556
|
+
}
|
package/dist/model/normalize.js
CHANGED
|
@@ -119,11 +119,11 @@ function normalizeBlock(value, index) {
|
|
|
119
119
|
throw invalidCandidate(`Model output[${index}] must be an object`, `output[${index}]`);
|
|
120
120
|
const block = value;
|
|
121
121
|
if (block.type === "text" || block.type === "reasoning") {
|
|
122
|
-
rejectUnknownKeys(value, ["type", "text"], `Model output[${index}]`);
|
|
122
|
+
rejectUnknownKeys(value, ["type", "text", "providerMetadata"], `Model output[${index}]`);
|
|
123
123
|
const text = value.text;
|
|
124
124
|
if (typeof text !== "string")
|
|
125
125
|
throw invalidCandidate(`Model output[${index}].text must be a string`, `output[${index}].text`);
|
|
126
|
-
return Object.freeze({ type: block.type, text });
|
|
126
|
+
return Object.freeze({ type: block.type, text, ...providerMetadata(value, index) });
|
|
127
127
|
}
|
|
128
128
|
if (block.type === "json") {
|
|
129
129
|
rejectUnknownKeys(value, ["type", "value"], `Model output[${index}]`);
|
|
@@ -140,7 +140,7 @@ function normalizeBlock(value, index) {
|
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
if (block.type === "tool-call") {
|
|
143
|
-
rejectUnknownKeys(value, ["type", "id", "name", "args", "raw"], `Model output[${index}]`);
|
|
143
|
+
rejectUnknownKeys(value, ["type", "id", "name", "args", "raw", "providerMetadata"], `Model output[${index}]`);
|
|
144
144
|
const raw = value;
|
|
145
145
|
if (raw.id !== undefined && typeof raw.id !== "string")
|
|
146
146
|
throw invalidCandidate(`Model output[${index}].id must be a string`, `output[${index}].id`);
|
|
@@ -159,6 +159,7 @@ function normalizeBlock(value, index) {
|
|
|
159
159
|
throw invalidCandidate(`Model output[${index}].raw must be a string`, `output[${index}].raw`);
|
|
160
160
|
return Object.freeze({
|
|
161
161
|
type: "tool-call",
|
|
162
|
+
...providerMetadata(value, index),
|
|
162
163
|
id: typeof raw.id === "string" ? raw.id : "",
|
|
163
164
|
name: typeof raw.name === "string" ? raw.name : "",
|
|
164
165
|
args,
|
|
@@ -240,3 +241,14 @@ function isFiniteNumber(value) {
|
|
|
240
241
|
function isNonNegativeInteger(value) {
|
|
241
242
|
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
242
243
|
}
|
|
244
|
+
function providerMetadata(value, index) {
|
|
245
|
+
const metadata = value.providerMetadata;
|
|
246
|
+
if (metadata === undefined)
|
|
247
|
+
return {};
|
|
248
|
+
try {
|
|
249
|
+
return { providerMetadata: copyJsonObject(metadata, "providerMetadata") };
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
throw invalidCandidate(`Model output[${index}].providerMetadata must be a JSON object`, `output[${index}].providerMetadata`, error);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -23,7 +23,6 @@ export declare class SessionScheduler {
|
|
|
23
23
|
private generation;
|
|
24
24
|
private stopPromise?;
|
|
25
25
|
private recordFailure?;
|
|
26
|
-
private readonly observers;
|
|
27
26
|
private readonly turns;
|
|
28
27
|
private readonly states;
|
|
29
28
|
constructor(id: string, agent: LoopAgent, session: Readonly<{
|
|
@@ -32,11 +31,12 @@ export declare class SessionScheduler {
|
|
|
32
31
|
}>, options?: {
|
|
33
32
|
readonly seed?: NormalizedSessionSeed;
|
|
34
33
|
readonly recorder?: SessionRecorder;
|
|
34
|
+
readonly observer?: Observer;
|
|
35
35
|
});
|
|
36
36
|
private readonly session;
|
|
37
37
|
private readonly recorder?;
|
|
38
|
+
private readonly observer?;
|
|
38
39
|
get snapshot(): SessionSnapshot;
|
|
39
|
-
observe(listener: Observer): () => void;
|
|
40
40
|
submit(event: InputEvent, options?: InputOptions): SubmissionStream;
|
|
41
41
|
continue(options?: InputOptions): SubmissionStream;
|
|
42
42
|
private submitWork;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HarnessError } from "../errors.js";
|
|
2
2
|
import { createId } from "../utils/ids.js";
|
|
3
|
-
import {
|
|
3
|
+
import { emitObserve } from "../utils/observe.js";
|
|
4
4
|
import { InputQueue, isInteractionReply, snapshotWork, watchInputAbort, } from "./input-queue.js";
|
|
5
5
|
import { SessionEventLog } from "./event-log.js";
|
|
6
6
|
import { SubmissionStream } from "./submission-stream.js";
|
|
@@ -28,7 +28,6 @@ export class SessionScheduler {
|
|
|
28
28
|
generation = 0;
|
|
29
29
|
stopPromise;
|
|
30
30
|
recordFailure;
|
|
31
|
-
observers = createObserverRegistry();
|
|
32
31
|
turns;
|
|
33
32
|
states;
|
|
34
33
|
constructor(id, agent, session, options = {}) {
|
|
@@ -37,6 +36,7 @@ export class SessionScheduler {
|
|
|
37
36
|
this.turns = new TurnRunner(agent, id, session);
|
|
38
37
|
this.session = session;
|
|
39
38
|
this.recorder = options.recorder;
|
|
39
|
+
this.observer = options.observer;
|
|
40
40
|
this.states = new CapabilityStateRegistry(agent.middleware, Object.freeze({ id, ...session }), (event) => this.emitObserve(event));
|
|
41
41
|
if (options.seed) {
|
|
42
42
|
const event = Object.freeze({
|
|
@@ -44,24 +44,15 @@ export class SessionScheduler {
|
|
|
44
44
|
revision: options.seed.revision,
|
|
45
45
|
transcriptEntries: options.seed.transcript.length,
|
|
46
46
|
});
|
|
47
|
-
|
|
48
|
-
// by one microtask so callers can subscribe immediately after run({ seed }).
|
|
49
|
-
queueMicrotask(() => {
|
|
50
|
-
if (!this.stopped)
|
|
51
|
-
this.emitObserve(event);
|
|
52
|
-
});
|
|
47
|
+
this.emitObserve(event);
|
|
53
48
|
}
|
|
54
49
|
}
|
|
55
50
|
session;
|
|
56
51
|
recorder;
|
|
52
|
+
observer;
|
|
57
53
|
get snapshot() {
|
|
58
54
|
return this.snapshotValue;
|
|
59
55
|
}
|
|
60
|
-
observe(listener) {
|
|
61
|
-
if (this.stopped)
|
|
62
|
-
return () => undefined;
|
|
63
|
-
return this.observers.observe(listener);
|
|
64
|
-
}
|
|
65
56
|
submit(event, options) {
|
|
66
57
|
return this.submitWork(event, options);
|
|
67
58
|
}
|
|
@@ -130,7 +121,6 @@ export class SessionScheduler {
|
|
|
130
121
|
}
|
|
131
122
|
this.emitObserve({ type: "session.stopped", reason });
|
|
132
123
|
await this.states.shutdown(stopError);
|
|
133
|
-
this.observers.clear();
|
|
134
124
|
})();
|
|
135
125
|
return this.stopPromise;
|
|
136
126
|
}
|
|
@@ -377,7 +367,7 @@ export class SessionScheduler {
|
|
|
377
367
|
}
|
|
378
368
|
}
|
|
379
369
|
emitObserve(event) {
|
|
380
|
-
this.
|
|
370
|
+
emitObserve(this.observer, event);
|
|
381
371
|
}
|
|
382
372
|
async commitCancelledPlan(pending, reason) {
|
|
383
373
|
this.snapshotValue = await this.commit(commitToolResults(this.snapshotValue, pending.turnId, pending.stepId, pending.plan.cancelledResults(reason)), "tool-results");
|
package/dist/session/seed.js
CHANGED
|
@@ -246,7 +246,7 @@ function validateSeedCandidate(value, index) {
|
|
|
246
246
|
fail(`Candidate output ${blockIndex} at transcript ${index} must be an object`);
|
|
247
247
|
const block = value;
|
|
248
248
|
if (block.type === "text" || block.type === "reasoning") {
|
|
249
|
-
exactKeys(block, ["type", "text"], `Candidate output ${blockIndex}`);
|
|
249
|
+
exactKeys(block, ["type", "text", "providerMetadata"], `Candidate output ${blockIndex}`);
|
|
250
250
|
if (typeof block.text !== "string")
|
|
251
251
|
fail(`Candidate output ${blockIndex} text must be a string`);
|
|
252
252
|
return;
|
|
@@ -257,7 +257,7 @@ function validateSeedCandidate(value, index) {
|
|
|
257
257
|
return;
|
|
258
258
|
}
|
|
259
259
|
if (block.type === "tool-call") {
|
|
260
|
-
exactKeys(block, ["type", "id", "name", "args", "raw"], `Candidate output ${blockIndex}`);
|
|
260
|
+
exactKeys(block, ["type", "id", "name", "args", "raw", "providerMetadata"], `Candidate output ${blockIndex}`);
|
|
261
261
|
requiredString(block.id, `Candidate output ${blockIndex} id`);
|
|
262
262
|
requiredString(block.name, `Candidate output ${blockIndex} name`);
|
|
263
263
|
if (block.raw !== undefined && typeof block.raw !== "string")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { InputHandle, InputOptions, MessageInput, Session, SessionEvent, SessionRunOptions, SessionSnapshot, SessionInput } from "../types/session.js";
|
|
2
2
|
import type { ToolSchemaSource } from "../types/tool.js";
|
|
3
|
-
import type { JsonValue
|
|
3
|
+
import type { JsonValue } from "../types/shared.js";
|
|
4
4
|
import type { LoopAgent } from "../build/agent.js";
|
|
5
5
|
export declare class LiveSession implements Session {
|
|
6
6
|
readonly id: string;
|
|
@@ -13,6 +13,5 @@ export declare class LiveSession implements Session {
|
|
|
13
13
|
interrupt(event: MessageInput, options?: InputOptions): InputHandle<any>;
|
|
14
14
|
continue(options?: InputOptions): InputHandle<any>;
|
|
15
15
|
stream(): AsyncIterable<SessionEvent<JsonValue>>;
|
|
16
|
-
observe(listener: Observer): () => void;
|
|
17
16
|
stop(reason?: string): Promise<void>;
|
|
18
17
|
}
|
package/dist/session/session.js
CHANGED
|
@@ -20,6 +20,7 @@ export class LiveSession {
|
|
|
20
20
|
this.scheduler = new SessionScheduler(id, agent, session, {
|
|
21
21
|
...(seed === undefined ? {} : { seed }),
|
|
22
22
|
...(options.recorder === undefined ? {} : { recorder: options.recorder }),
|
|
23
|
+
...(options.observer === undefined ? {} : { observer: options.observer }),
|
|
23
24
|
});
|
|
24
25
|
}
|
|
25
26
|
get state() {
|
|
@@ -43,9 +44,6 @@ export class LiveSession {
|
|
|
43
44
|
stream() {
|
|
44
45
|
return this.scheduler.events;
|
|
45
46
|
}
|
|
46
|
-
observe(listener) {
|
|
47
|
-
return this.scheduler.observe(listener);
|
|
48
|
-
}
|
|
49
47
|
stop(reason) {
|
|
50
48
|
return this.scheduler.stop(reason);
|
|
51
49
|
}
|
|
@@ -38,6 +38,9 @@ export function canonicalizeOutput(output) {
|
|
|
38
38
|
id: call.id,
|
|
39
39
|
name: call.name,
|
|
40
40
|
args: call.args,
|
|
41
|
+
...(block.providerMetadata === undefined
|
|
42
|
+
? {}
|
|
43
|
+
: { providerMetadata: block.providerMetadata }),
|
|
41
44
|
...(block.raw === undefined ? {} : { raw: block.raw }),
|
|
42
45
|
});
|
|
43
46
|
})),
|
package/dist/step/project.js
CHANGED
|
@@ -64,8 +64,9 @@ function projectEntry(entry) {
|
|
|
64
64
|
}
|
|
65
65
|
if (entry.kind === "candidate") {
|
|
66
66
|
const content = entry.candidate.output.flatMap((block) => {
|
|
67
|
-
if (block.type === "text"
|
|
68
|
-
|
|
67
|
+
if (block.type === "text" ||
|
|
68
|
+
(block.type === "reasoning" && block.providerMetadata !== undefined))
|
|
69
|
+
return [Object.freeze({ ...block })];
|
|
69
70
|
if (block.type === "json")
|
|
70
71
|
return [textPart(JSON.stringify(block.value))];
|
|
71
72
|
if (block.type === "tool-call")
|
|
@@ -75,6 +76,9 @@ function projectEntry(entry) {
|
|
|
75
76
|
id: block.id,
|
|
76
77
|
name: block.name,
|
|
77
78
|
args: copyJson(block.args),
|
|
79
|
+
...(block.providerMetadata === undefined
|
|
80
|
+
? {}
|
|
81
|
+
: { providerMetadata: copyJson(block.providerMetadata) }),
|
|
78
82
|
}),
|
|
79
83
|
];
|
|
80
84
|
return [];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ObserveEmit } from "../utils/observe.js";
|
|
2
|
+
import type { ModelAdapter } from "../types/model.js";
|
|
2
3
|
import type { InputEvent } from "../types/session.js";
|
|
3
4
|
import type { ActiveInteractionExecutionRecord, ActiveToolsExecutionRecord } from "../types/session.js";
|
|
4
5
|
import type { RequiredInteraction, ToolExecutionResume, ToolResult } from "../types/tool.js";
|
|
@@ -33,6 +34,7 @@ export interface ToolPlanRunContext {
|
|
|
33
34
|
readonly stepId: string;
|
|
34
35
|
};
|
|
35
36
|
readonly states?: CapabilityStateRegistry;
|
|
37
|
+
readonly onModelCall: ModelAdapter;
|
|
36
38
|
}
|
|
37
39
|
/** Owns one sealed plan's deterministic interaction and concurrent execution progress. */
|
|
38
40
|
export declare class ToolPlanRunner {
|
package/dist/turn/plan-runner.js
CHANGED
|
@@ -179,6 +179,7 @@ export class ToolPlanRunner {
|
|
|
179
179
|
callId: entry.call.callId,
|
|
180
180
|
invocationId: entry.invocationId,
|
|
181
181
|
signal: context.signal,
|
|
182
|
+
onModelCall: context.onModelCall,
|
|
182
183
|
resume: this.takeResume(entry.call.callId),
|
|
183
184
|
};
|
|
184
185
|
if (context.states?.has(entry.owner.middlewareId))
|
package/dist/turn/runner.js
CHANGED
|
@@ -150,6 +150,7 @@ export class TurnRunner {
|
|
|
150
150
|
observe: context.observe,
|
|
151
151
|
ids: { sessionId: this.sessionId, turnId: pending.turnId, stepId: pending.stepId },
|
|
152
152
|
states: context.states,
|
|
153
|
+
onModelCall: this.agent.invoke,
|
|
153
154
|
}, resume);
|
|
154
155
|
context.assertCurrent();
|
|
155
156
|
context.onPlanActive(undefined);
|
package/dist/types/model.d.ts
CHANGED
|
@@ -9,14 +9,17 @@ export interface ModelToolCall {
|
|
|
9
9
|
export type ModelOutputBlock = {
|
|
10
10
|
readonly type: "text";
|
|
11
11
|
readonly text: string;
|
|
12
|
+
readonly providerMetadata?: JsonObject;
|
|
12
13
|
} | {
|
|
13
14
|
readonly type: "reasoning";
|
|
14
15
|
readonly text: string;
|
|
16
|
+
readonly providerMetadata?: JsonObject;
|
|
15
17
|
} | {
|
|
16
18
|
readonly type: "json";
|
|
17
19
|
readonly value: JsonValue;
|
|
18
20
|
} | {
|
|
19
21
|
readonly type: "tool-call";
|
|
22
|
+
readonly providerMetadata?: JsonObject;
|
|
20
23
|
readonly id: string;
|
|
21
24
|
readonly name: string;
|
|
22
25
|
readonly args: JsonObject;
|
|
@@ -117,14 +120,20 @@ export interface ModelRequest {
|
|
|
117
120
|
readonly outputSchema?: JsonObject;
|
|
118
121
|
}
|
|
119
122
|
export type PromptContentPart = {
|
|
123
|
+
readonly type: "reasoning";
|
|
124
|
+
readonly text: string;
|
|
125
|
+
readonly providerMetadata?: JsonObject;
|
|
126
|
+
} | {
|
|
120
127
|
readonly type: "text";
|
|
121
128
|
readonly text: string;
|
|
129
|
+
readonly providerMetadata?: JsonObject;
|
|
122
130
|
} | {
|
|
123
131
|
readonly type: "media";
|
|
124
132
|
readonly mediaType: string;
|
|
125
133
|
readonly reference: JsonValue;
|
|
126
134
|
} | {
|
|
127
135
|
readonly type: "tool-call";
|
|
136
|
+
readonly providerMetadata?: JsonObject;
|
|
128
137
|
readonly id: string;
|
|
129
138
|
readonly name: string;
|
|
130
139
|
readonly args: JsonObject;
|
package/dist/types/session.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ModelCall, ModelCandidate } from "./model.js";
|
|
1
|
+
import type { ModelAdapter, ModelCall, ModelCandidate } from "./model.js";
|
|
2
2
|
import type { JsonObject, JsonValue, Observer, Tripwire } from "./shared.js";
|
|
3
3
|
import type { RequiredInteraction, SchemaOutput, ToolExecutionResume, ToolOwner, ToolResult, ToolSchemaSource } from "./tool.js";
|
|
4
4
|
export type InputEvent = {
|
|
@@ -70,11 +70,13 @@ export interface InputHandle<Output = string> {
|
|
|
70
70
|
readonly completed: Promise<InputCompletion<Output>>;
|
|
71
71
|
}
|
|
72
72
|
export interface SessionOptions {
|
|
73
|
+
readonly onModelCall: ModelAdapter;
|
|
73
74
|
readonly id?: string;
|
|
74
75
|
readonly userId?: string;
|
|
75
76
|
readonly context?: JsonObject;
|
|
76
77
|
readonly seed?: never;
|
|
77
78
|
readonly recorder?: SessionRecorder;
|
|
79
|
+
readonly observer?: Observer;
|
|
78
80
|
}
|
|
79
81
|
/** Stable host-owned facts available while a capability creates session-local state. */
|
|
80
82
|
export interface SessionIdentity {
|
|
@@ -83,8 +85,10 @@ export interface SessionIdentity {
|
|
|
83
85
|
readonly context?: JsonObject;
|
|
84
86
|
}
|
|
85
87
|
export interface SeededSessionOptions {
|
|
88
|
+
readonly onModelCall: ModelAdapter;
|
|
86
89
|
readonly seed: SessionSeed;
|
|
87
90
|
readonly recorder?: SessionRecorder;
|
|
91
|
+
readonly observer?: Observer;
|
|
88
92
|
readonly id?: never;
|
|
89
93
|
readonly userId?: never;
|
|
90
94
|
readonly context?: never;
|
|
@@ -222,6 +226,5 @@ export interface Session {
|
|
|
222
226
|
interrupt(event: MessageInput, options?: InputOptions): InputHandle;
|
|
223
227
|
continue(options?: InputOptions): InputHandle;
|
|
224
228
|
stream(): AsyncIterable<SessionEvent<JsonValue>>;
|
|
225
|
-
observe(listener: Observer): () => void;
|
|
226
229
|
stop(reason?: string): Promise<void>;
|
|
227
230
|
}
|
package/dist/types/tool.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ZodType } from "zod";
|
|
2
2
|
import type { DeferredOutcome, JsonObject, JsonValue } from "./shared.js";
|
|
3
|
+
import type { ModelAdapter } from "./model.js";
|
|
3
4
|
export interface SchemaIssue {
|
|
4
5
|
readonly path: readonly (string | number)[];
|
|
5
6
|
readonly code: string;
|
|
@@ -90,6 +91,8 @@ interface ToolExecutionContextBase {
|
|
|
90
91
|
readonly callId: string;
|
|
91
92
|
readonly invocationId: string;
|
|
92
93
|
readonly signal: AbortSignal;
|
|
94
|
+
/** The session's model callable, for tools that explicitly run a child agent. */
|
|
95
|
+
readonly onModelCall?: ModelAdapter;
|
|
93
96
|
readonly resume?: ToolExecutionResume;
|
|
94
97
|
}
|
|
95
98
|
export type ToolExecutionContext<State = never> = ToolExecutionContextBase & ([State] extends [never] ? object : {
|
package/dist/utils/observe.d.ts
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
1
|
import type { ObserveEvent, Observer } from "../types/shared.js";
|
|
2
2
|
export type ObserveEmit = (event: ObserveEvent | (() => ObserveEvent)) => void;
|
|
3
|
-
export
|
|
4
|
-
observe(listener: Observer): () => void;
|
|
5
|
-
emit(event: ObserveEvent | (() => ObserveEvent)): void;
|
|
6
|
-
clear(): void;
|
|
7
|
-
}
|
|
8
|
-
export declare function createObserverRegistry(): ObserverRegistry;
|
|
3
|
+
export declare function emitObserve(listener: Observer | undefined, event: ObserveEvent | (() => ObserveEvent)): void;
|
package/dist/utils/observe.js
CHANGED
|
@@ -1,29 +1,13 @@
|
|
|
1
|
-
export function
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
for (const listener of [...listeners]) {
|
|
14
|
-
try {
|
|
15
|
-
const result = listener(snapshot);
|
|
16
|
-
if (result && typeof result.then === "function") {
|
|
17
|
-
void Promise.resolve(result).catch(() => undefined);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
// Observation is deliberately fail-open.
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
clear() {
|
|
26
|
-
listeners.clear();
|
|
27
|
-
},
|
|
28
|
-
};
|
|
1
|
+
export function emitObserve(listener, event) {
|
|
2
|
+
if (!listener)
|
|
3
|
+
return;
|
|
4
|
+
const snapshot = Object.freeze({ ...(typeof event === "function" ? event() : event) });
|
|
5
|
+
try {
|
|
6
|
+
const result = listener(snapshot);
|
|
7
|
+
if (result && typeof result.then === "function")
|
|
8
|
+
void Promise.resolve(result).catch(() => undefined);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
// Observation is deliberately fail-open.
|
|
12
|
+
}
|
|
29
13
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nylorun/harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0-beta",
|
|
4
4
|
"description": "Nylorun's TypeScript agent runtime. See github.com/nylorun/harness.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -59,11 +59,11 @@
|
|
|
59
59
|
"prepack": "npm run build"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@types/node": "^
|
|
62
|
+
"@types/node": "^26.5.0",
|
|
63
63
|
"@typescript/native": "npm:typescript@^7.0.2",
|
|
64
64
|
"prettier": "^3.9.6",
|
|
65
65
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
66
|
-
"vitest": "^
|
|
66
|
+
"vitest": "^5.0.0",
|
|
67
67
|
"zod": "^4.1.12"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|