@agen-ai/agent-runtime 0.1.0
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/LICENSE +21 -0
- package/README.md +139 -0
- package/dist/adapterValidation.d.ts +4 -0
- package/dist/adapterValidation.js +242 -0
- package/dist/artifacts.d.ts +28 -0
- package/dist/artifacts.js +87 -0
- package/dist/configurationValidation.d.ts +3 -0
- package/dist/configurationValidation.js +35 -0
- package/dist/contractErrors.d.ts +17 -0
- package/dist/contractErrors.js +59 -0
- package/dist/evidence.d.ts +50 -0
- package/dist/evidence.js +368 -0
- package/dist/foundation.d.ts +8 -0
- package/dist/foundation.js +37 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +12 -0
- package/dist/internal/controlCharacters.d.ts +2 -0
- package/dist/internal/controlCharacters.js +7 -0
- package/dist/internal/serializedJsonBytes.d.ts +3 -0
- package/dist/internal/serializedJsonBytes.js +57 -0
- package/dist/outputValidation.d.ts +13 -0
- package/dist/outputValidation.js +174 -0
- package/dist/outputs.d.ts +81 -0
- package/dist/outputs.js +217 -0
- package/dist/providerCatalog.d.ts +13 -0
- package/dist/providerCatalog.js +33 -0
- package/dist/providerDriver.d.ts +41 -0
- package/dist/providerDriver.js +52 -0
- package/dist/providerInstanceRegistry.d.ts +40 -0
- package/dist/providerInstanceRegistry.js +322 -0
- package/dist/readiness.d.ts +22 -0
- package/dist/readiness.js +58 -0
- package/dist/sessionValidation.d.ts +19 -0
- package/dist/sessionValidation.js +767 -0
- package/dist/sessions.d.ts +133 -0
- package/dist/sessions.js +0 -0
- package/dist/steeringValidation.d.ts +4 -0
- package/dist/steeringValidation.js +36 -0
- package/dist/testing/conformance.d.ts +30 -0
- package/dist/testing/conformance.js +379 -0
- package/dist/testing/fakeProvider.d.ts +35 -0
- package/dist/testing/fakeProvider.js +367 -0
- package/dist/testing/index.d.ts +3 -0
- package/dist/testing/index.js +2 -0
- package/dist/text.d.ts +2 -0
- package/dist/text.js +16 -0
- package/package.json +62 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { AgentAuthenticationFlow, AgentError, AgentRequestResolution, AgentSessionBinding, AgentSessionBranchSource, AgentSessionConfiguration, AgentSessionId, AgentTurnId, AgentTurnInputContent, AgentTurnInteractionMode, AgentTurnInputPart, AgentTurnInterruptionReason } from "@agen-ai/agent-protocol";
|
|
2
|
+
import type { MaybePromise } from "./foundation.js";
|
|
3
|
+
import type { AgentProviderOutput } from "./outputs.js";
|
|
4
|
+
export interface AgentProviderSessionContext {
|
|
5
|
+
readonly sessionId: AgentSessionId;
|
|
6
|
+
readonly workingDirectory: string;
|
|
7
|
+
readonly configuration: AgentSessionConfiguration;
|
|
8
|
+
readonly signal?: AbortSignal;
|
|
9
|
+
}
|
|
10
|
+
export type AgentSessionBindingCreatedObserver = (binding: AgentSessionBinding) => void;
|
|
11
|
+
export interface AgentProviderCreateSessionInput extends AgentProviderSessionContext {
|
|
12
|
+
readonly onBindingCreated: AgentSessionBindingCreatedObserver;
|
|
13
|
+
}
|
|
14
|
+
export interface AgentProviderResumeSessionInput extends AgentProviderSessionContext {
|
|
15
|
+
readonly binding: AgentSessionBinding;
|
|
16
|
+
}
|
|
17
|
+
export interface AgentProviderBranchSessionInput extends AgentProviderSessionContext {
|
|
18
|
+
readonly source: AgentSessionBranchSource;
|
|
19
|
+
readonly onBindingCreated: AgentSessionBindingCreatedObserver;
|
|
20
|
+
}
|
|
21
|
+
export type AgentProviderExecutionStartedObserver = () => void;
|
|
22
|
+
export interface AgentProviderRunTurnInput {
|
|
23
|
+
readonly turnId: AgentTurnId;
|
|
24
|
+
readonly interactionMode: AgentTurnInteractionMode;
|
|
25
|
+
readonly parts: readonly AgentTurnInputPart[];
|
|
26
|
+
readonly summary?: string;
|
|
27
|
+
readonly deadlineAt?: string;
|
|
28
|
+
readonly signal?: AbortSignal;
|
|
29
|
+
readonly onProviderExecutionStarted?: AgentProviderExecutionStartedObserver;
|
|
30
|
+
}
|
|
31
|
+
export interface AgentProviderResolveRequestInput {
|
|
32
|
+
readonly resolution: AgentRequestResolution;
|
|
33
|
+
readonly signal?: AbortSignal;
|
|
34
|
+
}
|
|
35
|
+
export interface AgentProviderInterruptTurnInput {
|
|
36
|
+
readonly turnId: AgentTurnId;
|
|
37
|
+
readonly reason: AgentTurnInterruptionReason;
|
|
38
|
+
readonly requestedAt?: string;
|
|
39
|
+
readonly signal?: AbortSignal;
|
|
40
|
+
}
|
|
41
|
+
export interface AgentProviderApplyConfigurationInput {
|
|
42
|
+
readonly configuration: AgentSessionConfiguration;
|
|
43
|
+
readonly signal?: AbortSignal;
|
|
44
|
+
}
|
|
45
|
+
export interface AgentProviderSteerTurnInput extends AgentTurnInputContent {
|
|
46
|
+
readonly turnId: AgentTurnId;
|
|
47
|
+
readonly signal?: AbortSignal;
|
|
48
|
+
}
|
|
49
|
+
export type AgentProviderSessionCloseReason = "idle" | "shutdown" | "replaced" | "contract_rejected" | "error" | "other";
|
|
50
|
+
export interface AgentProviderCloseSessionInput {
|
|
51
|
+
readonly reason?: AgentProviderSessionCloseReason;
|
|
52
|
+
readonly signal?: AbortSignal;
|
|
53
|
+
}
|
|
54
|
+
export type AgentProviderOperationStatus = "accepted" | "completed" | "failed" | "canceled" | "waiting_for_request";
|
|
55
|
+
export interface AgentProviderOperationResult {
|
|
56
|
+
readonly status: AgentProviderOperationStatus;
|
|
57
|
+
readonly outputs?: readonly AgentProviderOutput[];
|
|
58
|
+
readonly error?: AgentError;
|
|
59
|
+
}
|
|
60
|
+
export type AgentTurnSteeringResult = Readonly<{
|
|
61
|
+
status: "delivered";
|
|
62
|
+
}> | Readonly<{
|
|
63
|
+
status: "rejected";
|
|
64
|
+
error: AgentError;
|
|
65
|
+
}> | Readonly<{
|
|
66
|
+
status: "delivery_uncertain";
|
|
67
|
+
error: AgentError;
|
|
68
|
+
}>;
|
|
69
|
+
export type AgentTurnInterruption = Readonly<{
|
|
70
|
+
kind: "unsupported";
|
|
71
|
+
}> | Readonly<{
|
|
72
|
+
kind: "supported";
|
|
73
|
+
interruptTurn: (input: AgentProviderInterruptTurnInput) => MaybePromise<AgentProviderOperationResult>;
|
|
74
|
+
}>;
|
|
75
|
+
export type AgentTurnSteering = Readonly<{
|
|
76
|
+
kind: "unsupported";
|
|
77
|
+
}> | Readonly<{
|
|
78
|
+
kind: "supported";
|
|
79
|
+
steerTurn: (input: AgentProviderSteerTurnInput) => MaybePromise<AgentTurnSteeringResult>;
|
|
80
|
+
}>;
|
|
81
|
+
export type AgentSessionConfigurationControl = Readonly<{
|
|
82
|
+
kind: "managed";
|
|
83
|
+
}> | Readonly<{
|
|
84
|
+
kind: "selectable";
|
|
85
|
+
applyConfiguration: (input: AgentProviderApplyConfigurationInput) => MaybePromise<AgentProviderOperationResult>;
|
|
86
|
+
}>;
|
|
87
|
+
export interface AgentProviderSession {
|
|
88
|
+
readonly binding: AgentSessionBinding;
|
|
89
|
+
readonly runTurn: (input: AgentProviderRunTurnInput) => AsyncIterable<AgentProviderOutput>;
|
|
90
|
+
readonly resolveRequest: (input: AgentProviderResolveRequestInput) => AsyncIterable<AgentProviderOutput>;
|
|
91
|
+
readonly interruption: AgentTurnInterruption;
|
|
92
|
+
readonly steering: AgentTurnSteering;
|
|
93
|
+
readonly configuration: AgentSessionConfigurationControl;
|
|
94
|
+
readonly close: (input: AgentProviderCloseSessionInput) => MaybePromise<void>;
|
|
95
|
+
}
|
|
96
|
+
export type AgentSessionResumption = Readonly<{
|
|
97
|
+
kind: "unsupported";
|
|
98
|
+
}> | Readonly<{
|
|
99
|
+
kind: "supported";
|
|
100
|
+
resumeSession: (input: AgentProviderResumeSessionInput) => MaybePromise<AgentProviderSession>;
|
|
101
|
+
}>;
|
|
102
|
+
export type AgentSessionBranching = Readonly<{
|
|
103
|
+
kind: "unsupported";
|
|
104
|
+
}> | Readonly<{
|
|
105
|
+
kind: "through_turn";
|
|
106
|
+
branchSession: (input: AgentProviderBranchSessionInput) => MaybePromise<AgentProviderSession>;
|
|
107
|
+
}>;
|
|
108
|
+
export interface AgentProviderAuthenticationStartInput {
|
|
109
|
+
readonly attemptId: string;
|
|
110
|
+
readonly flow: AgentAuthenticationFlow;
|
|
111
|
+
readonly deadlineAt?: string;
|
|
112
|
+
readonly signal?: AbortSignal;
|
|
113
|
+
}
|
|
114
|
+
export interface AgentProviderAuthenticationCancelInput {
|
|
115
|
+
readonly attemptId: string;
|
|
116
|
+
readonly providerLoginId?: string;
|
|
117
|
+
readonly reason?: "user_requested" | "timeout" | "shutdown" | "other";
|
|
118
|
+
readonly signal?: AbortSignal;
|
|
119
|
+
}
|
|
120
|
+
export type AgentProviderAuthentication = Readonly<{
|
|
121
|
+
kind: "unsupported";
|
|
122
|
+
}> | Readonly<{
|
|
123
|
+
kind: "supported";
|
|
124
|
+
start: (input: AgentProviderAuthenticationStartInput) => AsyncIterable<AgentProviderOutput>;
|
|
125
|
+
cancel: (input: AgentProviderAuthenticationCancelInput) => MaybePromise<AgentProviderOperationResult>;
|
|
126
|
+
}>;
|
|
127
|
+
export interface AgentProviderAdapter {
|
|
128
|
+
readonly createSession: (input: AgentProviderCreateSessionInput) => MaybePromise<AgentProviderSession>;
|
|
129
|
+
readonly resumption: AgentSessionResumption;
|
|
130
|
+
readonly branching: AgentSessionBranching;
|
|
131
|
+
readonly authentication: AgentProviderAuthentication;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=sessions.d.ts.map
|
package/dist/sessions.js
ADDED
|
File without changes
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type AgentProviderKey } from "@agen-ai/agent-protocol";
|
|
2
|
+
import type { AgentTurnSteeringResult } from "./sessions.js";
|
|
3
|
+
export declare function validateAgentTurnSteeringResult(candidate: AgentTurnSteeringResult, providerKey: AgentProviderKey): AgentTurnSteeringResult;
|
|
4
|
+
//# sourceMappingURL=steeringValidation.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseAgentError
|
|
3
|
+
} from "@agen-ai/agent-protocol";
|
|
4
|
+
import { throwAgentProviderContractError } from "./contractErrors.js";
|
|
5
|
+
function validateAgentTurnSteeringResult(candidate, providerKey) {
|
|
6
|
+
if (candidate === null || typeof candidate !== "object") {
|
|
7
|
+
return invalidSteeringResult(providerKey);
|
|
8
|
+
}
|
|
9
|
+
if (candidate.status === "delivered") {
|
|
10
|
+
if (Reflect.ownKeys(candidate).length !== 1) {
|
|
11
|
+
return invalidSteeringResult(providerKey);
|
|
12
|
+
}
|
|
13
|
+
return Object.freeze({ status: "delivered" });
|
|
14
|
+
}
|
|
15
|
+
if (!["rejected", "delivery_uncertain"].includes(candidate.status) || Reflect.ownKeys(candidate).length !== 2 || !("error" in candidate)) {
|
|
16
|
+
return invalidSteeringResult(providerKey);
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
return Object.freeze({
|
|
20
|
+
status: candidate.status,
|
|
21
|
+
error: parseAgentError(candidate.error)
|
|
22
|
+
});
|
|
23
|
+
} catch {
|
|
24
|
+
return invalidSteeringResult(providerKey);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function invalidSteeringResult(providerKey) {
|
|
28
|
+
return throwAgentProviderContractError(
|
|
29
|
+
providerKey,
|
|
30
|
+
"invalid_operation_result",
|
|
31
|
+
`Provider ${providerKey} returned an invalid steering result.`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
validateAgentTurnSteeringResult
|
|
36
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type AgentRequest, type AgentRequestResolution, type AgentSessionBinding, type AgentSessionBranchSource, type AgentSessionConfiguration, type AgentSessionId, type AgentTurnId, type AgentTurnInputContent } from "@agen-ai/agent-protocol";
|
|
2
|
+
import type { AgentProviderDriver, AgentProviderInstanceDefinition } from "../providerDriver.js";
|
|
3
|
+
import type { AgentProviderRunTurnInput } from "../sessions.js";
|
|
4
|
+
export interface AgentProviderConformanceScenario {
|
|
5
|
+
readonly driver: AgentProviderDriver;
|
|
6
|
+
readonly definition: AgentProviderInstanceDefinition;
|
|
7
|
+
readonly workingDirectory: string;
|
|
8
|
+
readonly configuration: AgentSessionConfiguration;
|
|
9
|
+
readonly updatedConfiguration?: AgentSessionConfiguration;
|
|
10
|
+
readonly createSessionId: AgentSessionId;
|
|
11
|
+
readonly resumeSessionId: AgentSessionId;
|
|
12
|
+
readonly branchSessionId?: AgentSessionId;
|
|
13
|
+
readonly abortedSessionId: AgentSessionId;
|
|
14
|
+
readonly interruptionSessionId: AgentSessionId;
|
|
15
|
+
readonly turn: AgentProviderRunTurnInput;
|
|
16
|
+
readonly interruptionTurn: AgentProviderRunTurnInput;
|
|
17
|
+
readonly resolutionFor: (request: AgentRequest) => AgentRequestResolution;
|
|
18
|
+
readonly branchSource?: (binding: AgentSessionBinding, turnId: AgentTurnId) => AgentSessionBranchSource;
|
|
19
|
+
readonly steeringInput?: AgentTurnInputContent;
|
|
20
|
+
}
|
|
21
|
+
export interface AgentProviderConformanceReport {
|
|
22
|
+
readonly providerKey: string;
|
|
23
|
+
readonly checks: readonly string[];
|
|
24
|
+
}
|
|
25
|
+
export declare class AgentProviderConformanceError extends Error {
|
|
26
|
+
readonly check: string;
|
|
27
|
+
constructor(check: string, message: string);
|
|
28
|
+
}
|
|
29
|
+
export declare function runAgentProviderConformance(scenario: AgentProviderConformanceScenario): Promise<AgentProviderConformanceReport>;
|
|
30
|
+
//# sourceMappingURL=conformance.d.ts.map
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import {
|
|
2
|
+
matchesAgentSessionBinding,
|
|
3
|
+
parseAgentRequestResolutionFor
|
|
4
|
+
} from "@agen-ai/agent-protocol";
|
|
5
|
+
import { isAgentOperationAbortError } from "../foundation.js";
|
|
6
|
+
import {
|
|
7
|
+
createAgentProviderRegistry,
|
|
8
|
+
AgentProviderRegistryError
|
|
9
|
+
} from "../providerInstanceRegistry.js";
|
|
10
|
+
class AgentProviderConformanceError extends Error {
|
|
11
|
+
constructor(check, message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.check = check;
|
|
14
|
+
this.name = "AgentProviderConformanceError";
|
|
15
|
+
}
|
|
16
|
+
check;
|
|
17
|
+
}
|
|
18
|
+
function requireCheck(condition, check, message) {
|
|
19
|
+
if (!condition) throw new AgentProviderConformanceError(check, message);
|
|
20
|
+
}
|
|
21
|
+
async function advanceToStartedTurn(session, input) {
|
|
22
|
+
const iterator = session.runTurn(input)[Symbol.asyncIterator]();
|
|
23
|
+
const outputs = [];
|
|
24
|
+
try {
|
|
25
|
+
for (; ; ) {
|
|
26
|
+
const next = await iterator.next();
|
|
27
|
+
requireCheck(
|
|
28
|
+
!next.done,
|
|
29
|
+
"turn_order",
|
|
30
|
+
"Turn ended before emitting turn.started."
|
|
31
|
+
);
|
|
32
|
+
outputs.push(next.value);
|
|
33
|
+
if (next.value.kind === "event" && next.value.event.type === "turn.started") {
|
|
34
|
+
return { iterator, outputs };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
await iterator.return?.();
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function collectRemainingOutputs(started) {
|
|
43
|
+
for (; ; ) {
|
|
44
|
+
const next = await started.iterator.next();
|
|
45
|
+
if (next.done) return started.outputs;
|
|
46
|
+
started.outputs.push(next.value);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function collectOutputs(outputs) {
|
|
50
|
+
const collected = [];
|
|
51
|
+
for await (const output of outputs) collected.push(output);
|
|
52
|
+
return collected;
|
|
53
|
+
}
|
|
54
|
+
function requireOperationStatus(result, statuses, check, message) {
|
|
55
|
+
requireCheck(statuses.includes(result.status), check, message);
|
|
56
|
+
}
|
|
57
|
+
function semanticEvents(outputs) {
|
|
58
|
+
return outputs.flatMap(
|
|
59
|
+
(output) => output.kind === "event" ? [output.event] : []
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
async function runAgentProviderConformance(scenario) {
|
|
63
|
+
const checks = [];
|
|
64
|
+
try {
|
|
65
|
+
await createAgentProviderRegistry({
|
|
66
|
+
drivers: [scenario.driver],
|
|
67
|
+
definitions: [scenario.definition, scenario.definition]
|
|
68
|
+
});
|
|
69
|
+
requireCheck(
|
|
70
|
+
false,
|
|
71
|
+
"duplicate_instance",
|
|
72
|
+
"Duplicate instance definitions were accepted."
|
|
73
|
+
);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
requireCheck(
|
|
76
|
+
error instanceof AgentProviderRegistryError && error.code === "duplicate_instance",
|
|
77
|
+
"duplicate_instance",
|
|
78
|
+
"Duplicate instance definitions did not fail with the registry contract error."
|
|
79
|
+
);
|
|
80
|
+
checks.push("duplicate_instance");
|
|
81
|
+
}
|
|
82
|
+
const registry = await createAgentProviderRegistry({
|
|
83
|
+
drivers: [scenario.driver],
|
|
84
|
+
definitions: [scenario.definition]
|
|
85
|
+
});
|
|
86
|
+
let disposed = false;
|
|
87
|
+
let createdSession = null;
|
|
88
|
+
try {
|
|
89
|
+
const instance = registry.requireInstance(scenario.definition.instanceId);
|
|
90
|
+
requireCheck(
|
|
91
|
+
instance.instanceId === scenario.definition.instanceId,
|
|
92
|
+
"instance_identity",
|
|
93
|
+
"Materialized instance identity changed."
|
|
94
|
+
);
|
|
95
|
+
requireCheck(
|
|
96
|
+
instance.capabilities.providerKey === scenario.definition.providerKey,
|
|
97
|
+
"capabilities",
|
|
98
|
+
"Capabilities identify another provider."
|
|
99
|
+
);
|
|
100
|
+
requireCheck(
|
|
101
|
+
instance.capabilities.authentication.kind === instance.adapter.authentication.kind,
|
|
102
|
+
"authentication_capability",
|
|
103
|
+
"Authentication capability does not match the adapter port."
|
|
104
|
+
);
|
|
105
|
+
checks.push(
|
|
106
|
+
"instance_identity",
|
|
107
|
+
"capabilities",
|
|
108
|
+
"authentication_capability"
|
|
109
|
+
);
|
|
110
|
+
const readiness = await registry.checkReadiness(
|
|
111
|
+
scenario.definition.instanceId
|
|
112
|
+
);
|
|
113
|
+
requireCheck(
|
|
114
|
+
readiness.status === "ready",
|
|
115
|
+
"readiness",
|
|
116
|
+
"Provider is not ready."
|
|
117
|
+
);
|
|
118
|
+
requireCheck(
|
|
119
|
+
!instance.capabilities.versionReporting || readiness.version !== void 0,
|
|
120
|
+
"version_reporting_capability",
|
|
121
|
+
"Version-reporting capability did not return a runtime version."
|
|
122
|
+
);
|
|
123
|
+
checks.push("readiness", "version_reporting_capability");
|
|
124
|
+
let createdBindingCount = 0;
|
|
125
|
+
const created = await instance.adapter.createSession({
|
|
126
|
+
sessionId: scenario.createSessionId,
|
|
127
|
+
workingDirectory: scenario.workingDirectory,
|
|
128
|
+
configuration: scenario.configuration,
|
|
129
|
+
onBindingCreated: () => {
|
|
130
|
+
createdBindingCount += 1;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
createdSession = created;
|
|
134
|
+
requireCheck(
|
|
135
|
+
createdBindingCount === 1,
|
|
136
|
+
"binding_callback",
|
|
137
|
+
"Create session did not report exactly one binding."
|
|
138
|
+
);
|
|
139
|
+
checks.push("create_session", "binding_callback");
|
|
140
|
+
const aborted = new AbortController();
|
|
141
|
+
aborted.abort(new DOMException("Conformance abort.", "AbortError"));
|
|
142
|
+
try {
|
|
143
|
+
await instance.adapter.createSession({
|
|
144
|
+
sessionId: scenario.abortedSessionId,
|
|
145
|
+
workingDirectory: scenario.workingDirectory,
|
|
146
|
+
configuration: scenario.configuration,
|
|
147
|
+
signal: aborted.signal,
|
|
148
|
+
onBindingCreated: () => void 0
|
|
149
|
+
});
|
|
150
|
+
requireCheck(false, "abort", "Pre-aborted createSession was accepted.");
|
|
151
|
+
} catch (error) {
|
|
152
|
+
requireCheck(
|
|
153
|
+
isAgentOperationAbortError(error),
|
|
154
|
+
"abort",
|
|
155
|
+
"Pre-aborted createSession did not fail with AbortError."
|
|
156
|
+
);
|
|
157
|
+
checks.push("abort");
|
|
158
|
+
}
|
|
159
|
+
const startedTurn = await advanceToStartedTurn(created, scenario.turn);
|
|
160
|
+
let turnOutputs;
|
|
161
|
+
try {
|
|
162
|
+
if (instance.capabilities.turns.steer.kind === "supported") {
|
|
163
|
+
requireCheck(
|
|
164
|
+
created.steering.kind === "supported",
|
|
165
|
+
"steering",
|
|
166
|
+
"Steering capability has no handler."
|
|
167
|
+
);
|
|
168
|
+
const result = await created.steering.steerTurn({
|
|
169
|
+
turnId: scenario.turn.turnId,
|
|
170
|
+
...scenario.steeringInput ?? {
|
|
171
|
+
parts: [
|
|
172
|
+
{ type: "text", text: "Additional conformance input." }
|
|
173
|
+
]
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
requireCheck(
|
|
177
|
+
result.status === "delivered",
|
|
178
|
+
"steering",
|
|
179
|
+
"Steering did not deliver to the live turn."
|
|
180
|
+
);
|
|
181
|
+
} else {
|
|
182
|
+
requireCheck(
|
|
183
|
+
created.steering.kind === "unsupported",
|
|
184
|
+
"steering",
|
|
185
|
+
"Unsupported steering exposed a handler."
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
turnOutputs = await collectRemainingOutputs(startedTurn);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
await startedTurn.iterator.return?.();
|
|
191
|
+
await created.close({ reason: "contract_rejected" });
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
const events = semanticEvents(turnOutputs);
|
|
195
|
+
requireCheck(
|
|
196
|
+
events[0]?.type === "turn.started",
|
|
197
|
+
"turn_order",
|
|
198
|
+
"Turn did not start first."
|
|
199
|
+
);
|
|
200
|
+
const lastEvent = events.at(-1);
|
|
201
|
+
requireCheck(
|
|
202
|
+
lastEvent?.type === "turn.completed" || lastEvent?.type === "turn.state_changed" && lastEvent.payload.state === "waiting_for_request",
|
|
203
|
+
"turn_order",
|
|
204
|
+
"Turn did not terminate or enter a waiting state."
|
|
205
|
+
);
|
|
206
|
+
checks.push("turn_order");
|
|
207
|
+
const openedRequest = events.find(
|
|
208
|
+
(event) => event.type === "request.opened"
|
|
209
|
+
);
|
|
210
|
+
if (openedRequest?.type === "request.opened") {
|
|
211
|
+
const resolution = parseAgentRequestResolutionFor(
|
|
212
|
+
openedRequest.payload.request,
|
|
213
|
+
scenario.resolutionFor(openedRequest.payload.request)
|
|
214
|
+
);
|
|
215
|
+
const resolutionOutputs = await collectOutputs(
|
|
216
|
+
created.resolveRequest({ resolution })
|
|
217
|
+
);
|
|
218
|
+
const resolutionBoundary = semanticEvents(resolutionOutputs).at(-1);
|
|
219
|
+
requireCheck(
|
|
220
|
+
resolutionBoundary?.type === "turn.completed" || resolutionBoundary?.type === "turn.state_changed" && resolutionBoundary.payload.state === "waiting_for_request",
|
|
221
|
+
"request_resolution",
|
|
222
|
+
"Matching request resolution did not reach a stable turn boundary."
|
|
223
|
+
);
|
|
224
|
+
checks.push("request_resolution");
|
|
225
|
+
}
|
|
226
|
+
checks.push("steering");
|
|
227
|
+
if (instance.capabilities.turns.interrupt) {
|
|
228
|
+
let interruptedBindingCount = 0;
|
|
229
|
+
const interrupted = await instance.adapter.createSession({
|
|
230
|
+
sessionId: scenario.interruptionSessionId,
|
|
231
|
+
workingDirectory: scenario.workingDirectory,
|
|
232
|
+
configuration: scenario.configuration,
|
|
233
|
+
onBindingCreated: () => {
|
|
234
|
+
interruptedBindingCount += 1;
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
requireCheck(
|
|
238
|
+
interruptedBindingCount === 1 && interrupted.interruption.kind === "supported",
|
|
239
|
+
"interruption",
|
|
240
|
+
"Interruption capability did not open a conforming session handler."
|
|
241
|
+
);
|
|
242
|
+
let liveTurn = null;
|
|
243
|
+
try {
|
|
244
|
+
liveTurn = await advanceToStartedTurn(
|
|
245
|
+
interrupted,
|
|
246
|
+
scenario.interruptionTurn
|
|
247
|
+
);
|
|
248
|
+
if (interrupted.interruption.kind !== "supported") {
|
|
249
|
+
throw new AgentProviderConformanceError(
|
|
250
|
+
"interruption",
|
|
251
|
+
"Interruption capability has no handler."
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
const result = await interrupted.interruption.interruptTurn({
|
|
255
|
+
turnId: scenario.interruptionTurn.turnId,
|
|
256
|
+
reason: "user_requested"
|
|
257
|
+
});
|
|
258
|
+
requireOperationStatus(
|
|
259
|
+
result,
|
|
260
|
+
["accepted", "completed", "canceled"],
|
|
261
|
+
"interruption",
|
|
262
|
+
"Live turn interruption did not succeed."
|
|
263
|
+
);
|
|
264
|
+
} finally {
|
|
265
|
+
await liveTurn?.iterator.return?.();
|
|
266
|
+
await interrupted.close({ reason: "other" });
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
requireCheck(
|
|
270
|
+
created.interruption.kind === "unsupported",
|
|
271
|
+
"interruption",
|
|
272
|
+
"Unsupported interruption exposed a handler."
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
checks.push("interruption");
|
|
276
|
+
if (instance.capabilities.configuration.kind === "selectable") {
|
|
277
|
+
requireCheck(
|
|
278
|
+
created.configuration.kind === "selectable" && scenario.updatedConfiguration !== void 0,
|
|
279
|
+
"configuration",
|
|
280
|
+
"Selectable configuration requires a handler and updated fixture."
|
|
281
|
+
);
|
|
282
|
+
const result = await created.configuration.applyConfiguration({
|
|
283
|
+
configuration: scenario.updatedConfiguration
|
|
284
|
+
});
|
|
285
|
+
requireOperationStatus(
|
|
286
|
+
result,
|
|
287
|
+
["completed"],
|
|
288
|
+
"configuration",
|
|
289
|
+
"Selectable configuration did not complete atomically."
|
|
290
|
+
);
|
|
291
|
+
} else {
|
|
292
|
+
requireCheck(
|
|
293
|
+
created.configuration.kind === "managed",
|
|
294
|
+
"configuration",
|
|
295
|
+
"Managed configuration exposed a selection handler."
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
checks.push("configuration");
|
|
299
|
+
await created.close({ reason: "idle" });
|
|
300
|
+
await created.close({ reason: "idle" });
|
|
301
|
+
checks.push("idempotent_session_close");
|
|
302
|
+
if (instance.capabilities.sessions.resume) {
|
|
303
|
+
requireCheck(
|
|
304
|
+
instance.adapter.resumption.kind === "supported",
|
|
305
|
+
"resume_session",
|
|
306
|
+
"Resume capability has no adapter port."
|
|
307
|
+
);
|
|
308
|
+
const resumed = await instance.adapter.resumption.resumeSession({
|
|
309
|
+
sessionId: scenario.resumeSessionId,
|
|
310
|
+
workingDirectory: scenario.workingDirectory,
|
|
311
|
+
configuration: scenario.configuration,
|
|
312
|
+
binding: created.binding
|
|
313
|
+
});
|
|
314
|
+
requireCheck(
|
|
315
|
+
matchesAgentSessionBinding(resumed.binding, created.binding),
|
|
316
|
+
"resume_session",
|
|
317
|
+
"Resume changed the provider binding."
|
|
318
|
+
);
|
|
319
|
+
await resumed.close({ reason: "idle" });
|
|
320
|
+
} else {
|
|
321
|
+
requireCheck(
|
|
322
|
+
instance.adapter.resumption.kind === "unsupported",
|
|
323
|
+
"resume_session",
|
|
324
|
+
"Unsupported resume exposed an adapter port."
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
checks.push("resume_session");
|
|
328
|
+
if (instance.capabilities.sessions.branch.kind === "through_turn") {
|
|
329
|
+
requireCheck(
|
|
330
|
+
instance.adapter.branching.kind === "through_turn" && scenario.branchSessionId !== void 0 && scenario.branchSource !== void 0,
|
|
331
|
+
"branch_session",
|
|
332
|
+
"Branch capability requires a handler and branch fixtures."
|
|
333
|
+
);
|
|
334
|
+
let branchBindingCount = 0;
|
|
335
|
+
const branched = await instance.adapter.branching.branchSession({
|
|
336
|
+
sessionId: scenario.branchSessionId,
|
|
337
|
+
workingDirectory: scenario.workingDirectory,
|
|
338
|
+
configuration: scenario.configuration,
|
|
339
|
+
source: scenario.branchSource(created.binding, scenario.turn.turnId),
|
|
340
|
+
onBindingCreated: () => {
|
|
341
|
+
branchBindingCount += 1;
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
requireCheck(
|
|
345
|
+
branchBindingCount === 1 && !matchesAgentSessionBinding(branched.binding, created.binding),
|
|
346
|
+
"branch_session",
|
|
347
|
+
"Branch did not report exactly one new provider binding."
|
|
348
|
+
);
|
|
349
|
+
await branched.close({ reason: "idle" });
|
|
350
|
+
} else {
|
|
351
|
+
requireCheck(
|
|
352
|
+
instance.adapter.branching.kind === "unsupported",
|
|
353
|
+
"branch_session",
|
|
354
|
+
"Unsupported branch exposed an adapter port."
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
checks.push("branch_session");
|
|
358
|
+
await registry.dispose();
|
|
359
|
+
await registry.dispose();
|
|
360
|
+
disposed = true;
|
|
361
|
+
requireCheck(
|
|
362
|
+
registry.listInstances().length === 0,
|
|
363
|
+
"disposal",
|
|
364
|
+
"Disposed registry retained instances."
|
|
365
|
+
);
|
|
366
|
+
checks.push("idempotent_disposal");
|
|
367
|
+
return Object.freeze({
|
|
368
|
+
providerKey: scenario.definition.providerKey,
|
|
369
|
+
checks: Object.freeze(checks)
|
|
370
|
+
});
|
|
371
|
+
} finally {
|
|
372
|
+
await createdSession?.close({ reason: "contract_rejected" });
|
|
373
|
+
if (!disposed) await registry.dispose();
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
export {
|
|
377
|
+
AgentProviderConformanceError,
|
|
378
|
+
runAgentProviderConformance
|
|
379
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type AgentCapabilities, type AgentInstanceId, type AgentRequestResolution, type AgentSessionId, type AgentTurnId } from "@agen-ai/agent-protocol";
|
|
2
|
+
import { type AgentProviderDriver, type AgentProviderInstanceDefinition } from "../providerDriver.js";
|
|
3
|
+
import type { AgentProviderSteerTurnInput, AgentTurnSteeringResult } from "../sessions.js";
|
|
4
|
+
export interface FakeAgentProviderOptions {
|
|
5
|
+
readonly providerKey?: string;
|
|
6
|
+
readonly instanceId?: string;
|
|
7
|
+
readonly capabilities?: AgentCapabilities;
|
|
8
|
+
readonly now?: () => string;
|
|
9
|
+
readonly steeringResult?: AgentTurnSteeringResult;
|
|
10
|
+
}
|
|
11
|
+
export type FakeAgentSteeringInput = Readonly<Omit<AgentProviderSteerTurnInput, "signal">>;
|
|
12
|
+
export interface FakeAgentProviderSnapshot {
|
|
13
|
+
readonly materializationCount: number;
|
|
14
|
+
readonly instanceDisposeCount: number;
|
|
15
|
+
readonly createdSessionIds: readonly AgentSessionId[];
|
|
16
|
+
readonly resumedSessionIds: readonly AgentSessionId[];
|
|
17
|
+
readonly branchedSessionIds: readonly AgentSessionId[];
|
|
18
|
+
readonly turnIds: readonly AgentTurnId[];
|
|
19
|
+
readonly resolutions: readonly AgentRequestResolution[];
|
|
20
|
+
readonly interruptedTurnIds: readonly AgentTurnId[];
|
|
21
|
+
readonly steeringInputs: readonly FakeAgentSteeringInput[];
|
|
22
|
+
readonly configurationRevisions: readonly string[];
|
|
23
|
+
readonly closeCounts: Readonly<Record<string, number>>;
|
|
24
|
+
}
|
|
25
|
+
export interface FakeAgentProvider {
|
|
26
|
+
readonly driver: AgentProviderDriver;
|
|
27
|
+
readonly definition: AgentProviderInstanceDefinition;
|
|
28
|
+
readonly capabilities: AgentCapabilities;
|
|
29
|
+
readonly snapshot: () => FakeAgentProviderSnapshot;
|
|
30
|
+
}
|
|
31
|
+
export declare function createFakeAgentProvider(options?: FakeAgentProviderOptions): FakeAgentProvider;
|
|
32
|
+
export declare function createFakeAgentSessionId(value: string): AgentSessionId;
|
|
33
|
+
export declare function createFakeAgentTurnId(value: string): AgentTurnId;
|
|
34
|
+
export declare function createFakeAgentInstanceId(value: string): AgentInstanceId;
|
|
35
|
+
//# sourceMappingURL=fakeProvider.d.ts.map
|