@anureo/conformance 0.0.0-stage → 0.3.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 +201 -0
- package/README.md +69 -2
- package/examples/standalone.d.ts +1 -0
- package/examples/standalone.js +16 -0
- package/package.json +56 -4
- package/src/fake-claude.d.ts +78 -0
- package/src/fake-claude.js +68 -0
- package/src/fake.d.ts +122 -0
- package/src/fake.js +284 -0
- package/src/fixtures.d.ts +38 -0
- package/src/fixtures.js +0 -0
- package/src/index.d.ts +6 -0
- package/src/index.js +6 -0
- package/src/native.d.ts +44 -0
- package/src/native.js +96 -0
- package/src/suite.d.ts +60 -0
- package/src/suite.js +289 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2026 Yoichi
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,3 +1,70 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @anureo/conformance
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The Anureo SDK conformance suites drive the revised AHP v1 client through the public runtime API.
|
|
4
|
+
The shared suite, `runConformance`, drives any `HarnessAdapter` to verify the shared behavioral contract that AHP implies.
|
|
5
|
+
Passing the shared suite establishes conformance only within that shared acceptance scope.
|
|
6
|
+
Full adapter conformance also requires the native suite, `runNativeConformance`, which covers overlap,
|
|
7
|
+
active close and independent observations, with native evidence supplied by adapter fixtures.
|
|
8
|
+
Passing a subset does not establish full adapter conformance. See the [acceptance plan](../../docs/native-harness-lifecycle.md#9-实施顺序与验收).
|
|
9
|
+
|
|
10
|
+
> Part of the [Anureo SDK workspace](../../README.md). Install with `npm install @anureo/conformance`.
|
|
11
|
+
|
|
12
|
+
## Running a backend through the suite
|
|
13
|
+
|
|
14
|
+
Implement a `ConformanceFactory` — one driver per scenario — and hand it to `runConformance`:
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { runConformance, createScriptedAdapter, type ConformanceFactory } from '@anureo/conformance'
|
|
18
|
+
|
|
19
|
+
const factory: ConformanceFactory = (scenario) => ({
|
|
20
|
+
adapter: createScriptedAdapter(), // your adapter here
|
|
21
|
+
openParams: {
|
|
22
|
+
requestId: 'open', mode: 'new', cwd: process.cwd(),
|
|
23
|
+
binding: { sessionId: 'conformance', adapterId: 'scripted', runtimeConfigId: 'test' },
|
|
24
|
+
},
|
|
25
|
+
expectedText: 'conformance',
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const report = await runConformance(factory, { timeoutMs: 3000 })
|
|
29
|
+
if (!report.passed) {
|
|
30
|
+
for (const entry of report.cases) console.log(entry.scenario, entry.status, entry.message)
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Each case returns `passed`, `failed`, or `not-applicable`:
|
|
35
|
+
|
|
36
|
+
- A scenario is **not-applicable** when its capability was not declared at initialization (e.g. `steer` for a backend without `input.steer.atomicMatch`).
|
|
37
|
+
- A **declared** capability must actually pass its scenario — a missing fixture is a failure, not a skip. Declaring fork without a working fork is exactly the bug this suite exists to catch.
|
|
38
|
+
|
|
39
|
+
## Scenarios
|
|
40
|
+
|
|
41
|
+
`lifecycle`, `immediate`, `identity`, `approval`, `userInput`, `elicitation`, `dynamicTool`, `interrupt`, `steer`, `fork`, `revert`, `history`, `unknown-item`, `disconnect`, `backpressure`.
|
|
42
|
+
|
|
43
|
+
The scenario list is the shared acceptance scope, not a claim that every protocol capability has exhaustive coverage. All four adapters in this workspace (Codex, Claude, ACP, AHP) are validated against the same suite; several adapters also use it as their unit-test backbone.
|
|
44
|
+
|
|
45
|
+
## Exports
|
|
46
|
+
|
|
47
|
+
| Entry | Contents |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `@anureo/conformance` | `runConformance`, `ConformanceFactory`, `ConformanceDriver`, `ConformanceReport`, `CONFORMANCE_SCENARIOS`, plus everything from `/fake`, `/fixtures`, and the Claude fake below |
|
|
50
|
+
| `@anureo/conformance/fake` | `createScriptedAdapter` (`ScriptedHarnessAdapter`), `FAKE_CAPABILITIES`, `FakeAdapterOptions` — an in-memory, official-API-shaped adapter that records calls and emits scripted results |
|
|
51
|
+
| `@anureo/conformance/fixtures` | Native-wire fixture types: `ConformanceFixture`, `NativeFixtureFrame`, `WireFlavor` (`codex` / `claude` / `acp` / `ahp`), `MethodFixture` |
|
|
52
|
+
|
|
53
|
+
Also included: `ScriptedClaudeQuery` / `createFakeClaudeQuery` — a minimal injectable query facade implementing the Claude SDK's structural surface (messages, interrupt, setModel, rewindFiles, supportedModels…), used to drive `@anureo/adapter-claude` without credentials.
|
|
54
|
+
|
|
55
|
+
## Scripted backends under scripts/
|
|
56
|
+
|
|
57
|
+
`scripts/` contains standalone fake backend processes (`fake-acp.ts`, `fake-ahp.ts`, `fake-codex.ts`, `fake-wire.ts`) that speak their real native wire protocols over stdio. Conformance tests use these processes to exercise real wire conversations over stdio.
|
|
58
|
+
|
|
59
|
+
## Development
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
cd packages/conformance
|
|
63
|
+
bun run typecheck
|
|
64
|
+
bun test src
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Documentation
|
|
68
|
+
|
|
69
|
+
- [Architecture](../../docs/architecture.md) — the conformance layer and validation scope (currently in Chinese)
|
|
70
|
+
- [AHP v1 protocol](../../spec/ahp-v1.md)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createClient } from '@anureo/sdk';
|
|
2
|
+
import { createScriptedAdapter } from '@anureo/conformance/fake';
|
|
3
|
+
// Runs a scripted backend without an account or model service.
|
|
4
|
+
const client = await createClient({
|
|
5
|
+
adapter: createScriptedAdapter(),
|
|
6
|
+
identity: { adapterId: 'scripted', runtimeConfigId: 'local' },
|
|
7
|
+
});
|
|
8
|
+
try {
|
|
9
|
+
const session = await client.sessions.create({ cwd: process.cwd() });
|
|
10
|
+
const turn = await session.submit({ input: 'Hello from Anureo SDK' });
|
|
11
|
+
console.log((await turn.result()).text);
|
|
12
|
+
await session.close();
|
|
13
|
+
}
|
|
14
|
+
finally {
|
|
15
|
+
await client.dispose();
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anureo/conformance",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"description": "Shared conformance suite and scripted fixtures for Anureo SDK adapters and AHP implementations",
|
|
4
|
+
"version": "0.3.0",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"anureo",
|
|
8
|
+
"ahp",
|
|
9
|
+
"harness",
|
|
10
|
+
"agent",
|
|
11
|
+
"conformance",
|
|
12
|
+
"testing"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/hi-youichi/anureo-sdk#readme",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/hi-youichi/anureo-sdk.git",
|
|
18
|
+
"directory": "packages/conformance"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/hi-youichi/anureo-sdk/issues"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./src/index.d.ts",
|
|
30
|
+
"import": "./src/index.js",
|
|
31
|
+
"default": "./src/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./fake": {
|
|
34
|
+
"types": "./src/fake.d.ts",
|
|
35
|
+
"import": "./src/fake.js",
|
|
36
|
+
"default": "./src/fake.js"
|
|
37
|
+
},
|
|
38
|
+
"./fixtures": {
|
|
39
|
+
"types": "./src/fixtures.d.ts",
|
|
40
|
+
"import": "./src/fixtures.js",
|
|
41
|
+
"default": "./src/fixtures.js"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@anureo/protocol": "0.3.0",
|
|
46
|
+
"@anureo/sdk": "0.3.0"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=22"
|
|
50
|
+
},
|
|
51
|
+
"files": [
|
|
52
|
+
"src",
|
|
53
|
+
"bin",
|
|
54
|
+
"schema",
|
|
55
|
+
"protocol",
|
|
56
|
+
"examples"
|
|
57
|
+
]
|
|
58
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { JsonObject, JsonValue } from "@anureo/protocol";
|
|
2
|
+
/** Configure the initial output, input behavior and simulated capabilities of a Claude query fixture. */
|
|
3
|
+
export interface ClaudeQueryScript {
|
|
4
|
+
/** Initial messages cloned and buffered in order within the default BoundedEventQueue limits. */
|
|
5
|
+
messages: JsonObject[];
|
|
6
|
+
/**
|
|
7
|
+
* Supply a response script that streamInput awaits after recording each input.
|
|
8
|
+
* Await emit to buffer a message or deliver it to a waiting reader. It can wait for
|
|
9
|
+
* queue capacity and reject on closure or admission failure. Keep emitted objects stable
|
|
10
|
+
* while buffered. Callback failures reject streamInput and leave admitted output available.
|
|
11
|
+
*/
|
|
12
|
+
onInput?: (input: JsonValue, emit: (message: JsonObject) => Promise<void>) => Promise<void>;
|
|
13
|
+
/** Returned by reference when supplied, including an empty list; omission selects the fake model. */
|
|
14
|
+
models?: Array<{
|
|
15
|
+
value: string;
|
|
16
|
+
displayName: string;
|
|
17
|
+
description: string;
|
|
18
|
+
}>;
|
|
19
|
+
/** Set true to allow simulated rewind. Omission or false returns a "No checkpoint" denial. */
|
|
20
|
+
canRewind?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Script a Claude query for a test without launching a peer. Controls record observations
|
|
24
|
+
* for assertions. To use one with a Claude adapter's queryFactory, bridge the factory's
|
|
25
|
+
* prompt into streamInput, project the output as native SDK messages and supply additional
|
|
26
|
+
* Query methods the adapter requires, such as applyFlagSettings.
|
|
27
|
+
* Direct callers end output with finish or close and drain or return the output iterator.
|
|
28
|
+
*/
|
|
29
|
+
export declare class ScriptedClaudeQuery implements AsyncIterable<JsonObject> {
|
|
30
|
+
private readonly script;
|
|
31
|
+
/** Ordered input/control observations; input values are retained by reference. */
|
|
32
|
+
readonly calls: Array<{
|
|
33
|
+
method: string;
|
|
34
|
+
value?: JsonValue;
|
|
35
|
+
}>;
|
|
36
|
+
/** Clone and buffer initial messages; cloning or queue admission failures throw synchronously. */
|
|
37
|
+
constructor(script: ClaudeQueryScript);
|
|
38
|
+
/**
|
|
39
|
+
* Claim exclusive output reads. A second acquisition throws EVENT_STREAM_ALREADY_OWNED
|
|
40
|
+
* until the first iterator releases ownership. Overlapping pending reads reject with
|
|
41
|
+
* CONCURRENT_EVENT_READ. Call return when stopping early. If finish or close settled
|
|
42
|
+
* an already pending read, call return or next again to release ownership.
|
|
43
|
+
*/
|
|
44
|
+
[Symbol.asyncIterator](): AsyncIterator<JsonObject>;
|
|
45
|
+
/**
|
|
46
|
+
* Consume one input stream in order, resolving at EOF after its onInput calls complete.
|
|
47
|
+
* Input, callback and awaited emit failures reject without closing output. The caller
|
|
48
|
+
* owns input cancellation and output closure; consume output concurrently if emits fill the queue.
|
|
49
|
+
*/
|
|
50
|
+
streamInput(stream: AsyncIterable<JsonValue>): Promise<void>;
|
|
51
|
+
/** Record an interrupt without cancelling input or ending output. */
|
|
52
|
+
interrupt(): Promise<void>;
|
|
53
|
+
/** Record a model selection; omitted or empty model strings produce a call without a value. */
|
|
54
|
+
setModel(model?: string): Promise<void>;
|
|
55
|
+
/** Record a permission mode without validating or applying it. */
|
|
56
|
+
setPermissionMode(mode: string): Promise<void>;
|
|
57
|
+
/** Record a thinking-token budget, including null, without validating or applying it. */
|
|
58
|
+
setMaxThinkingTokens(tokens: number | null): Promise<void>;
|
|
59
|
+
/** Record the checkpoint ID and return configured availability; denial is a result, and no files change. */
|
|
60
|
+
rewindFiles(id: string): Promise<{
|
|
61
|
+
canRewind: boolean;
|
|
62
|
+
error?: string;
|
|
63
|
+
}>;
|
|
64
|
+
/** Resolve with the supplied list by reference or a fresh single-model list. This adds no call record. */
|
|
65
|
+
supportedModels(): Promise<{
|
|
66
|
+
value: string;
|
|
67
|
+
displayName: string;
|
|
68
|
+
description: string;
|
|
69
|
+
}[]>;
|
|
70
|
+
/** End output admission once and wake blocked emits to reject. Buffered messages still drain and input stays open. */
|
|
71
|
+
finish(): void;
|
|
72
|
+
/** Record close once and end output admission; buffered messages still drain and input is not cancelled. */
|
|
73
|
+
close(): void;
|
|
74
|
+
private readonly queue;
|
|
75
|
+
private closeCalled;
|
|
76
|
+
}
|
|
77
|
+
/** Create a query with its own call log and output queue. Initial cloning or admission failures throw synchronously. */
|
|
78
|
+
export declare function createFakeClaudeQuery(script: ClaudeQueryScript): ScriptedClaudeQuery;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { BoundedEventQueue } from "@anureo/sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Script a Claude query for a test without launching a peer. Controls record observations
|
|
4
|
+
* for assertions. To use one with a Claude adapter's queryFactory, bridge the factory's
|
|
5
|
+
* prompt into streamInput, project the output as native SDK messages and supply additional
|
|
6
|
+
* Query methods the adapter requires, such as applyFlagSettings.
|
|
7
|
+
* Direct callers end output with finish or close and drain or return the output iterator.
|
|
8
|
+
*/
|
|
9
|
+
export class ScriptedClaudeQuery {
|
|
10
|
+
script;
|
|
11
|
+
/** Ordered input/control observations; input values are retained by reference. */
|
|
12
|
+
calls = [];
|
|
13
|
+
/** Clone and buffer initial messages; cloning or queue admission failures throw synchronously. */
|
|
14
|
+
constructor(script) {
|
|
15
|
+
this.script = script;
|
|
16
|
+
for (const message of script.messages)
|
|
17
|
+
this.queue.push(structuredClone(message));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Claim exclusive output reads. A second acquisition throws EVENT_STREAM_ALREADY_OWNED
|
|
21
|
+
* until the first iterator releases ownership. Overlapping pending reads reject with
|
|
22
|
+
* CONCURRENT_EVENT_READ. Call return when stopping early. If finish or close settled
|
|
23
|
+
* an already pending read, call return or next again to release ownership.
|
|
24
|
+
*/
|
|
25
|
+
[Symbol.asyncIterator]() { return this.queue[Symbol.asyncIterator](); }
|
|
26
|
+
/**
|
|
27
|
+
* Consume one input stream in order, resolving at EOF after its onInput calls complete.
|
|
28
|
+
* Input, callback and awaited emit failures reject without closing output. The caller
|
|
29
|
+
* owns input cancellation and output closure; consume output concurrently if emits fill the queue.
|
|
30
|
+
*/
|
|
31
|
+
async streamInput(stream) {
|
|
32
|
+
for await (const input of stream) {
|
|
33
|
+
this.calls.push({ method: "input", value: input });
|
|
34
|
+
await this.script.onInput?.(input, (message) => this.queue.write(message));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Record an interrupt without cancelling input or ending output. */
|
|
38
|
+
async interrupt() { this.calls.push({ method: "interrupt" }); }
|
|
39
|
+
/** Record a model selection; omitted or empty model strings produce a call without a value. */
|
|
40
|
+
async setModel(model) { this.calls.push({ method: "setModel", ...(model ? { value: model } : {}) }); }
|
|
41
|
+
/** Record a permission mode without validating or applying it. */
|
|
42
|
+
async setPermissionMode(mode) { this.calls.push({ method: "setPermissionMode", value: mode }); }
|
|
43
|
+
/** Record a thinking-token budget, including null, without validating or applying it. */
|
|
44
|
+
async setMaxThinkingTokens(tokens) { this.calls.push({ method: "setMaxThinkingTokens", value: tokens }); }
|
|
45
|
+
/** Record the checkpoint ID and return configured availability; denial is a result, and no files change. */
|
|
46
|
+
async rewindFiles(id) {
|
|
47
|
+
this.calls.push({ method: "rewindFiles", value: id });
|
|
48
|
+
return this.script.canRewind ? { canRewind: true } : { canRewind: false, error: "No checkpoint" };
|
|
49
|
+
}
|
|
50
|
+
/** Resolve with the supplied list by reference or a fresh single-model list. This adds no call record. */
|
|
51
|
+
async supportedModels() { return this.script.models ?? [{ value: "fake-model", displayName: "Fake", description: "Fixture" }]; }
|
|
52
|
+
/** End output admission once and wake blocked emits to reject. Buffered messages still drain and input stays open. */
|
|
53
|
+
finish() { this.queue.end(); }
|
|
54
|
+
/** Record close once and end output admission; buffered messages still drain and input is not cancelled. */
|
|
55
|
+
close() {
|
|
56
|
+
if (this.closeCalled)
|
|
57
|
+
return;
|
|
58
|
+
this.closeCalled = true;
|
|
59
|
+
this.calls.push({ method: "close" });
|
|
60
|
+
this.queue.end();
|
|
61
|
+
}
|
|
62
|
+
queue = new BoundedEventQueue();
|
|
63
|
+
closeCalled = false;
|
|
64
|
+
}
|
|
65
|
+
/** Create a query with its own call log and output queue. Initial cloning or admission failures throw synchronously. */
|
|
66
|
+
export function createFakeClaudeQuery(script) {
|
|
67
|
+
return new ScriptedClaudeQuery(script);
|
|
68
|
+
}
|
package/src/fake.d.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { type BackendSession, type HarnessAdapter, type SessionEventSink, type SessionMethod } from "@anureo/sdk";
|
|
2
|
+
import { type Capabilities, type AhpRequestMap, type AhpResultMap, type HistoryItem, type InitializeParams, type InitializeResult, type SessionBinding, type SessionOpenParams, type SessionState, type AhpEventPayload } from "@anureo/protocol";
|
|
3
|
+
import type { ConformanceFactory } from "./suite.js";
|
|
4
|
+
/** One backend dispatch, recorded before any custom hook runs. */
|
|
5
|
+
export interface FakeCall {
|
|
6
|
+
method: SessionMethod;
|
|
7
|
+
/** The dispatched object also used by the default handler if the hook returns undefined. */
|
|
8
|
+
params: AhpRequestMap[SessionMethod];
|
|
9
|
+
}
|
|
10
|
+
/** Configure an injectable in-memory backend for SDK, workflow and application fixtures. */
|
|
11
|
+
export interface FakeAdapterOptions {
|
|
12
|
+
/** Replaces the complete default capability set. Adapter capability reads clone it. */
|
|
13
|
+
capabilities?: Capabilities;
|
|
14
|
+
/**
|
|
15
|
+
* ScriptedBackendSession.execute awaits this hook after recording a call. Return undefined to
|
|
16
|
+
* continue with default dispatch, or return a method-matching result to supply the entire
|
|
17
|
+
* response. In the latter case the hook owns the call's events and state changes. A throw or
|
|
18
|
+
* rejection rejects backend dispatch.
|
|
19
|
+
*/
|
|
20
|
+
execute?: (call: FakeCall, sink: SessionEventSink) => Promise<AhpResultMap[SessionMethod] | undefined>;
|
|
21
|
+
/**
|
|
22
|
+
* Publish setup events or hold session acquisition. ScriptedHarnessAdapter.openSession awaits
|
|
23
|
+
* this hook after recording the backend and copying any fork history. A throw or rejection
|
|
24
|
+
* rejects acquisition and leaves the recorded fixture state available.
|
|
25
|
+
*/
|
|
26
|
+
onOpen?: (params: SessionOpenParams, sink: SessionEventSink) => Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
/** Default advertised capabilities. Adapter reads clone the current contents of this mutable template. */
|
|
29
|
+
export declare const FAKE_CAPABILITIES: Capabilities;
|
|
30
|
+
/** Create a fresh adapter to pass to createClient. Await disposal of the client when the fixture is done. */
|
|
31
|
+
export declare function createScriptedAdapter(options?: FakeAdapterOptions): ScriptedHarnessAdapter;
|
|
32
|
+
/**
|
|
33
|
+
* Supply runConformance with a fresh in-memory adapter per case, including held turns and
|
|
34
|
+
* observation faults. A supplied capability set replaces the defaults for applicability checks.
|
|
35
|
+
* The runner owns each client and its adapter.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createScriptedConformanceFactory(capabilities?: Capabilities): ConformanceFactory;
|
|
38
|
+
/**
|
|
39
|
+
* Exercise the SDK with an in-memory adapter that records opens and backend dispatches. By default
|
|
40
|
+
* a submission echoes text and publishes completion before acknowledging it. Hooks can supply
|
|
41
|
+
* other behavior. Await Client.dispose to release SDK ownership while retaining fixture records
|
|
42
|
+
* for assertions.
|
|
43
|
+
*/
|
|
44
|
+
export declare class ScriptedHarnessAdapter implements HarnessAdapter {
|
|
45
|
+
readonly options: FakeAdapterOptions;
|
|
46
|
+
/** Retain fixture options by reference for subsequent capability reads, opens and dispatches. */
|
|
47
|
+
constructor(options?: FakeAdapterOptions);
|
|
48
|
+
/** Backend dispatches in arrival order, including calls intercepted by execute. */
|
|
49
|
+
readonly calls: FakeCall[];
|
|
50
|
+
/** Detached open attempts, including attempts whose hook or fork validation fails. */
|
|
51
|
+
readonly opened: SessionOpenParams[];
|
|
52
|
+
/** Latest supplied event sink per session ID, retained for fixture-controlled publication/failure. */
|
|
53
|
+
readonly sinks: Map<string, SessionEventSink>;
|
|
54
|
+
/** Latest created backend per session ID, recorded before fork validation and onOpen. */
|
|
55
|
+
readonly backends: Map<string, ScriptedBackendSession>;
|
|
56
|
+
/** Number of disposal calls received, including any repeated direct calls. */
|
|
57
|
+
disposeCount: number;
|
|
58
|
+
/** Return detached declared capabilities, using FAKE_CAPABILITIES when omitted. */
|
|
59
|
+
get capabilities(): Capabilities;
|
|
60
|
+
/**
|
|
61
|
+
* Return scripted runtime metadata and cloned capabilities after negotiating AHP v1. The SDK
|
|
62
|
+
* adapter wrapper forwards the result to Client.initialize for validation and storage. Malformed
|
|
63
|
+
* requests and unsupported versions reject.
|
|
64
|
+
*/
|
|
65
|
+
initialize(params: InitializeParams): Promise<InitializeResult>;
|
|
66
|
+
/**
|
|
67
|
+
* Supply OutputSession.open with a backend for SDK attachment. Record the backend and sink
|
|
68
|
+
* before copying fork history so a failed setup is observable. Missing source or boundary
|
|
69
|
+
* rejects with SESSION_NOT_FOUND or TURN_NOT_FOUND, respectively. Await onOpen before returning
|
|
70
|
+
* the backend. Hook failures reject and leave the recorded fixture state available.
|
|
71
|
+
*/
|
|
72
|
+
openSession(params: SessionOpenParams, sink: SessionEventSink): Promise<BackendSession>;
|
|
73
|
+
/** Count adapter release. The SDK wrapper calls this after session cleanup attempts. */
|
|
74
|
+
dispose(): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Give openSession a detached prefix of the source history before setup hooks run. A missing
|
|
77
|
+
* source or boundary throws so open rejects with the fork's fixture records still available.
|
|
78
|
+
*/
|
|
79
|
+
private copyForkHistory;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Inspect or customize the in-memory session recorded by ScriptedHarnessAdapter.openSession.
|
|
83
|
+
* OutputSession dispatches validated SDK requests to this backend. Direct fixture calls to execute
|
|
84
|
+
* bypass those SDK guards.
|
|
85
|
+
*/
|
|
86
|
+
export declare class ScriptedBackendSession implements BackendSession {
|
|
87
|
+
private readonly adapter;
|
|
88
|
+
/** Callbacks supplied at open for event publication, interactions and source faults. */
|
|
89
|
+
readonly sink: SessionEventSink;
|
|
90
|
+
/** Requested identity with a deterministic scripted nativeRef, adopted by the SDK at attachment. */
|
|
91
|
+
readonly binding: SessionBinding;
|
|
92
|
+
/** Capabilities cloned from the adapter at construction and shared with the fixture state. */
|
|
93
|
+
readonly capabilities: Capabilities;
|
|
94
|
+
/** Mutable native history. Default backend history/read returns this array by reference. */
|
|
95
|
+
readonly history: HistoryItem[];
|
|
96
|
+
/** Number of disposal calls received, including any repeated direct calls. */
|
|
97
|
+
disposeCount: number;
|
|
98
|
+
/** Initial attachment snapshot. Default configuration dispatch replaces it without publishing a state event. */
|
|
99
|
+
state: SessionState;
|
|
100
|
+
/** Build a ready attachment snapshot and retain the adapter and event sink for dispatch. */
|
|
101
|
+
constructor(adapter: ScriptedHarnessAdapter, params: SessionOpenParams,
|
|
102
|
+
/** Callbacks supplied at open for event publication, interactions and source faults. */
|
|
103
|
+
sink: SessionEventSink);
|
|
104
|
+
/** Await ordered sink publication. Supply the owning turn ID for item events. Sink failures reject. */
|
|
105
|
+
emit(payload: AhpEventPayload, turnId?: string): Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* Resolve a scripted result for OutputSession.execute, which uses receipts to bind submissions
|
|
108
|
+
* before returning to Session for request-result validation. Record the call and await the
|
|
109
|
+
* custom hook before using defaults. Defaults that publish events await them before returning
|
|
110
|
+
* a receipt. Sink and hook failures reject dispatch. Default interaction dispatch rejects
|
|
111
|
+
* because Session handles it in its registry.
|
|
112
|
+
*/
|
|
113
|
+
execute<M extends SessionMethod>(method: M, params: AhpRequestMap[M]): Promise<AhpResultMap[M]>;
|
|
114
|
+
/** Count backend release calls from the SDK or direct fixture callers and retain state for assertions. */
|
|
115
|
+
dispose(): Promise<void>;
|
|
116
|
+
/**
|
|
117
|
+
* Produce the default submission receipt after publishing start, preview, final item and terminal.
|
|
118
|
+
* Reuse an item ID across turns to exercise SDK scoping. Store the completed item before terminal
|
|
119
|
+
* publication, where a sink failure can still reject the dispatch.
|
|
120
|
+
*/
|
|
121
|
+
private submitInput;
|
|
122
|
+
}
|