@anureo/adapter-claude 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.
@@ -0,0 +1,26 @@
1
+ import { type CatalogProbeOptions } from '@anureo/sdk/diagnostics';
2
+ import type { ClaudeAdapterOptions } from "./options.js";
3
+ /** Create a reusable catalog for discovering Claude models without submitting prompts.
4
+ * Construction retains the adapter launch configuration. When a probe opens a query, it uses
5
+ * the current working directory and disables session persistence and file checkpoints.
6
+ * Await dispose when finished with the catalog. Aborting an active probe requests disposal of
7
+ * the shared connection and can affect concurrent callers. Pass a signal to request cancellation.
8
+ * Deadline enforcement belongs to the caller even when timeoutMs is supplied.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { createClaudeCatalogSession } from '@anureo/adapter-claude/catalog'
13
+ *
14
+ * const catalog = createClaudeCatalogSession({ defaultHome: true })
15
+ * try {
16
+ * console.log(await catalog.listModels())
17
+ * } finally {
18
+ * await catalog.dispose()
19
+ * }
20
+ * ```
21
+ */
22
+ export declare function createClaudeCatalogSession(options: ClaudeAdapterOptions): {
23
+ prepare: (context?: CatalogProbeOptions) => Promise<void>;
24
+ listModels: (context?: CatalogProbeOptions) => Promise<import("@anthropic-ai/claude-agent-sdk").ModelInfo[]>;
25
+ dispose: () => Promise<void>;
26
+ };
package/src/catalog.js ADDED
@@ -0,0 +1,168 @@
1
+ // Owns the resident Claude query for prompt-free model discovery.
2
+ import { query } from '@anthropic-ai/claude-agent-sdk';
3
+ import { BoundedEventQueue, HarnessSdkError } from '@anureo/sdk';
4
+ import { catalogPhase } from '@anureo/sdk/diagnostics';
5
+ import { createClaudeAdapter } from "./adapter.js";
6
+ /** Create a reusable catalog for discovering Claude models without submitting prompts.
7
+ * Construction retains the adapter launch configuration. When a probe opens a query, it uses
8
+ * the current working directory and disables session persistence and file checkpoints.
9
+ * Await dispose when finished with the catalog. Aborting an active probe requests disposal of
10
+ * the shared connection and can affect concurrent callers. Pass a signal to request cancellation.
11
+ * Deadline enforcement belongs to the caller even when timeoutMs is supplied.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { createClaudeCatalogSession } from '@anureo/adapter-claude/catalog'
16
+ *
17
+ * const catalog = createClaudeCatalogSession({ defaultHome: true })
18
+ * try {
19
+ * console.log(await catalog.listModels())
20
+ * } finally {
21
+ * await catalog.dispose()
22
+ * }
23
+ * ```
24
+ */
25
+ export function createClaudeCatalogSession(options) {
26
+ const adapter = createClaudeAdapter(options);
27
+ let stream;
28
+ let input;
29
+ let pendingHandshakeModels;
30
+ let preparing;
31
+ let disposing;
32
+ return { prepare, listModels, dispose };
33
+ /** Warm up the catalog by awaiting launch and initial model discovery.
34
+ * Wait for ongoing disposal before reusing a prepared query or starting a new one. Concurrent
35
+ * callers share preparation, and the caller that starts it supplies the spawn and handshake
36
+ * observers. An already-aborted signal rejects with RPC_ABORTED before touching the connection.
37
+ * Startup failures reject after cleanup, with any cleanup failure taking precedence.
38
+ */
39
+ function prepare(context = {}) {
40
+ return withProbeCleanup(context, async () => {
41
+ await disposing;
42
+ if (preparing) {
43
+ await preparing;
44
+ return;
45
+ }
46
+ if (stream)
47
+ return;
48
+ preparing = openConnection(context)
49
+ .catch(async (cause) => {
50
+ // Shared disposal can be waiting for this preparation to settle.
51
+ await closeConnection();
52
+ throw cause;
53
+ })
54
+ .finally(() => { preparing = undefined; });
55
+ await preparing;
56
+ });
57
+ }
58
+ /** Return native model metadata, preparing the catalog if needed.
59
+ * Use each query's initial discovery once. Later reads refresh via native reinitialization or
60
+ * replace the query when it lacks that capability. The supplied observer receives the spawn
61
+ * and handshake phases this call starts, and model reads are reported after initial preparation.
62
+ * Success leaves the connection open for reuse. An already-aborted signal rejects with
63
+ * RPC_ABORTED before work. Probe failures await shared cleanup before rejecting, with any
64
+ * cleanup failure taking precedence.
65
+ */
66
+ async function listModels(context = {}) {
67
+ return withProbeCleanup(context, async () => {
68
+ await prepare(context);
69
+ return catalogPhase(context, 'models', () => readModels(context));
70
+ });
71
+ }
72
+ /** Release the directory connection and allow a later probe to reopen it.
73
+ * End input, close the query and await its optional native return(). Then wait for pending
74
+ * preparation to settle and close any query it established in the meantime. Concurrent callers
75
+ * share the cleanup promise. A cleanup failure rejects that promise and skips remaining steps.
76
+ */
77
+ function dispose() {
78
+ return disposing ??= (async () => {
79
+ await closeConnection();
80
+ await preparing?.catch(() => { });
81
+ // Preparation may establish a connection while dispose waits for it to finish.
82
+ await closeConnection();
83
+ })().finally(() => { disposing = undefined; });
84
+ }
85
+ /** Run spawn and handshake in order for prepare, caching initial models for listModels.
86
+ * Retain the input queue before launch so prepare's rollback or dispose can end it after failure.
87
+ */
88
+ async function openConnection(context) {
89
+ input = new BoundedEventQueue();
90
+ await catalogPhase(context, 'spawn', launchDirectoryQuery);
91
+ pendingHandshakeModels = await catalogPhase(context, 'handshake', () => stream.supportedModels());
92
+ }
93
+ /** Launch the query for openConnection's spawn phase. Publish it to shared state as soon as
94
+ * the factory returns so cleanup can reach it. A launch failure rejects the phase.
95
+ */
96
+ async function launchDirectoryQuery() {
97
+ stream = (options.queryFactory ?? query)({
98
+ prompt: input,
99
+ options: {
100
+ ...adapter.queryOptions(process.cwd()),
101
+ persistSession: false,
102
+ sessionStore: undefined,
103
+ enableFileCheckpointing: false,
104
+ mcpServers: {},
105
+ settingSources: [],
106
+ },
107
+ });
108
+ }
109
+ /** Supply listModels with cached discovery or refreshed models during its models phase.
110
+ * A query without reinitialize needs a replacement to obtain fresh metadata. Await disposal
111
+ * before reopening so a failed close rejects the read before a replacement starts.
112
+ */
113
+ async function readModels(context) {
114
+ if (pendingHandshakeModels)
115
+ return takeHandshakeModels();
116
+ const current = stream;
117
+ if (current.reinitialize)
118
+ return (await current.reinitialize()).models;
119
+ await dispose();
120
+ await prepare(context);
121
+ return takeHandshakeModels();
122
+ }
123
+ /** Transfer the handshake result to readModels and clear the cache so the next read refreshes. */
124
+ function takeHandshakeModels() {
125
+ const models = pendingHandshakeModels;
126
+ pendingHandshakeModels = undefined;
127
+ return models;
128
+ }
129
+ /** Scope a cancellation listener to each prepare or listModels call. The signal invokes it to
130
+ * request shared disposal so native closure can settle pending work. Reject pre-aborted probes
131
+ * before registration. If work rejects, await disposal and propagate the cleanup failure if any,
132
+ * otherwise propagate the work failure.
133
+ */
134
+ async function withProbeCleanup(context, work) {
135
+ const disposeOnAbort = () => {
136
+ // The abort event has no caller awaiting this cleanup promise.
137
+ void dispose().catch(() => { });
138
+ };
139
+ if (context.signal?.aborted) {
140
+ throw new HarnessSdkError('RPC_ABORTED');
141
+ }
142
+ context.signal?.addEventListener('abort', disposeOnAbort, { once: true });
143
+ try {
144
+ return await work();
145
+ }
146
+ catch (cause) {
147
+ await dispose();
148
+ throw cause;
149
+ }
150
+ finally {
151
+ context.signal?.removeEventListener('abort', disposeOnAbort);
152
+ }
153
+ }
154
+ /** Release the current input and query for prepare's startup rollback or either disposal pass.
155
+ * Clear shared query state before calling native cleanup so overlapping calls cannot close the
156
+ * same query twice. End input before closing, then await native return when supplied. Native
157
+ * cleanup failures reject the caller's cleanup path.
158
+ */
159
+ async function closeConnection() {
160
+ const current = stream;
161
+ stream = undefined;
162
+ pendingHandshakeModels = undefined;
163
+ input?.end();
164
+ input = undefined;
165
+ current?.close();
166
+ await current?.return?.();
167
+ }
168
+ }
@@ -0,0 +1,22 @@
1
+ import type { FileChange, Item } from '@anureo/protocol';
2
+ /**
3
+ * Project native Edit, MultiEdit and Write payloads into file evidence using only the supplied data.
4
+ * Return undefined for other tools, an empty array when neither payload supplies a string path,
5
+ * or one change. Prefer result.filePath over input.file_path and fall back to input evidence
6
+ * when the result supplies no usable material.
7
+ * ClaudeBackendSession.blockItem attaches input evidence to live and history items.
8
+ * completeClaudeTool projects evidence again when a matching native result arrives.
9
+ */
10
+ export declare function claudeFileChanges(name: string, input: unknown, result?: unknown): FileChange[] | undefined;
11
+ /**
12
+ * Return a new tool item from an earlier call and its matching native result. The caller supplies
13
+ * JSON-normalized and privacy-projected content. The item exposes content as output and uses
14
+ * structuredContent for file evidence when it is non-nullish, otherwise using content.
15
+ * Declined and interrupted statuses survive later results. Other calls fail on truthy is_error
16
+ * and complete otherwise.
17
+ * ClaudeBackendSession.mapMessage stores and emits the item as item/completed, and projectHistory
18
+ * replaces the earlier call's item in the history snapshot.
19
+ */
20
+ export declare function completeClaudeTool(previous: Extract<Item, {
21
+ type: 'toolCall';
22
+ }>, block: Record<string, unknown>): Item;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Project native Edit, MultiEdit and Write payloads into file evidence using only the supplied data.
3
+ * Return undefined for other tools, an empty array when neither payload supplies a string path,
4
+ * or one change. Prefer result.filePath over input.file_path and fall back to input evidence
5
+ * when the result supplies no usable material.
6
+ * ClaudeBackendSession.blockItem attaches input evidence to live and history items.
7
+ * completeClaudeTool projects evidence again when a matching native result arrives.
8
+ */
9
+ export function claudeFileChanges(name, input, result) {
10
+ if (!['Edit', 'MultiEdit', 'Write'].includes(name))
11
+ return undefined;
12
+ const data = record(input);
13
+ const output = record(result);
14
+ const path = typeof output.filePath === 'string'
15
+ ? output.filePath
16
+ : typeof data.file_path === 'string' ? data.file_path : undefined;
17
+ if (path === undefined)
18
+ return [];
19
+ return [resultFileChange(path, name, output) ?? inputFileChange(path, name, data)];
20
+ }
21
+ /**
22
+ * Return a new tool item from an earlier call and its matching native result. The caller supplies
23
+ * JSON-normalized and privacy-projected content. The item exposes content as output and uses
24
+ * structuredContent for file evidence when it is non-nullish, otherwise using content.
25
+ * Declined and interrupted statuses survive later results. Other calls fail on truthy is_error
26
+ * and complete otherwise.
27
+ * ClaudeBackendSession.mapMessage stores and emits the item as item/completed, and projectHistory
28
+ * replaces the earlier call's item in the history snapshot.
29
+ */
30
+ export function completeClaudeTool(previous, block) {
31
+ const name = previous.nativeType ?? previous.title;
32
+ const fileChanges = claudeFileChanges(name, previous.input, block.structuredContent ?? block.content);
33
+ let status = previous.status;
34
+ if (status !== 'declined' && status !== 'interrupted') {
35
+ status = block.is_error ? 'failed' : 'completed';
36
+ }
37
+ return {
38
+ ...previous,
39
+ status,
40
+ output: block.content,
41
+ ...(fileChanges !== undefined ? { fileChanges } : {}),
42
+ };
43
+ }
44
+ function record(value) {
45
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
46
+ return {};
47
+ return value;
48
+ }
49
+ /** Try raw patch, whole-file text, structured hunks and Write output in that order before falling back to input. */
50
+ function resultFileChange(path, name, output) {
51
+ if (typeof output.patch === 'string') {
52
+ return { path, operation: 'modify', materials: [{ format: 'unified', patch: output.patch }], completeness: 'complete' };
53
+ }
54
+ if ((output.oldText === null || typeof output.oldText === 'string') && typeof output.newText === 'string') {
55
+ return {
56
+ path,
57
+ operation: output.oldText === null ? 'add' : 'modify',
58
+ materials: [{ format: 'text', scope: 'file', oldText: output.oldText, newText: output.newText }],
59
+ completeness: 'complete',
60
+ };
61
+ }
62
+ const patch = structuredPatch(output.structuredPatch);
63
+ if (patch !== undefined) {
64
+ return { path, operation: output.type === 'create' ? 'add' : 'modify', materials: [{ format: 'unified', patch }], completeness: 'complete' };
65
+ }
66
+ if (name !== 'Write' || typeof output.content !== 'string')
67
+ return undefined;
68
+ // An update can omit the original text. Null is evidence of an absent file only for create.
69
+ const originalFile = output.originalFile;
70
+ const created = output.type === 'create';
71
+ if (typeof originalFile !== 'string' && !(created && originalFile === null))
72
+ return undefined;
73
+ return {
74
+ path,
75
+ operation: created ? 'add' : 'modify',
76
+ materials: [{ format: 'text', scope: 'file', oldText: originalFile, newText: output.content }],
77
+ completeness: 'complete',
78
+ };
79
+ }
80
+ /**
81
+ * Preserve Edit and MultiEdit input fragments in request order. Only a single Edit with both
82
+ * fragments and no replace_all flag set to true is complete evidence. Write input alone leaves
83
+ * the operation and material unknown because it does not establish the prior state of the file.
84
+ */
85
+ function inputFileChange(path, name, data) {
86
+ if (name === 'Write') {
87
+ return { path, operation: 'unknown', materials: [{ format: 'unavailable', reason: 'notProvided' }], completeness: 'partial' };
88
+ }
89
+ const edits = name === 'MultiEdit' && Array.isArray(data.edits) ? data.edits : [data];
90
+ const materials = edits.flatMap(value => {
91
+ const edit = record(value);
92
+ if (typeof edit.old_string !== 'string' || typeof edit.new_string !== 'string')
93
+ return [];
94
+ return [{ format: 'text', scope: 'fragment', oldText: edit.old_string, newText: edit.new_string }];
95
+ });
96
+ // A result that only reports success cannot prove replacement count or independent edits.
97
+ const partial = name === 'MultiEdit'
98
+ || edits.some(value => record(value).replace_all === true)
99
+ || materials.length !== edits.length;
100
+ return {
101
+ path,
102
+ operation: 'modify',
103
+ materials: materials.length ? materials : [{ format: 'unavailable', reason: 'notProvided' }],
104
+ completeness: partial || !materials.length ? 'partial' : 'complete',
105
+ };
106
+ }
107
+ /** Render a nonempty pinned-SDK hunk array in native order after checking its hunk shapes. */
108
+ function structuredPatch(value) {
109
+ if (!Array.isArray(value) || !value.length)
110
+ return undefined;
111
+ const hunks = value.map(record);
112
+ if (!hunks.every(isStructuredPatchHunk))
113
+ return undefined;
114
+ return hunks.map(hunk => `@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@\n${hunk.lines.join('\n')}\n`).join('');
115
+ }
116
+ /** Accept diff markers, including the backslash marker for a missing final newline. */
117
+ function isPatchLine(line) {
118
+ return typeof line === 'string' && /^[ +\\-]/.test(line);
119
+ }
120
+ /** Require nonnegative integer range fields and diff markers before using native positions. */
121
+ function isStructuredPatchHunk(hunk) {
122
+ const hasValidRange = ['oldStart', 'oldLines', 'newStart', 'newLines'].every(key => Number.isInteger(hunk[key]) && hunk[key] >= 0);
123
+ return hasValidRange && Array.isArray(hunk.lines) && hunk.lines.every(isPatchLine);
124
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { ClaudeNativeAdapter, ClaudeBackendSession, createClaudeAdapter } from "./adapter.js";
2
+ export type { ClaudeAdapterOptions, ClaudeQuery, ClaudeQueryFactory } from "./options.js";
3
+ export { FileSessionStore } from "./store.js";
4
+ export { createClaudeCatalogSession } from "./catalog.js";
package/src/index.js ADDED
@@ -0,0 +1,4 @@
1
+ // Public application and extension exports; runtime implementations live in their owning modules.
2
+ export { ClaudeNativeAdapter, ClaudeBackendSession, createClaudeAdapter } from "./adapter.js";
3
+ export { FileSessionStore } from "./store.js";
4
+ export { createClaudeCatalogSession } from "./catalog.js";
@@ -0,0 +1,47 @@
1
+ import type { query, getSessionMessages, getSessionInfo, forkSession, Query, SDKMessage, SessionStore } from "@anthropic-ai/claude-agent-sdk";
2
+ /** Implement message iteration, session controls and discovery for an injected Claude backend.
3
+ * End message iteration when the query closes. Session and catalog cleanup also await a native
4
+ * return() method when present. One-shot ClaudeNativeAdapter.listModels calls close alone. */
5
+ export type ClaudeQuery = AsyncIterable<SDKMessage> & Pick<Query, "interrupt" | "setModel" | "setPermissionMode" | "setMaxThinkingTokens" | "applyFlagSettings" | "rewindFiles" | "supportedModels" | "close">;
6
+ /** Supply a native-compatible query for adapter sessions or prompt-less catalog discovery.
7
+ * This factory runs synchronously for each query, including replacements. The operation that
8
+ * invokes the factory owns its query and propagates startup failures according to its contract. */
9
+ export type ClaudeQueryFactory = (params: Parameters<typeof query>[0]) => ClaudeQuery;
10
+ /** Configure Claude launch, state storage and host-supplied query or transcript operations.
11
+ * Initialization accepts an explicit Claude home, defaultHome or a custom query factory.
12
+ * Native launch begins with discovery or a session open. Dispose the SDK client or catalog
13
+ * after use and manage the lifecycle of any injected store separately. */
14
+ export interface ClaudeAdapterOptions {
15
+ /** Native executable path; takes precedence over the executable alias. */
16
+ command?: string;
17
+ /** Alias for command when command is omitted; omission of both uses the native SDK executable. */
18
+ executable?: string;
19
+ /** Extra child arguments appended after the native SDK arguments. */
20
+ args?: string[];
21
+ /** Explicit launch environment layered over the adapter's platform-essential variables. */
22
+ env?: Record<string, string | undefined>;
23
+ /** Adapter state directory, defaulting to env.CLAUDE_CONFIG_DIR; also selects native home unless defaultHome. */
24
+ stateDir?: string;
25
+ /** Suppress the stateDir native-home override while retaining adapter storage. Explicit
26
+ * env.CLAUDE_CONFIG_DIR still applies; otherwise the CLI uses its default home and login. */
27
+ defaultHome?: boolean;
28
+ /** Replace native query creation for host integrations or scripted tests; adapter/catalog cleanup owns each returned query. */
29
+ queryFactory?: ClaudeQueryFactory;
30
+ /** Inject an opaque native store instead of the state-directory store; the host retains its lifecycle. */
31
+ sessionStore?: SessionStore;
32
+ /** Replace native transcript reads during fork setup, history snapshot creation and stored
33
+ * revert-boundary lookup. Receives the session cwd and a wrapper around the selected store.
34
+ * Read failures reject the operation that requested the transcript. */
35
+ getSessionMessages?: typeof getSessionMessages;
36
+ /** Replace title reads for resumed queries, committed main-session appends and terminal results.
37
+ * Use the supplied session ID, cwd and optional store. Read failures trigger a best-effort warning. */
38
+ getSessionInfo?: typeof getSessionInfo;
39
+ /** Replace native transcript copying for fork and conversation revert when a cutoff entry exists.
40
+ * Return the destination sessionId. Rejections fail the operation. A revert that already rewound
41
+ * files reports REVERT_PARTIAL with the applied file scope. */
42
+ forkSession?: typeof forkSession;
43
+ /** Enable native file checkpoints/revert; defaults to false. History/conversation support also requires a store. */
44
+ enableFileCheckpointing?: boolean;
45
+ /** Script-based external executables can specify the interpreter; native binaries need none. */
46
+ executableRuntime?: "node" | "bun" | "deno";
47
+ }
package/src/options.js ADDED
File without changes
@@ -0,0 +1,17 @@
1
+ import { type ValidateFunction } from 'ajv';
2
+ import type { JsonObject, JsonSchema } from '@anureo/protocol';
3
+ /** Internal pair retained by ClaudeBackendSession for the active query format. */
4
+ export interface OutputSchema {
5
+ /** Detached schema used for format comparison and native query setup. Treat it as read-only. */
6
+ schema: JsonObject;
7
+ /** Synchronous draft-07 validator used to decide whether terminal output can be published as JSON. */
8
+ validate: ValidateFunction;
9
+ }
10
+ /**
11
+ * Compile a detached, self-contained draft-07 schema with an explicit object root. The validator
12
+ * skips format assertions. Invalid, asynchronous or uncloneable schemas throw INVALID_PARAMS
13
+ * synchronously with a message that omits schema contents.
14
+ * ClaudeBackendSession.submitInput compiles a changed schema before replacing the query or
15
+ * dispatching input. completeTurn uses the retained validator before publishing final JSON text.
16
+ */
17
+ export declare function compileOutputSchema(schema: JsonSchema): OutputSchema;
@@ -0,0 +1,23 @@
1
+ import Ajv from 'ajv';
2
+ import { HarnessSdkError } from '@anureo/sdk';
3
+ /**
4
+ * Compile a detached, self-contained draft-07 schema with an explicit object root. The validator
5
+ * skips format assertions. Invalid, asynchronous or uncloneable schemas throw INVALID_PARAMS
6
+ * synchronously with a message that omits schema contents.
7
+ * ClaudeBackendSession.submitInput compiles a changed schema before replacing the query or
8
+ * dispatching input. completeTurn uses the retained validator before publishing final JSON text.
9
+ */
10
+ export function compileOutputSchema(schema) {
11
+ if (!schema || typeof schema !== 'object' || schema.type !== 'object' || schema.$async) {
12
+ throw new HarnessSdkError('INVALID_PARAMS', 'Claude outputSchema must declare an object root and cannot be asynchronous');
13
+ }
14
+ try {
15
+ const copy = structuredClone(schema);
16
+ const ajv = new Ajv({ allErrors: true, validateFormats: false, multipleOfPrecision: 6, logger: false });
17
+ return { schema: copy, validate: ajv.compile(copy) };
18
+ }
19
+ catch {
20
+ // Schema descriptions, enum values and reference URLs may contain private data.
21
+ throw new HarnessSdkError('INVALID_PARAMS', 'Claude outputSchema must be a valid, self-contained draft-07 JSON Schema');
22
+ }
23
+ }
package/src/store.d.ts ADDED
@@ -0,0 +1,71 @@
1
+ import type { SessionKey, SessionStore, SessionStoreEntry } from "@anthropic-ai/claude-agent-sdk";
2
+ /** Persist JSON-safe, opaque Claude transcript entries in a host-owned directory.
3
+ * Supply this as sessionStore to ClaudeNativeAdapter. With a configured state directory and no
4
+ * supplied store, the adapter creates one in its ahp-session-store subdirectory and also uses it
5
+ * to persist turn IDs for boundary lookup. The native SDK invokes the SessionStore methods for
6
+ * mirroring, resume and history. Transcript keys include the project, session and optional
7
+ * subpath. The host owns retention and coordinates deletion with writers. Construction opens no
8
+ * resources and requires no disposal. */
9
+ export declare class FileSessionStore implements SessionStore {
10
+ readonly directory: string;
11
+ /** Use directory for transcripts and turn maps. Writes create missing directories with mode 0700. */
12
+ constructor(directory: string);
13
+ /** Commit a batch of JSON-safe entries under the lock for this transcript key.
14
+ * A truthy UUID replaces its existing entry in place. New UUIDs and entries without a truthy
15
+ * UUID append in batch order. Resolve after file sync, atomic rename and lock cleanup.
16
+ * An unreclaimable lock that is still contended at the 10-second retry deadline rejects with
17
+ * SESSION_STORE_BUSY. Transcript read, write and cleanup failures also reject, including
18
+ * cleanup after publication. ClaudeBackendSession.observedStore awaits successful
19
+ * main-transcript appends before scheduling a title refresh for the active query. Rejections
20
+ * return to the native SDK. */
21
+ append(key: SessionKey, entries: SessionStoreEntry[]): Promise<void>;
22
+ /** Load a detached full transcript, or null when the requested file is absent.
23
+ * An envelope without a truthy key or an entries array rejects with SESSION_STORE_INVALID.
24
+ * Other filesystem and JSON parse failures propagate. ClaudeBackendSession.boundaryPoint and
25
+ * structuredHistory also read these entries to include opaque records omitted by native history
26
+ * in fork/revert cutoffs and structured output in history pages, respectively. */
27
+ load(key: SessionKey): Promise<SessionStoreEntry[] | null>;
28
+ /** List transcripts with no truthy subpath for a project in filesystem order.
29
+ * The stored mtime is in Unix epoch milliseconds and advances on every committed append.
30
+ * A missing directory yields an empty list. Read and envelope errors anywhere in the
31
+ * transcript scan reject the listing. */
32
+ listSessions(projectKey: string): Promise<Array<{
33
+ sessionId: string;
34
+ mtime: number;
35
+ }>>;
36
+ /** List nonempty transcript subpaths for a project and session in filesystem order.
37
+ * A missing directory yields an empty list. Scan failures propagate as in listSessions. */
38
+ listSubkeys(key: {
39
+ projectKey: string;
40
+ sessionId: string;
41
+ }): Promise<string[]>;
42
+ /** Remove the transcript for this exact key, accepting an absent file.
43
+ * Other subpaths and turn maps remain. The caller must coordinate this unlocked deletion with
44
+ * writers. Other removal failures reject. */
45
+ delete(key: SessionKey): Promise<void>;
46
+ /** Load adapter boundary metadata mapping native input UUIDs to AHP turn IDs by native session ID.
47
+ * Return the parsed JSON as stored, or an empty map when the file is absent. Other read and
48
+ * parse failures reject.
49
+ * ClaudeBackendSession.open restores the map for the active session or reads it from the fork
50
+ * source. ClaudeBackendSession.boundaryPoint uses the map to resolve AHP turn IDs to candidates
51
+ * that it verifies in native history. */
52
+ loadTurnMap(sessionId: string): Promise<Record<string, string>>;
53
+ /** Replace the adapter's turn map for a native session ID.
54
+ * Resolve after file sync, atomic rename and temporary-file cleanup. Callers must order
55
+ * concurrent replacements if publication order matters. Cleanup can reject after publication.
56
+ * ClaudeBackendSession.open and restoreConversation await the save before starting a query.
57
+ * Saves during input association or a native ID change can fail the query pump. */
58
+ saveTurnMap(sessionId: string, turns: Record<string, string>): Promise<void>;
59
+ /** Keep each transcript at its stable hashed path. Omitted subpaths use the null slot in the
60
+ * tuple, preserving the on-disk key format and keeping an explicit empty string distinct. */
61
+ private transcriptPath;
62
+ /** Read a transcript envelope for load, append or a scan. Return null on absence and reject
63
+ * malformed JSON or a missing key/entries array so append cannot overwrite those records. */
64
+ private readTranscript;
65
+ /** Scan the hashed transcript filenames for both listing methods. Preserve directory order and
66
+ * skip files removed before they can be read. Directory absence also yields an empty result.
67
+ * Other read or envelope errors reject before the caller filters by project or session. */
68
+ private scanTranscripts;
69
+ /** Keep boundary metadata at its stable session-ID path, outside the transcript filename pattern. */
70
+ private turnMapPath;
71
+ }