@nylorun/harness 0.12.0-beta → 0.15.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 +71 -0
- package/HOST_CONTRACT.md +27 -0
- package/README.md +86 -5
- package/dist/compatibility.d.ts +3 -0
- package/dist/compatibility.js +3 -0
- package/dist/definition/agent-definition.d.ts +19 -0
- package/dist/definition/agent-definition.js +20 -0
- package/dist/definition/compatibility.d.ts +11 -0
- package/dist/definition/compatibility.js +30 -0
- package/dist/definition/registry.d.ts +10 -0
- package/dist/definition/registry.js +49 -0
- package/dist/index.d.ts +4 -15
- package/dist/index.js +1 -6
- package/dist/loop/dispatch.d.ts +2 -0
- package/dist/loop/dispatch.js +327 -0
- package/dist/loop/host-suspension.d.ts +6 -0
- package/dist/loop/host-suspension.js +10 -0
- package/dist/loop/initial-state.d.ts +13 -0
- package/dist/loop/initial-state.js +30 -0
- package/dist/loop/invocation.d.ts +24 -0
- package/dist/loop/invocation.js +37 -0
- package/dist/{model → loop/model}/adapters.d.ts +4 -2
- package/dist/{model → loop/model}/adapters.js +3 -2
- package/dist/{model → loop/model}/normalize.d.ts +2 -2
- package/dist/{model → loop/model}/normalize.js +2 -2
- package/dist/{model → loop/model}/prepared.d.ts +3 -3
- package/dist/{step → loop/model}/project.d.ts +1 -1
- package/dist/{step → loop/model}/project.js +2 -10
- package/dist/loop/observe.d.ts +17 -0
- package/dist/loop/observe.js +53 -0
- package/dist/loop/options.d.ts +11 -0
- package/dist/loop/options.js +69 -0
- package/dist/loop/resume.d.ts +4 -0
- package/dist/loop/resume.js +52 -0
- package/dist/loop/run.d.ts +5 -0
- package/dist/loop/run.js +264 -0
- package/dist/loop/state.d.ts +8 -0
- package/dist/loop/state.js +218 -0
- package/dist/{step → loop/step}/canonicalize.d.ts +2 -2
- package/dist/{step → loop/step}/canonicalize.js +2 -2
- package/dist/{step → loop/step}/compose.d.ts +3 -2
- package/dist/{step → loop/step}/compose.js +8 -5
- package/dist/loop/step/context-draft.d.ts +8 -0
- package/dist/{step → loop/step}/context-draft.js +5 -22
- package/dist/loop/step/describe.d.ts +9 -0
- package/dist/loop/step/describe.js +22 -0
- package/dist/loop/step/dynamics.d.ts +29 -0
- package/dist/loop/step/dynamics.js +147 -0
- package/dist/{step → loop/step}/model-configuration.d.ts +4 -3
- package/dist/{step → loop/step}/model-configuration.js +3 -3
- package/dist/loop/step/resolve.d.ts +11 -0
- package/dist/{step → loop/step}/resolve.js +5 -3
- package/dist/loop/step/run.d.ts +30 -0
- package/dist/{step → loop/step}/run.js +104 -53
- package/dist/loop/step/runtime.d.ts +40 -0
- package/dist/{step → loop/step}/seal.d.ts +13 -8
- package/dist/{step → loop/step}/seal.js +8 -6
- package/dist/{step → loop/step}/slot-assembly.d.ts +4 -4
- package/dist/{step → loop/step}/slot-assembly.js +4 -4
- package/dist/{step → loop/step}/step-context.d.ts +11 -12
- package/dist/{step → loop/step}/step-context.js +5 -27
- package/dist/loop/tool-result.d.ts +4 -0
- package/dist/loop/tool-result.js +30 -0
- package/dist/loop/transcript.d.ts +2 -0
- package/dist/{session/seed.js → loop/transcript.js} +11 -41
- package/dist/loop/waits.d.ts +50 -0
- package/dist/loop/waits.js +11 -0
- package/dist/run/durable.d.ts +71 -0
- package/dist/run/durable.js +164 -0
- package/dist/run/index.d.ts +33 -0
- package/dist/run/index.js +38 -0
- package/dist/types/client.d.ts +88 -0
- package/dist/types/execution.d.ts +114 -0
- package/dist/utils/ids.d.ts +1 -0
- package/dist/utils/ids.js +11 -1
- package/package.json +18 -6
- package/dist/build/agent.d.ts +0 -17
- package/dist/build/agent.js +0 -32
- package/dist/build/assemble.d.ts +0 -7
- package/dist/build/assemble.js +0 -41
- package/dist/build/bind-tool.d.ts +0 -3
- package/dist/build/bind-tool.js +0 -18
- package/dist/build/builder.d.ts +0 -38
- package/dist/build/builder.js +0 -136
- package/dist/build/helpers.d.ts +0 -6
- package/dist/build/helpers.js +0 -4
- package/dist/build/manifest.d.ts +0 -7
- package/dist/build/manifest.js +0 -20
- package/dist/build/schema.d.ts +0 -14
- package/dist/build/schema.js +0 -205
- package/dist/errors.d.ts +0 -17
- package/dist/errors.js +0 -17
- package/dist/session/capability-state.d.ts +0 -14
- package/dist/session/capability-state.js +0 -67
- package/dist/session/event-log.d.ts +0 -12
- package/dist/session/event-log.js +0 -63
- package/dist/session/input-queue.d.ts +0 -41
- package/dist/session/input-queue.js +0 -94
- package/dist/session/output-contract.d.ts +0 -6
- package/dist/session/output-contract.js +0 -12
- package/dist/session/record.d.ts +0 -11
- package/dist/session/record.js +0 -26
- package/dist/session/scheduler.d.ts +0 -62
- package/dist/session/scheduler.js +0 -475
- package/dist/session/seed.d.ts +0 -10
- package/dist/session/session.d.ts +0 -17
- package/dist/session/session.js +0 -101
- package/dist/session/state.d.ts +0 -15
- package/dist/session/state.js +0 -48
- package/dist/session/submission-stream.d.ts +0 -16
- package/dist/session/submission-stream.js +0 -46
- package/dist/step/context-draft.d.ts +0 -10
- package/dist/step/resolve.d.ts +0 -10
- package/dist/step/run.d.ts +0 -33
- package/dist/turn/plan-runner.d.ts +0 -72
- package/dist/turn/plan-runner.js +0 -389
- package/dist/turn/runner.d.ts +0 -69
- package/dist/turn/runner.js +0 -159
- package/dist/types/manifest.d.ts +0 -19
- package/dist/types/middleware.d.ts +0 -95
- package/dist/types/model.d.ts +0 -184
- package/dist/types/session.d.ts +0 -230
- package/dist/types/session.js +0 -1
- package/dist/types/shared.d.ts +0 -249
- package/dist/types/shared.js +0 -1
- package/dist/types/tool.d.ts +0 -139
- package/dist/types/tool.js +0 -1
- package/dist/utils/immutable.d.ts +0 -5
- package/dist/utils/immutable.js +0 -68
- package/dist/utils/maps.d.ts +0 -1
- package/dist/utils/maps.js +0 -37
- package/dist/utils/observe.d.ts +0 -3
- package/dist/utils/observe.js +0 -13
- /package/dist/{model → loop/model}/prepared.js +0 -0
- /package/dist/{types/manifest.js → loop/step/runtime.js} +0 -0
- /package/dist/types/{middleware.js → client.js} +0 -0
- /package/dist/types/{model.js → execution.js} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HostSuspension } from "../host-suspension.js";
|
|
2
|
+
import { HarnessError, isHarnessError } from "@nylorun/core/define";
|
|
2
3
|
import { isBrandedResponse, StepContext } from "./step-context.js";
|
|
3
4
|
export async function runMiddleware(middleware, context, terminal, observe) {
|
|
4
5
|
const dispatch = async (index) => {
|
|
@@ -49,13 +50,15 @@ export async function runMiddleware(middleware, context, terminal, observe) {
|
|
|
49
50
|
handlerError ??= error;
|
|
50
51
|
}
|
|
51
52
|
}
|
|
53
|
+
if (handlerError instanceof HostSuspension)
|
|
54
|
+
throw handlerError;
|
|
52
55
|
if (handlerError) {
|
|
53
56
|
if (nextCalledTwice ||
|
|
54
57
|
(isHarnessError(handlerError) && handlerError.code === "middleware.next-called-twice")) {
|
|
55
58
|
return context.tripwire({
|
|
56
59
|
code: "middleware.next-called-twice",
|
|
57
60
|
message: message(handlerError),
|
|
58
|
-
scope: "
|
|
61
|
+
scope: "execution",
|
|
59
62
|
});
|
|
60
63
|
}
|
|
61
64
|
return context.tripwire({
|
|
@@ -70,8 +73,8 @@ export async function runMiddleware(middleware, context, terminal, observe) {
|
|
|
70
73
|
message: `Middleware '${item.id}' must return the StepResponse from next()`,
|
|
71
74
|
// Calling next() completed the step successfully; forgetting to
|
|
72
75
|
// return that response is isolated to this step. A non-response
|
|
73
|
-
// replacement remains a
|
|
74
|
-
scope: result === undefined ? "step" : "
|
|
76
|
+
// replacement remains a execution-level middleware-contract breach.
|
|
77
|
+
scope: result === undefined ? "step" : "execution",
|
|
75
78
|
});
|
|
76
79
|
}
|
|
77
80
|
return inner;
|
|
@@ -80,7 +83,7 @@ export async function runMiddleware(middleware, context, terminal, observe) {
|
|
|
80
83
|
return context.tripwire({
|
|
81
84
|
code: "middleware.invalid-response",
|
|
82
85
|
message: `Middleware '${item.id}' must return a branded StepResponse`,
|
|
83
|
-
scope: "
|
|
86
|
+
scope: "execution",
|
|
84
87
|
});
|
|
85
88
|
}
|
|
86
89
|
return result;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ContextMutationOptions, ContextSnapshot } from "@nylorun/core/define";
|
|
2
|
+
import type { ContextItem } from "@nylorun/core/define";
|
|
3
|
+
/** Per-step model-visible context supplied explicitly by middleware. */
|
|
4
|
+
export declare class ContextDraft {
|
|
5
|
+
#private;
|
|
6
|
+
set(middlewareId: string, middlewareOrder: number, slot: string, items: readonly ContextItem[], options?: ContextMutationOptions): void;
|
|
7
|
+
snapshot(): ContextSnapshot;
|
|
8
|
+
}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import { HarnessError } from "
|
|
2
|
-
import { copyJson } from "
|
|
1
|
+
import { HarnessError } from "@nylorun/core/define";
|
|
2
|
+
import { copyJson } from "@nylorun/core/define";
|
|
3
3
|
import { SlotDraft } from "./slot-assembly.js";
|
|
4
4
|
const CONTEXT_TYPE = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
|
|
5
|
-
/** Per-step
|
|
5
|
+
/** Per-step model-visible context supplied explicitly by middleware. */
|
|
6
6
|
export class ContextDraft {
|
|
7
|
-
hostContext;
|
|
8
7
|
#slots = new SlotDraft();
|
|
9
|
-
constructor(hostContext) {
|
|
10
|
-
this.hostContext = hostContext;
|
|
11
|
-
}
|
|
12
8
|
set(middlewareId, middlewareOrder, slot, items, options) {
|
|
13
9
|
if (!Array.isArray(items))
|
|
14
10
|
throw new HarnessError("context.invalid-item", "Runtime context items must be an array");
|
|
@@ -27,14 +23,8 @@ export class ContextDraft {
|
|
|
27
23
|
}
|
|
28
24
|
snapshot() {
|
|
29
25
|
const slots = this.#slots.values();
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
: [Object.freeze({ type: "session", value: copyJson(this.hostContext) })];
|
|
33
|
-
const items = Object.freeze([...host, ...slots.flatMap((slot) => slot.value)]);
|
|
34
|
-
const contributors = Object.freeze([
|
|
35
|
-
...(this.hostContext === undefined ? [] : [hostContributor()]),
|
|
36
|
-
...slots.map((slot) => contributor(slot.owner, slot.reason)),
|
|
37
|
-
]);
|
|
26
|
+
const items = Object.freeze(slots.flatMap((slot) => slot.value));
|
|
27
|
+
const contributors = Object.freeze(slots.map((slot) => contributor(slot.owner, slot.reason)));
|
|
38
28
|
return Object.freeze({ items, contributors });
|
|
39
29
|
}
|
|
40
30
|
}
|
|
@@ -48,13 +38,6 @@ function normalizeItem(item) {
|
|
|
48
38
|
value: copyJson(item.value),
|
|
49
39
|
});
|
|
50
40
|
}
|
|
51
|
-
function hostContributor() {
|
|
52
|
-
return Object.freeze({
|
|
53
|
-
middlewareId: "host",
|
|
54
|
-
slot: "session",
|
|
55
|
-
order: 0,
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
41
|
function contributor(owner, reason) {
|
|
59
42
|
return Object.freeze({
|
|
60
43
|
middlewareId: owner.middlewareId,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ModelConfigurationSnapshot } from "@nylorun/core/define";
|
|
2
|
+
import type { ToolDescriptor } from "@nylorun/core/define";
|
|
3
|
+
import type { BoundToolDefinition } from "@nylorun/core/define";
|
|
4
|
+
/** Executable configuration stays inside the invocation. */
|
|
5
|
+
export interface InternalModelConfigurationSnapshot extends Omit<ModelConfigurationSnapshot, "tools"> {
|
|
6
|
+
readonly tools: readonly BoundToolDefinition[];
|
|
7
|
+
}
|
|
8
|
+
export declare function describeTool(tool: BoundToolDefinition): ToolDescriptor;
|
|
9
|
+
export declare function describeConfiguration(configuration: InternalModelConfigurationSnapshot): ModelConfigurationSnapshot;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { copyJson } from "@nylorun/core/define";
|
|
2
|
+
export function describeTool(tool) {
|
|
3
|
+
return copyJson({
|
|
4
|
+
name: tool.name,
|
|
5
|
+
...(tool.description === undefined ? {} : { description: tool.description }),
|
|
6
|
+
owner: tool.owner,
|
|
7
|
+
inputSchema: { jsonSchema: tool.inputSchema.jsonSchema },
|
|
8
|
+
...(tool.outputSchema === undefined
|
|
9
|
+
? {}
|
|
10
|
+
: { outputSchema: { jsonSchema: tool.outputSchema.jsonSchema } }),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export function describeConfiguration(configuration) {
|
|
14
|
+
return copyJson({
|
|
15
|
+
version: configuration.version,
|
|
16
|
+
...(configuration.model === undefined ? {} : { model: configuration.model }),
|
|
17
|
+
instructions: configuration.instructions,
|
|
18
|
+
tools: configuration.tools.map(describeTool),
|
|
19
|
+
toolContracts: configuration.toolContracts,
|
|
20
|
+
contributors: configuration.contributors,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AgentDefinition } from "../../definition/agent-definition.js";
|
|
2
|
+
import type { Decision } from "@nylorun/core/define";
|
|
3
|
+
import type { JsonValue } from "@nylorun/core/define";
|
|
4
|
+
import type { PromptItem } from "@nylorun/core/define";
|
|
5
|
+
import type { InputEvent } from "@nylorun/core/define";
|
|
6
|
+
import type { ModelConfigurationDraft } from "./model-configuration.js";
|
|
7
|
+
export interface DynamicsContext {
|
|
8
|
+
readonly agent: AgentDefinition;
|
|
9
|
+
readonly info?: unknown;
|
|
10
|
+
readonly step: number;
|
|
11
|
+
readonly arrivals: readonly InputEvent[];
|
|
12
|
+
readonly messages: readonly PromptItem[];
|
|
13
|
+
sessionState: Record<string, JsonValue>;
|
|
14
|
+
}
|
|
15
|
+
/** Run all beforeModelCall handlers in capability `.use()` order. */
|
|
16
|
+
export declare function applyBeforeModelCall(ctx: DynamicsContext, configuration: ModelConfigurationDraft): Promise<{
|
|
17
|
+
readonly blocked?: string;
|
|
18
|
+
}>;
|
|
19
|
+
/** Run all afterModelCall handlers; merge with strictest-wins rules. */
|
|
20
|
+
export declare function applyAfterModelCall(ctx: DynamicsContext, args: {
|
|
21
|
+
readonly text?: string;
|
|
22
|
+
readonly toolCalls: readonly {
|
|
23
|
+
readonly id: string;
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly args: JsonValue;
|
|
26
|
+
}[];
|
|
27
|
+
}): Promise<Decision>;
|
|
28
|
+
export declare function mergeDecisions(decisions: readonly Decision[]): Decision;
|
|
29
|
+
export declare function hasDynamics(agent: AgentDefinition): boolean;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { HarnessError } from "@nylorun/core/define";
|
|
2
|
+
/** Run all beforeModelCall handlers in capability `.use()` order. */
|
|
3
|
+
export async function applyBeforeModelCall(ctx, configuration) {
|
|
4
|
+
const patches = [];
|
|
5
|
+
for (const capability of ctx.agent.manifest.capabilities) {
|
|
6
|
+
const impl = ctx.agent.implementations[capability.id];
|
|
7
|
+
if (!impl?.beforeModelCall)
|
|
8
|
+
continue;
|
|
9
|
+
const raw = await impl.beforeModelCall({
|
|
10
|
+
input: projectInput(ctx.arrivals),
|
|
11
|
+
info: ctx.info,
|
|
12
|
+
state: { ...ctx.sessionState },
|
|
13
|
+
messages: ctx.messages,
|
|
14
|
+
step: ctx.step,
|
|
15
|
+
});
|
|
16
|
+
const patch = normalizeCapabilityPatch(capability.id, raw ?? {}, capability.id === "agent");
|
|
17
|
+
patches.push({ id: capability.id, patch, agentLevel: capability.id === "agent" });
|
|
18
|
+
}
|
|
19
|
+
if (!patches.length)
|
|
20
|
+
return {};
|
|
21
|
+
let blocked;
|
|
22
|
+
const capabilitySwitches = new Map();
|
|
23
|
+
const toolSwitches = new Map();
|
|
24
|
+
const extraInstructions = [];
|
|
25
|
+
for (const { id, patch, agentLevel } of patches) {
|
|
26
|
+
if (patch.block)
|
|
27
|
+
blocked = patch.block;
|
|
28
|
+
if (patch.state) {
|
|
29
|
+
for (const [key, value] of Object.entries(patch.state))
|
|
30
|
+
ctx.sessionState[key] = value;
|
|
31
|
+
}
|
|
32
|
+
if (patch.instructions)
|
|
33
|
+
extraInstructions.push(...patch.instructions);
|
|
34
|
+
if (patch.capabilities) {
|
|
35
|
+
for (const [name, on] of Object.entries(patch.capabilities)) {
|
|
36
|
+
if (!agentLevel && name !== id)
|
|
37
|
+
throw new HarnessError("configuration.invalid", `Capability '${id}' beforeModelCall may only toggle itself, not '${name}'`);
|
|
38
|
+
if (!ctx.agent.manifest.capabilities.some((item) => item.id === name))
|
|
39
|
+
throw new HarnessError("configuration.invalid", `beforeModelCall referenced unknown capability '${name}'`);
|
|
40
|
+
capabilitySwitches.set(name, on);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (patch.tools) {
|
|
44
|
+
for (const [name, on] of Object.entries(patch.tools)) {
|
|
45
|
+
const known = ctx.agent.manifest.capabilities.some((cap) => cap.tools?.some((tool) => tool.name === name));
|
|
46
|
+
if (!known)
|
|
47
|
+
throw new HarnessError("configuration.invalid", `beforeModelCall referenced unknown tool '${name}'`);
|
|
48
|
+
if (!agentLevel) {
|
|
49
|
+
const owned = ctx.agent.manifest.capabilities
|
|
50
|
+
.find((cap) => cap.id === id)
|
|
51
|
+
?.tools?.some((tool) => tool.name === name);
|
|
52
|
+
if (!owned)
|
|
53
|
+
throw new HarnessError("configuration.invalid", `Capability '${id}' beforeModelCall may only toggle its own tools`);
|
|
54
|
+
}
|
|
55
|
+
toolSwitches.set(name, on);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (blocked)
|
|
60
|
+
return { blocked };
|
|
61
|
+
let order = 10_000;
|
|
62
|
+
for (const [capabilityId, on] of capabilitySwitches) {
|
|
63
|
+
if (on)
|
|
64
|
+
continue;
|
|
65
|
+
configuration.setTools("dynamics", order++, capabilityId, []);
|
|
66
|
+
}
|
|
67
|
+
for (const [toolName, on] of toolSwitches) {
|
|
68
|
+
if (on)
|
|
69
|
+
continue;
|
|
70
|
+
for (const capability of ctx.agent.manifest.capabilities) {
|
|
71
|
+
const tools = ctx.agent.implementations[capability.id]?.tools;
|
|
72
|
+
if (!tools?.[toolName])
|
|
73
|
+
continue;
|
|
74
|
+
const remaining = Object.values(tools).filter((tool) => tool.name !== toolName);
|
|
75
|
+
configuration.setTools("dynamics", order++, capability.id, remaining);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (extraInstructions.length)
|
|
79
|
+
configuration.setInstructions("dynamics", order++, "dynamics", Object.freeze([...extraInstructions]));
|
|
80
|
+
return {};
|
|
81
|
+
}
|
|
82
|
+
/** Run all afterModelCall handlers; merge with strictest-wins rules. */
|
|
83
|
+
export async function applyAfterModelCall(ctx, args) {
|
|
84
|
+
const decisions = [];
|
|
85
|
+
for (const capability of ctx.agent.manifest.capabilities) {
|
|
86
|
+
const impl = ctx.agent.implementations[capability.id];
|
|
87
|
+
if (!impl?.afterModelCall)
|
|
88
|
+
continue;
|
|
89
|
+
const decision = await impl.afterModelCall(args, {
|
|
90
|
+
info: ctx.info,
|
|
91
|
+
state: { ...ctx.sessionState },
|
|
92
|
+
});
|
|
93
|
+
decisions.push(decision ?? {});
|
|
94
|
+
}
|
|
95
|
+
return mergeDecisions(decisions);
|
|
96
|
+
}
|
|
97
|
+
export function mergeDecisions(decisions) {
|
|
98
|
+
let block;
|
|
99
|
+
let retry;
|
|
100
|
+
const deny = new Map();
|
|
101
|
+
const approve = new Map();
|
|
102
|
+
let text;
|
|
103
|
+
for (const decision of decisions) {
|
|
104
|
+
if (decision.block)
|
|
105
|
+
block = decision.block;
|
|
106
|
+
if (decision.retry)
|
|
107
|
+
retry = decision.retry;
|
|
108
|
+
for (const item of decision.deny ?? [])
|
|
109
|
+
deny.set(item.id, item.reason);
|
|
110
|
+
for (const item of decision.approve ?? [])
|
|
111
|
+
approve.set(item.id, item.prompt);
|
|
112
|
+
if (decision.text !== undefined)
|
|
113
|
+
text = decision.text;
|
|
114
|
+
}
|
|
115
|
+
if (block)
|
|
116
|
+
return { block };
|
|
117
|
+
if (retry)
|
|
118
|
+
return { retry, ...(text === undefined ? {} : { text }) };
|
|
119
|
+
return {
|
|
120
|
+
...(text === undefined ? {} : { text }),
|
|
121
|
+
...(deny.size ? { deny: [...deny.entries()].map(([id, reason]) => ({ id, reason })) } : {}),
|
|
122
|
+
...(approve.size
|
|
123
|
+
? { approve: [...approve.entries()].map(([id, prompt]) => ({ id, prompt })) }
|
|
124
|
+
: {}),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
export function hasDynamics(agent) {
|
|
128
|
+
return agent.manifest.capabilities.some((capability) => capability.beforeModelCall || capability.afterModelCall);
|
|
129
|
+
}
|
|
130
|
+
function normalizeCapabilityPatch(id, patch, agentLevel) {
|
|
131
|
+
if (patch.active === undefined)
|
|
132
|
+
return patch;
|
|
133
|
+
if (agentLevel)
|
|
134
|
+
throw new HarnessError("configuration.invalid", "Agent-level beforeModelCall must use capabilities{}, not active");
|
|
135
|
+
return {
|
|
136
|
+
...patch,
|
|
137
|
+
capabilities: { ...(patch.capabilities ?? {}), [id]: patch.active },
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function projectInput(arrivals) {
|
|
141
|
+
const message = arrivals.find((item) => item.kind === "user-message");
|
|
142
|
+
if (!message)
|
|
143
|
+
return undefined;
|
|
144
|
+
if ("text" in message && typeof message.text === "string")
|
|
145
|
+
return message.text;
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { InternalModelConfigurationSnapshot } from "./describe.js";
|
|
2
|
+
import type { ModelConfigurationMutationOptions, ModelDirective } from "@nylorun/core/define";
|
|
3
|
+
import type { ToolDefinition } from "@nylorun/core/define";
|
|
3
4
|
/** Per-step, middleware-owned model configuration draft. It never persists past the call. */
|
|
4
5
|
export declare class ModelConfigurationDraft {
|
|
5
6
|
#private;
|
|
@@ -9,5 +10,5 @@ export declare class ModelConfigurationDraft {
|
|
|
9
10
|
select(middlewareId: string, middlewareOrder: number, directive: ModelDirective, options?: Omit<ModelConfigurationMutationOptions, "order">): void;
|
|
10
11
|
replace(middlewareId: string, middlewareOrder: number, directive: ModelDirective, options?: Omit<ModelConfigurationMutationOptions, "order">): void;
|
|
11
12
|
clear(options?: Omit<ModelConfigurationMutationOptions, "order">): void;
|
|
12
|
-
snapshot():
|
|
13
|
+
snapshot(): InternalModelConfigurationSnapshot;
|
|
13
14
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { bindTool } from "
|
|
2
|
-
import { HarnessError, isHarnessError } from "
|
|
1
|
+
import { bindTool } from "@nylorun/core/define";
|
|
2
|
+
import { HarnessError, isHarnessError } from "@nylorun/core/define";
|
|
3
3
|
import { normalizeDirective, sameDirective } from "../model/normalize.js";
|
|
4
|
-
import { copyJson } from "
|
|
4
|
+
import { copyJson } from "@nylorun/core/define";
|
|
5
5
|
import { checkedReason, slotOwner, SlotDraft } from "./slot-assembly.js";
|
|
6
6
|
/** Per-step, middleware-owned model configuration draft. It never persists past the call. */
|
|
7
7
|
export class ModelConfigurationDraft {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TurnOutputContract } from "@nylorun/core/define";
|
|
2
|
+
import type { InputEvent } from "@nylorun/core/define";
|
|
3
|
+
import type { ModelRequest } from "@nylorun/core/define";
|
|
4
|
+
import type { ToolResult } from "@nylorun/core/define";
|
|
5
|
+
import type { StepContext } from "./step-context.js";
|
|
6
|
+
export declare function resolveModelRequest(input: {
|
|
7
|
+
context: StepContext;
|
|
8
|
+
arrivals: readonly InputEvent[];
|
|
9
|
+
toolResults: readonly ToolResult[];
|
|
10
|
+
output?: TurnOutputContract;
|
|
11
|
+
}): ModelRequest;
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
+
import { describeConfiguration } from "./describe.js";
|
|
1
2
|
export function resolveModelRequest(input) {
|
|
2
3
|
const ctx = input.context;
|
|
4
|
+
const configuration = describeConfiguration(ctx.configurationSnapshot());
|
|
3
5
|
return Object.freeze({
|
|
4
|
-
|
|
6
|
+
executionId: ctx.input.executionId,
|
|
5
7
|
turnId: ctx.input.turnId,
|
|
6
8
|
stepId: ctx.input.stepId,
|
|
7
9
|
...(ctx.selectedDirective === undefined ? {} : { model: ctx.selectedDirective }),
|
|
8
|
-
configuration
|
|
10
|
+
configuration,
|
|
9
11
|
instructions: Object.freeze([...ctx.instructions]),
|
|
10
12
|
context: ctx.contextSnapshot(),
|
|
11
13
|
transcript: Object.freeze([...ctx.input.transcript]),
|
|
12
14
|
arrivals: Object.freeze([...input.arrivals]),
|
|
13
15
|
toolResults: Object.freeze([...input.toolResults]),
|
|
14
|
-
tools:
|
|
16
|
+
tools: configuration.tools,
|
|
15
17
|
...(input.output === undefined ? {} : { outputSchema: input.output.schema.jsonSchema }),
|
|
16
18
|
});
|
|
17
19
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ExecutionSnapshot, StepRuntime } from "./runtime.js";
|
|
2
|
+
import type { TurnOutputContract } from "@nylorun/core/define";
|
|
3
|
+
import type { ModelCandidate } from "@nylorun/core/define";
|
|
4
|
+
import type { InputEvent } from "@nylorun/core/define";
|
|
5
|
+
import type { ToolResult } from "@nylorun/core/define";
|
|
6
|
+
import type { ObserveEmit } from "../observe.js";
|
|
7
|
+
import { type SealedStepOutput } from "./seal.js";
|
|
8
|
+
export interface StepRunResult {
|
|
9
|
+
readonly stepId: string;
|
|
10
|
+
readonly candidate?: ModelCandidate;
|
|
11
|
+
readonly output: SealedStepOutput;
|
|
12
|
+
readonly requestedModelId?: string;
|
|
13
|
+
readonly modelInvocationId?: string;
|
|
14
|
+
readonly retry?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function runStep(input: {
|
|
17
|
+
agent: StepRuntime;
|
|
18
|
+
observe: ObserveEmit;
|
|
19
|
+
state: ExecutionSnapshot;
|
|
20
|
+
executionId: string;
|
|
21
|
+
turnId: string;
|
|
22
|
+
stepId: string;
|
|
23
|
+
turnNumber: number;
|
|
24
|
+
stepNumber: number;
|
|
25
|
+
arrivals: readonly InputEvent[];
|
|
26
|
+
toolResults: readonly ToolResult[];
|
|
27
|
+
signal: AbortSignal;
|
|
28
|
+
info?: unknown;
|
|
29
|
+
output?: TurnOutputContract;
|
|
30
|
+
}): Promise<StepRunResult>;
|
|
@@ -1,31 +1,34 @@
|
|
|
1
|
+
import { HostSuspension } from "../host-suspension.js";
|
|
1
2
|
import { normalizeCandidate } from "../model/normalize.js";
|
|
2
|
-
import { HarnessError, isHarnessError } from "
|
|
3
|
-
import { copyJson } from "
|
|
3
|
+
import { HarnessError, isHarnessError } from "@nylorun/core/define";
|
|
4
|
+
import { copyJson } from "@nylorun/core/define";
|
|
4
5
|
import { runMiddleware } from "./compose.js";
|
|
5
6
|
import { ContextDraft } from "./context-draft.js";
|
|
6
7
|
import { StepContext } from "./step-context.js";
|
|
7
|
-
import { projectModelCall } from "
|
|
8
|
+
import { projectModelCall } from "../model/project.js";
|
|
8
9
|
import { resolveModelRequest } from "./resolve.js";
|
|
9
10
|
import { sealStep } from "./seal.js";
|
|
10
11
|
import { ModelConfigurationDraft } from "./model-configuration.js";
|
|
11
|
-
import { createId } from "
|
|
12
|
+
import { createId } from "../../utils/ids.js";
|
|
13
|
+
import { applyAfterModelCall, applyBeforeModelCall, hasDynamics, } from "./dynamics.js";
|
|
12
14
|
export async function runStep(input) {
|
|
13
|
-
let state = input.state;
|
|
14
15
|
let requestedModelId;
|
|
16
|
+
let modelInvocationId;
|
|
17
|
+
let retryFeedback;
|
|
15
18
|
const stepInput = Object.freeze({
|
|
16
|
-
|
|
19
|
+
executionId: input.executionId,
|
|
17
20
|
turnId: input.turnId,
|
|
18
21
|
stepId: input.stepId,
|
|
19
22
|
turnNumber: input.turnNumber,
|
|
20
23
|
stepNumber: input.stepNumber,
|
|
21
|
-
|
|
24
|
+
info: input.info,
|
|
22
25
|
arrivals: Object.freeze([...input.arrivals]),
|
|
23
26
|
toolResults: Object.freeze([...input.toolResults]),
|
|
24
27
|
transcript: Object.freeze([...input.state.transcript]),
|
|
25
28
|
});
|
|
26
29
|
const configuration = new ModelConfigurationDraft();
|
|
27
|
-
const runtimeContext = new ContextDraft(
|
|
28
|
-
const context = new StepContext(stepInput, input.observe, configuration, runtimeContext
|
|
30
|
+
const runtimeContext = new ContextDraft();
|
|
31
|
+
const context = new StepContext(stepInput, input.observe, configuration, runtimeContext);
|
|
29
32
|
input.observe(() => ({
|
|
30
33
|
type: "step.started",
|
|
31
34
|
turnId: input.turnId,
|
|
@@ -35,13 +38,59 @@ export async function runStep(input) {
|
|
|
35
38
|
attributes: snapshotStepStart(stepInput),
|
|
36
39
|
}));
|
|
37
40
|
await runMiddleware(input.agent.middleware, context, async () => {
|
|
41
|
+
input.signal.throwIfAborted();
|
|
38
42
|
if (context.currentTripwire)
|
|
39
43
|
return context.tripwire(context.currentTripwire);
|
|
44
|
+
const definition = input.agent.definition;
|
|
45
|
+
const dynamicsCtx = definition && hasDynamics(definition)
|
|
46
|
+
? {
|
|
47
|
+
agent: definition,
|
|
48
|
+
info: input.info,
|
|
49
|
+
step: input.stepNumber - 1,
|
|
50
|
+
arrivals: input.arrivals,
|
|
51
|
+
messages: projectModelCall(resolveModelRequest({
|
|
52
|
+
context,
|
|
53
|
+
arrivals: input.arrivals,
|
|
54
|
+
toolResults: input.toolResults,
|
|
55
|
+
output: input.output,
|
|
56
|
+
})).prompt,
|
|
57
|
+
sessionState: input.agent.sessionState ?? {},
|
|
58
|
+
}
|
|
59
|
+
: undefined;
|
|
60
|
+
if (dynamicsCtx) {
|
|
61
|
+
try {
|
|
62
|
+
const before = await applyBeforeModelCall(dynamicsCtx, configuration);
|
|
63
|
+
if (input.agent.sessionState) {
|
|
64
|
+
Object.assign(input.agent.sessionState, dynamicsCtx.sessionState);
|
|
65
|
+
}
|
|
66
|
+
if (input.agent.recordAfterDynamics)
|
|
67
|
+
await input.agent.recordAfterDynamics();
|
|
68
|
+
if (before.blocked) {
|
|
69
|
+
return context.tripwire({
|
|
70
|
+
code: "dynamics.blocked",
|
|
71
|
+
message: before.blocked,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (error instanceof HostSuspension)
|
|
77
|
+
throw error;
|
|
78
|
+
return context.tripwire({
|
|
79
|
+
code: isHarnessError(error) ? error.code : "configuration.invalid",
|
|
80
|
+
message: error instanceof Error ? error.message : String(error),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
40
84
|
try {
|
|
41
|
-
|
|
85
|
+
const selected = configuration.snapshot();
|
|
86
|
+
for (const tool of selected.tools)
|
|
87
|
+
input.agent.registry.reference(tool);
|
|
88
|
+
context.sealConfiguration(selected);
|
|
42
89
|
context.sealContext(runtimeContext.snapshot());
|
|
43
90
|
}
|
|
44
91
|
catch (error) {
|
|
92
|
+
if (error instanceof HostSuspension)
|
|
93
|
+
throw error;
|
|
45
94
|
const message = error instanceof Error ? error.message : String(error);
|
|
46
95
|
return context.tripwire({
|
|
47
96
|
code: isHarnessError(error) ? error.code : "configuration.invalid",
|
|
@@ -57,16 +106,10 @@ export async function runStep(input) {
|
|
|
57
106
|
requestedModelId = request.model?.id;
|
|
58
107
|
const call = projectModelCall(request);
|
|
59
108
|
const invocationId = createId("invocation");
|
|
60
|
-
|
|
61
|
-
kind: "model",
|
|
62
|
-
turnId: input.turnId,
|
|
63
|
-
stepId: input.stepId,
|
|
64
|
-
invocationId,
|
|
65
|
-
call,
|
|
66
|
-
});
|
|
67
|
-
state = await input.recordModelRequested(state, active);
|
|
109
|
+
modelInvocationId = invocationId;
|
|
68
110
|
input.observe(() => ({
|
|
69
111
|
type: "model.requested",
|
|
112
|
+
invocationId,
|
|
70
113
|
turnId: input.turnId,
|
|
71
114
|
stepId: input.stepId,
|
|
72
115
|
...(request.model?.id === undefined ? {} : { requestedModelId: request.model.id }),
|
|
@@ -91,6 +134,7 @@ export async function runStep(input) {
|
|
|
91
134
|
const reported = Object.freeze({ adapter: value.adapter, call: copyJson(value.call) });
|
|
92
135
|
input.observe(() => ({
|
|
93
136
|
type: "model.prepared",
|
|
137
|
+
invocationId,
|
|
94
138
|
turnId: input.turnId,
|
|
95
139
|
stepId: input.stepId,
|
|
96
140
|
...(request.model?.id === undefined ? {} : { requestedModelId: request.model.id }),
|
|
@@ -100,18 +144,52 @@ export async function runStep(input) {
|
|
|
100
144
|
});
|
|
101
145
|
if (input.signal.aborted)
|
|
102
146
|
throw input.signal.reason;
|
|
103
|
-
if (isDeferred(outcome))
|
|
104
|
-
return context.deferModel(Object.freeze({
|
|
105
|
-
...active,
|
|
106
|
-
...(outcome.token === undefined ? {} : { token: copyJson(outcome.token) }),
|
|
107
|
-
}));
|
|
108
147
|
const minted = context.mintFromModel(normalizeCandidate(outcome));
|
|
109
148
|
const candidate = context.currentCandidate;
|
|
110
149
|
if (!candidate)
|
|
111
150
|
throw new HarnessError("model.candidate-missing", "Model candidate missing after mint");
|
|
151
|
+
if (dynamicsCtx) {
|
|
152
|
+
const text = candidate.output
|
|
153
|
+
?.filter((block) => block.type === "text")
|
|
154
|
+
.map((block) => block.text)
|
|
155
|
+
.join("");
|
|
156
|
+
const toolCalls = (candidate.output ?? [])
|
|
157
|
+
.filter((block) => block.type === "tool-call")
|
|
158
|
+
.map((block) => {
|
|
159
|
+
const call = block;
|
|
160
|
+
return { id: call.id, name: call.name, args: call.args };
|
|
161
|
+
});
|
|
162
|
+
const decision = await applyAfterModelCall(dynamicsCtx, {
|
|
163
|
+
...(text ? { text } : {}),
|
|
164
|
+
toolCalls,
|
|
165
|
+
});
|
|
166
|
+
if (input.agent.recordAfterDynamics)
|
|
167
|
+
await input.agent.recordAfterDynamics();
|
|
168
|
+
if (decision.block) {
|
|
169
|
+
return context.tripwire({ code: "dynamics.blocked", message: decision.block });
|
|
170
|
+
}
|
|
171
|
+
if (decision.retry) {
|
|
172
|
+
retryFeedback = decision.retry;
|
|
173
|
+
return context.tripwire({ code: "dynamics.retry", message: decision.retry });
|
|
174
|
+
}
|
|
175
|
+
if (decision.text !== undefined) {
|
|
176
|
+
minted.replace({
|
|
177
|
+
output: [{ type: "text", text: decision.text }],
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
for (const item of decision.deny ?? [])
|
|
181
|
+
minted.deny(item.id, item.reason);
|
|
182
|
+
for (const item of decision.approve ?? [])
|
|
183
|
+
minted.requireInteraction(item.id, {
|
|
184
|
+
kind: "approval",
|
|
185
|
+
prompt: item.prompt,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
112
188
|
return minted;
|
|
113
189
|
}
|
|
114
190
|
catch (error) {
|
|
191
|
+
if (error instanceof HostSuspension)
|
|
192
|
+
throw error;
|
|
115
193
|
if (input.signal.aborted)
|
|
116
194
|
throw input.signal.reason;
|
|
117
195
|
return context.tripwire({
|
|
@@ -124,47 +202,20 @@ export async function runStep(input) {
|
|
|
124
202
|
const candidate = output.kind === "tools" ? output.plan.candidate : context.currentCandidate;
|
|
125
203
|
return Object.freeze({
|
|
126
204
|
stepId: input.stepId,
|
|
127
|
-
state,
|
|
128
205
|
...(candidate ? { candidate } : {}),
|
|
129
206
|
...(requestedModelId === undefined ? {} : { requestedModelId }),
|
|
130
207
|
output,
|
|
208
|
+
...(modelInvocationId ? { modelInvocationId } : {}),
|
|
209
|
+
...(retryFeedback ? { retry: retryFeedback } : {}),
|
|
131
210
|
});
|
|
132
211
|
}
|
|
133
|
-
function isDeferred(value) {
|
|
134
|
-
return (typeof value === "object" && value !== null && "kind" in value && value.kind === "deferred");
|
|
135
|
-
}
|
|
136
212
|
function snapshotStepStart(input) {
|
|
137
|
-
const session = Object.freeze({
|
|
138
|
-
...(input.session.userId === undefined ? {} : { userId: input.session.userId }),
|
|
139
|
-
...(input.session.context === undefined ? {} : { context: copyJson(input.session.context) }),
|
|
140
|
-
});
|
|
141
213
|
return Object.freeze({
|
|
142
|
-
...(Object.keys(session).length === 0 ? {} : { session }),
|
|
143
214
|
arrivals: copyJson(input.arrivals),
|
|
144
215
|
toolResults: copyJson(input.toolResults),
|
|
145
216
|
transcript: input.transcript,
|
|
146
217
|
});
|
|
147
218
|
}
|
|
148
219
|
function snapshotConfiguration(configuration) {
|
|
149
|
-
return
|
|
150
|
-
version: configuration.version,
|
|
151
|
-
...(configuration.model === undefined ? {} : { model: copyJson(configuration.model) }),
|
|
152
|
-
instructions: copyJson(configuration.instructions),
|
|
153
|
-
tools: snapshotTools(configuration.tools),
|
|
154
|
-
toolContracts: copyJson(configuration.toolContracts),
|
|
155
|
-
contributors: copyJson(configuration.contributors),
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
function snapshotTools(tools) {
|
|
159
|
-
return Object.freeze(tools.map((tool) => Object.freeze({
|
|
160
|
-
name: tool.name,
|
|
161
|
-
...(tool.description === undefined ? {} : { description: tool.description }),
|
|
162
|
-
owner: tool.owner,
|
|
163
|
-
inputSchema: Object.freeze({ jsonSchema: copyJson(tool.inputSchema.jsonSchema) }),
|
|
164
|
-
...(tool.outputSchema === undefined
|
|
165
|
-
? {}
|
|
166
|
-
: {
|
|
167
|
-
outputSchema: Object.freeze({ jsonSchema: copyJson(tool.outputSchema.jsonSchema) }),
|
|
168
|
-
}),
|
|
169
|
-
})));
|
|
220
|
+
return copyJson(configuration);
|
|
170
221
|
}
|