@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 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
- # Temporary Holding Version
1
+ # @anureo/conformance
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
+ }