@anureo/sdk 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.
Files changed (76) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +185 -2
  3. package/package.json +66 -4
  4. package/src/adapter/identity.d.ts +41 -0
  5. package/src/adapter/identity.js +71 -0
  6. package/src/client-options.d.ts +71 -0
  7. package/src/client-options.js +0 -0
  8. package/src/client.d.ts +106 -0
  9. package/src/client.js +254 -0
  10. package/src/diagnostics/errors.d.ts +15 -0
  11. package/src/diagnostics/errors.js +59 -0
  12. package/src/diagnostics/events.d.ts +46 -0
  13. package/src/diagnostics/events.js +47 -0
  14. package/src/diagnostics.d.ts +20 -0
  15. package/src/diagnostics.js +56 -0
  16. package/src/errors.d.ts +48 -0
  17. package/src/errors.js +63 -0
  18. package/src/index.d.ts +27 -0
  19. package/src/index.js +23 -0
  20. package/src/lifecycle/approval.d.ts +16 -0
  21. package/src/lifecycle/approval.js +18 -0
  22. package/src/lifecycle/deferred.d.ts +10 -0
  23. package/src/lifecycle/deferred.js +12 -0
  24. package/src/lifecycle/errors.d.ts +18 -0
  25. package/src/lifecycle/errors.js +45 -0
  26. package/src/lifecycle/events.d.ts +35 -0
  27. package/src/lifecycle/events.js +141 -0
  28. package/src/lifecycle/execution-observation.d.ts +33 -0
  29. package/src/lifecycle/execution-observation.js +0 -0
  30. package/src/lifecycle/immutable.d.ts +12 -0
  31. package/src/lifecycle/immutable.js +28 -0
  32. package/src/lifecycle/input.d.ts +9 -0
  33. package/src/lifecycle/input.js +19 -0
  34. package/src/lifecycle/interactions.d.ts +129 -0
  35. package/src/lifecycle/interactions.js +282 -0
  36. package/src/lifecycle/limits.d.ts +7 -0
  37. package/src/lifecycle/limits.js +13 -0
  38. package/src/lifecycle/result.d.ts +77 -0
  39. package/src/lifecycle/result.js +213 -0
  40. package/src/lifecycle/value.d.ts +4 -0
  41. package/src/lifecycle/value.js +5 -0
  42. package/src/ports.d.ts +224 -0
  43. package/src/ports.js +0 -0
  44. package/src/privacy/file-change-limits.d.ts +20 -0
  45. package/src/privacy/file-change-limits.js +124 -0
  46. package/src/privacy/secrets.d.ts +205 -0
  47. package/src/privacy/secrets.js +564 -0
  48. package/src/process.d.ts +92 -0
  49. package/src/process.js +331 -0
  50. package/src/queue.d.ts +143 -0
  51. package/src/queue.js +266 -0
  52. package/src/request.d.ts +28 -0
  53. package/src/request.js +81 -0
  54. package/src/requests/guard.d.ts +15 -0
  55. package/src/requests/guard.js +93 -0
  56. package/src/session.d.ts +459 -0
  57. package/src/session.js +1135 -0
  58. package/src/structured-output/adapter.d.ts +8 -0
  59. package/src/structured-output/adapter.js +54 -0
  60. package/src/structured-output/bridge.d.ts +81 -0
  61. package/src/structured-output/bridge.js +274 -0
  62. package/src/structured-output/errors.d.ts +17 -0
  63. package/src/structured-output/errors.js +31 -0
  64. package/src/structured-output/limits.d.ts +19 -0
  65. package/src/structured-output/limits.js +0 -0
  66. package/src/structured-output/schema.d.ts +20 -0
  67. package/src/structured-output/schema.js +122 -0
  68. package/src/structured-output/session.d.ts +329 -0
  69. package/src/structured-output/session.js +846 -0
  70. package/src/structured-output/stdio.mjs +83 -0
  71. package/src/structured-output/tool.d.ts +38 -0
  72. package/src/structured-output/tool.js +34 -0
  73. package/src/turn.d.ts +75 -0
  74. package/src/turn.js +95 -0
  75. package/src/types.d.ts +62 -0
  76. package/src/types.js +0 -0
@@ -0,0 +1,106 @@
1
+ import { type AhpRequestMap, type SessionBinding, type SessionOpenParams } from '@anureo/protocol';
2
+ import type { RequestOptions } from "./types.js";
3
+ import type { ClientOptions } from "./client-options.js";
4
+ import { Session } from "./session.js";
5
+ export { Session } from "./session.js";
6
+ export type { ClientOptions, InteractionHandlers, SubmitOptions } from "./client-options.js";
7
+ /**
8
+ * Open and manage sessions through one adapter. Use createClient for an initialized client, or
9
+ * construct and initialize it explicitly. Await dispose to release owned sessions and the adapter.
10
+ */
11
+ export declare class Client {
12
+ private readonly options;
13
+ /**
14
+ * Take ownership of the adapter without starting native I/O. Invalid identity, interaction
15
+ * registrations or retention limits throw INVALID_PARAMS. Direct callers must await initialize
16
+ * before opening sessions and later await dispose, including after failed initialization.
17
+ */
18
+ constructor(options: ClientOptions);
19
+ /** Read a fresh copy of the adapter and runtime configuration IDs used to check session bindings. */
20
+ get identity(): ClientOptions['identity'];
21
+ /**
22
+ * Negotiate adapter capabilities before opening sessions. Each call invokes adapter negotiation
23
+ * once if admitted and stores the validated capability snapshot on success. The wait defaults to
24
+ * 30 seconds unless configured by runtime.requestTimeoutMs. Invalid waits, adapter or protocol
25
+ * failures, abort and timeout reject. A negotiation that finishes after a failed wait cannot update
26
+ * the client's capabilities. createClient awaits this method and handles cleanup on failure.
27
+ */
28
+ initialize(wait?: RequestOptions): Promise<void>;
29
+ /**
30
+ * Open with explicit request and binding IDs. Resolve with the client-owned Session after backend
31
+ * attachment and initial state publication. Invalid input, unsupported mode, setup or wait failure
32
+ * rejects. The attachment wait defaults to 120 seconds unless configured by runtime.openTimeoutMs.
33
+ * If the wait ends early, the SDK closes local observation and still releases a late backend.
34
+ */
35
+ openSession(params: SessionOpenParams, wait?: RequestOptions): Promise<Session>;
36
+ /**
37
+ * Release the client and its sessions. Stop admitting opens immediately and share the disposal
38
+ * promise across calls. Await all owned session disposal attempts before releasing the adapter.
39
+ * Reject with RESOURCE_RELEASE_FAILED if release fails. Disposal ends local observation and leaves
40
+ * native execution stop unconfirmed. An open that delivers a backend after teardown releases it
41
+ * on its own attachment path.
42
+ */
43
+ dispose(): Promise<void>;
44
+ private initialized?;
45
+ private closed;
46
+ private disposing?;
47
+ private readonly owned;
48
+ private readonly createId;
49
+ /** Open sessions using generated request IDs and the same attachment wait and ownership as openSession. */
50
+ readonly sessions: {
51
+ /** Open a new session with a fresh binding. A throwing ID generator fails the call synchronously. */
52
+ create: (options: Omit<SessionOpenParams, "requestId" | "binding" | "mode">, wait?: RequestOptions) => Promise<Session>;
53
+ /** Reopen a saved session reference when supported. Foreign bindings reject and ID-generator throws propagate synchronously. */
54
+ resume: (reference: {
55
+ binding: SessionBinding;
56
+ cwd: string;
57
+ }, wait?: RequestOptions) => Promise<Session>;
58
+ /** Open a fresh session at a supported boundary in the referenced session. Foreign bindings reject before ID generation, which can throw synchronously. */
59
+ fork: (reference: {
60
+ binding: SessionBinding;
61
+ cwd: string;
62
+ }, options: {
63
+ boundary: AhpRequestMap["session/revert"]["boundary"];
64
+ }, wait?: RequestOptions) => Promise<Session>;
65
+ };
66
+ /**
67
+ * sessions.create and sessions.fork use this for a fresh destination after generating the request
68
+ * ID. Snapshot the configured identity before invoking the host's session ID generator.
69
+ */
70
+ private newBinding;
71
+ /**
72
+ * dispose caches this release attempt. Close admission before taking the session snapshot and
73
+ * keep the adapter alive until all session disposals settle. Report failures in session order,
74
+ * followed by the adapter failure, through the shared disposal promise.
75
+ */
76
+ private disposeOwnedResources;
77
+ /**
78
+ * openSession and sessions.create/resume/fork return this promise to hosts. Validate before
79
+ * allocating a Session, then own it before native callbacks or disposal can race attachment.
80
+ * Resolve the attached session, or remove it and await disposal before rejecting setup/wait failure
81
+ * (aggregated if cleanup fails). attachSession continues to release late backends after wait failure.
82
+ */
83
+ private open;
84
+ /**
85
+ * Client.open uses the returned negotiated capabilities to construct Session. Preserve admission
86
+ * precedence: wait validity, abort, client state, protocol shape, binding, then mode capability.
87
+ * Throws before session allocation/native dispatch; open exposes that failure as its rejection.
88
+ */
89
+ private validateOpen;
90
+ /**
91
+ * Client.open passes this operation to waitForRequest. Give the wrapped adapter a separate params
92
+ * clone and the Session sink, then await attachment/publication. This work outlives caller abort or
93
+ * timeout: OutputSession.open or Session.attach disposes a backend arriving after teardown.
94
+ * Native/attachment failures reject the operation and remain observed by waitForRequest.
95
+ */
96
+ private attachSession;
97
+ }
98
+ /**
99
+ * Create and initialize a client before opening sessions. A pre-aborted wait rejects before the
100
+ * client takes ownership of the adapter. Once construction succeeds, initialization failure triggers
101
+ * disposal before rejection. The original cause is preserved unless disposal also fails, in which
102
+ * case both failures are reported as RESOURCE_RELEASE_FAILED. Await dispose on the returned client.
103
+ * createRefactorClient and createReviewClient return this promise to their CLI commands, which
104
+ * await disposal after the run and report output.
105
+ */
106
+ export declare function createClient(options: ClientOptions, wait?: RequestOptions): Promise<Client>;
package/src/client.js ADDED
@@ -0,0 +1,254 @@
1
+ // Owns adapter negotiation, session open admission and client-wide resource release.
2
+ import { randomUUID } from 'node:crypto';
3
+ import { AHP_PROTOCOL_CONTRACT, assertRequest, assertResult, supportsCapability } from '@anureo/protocol';
4
+ import { HarnessSdkError } from "./errors.js";
5
+ import { invalidWait, waitForRequest } from "./request.js";
6
+ import { Session } from "./session.js";
7
+ import { immutable } from "./lifecycle/immutable.js";
8
+ import { positive } from "./lifecycle/limits.js";
9
+ import { combined } from "./lifecycle/errors.js";
10
+ import { withStructuredOutput } from "./structured-output/adapter.js";
11
+ export { Session } from "./session.js";
12
+ /**
13
+ * Open and manage sessions through one adapter. Use createClient for an initialized client, or
14
+ * construct and initialize it explicitly. Await dispose to release owned sessions and the adapter.
15
+ */
16
+ export class Client {
17
+ options;
18
+ /**
19
+ * Take ownership of the adapter without starting native I/O. Invalid identity, interaction
20
+ * registrations or retention limits throw INVALID_PARAMS. Direct callers must await initialize
21
+ * before opening sessions and later await dispose, including after failed initialization.
22
+ */
23
+ constructor(options) {
24
+ this.options = options;
25
+ this.options = {
26
+ ...options,
27
+ adapter: withStructuredOutput(options.adapter, {
28
+ queue: options.runtime?.queue,
29
+ maxSubmissions: options.limits?.maxSubmissions,
30
+ maxResultBytes: options.limits?.maxResultBytes,
31
+ maxItems: options.limits?.maxItems,
32
+ }),
33
+ };
34
+ this.createId = options.runtime?.createId ?? randomUUID;
35
+ assertClientOptions(options);
36
+ }
37
+ /** Read a fresh copy of the adapter and runtime configuration IDs used to check session bindings. */
38
+ get identity() {
39
+ return { adapterId: this.options.identity.adapterId, runtimeConfigId: this.options.identity.runtimeConfigId };
40
+ }
41
+ /**
42
+ * Negotiate adapter capabilities before opening sessions. Each call invokes adapter negotiation
43
+ * once if admitted and stores the validated capability snapshot on success. The wait defaults to
44
+ * 30 seconds unless configured by runtime.requestTimeoutMs. Invalid waits, adapter or protocol
45
+ * failures, abort and timeout reject. A negotiation that finishes after a failed wait cannot update
46
+ * the client's capabilities. createClient awaits this method and handles cleanup on failure.
47
+ */
48
+ async initialize(wait) {
49
+ if (invalidWait(wait))
50
+ throw new HarnessSdkError('INVALID_PARAMS');
51
+ const result = await waitForRequest(() => this.options.adapter.initialize({
52
+ protocolVersions: [1],
53
+ protocolContract: AHP_PROTOCOL_CONTRACT,
54
+ clientInfo: { name: 'anureo-native-lifecycle', version: '0.2.0' },
55
+ // Own keys advertise support even when the host will respond manually.
56
+ clientCapabilities: { interactions: Object.keys(this.options.interactions ?? {}) },
57
+ }), wait, this.options.runtime?.requestTimeoutMs);
58
+ assertResult('initialize', result);
59
+ this.initialized = immutable(result.capabilities);
60
+ }
61
+ /**
62
+ * Open with explicit request and binding IDs. Resolve with the client-owned Session after backend
63
+ * attachment and initial state publication. Invalid input, unsupported mode, setup or wait failure
64
+ * rejects. The attachment wait defaults to 120 seconds unless configured by runtime.openTimeoutMs.
65
+ * If the wait ends early, the SDK closes local observation and still releases a late backend.
66
+ */
67
+ openSession(params, wait) { return this.open(params, wait); }
68
+ /**
69
+ * Release the client and its sessions. Stop admitting opens immediately and share the disposal
70
+ * promise across calls. Await all owned session disposal attempts before releasing the adapter.
71
+ * Reject with RESOURCE_RELEASE_FAILED if release fails. Disposal ends local observation and leaves
72
+ * native execution stop unconfirmed. An open that delivers a backend after teardown releases it
73
+ * on its own attachment path.
74
+ */
75
+ dispose() {
76
+ return this.disposing ??= this.disposeOwnedResources();
77
+ }
78
+ // TypeScript emits these as own fields. Keep their initialization order relative to sessions.
79
+ initialized;
80
+ closed = false;
81
+ disposing;
82
+ owned = new Set();
83
+ createId;
84
+ /** Open sessions using generated request IDs and the same attachment wait and ownership as openSession. */
85
+ sessions = {
86
+ /** Open a new session with a fresh binding. A throwing ID generator fails the call synchronously. */
87
+ create: (options, wait) => this.open({
88
+ ...options, mode: 'new', requestId: this.createId(),
89
+ binding: this.newBinding(),
90
+ }, wait),
91
+ /** Reopen a saved session reference when supported. Foreign bindings reject and ID-generator throws propagate synchronously. */
92
+ resume: (reference, wait) => this.open({ ...reference, mode: 'resume', requestId: this.createId() }, wait),
93
+ /** Open a fresh session at a supported boundary in the referenced session. Foreign bindings reject before ID generation, which can throw synchronously. */
94
+ fork: (reference, options, wait) => {
95
+ if (!matchesIdentity(reference.binding, this.options.identity))
96
+ return Promise.reject(new HarnessSdkError('SESSION_BINDING_MISMATCH'));
97
+ return this.open({
98
+ cwd: reference.cwd, mode: 'fork', requestId: this.createId(),
99
+ binding: this.newBinding(),
100
+ fork: { sourceSessionId: reference.binding.sessionId, nativeRef: reference.binding.nativeRef, boundary: options.boundary },
101
+ }, wait);
102
+ },
103
+ };
104
+ /**
105
+ * sessions.create and sessions.fork use this for a fresh destination after generating the request
106
+ * ID. Snapshot the configured identity before invoking the host's session ID generator.
107
+ */
108
+ newBinding() {
109
+ return { ...this.options.identity, sessionId: this.createId() };
110
+ }
111
+ /**
112
+ * dispose caches this release attempt. Close admission before taking the session snapshot and
113
+ * keep the adapter alive until all session disposals settle. Report failures in session order,
114
+ * followed by the adapter failure, through the shared disposal promise.
115
+ */
116
+ async disposeOwnedResources() {
117
+ this.closed = true;
118
+ const sessions = await Promise.allSettled([...this.owned].map(session => session.dispose()));
119
+ const [adapter] = await Promise.allSettled([this.options.adapter.dispose()]);
120
+ this.owned.clear();
121
+ const failures = [...sessions, adapter].filter((result) => result.status === 'rejected');
122
+ if (failures.length)
123
+ throw combined(failures.map(failure => failure.reason));
124
+ }
125
+ /**
126
+ * openSession and sessions.create/resume/fork return this promise to hosts. Validate before
127
+ * allocating a Session, then own it before native callbacks or disposal can race attachment.
128
+ * Resolve the attached session, or remove it and await disposal before rejecting setup/wait failure
129
+ * (aggregated if cleanup fails). attachSession continues to release late backends after wait failure.
130
+ */
131
+ async open(params, wait) {
132
+ const capabilities = this.validateOpen(params, wait);
133
+ const session = new Session(structuredClone(params), this.options, capabilities, this.createId);
134
+ // Own the sink before dispatch: native events and client disposal can race attachment.
135
+ this.owned.add(session);
136
+ try {
137
+ await waitForRequest(() => this.attachSession(params, session), wait, this.options.runtime?.openTimeoutMs ?? 120_000);
138
+ if (this.closed)
139
+ throw new HarnessSdkError('CLIENT_DISPOSED');
140
+ return session;
141
+ }
142
+ catch (cause) {
143
+ this.owned.delete(session);
144
+ return rethrowAfterCleanup(cause, () => session.dispose());
145
+ }
146
+ }
147
+ /**
148
+ * Client.open uses the returned negotiated capabilities to construct Session. Preserve admission
149
+ * precedence: wait validity, abort, client state, protocol shape, binding, then mode capability.
150
+ * Throws before session allocation/native dispatch; open exposes that failure as its rejection.
151
+ */
152
+ validateOpen(params, wait) {
153
+ if (invalidWait(wait))
154
+ throw new HarnessSdkError('INVALID_PARAMS');
155
+ if (wait?.signal?.aborted)
156
+ throw new HarnessSdkError('REQUEST_ABORTED');
157
+ if (this.closed)
158
+ throw new HarnessSdkError('CLIENT_DISPOSED');
159
+ if (!this.initialized)
160
+ throw new HarnessSdkError('NOT_INITIALIZED');
161
+ assertRequest('session/open', params);
162
+ if (!matchesIdentity(params.binding, this.options.identity))
163
+ throw new HarnessSdkError('SESSION_BINDING_MISMATCH');
164
+ assertOpenCapability(params, this.initialized);
165
+ return this.initialized;
166
+ }
167
+ /**
168
+ * Client.open passes this operation to waitForRequest. Give the wrapped adapter a separate params
169
+ * clone and the Session sink, then await attachment/publication. This work outlives caller abort or
170
+ * timeout: OutputSession.open or Session.attach disposes a backend arriving after teardown.
171
+ * Native/attachment failures reject the operation and remain observed by waitForRequest.
172
+ */
173
+ async attachSession(params, session) {
174
+ const backend = await this.options.adapter.openSession(structuredClone(params), session);
175
+ await session.attach(backend);
176
+ }
177
+ }
178
+ /**
179
+ * Create and initialize a client before opening sessions. A pre-aborted wait rejects before the
180
+ * client takes ownership of the adapter. Once construction succeeds, initialization failure triggers
181
+ * disposal before rejection. The original cause is preserved unless disposal also fails, in which
182
+ * case both failures are reported as RESOURCE_RELEASE_FAILED. Await dispose on the returned client.
183
+ * createRefactorClient and createReviewClient return this promise to their CLI commands, which
184
+ * await disposal after the run and report output.
185
+ */
186
+ export async function createClient(options, wait) {
187
+ if (wait?.signal?.aborted)
188
+ throw new HarnessSdkError('REQUEST_ABORTED');
189
+ const client = new Client({
190
+ ...options, identity: { ...options.identity }, runtime: { ...options.runtime },
191
+ interactions: { ...options.interactions }, limits: { ...options.limits },
192
+ });
193
+ try {
194
+ await client.initialize(wait);
195
+ return client;
196
+ }
197
+ catch (cause) {
198
+ return rethrowAfterCleanup(cause, () => client.dispose());
199
+ }
200
+ }
201
+ /**
202
+ * Client's constructor checks host identity, retention limits and interaction registrations here
203
+ * after installing the output wrapper. Throw INVALID_PARAMS before native initialization; no host
204
+ * callback is invoked and no native resource is acquired by these checks.
205
+ */
206
+ function assertClientOptions(options) {
207
+ for (const value of [options.identity.adapterId, options.identity.runtimeConfigId]) {
208
+ if (typeof value !== 'string' || !value.trim())
209
+ throw new HarnessSdkError('INVALID_PARAMS');
210
+ }
211
+ for (const value of Object.values(options.limits ?? {}))
212
+ positive(value, 1);
213
+ for (const [kind, handler] of Object.entries(options.interactions ?? {})) {
214
+ if (!['approval', 'userInput', 'elicitation', 'dynamicTool'].includes(kind)
215
+ || handler !== undefined && typeof handler !== 'function')
216
+ throw new HarnessSdkError('INVALID_PARAMS');
217
+ }
218
+ }
219
+ /**
220
+ * Return whether a binding belongs to this adapter/configuration, without comparing session IDs.
221
+ * Client.sessions.fork rejects foreign sources with this result before generating IDs;
222
+ * Client.validateOpen rejects foreign destination bindings before allocating Session state.
223
+ */
224
+ function matchesIdentity(binding, identity) {
225
+ return binding.adapterId === identity.adapterId && binding.runtimeConfigId === identity.runtimeConfigId;
226
+ }
227
+ /**
228
+ * Client.validateOpen calls this only after protocol shape and binding validation. Reject unsupported
229
+ * resume/fork modes with CAPABILITY_UNSUPPORTED before native work; new sessions need no mode
230
+ * capability. Keep shape checks with the caller so malformed fork details fail before capability checks.
231
+ */
232
+ function assertOpenCapability(params, capabilities) {
233
+ if (params.mode === 'resume' && !supportsCapability(capabilities, 'session.resume')) {
234
+ throw new HarnessSdkError('CAPABILITY_UNSUPPORTED');
235
+ }
236
+ // assertRequest requires fork details in fork mode, although the public type leaves them optional.
237
+ if (params.mode === 'fork' && params.fork && !supportsCapability(capabilities, 'session.fork', params.fork.boundary.relation)) {
238
+ throw new HarnessSdkError('CAPABILITY_UNSUPPORTED');
239
+ }
240
+ }
241
+ /**
242
+ * Client.open supplies Session.dispose after failed attachment/wait; createClient supplies
243
+ * Client.dispose after failed initialization. Await that cleanup before rejecting with the original
244
+ * cause, or a privacy-safe aggregate if cleanup also fails. Callers return this rejection to hosts.
245
+ */
246
+ async function rethrowAfterCleanup(cause, cleanup) {
247
+ try {
248
+ await cleanup();
249
+ }
250
+ catch (cleanupFailure) {
251
+ throw combined([cause, cleanupFailure]);
252
+ }
253
+ throw cause;
254
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Classify a catalog failure for reporting without disclosing raw text. Return the allowlisted
3
+ * string or CATALOG_QUERY_FAILED. An object's own or inherited code property takes precedence,
4
+ * even when invalid. An Error without that property uses its message as the candidate.
5
+ * Property getters and proxy traps may throw synchronously. catalogPhase's failure report uses
6
+ * the returned value as errorCode.
7
+ */
8
+ export declare function safeCatalogErrorCode(cause: unknown): string;
9
+ /**
10
+ * Internal to diagnostics. reportCatalogFailure reads this value for the event's nativeRpcCode.
11
+ * Return undefined for a missing or nonnumeric rpcCode. Preserve separate reads for the type
12
+ * check and the returned value, which an accessor can observe. Property getters and proxy traps
13
+ * can throw synchronously, preventing the report and replacing the phase rejection.
14
+ */
15
+ export declare function readNativeRpcCode(cause: unknown): number | undefined;
@@ -0,0 +1,59 @@
1
+ // Projects failure classifications without retaining native error text.
2
+ /**
3
+ * Classify a catalog failure for reporting without disclosing raw text. Return the allowlisted
4
+ * string or CATALOG_QUERY_FAILED. An object's own or inherited code property takes precedence,
5
+ * even when invalid. An Error without that property uses its message as the candidate.
6
+ * Property getters and proxy traps may throw synchronously. catalogPhase's failure report uses
7
+ * the returned value as errorCode.
8
+ */
9
+ export function safeCatalogErrorCode(cause) {
10
+ let code;
11
+ if (cause && typeof cause === 'object' && 'code' in cause) {
12
+ code = cause.code;
13
+ }
14
+ else if (cause instanceof Error) {
15
+ code = cause.message;
16
+ }
17
+ return typeof code === 'string' && safeCatalogCodes.has(code) ? code : 'CATALOG_QUERY_FAILED';
18
+ }
19
+ /**
20
+ * Internal to diagnostics. reportCatalogFailure reads this value for the event's nativeRpcCode.
21
+ * Return undefined for a missing or nonnumeric rpcCode. Preserve separate reads for the type
22
+ * check and the returned value, which an accessor can observe. Property getters and proxy traps
23
+ * can throw synchronously, preventing the report and replacing the phase rejection.
24
+ */
25
+ export function readNativeRpcCode(cause) {
26
+ if (!cause || typeof cause !== 'object' || !('rpcCode' in cause))
27
+ return undefined;
28
+ if (typeof cause.rpcCode !== 'number')
29
+ return undefined;
30
+ return cause.rpcCode;
31
+ }
32
+ const safeCatalogCodes = new Set([
33
+ 'AUTH_REQUIRED',
34
+ 'AUTH_FORBIDDEN',
35
+ 'HARNESS_UNAVAILABLE',
36
+ 'HARNESS_CONFIG_INVALID',
37
+ 'REQUEST_TIMEOUT',
38
+ 'RPC_ABORTED',
39
+ 'CATALOG_REFRESH_TIMEOUT',
40
+ 'CATALOG_CANCELLED',
41
+ 'CATALOG_LIMIT_EXCEEDED',
42
+ 'CATALOG_SNAPSHOT_INVALID',
43
+ 'CATALOG_MODELS_INVALID',
44
+ 'MODELS_UNAVAILABLE',
45
+ 'NATIVE_RPC_ERROR',
46
+ 'PROTOCOL_ERROR',
47
+ 'CONNECTION_CLOSED',
48
+ 'RUNTIME_DISPOSED',
49
+ 'PROCESS_DISPOSE_FAILED',
50
+ 'PROCESS_WRITE_FAILED',
51
+ 'FRAME_TOO_LARGE',
52
+ 'MODEL_LIMIT_EXCEEDED',
53
+ 'BACKPRESSURE_EXCEEDED',
54
+ 'COMMAND_FORMAT_UNSUPPORTED',
55
+ 'EXECUTABLE_NOT_FOUND',
56
+ 'ENOENT',
57
+ 'EACCES',
58
+ 'EPERM',
59
+ ]);
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Observe a catalog operation's progress in LogTape or an onPhase callback. catalogPhase supplies
3
+ * error classifications. Hosts publishing their own events are responsible for safe field values.
4
+ */
5
+ export interface CatalogProbeEvent {
6
+ /** Operation being observed; diagnostics do not enforce an order between phases. */
7
+ phase: 'spawn' | 'handshake' | 'models' | 'dispose';
8
+ /** catalogPhase reports cancelled when its signal is aborted at failure reporting, regardless of cause. */
9
+ outcome: 'started' | 'success' | 'error' | 'cancelled';
10
+ /**
11
+ * Elapsed milliseconds. catalogPhase includes synchronous started delivery and measures before
12
+ * delivering the terminal event. Started reports omit this field. LogTape receives rounded values.
13
+ */
14
+ durationMs?: number;
15
+ /** Safe allowlisted classification, never raw native error text. */
16
+ errorCode?: string;
17
+ /** Native RPC classification. catalogPhase includes this when the failure supplies a numeric rpcCode. */
18
+ nativeRpcCode?: number;
19
+ }
20
+ /**
21
+ * Configure observation and cancellation for a catalog probe. Codex and Claude catalog sessions
22
+ * accept these options on prepare and listModels and retain responsibility for native cleanup.
23
+ */
24
+ export interface CatalogProbeOptions {
25
+ /** catalogPhase checks this before work, after fulfillment, and when choosing a failure outcome. */
26
+ signal?: AbortSignal;
27
+ /**
28
+ * Optional budget in milliseconds for the probe owner. In Codex, the options that start a
29
+ * connection set its per-RPC timeout, defaulting to 10,000 ms. Hosts set their own deadlines
30
+ * for Claude probes. The diagnostics helpers leave timeout enforcement to the probe owner.
31
+ */
32
+ timeoutMs?: number;
33
+ /**
34
+ * Register an observer for this probe. emitCatalogPhase invokes it synchronously with the original
35
+ * event after attempting to log. Synchronous throws are ignored and the host handles any
36
+ * rejections from asynchronous callbacks.
37
+ */
38
+ onPhase?: (event: CatalogProbeEvent) => void;
39
+ }
40
+ /**
41
+ * Publish a catalog report whose fields are safe to disclose. Attempt to log the known fields, then
42
+ * pass the original event to onPhase even if logging throws. Suppress synchronous observer throws.
43
+ * Return after the synchronous calls, leaving any asynchronous work to sinks and observers.
44
+ * catalogPhase calls this for its start and outcome reports. Hosts configure and dispose LogTape sinks.
45
+ */
46
+ export declare function emitCatalogPhase(options: CatalogProbeOptions, event: CatalogProbeEvent): void;
@@ -0,0 +1,47 @@
1
+ // Publishes catalog observations through host-configured logging and synchronous callbacks.
2
+ import { getLogger } from '@logtape/logtape';
3
+ /**
4
+ * Publish a catalog report whose fields are safe to disclose. Attempt to log the known fields, then
5
+ * pass the original event to onPhase even if logging throws. Suppress synchronous observer throws.
6
+ * Return after the synchronous calls, leaving any asynchronous work to sinks and observers.
7
+ * catalogPhase calls this for its start and outcome reports. Hosts configure and dispose LogTape sinks.
8
+ */
9
+ export function emitCatalogPhase(options, event) {
10
+ logCatalogPhase(event);
11
+ try {
12
+ options.onPhase?.(event);
13
+ }
14
+ catch { /* An observer failure must leave the probe outcome intact. */ }
15
+ }
16
+ const catalogLogger = getLogger(['anureo', 'catalog']);
17
+ /**
18
+ * emitCatalogPhase calls this before its observer so callback mutation cannot change logged fields.
19
+ * Route errors to error, cancellations to warning, and other outcomes to debug. Suppress projection
20
+ * and logging failures so emitCatalogPhase can still invoke the observer.
21
+ */
22
+ function logCatalogPhase(event) {
23
+ try {
24
+ const properties = catalogLogProperties(event);
25
+ const message = 'catalog {phase} {outcome}';
26
+ if (event.outcome === 'error')
27
+ catalogLogger.error(message, properties);
28
+ else if (event.outcome === 'cancelled')
29
+ catalogLogger.warn(message, properties);
30
+ else
31
+ catalogLogger.debug(message, properties);
32
+ }
33
+ catch { /* Deliver the event to the observer even when logging fails. */ }
34
+ }
35
+ /**
36
+ * Give logCatalogPhase only the fields intended for logging. Round duration on the copy so
37
+ * observers still receive the original value. Reading the event can throw before logging.
38
+ */
39
+ function catalogLogProperties(event) {
40
+ return {
41
+ phase: event.phase,
42
+ outcome: event.outcome,
43
+ ...(event.durationMs === undefined ? {} : { durationMs: Math.round(event.durationMs) }),
44
+ ...(event.errorCode === undefined ? {} : { errorCode: event.errorCode }),
45
+ ...(event.nativeRpcCode === undefined ? {} : { nativeRpcCode: event.nativeRpcCode }),
46
+ };
47
+ }
@@ -0,0 +1,20 @@
1
+ import { type CatalogProbeEvent, type CatalogProbeOptions } from "./diagnostics/events.js";
2
+ export { emitCatalogPhase } from "./diagnostics/events.js";
3
+ export type { CatalogProbeEvent, CatalogProbeOptions } from "./diagnostics/events.js";
4
+ export { safeCatalogErrorCode } from "./diagnostics/errors.js";
5
+ /**
6
+ * Run one catalog phase and report its start and outcome. Deliver started before checking the
7
+ * signal, then invoke work at most once. Check cancellation again after work fulfills. Cancellation
8
+ * at either check rejects with RPC_ABORTED. While work is pending, the phase waits for it to settle.
9
+ * Resolve with the work value after success delivery, or reject with the original synchronous throw
10
+ * or promise rejection after failure delivery, including when that outcome is cancelled.
11
+ *
12
+ * The probe owner enforces deadlines, interrupts native work if needed, and releases resources
13
+ * even when the phase discards a successful result. Logger and synchronous observer failures are
14
+ * isolated. A cause or signal property that throws during failure reporting prevents terminal
15
+ * delivery and becomes the rejection.
16
+ *
17
+ * The Codex and Claude catalog sessions await the spawn and handshake phases during preparation and
18
+ * return the models phase value from listModels. Session users must await dispose after use.
19
+ */
20
+ export declare function catalogPhase<T>(options: CatalogProbeOptions, phase: CatalogProbeEvent['phase'], work: () => Promise<T>): Promise<T>;
@@ -0,0 +1,56 @@
1
+ // Observes catalog work while probe owners retain native resources and cleanup.
2
+ import { emitCatalogPhase } from "./diagnostics/events.js";
3
+ import { readNativeRpcCode, safeCatalogErrorCode } from "./diagnostics/errors.js";
4
+ export { emitCatalogPhase } from "./diagnostics/events.js";
5
+ export { safeCatalogErrorCode } from "./diagnostics/errors.js";
6
+ /**
7
+ * Run one catalog phase and report its start and outcome. Deliver started before checking the
8
+ * signal, then invoke work at most once. Check cancellation again after work fulfills. Cancellation
9
+ * at either check rejects with RPC_ABORTED. While work is pending, the phase waits for it to settle.
10
+ * Resolve with the work value after success delivery, or reject with the original synchronous throw
11
+ * or promise rejection after failure delivery, including when that outcome is cancelled.
12
+ *
13
+ * The probe owner enforces deadlines, interrupts native work if needed, and releases resources
14
+ * even when the phase discards a successful result. Logger and synchronous observer failures are
15
+ * isolated. A cause or signal property that throws during failure reporting prevents terminal
16
+ * delivery and becomes the rejection.
17
+ *
18
+ * The Codex and Claude catalog sessions await the spawn and handshake phases during preparation and
19
+ * return the models phase value from listModels. Session users must await dispose after use.
20
+ */
21
+ export async function catalogPhase(options, phase, work) {
22
+ const started = performance.now();
23
+ emitCatalogPhase(options, { phase, outcome: 'started' });
24
+ try {
25
+ throwIfCatalogAborted(options.signal);
26
+ const result = await work();
27
+ throwIfCatalogAborted(options.signal);
28
+ emitCatalogPhase(options, { phase, outcome: 'success', durationMs: performance.now() - started });
29
+ return result;
30
+ }
31
+ catch (cause) {
32
+ reportCatalogFailure(options, phase, started, cause);
33
+ throw cause;
34
+ }
35
+ }
36
+ /** Throw RPC_ABORTED at catalogPhase's pre/post-work checks so an aborted probe cannot publish success. */
37
+ function throwIfCatalogAborted(signal) {
38
+ if (signal?.aborted)
39
+ throw Object.assign(new Error('RPC_ABORTED'), { code: 'RPC_ABORTED' });
40
+ }
41
+ /**
42
+ * Publish catalogPhase's failure report after classifying the cause, then let catalogPhase rethrow
43
+ * that cause. Classify before sampling cancellation so the outcome reflects the signal at reporting
44
+ * time. If classification or sampling throws, that failure escapes before a report is delivered.
45
+ */
46
+ function reportCatalogFailure(options, phase, startedAt, cause) {
47
+ const errorCode = safeCatalogErrorCode(cause);
48
+ const nativeRpcCode = readNativeRpcCode(cause);
49
+ emitCatalogPhase(options, {
50
+ phase,
51
+ outcome: options.signal?.aborted ? 'cancelled' : 'error',
52
+ durationMs: performance.now() - startedAt,
53
+ errorCode,
54
+ ...(nativeRpcCode === undefined ? {} : { nativeRpcCode }),
55
+ });
56
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Inspect SDK or adapter failures by code and outcome. Producers must supply safe messages and
3
+ * details. Check outcomeUnknown before retrying an operation that may already have taken effect.
4
+ */
5
+ export declare class HarnessSdkError extends Error {
6
+ /** Safe machine-readable failure code selected by the owning subsystem. */
7
+ readonly code: string;
8
+ /** Whether the operation’s safe failure policy permits a caller retry. */
9
+ readonly retryable: boolean;
10
+ /** Native acceptance/completion could not be established; retry may duplicate work. */
11
+ readonly outcomeUnknown: boolean;
12
+ /** Already-sanitized structured diagnostics supplied by the owning subsystem. */
13
+ readonly details?: Record<string, unknown> | undefined;
14
+ /**
15
+ * Create a failure with an already-sanitized code, message and diagnostics. The message defaults
16
+ * to the code, both flags default to false, and details are retained by reference.
17
+ */
18
+ constructor(
19
+ /** Safe machine-readable failure code selected by the owning subsystem. */
20
+ code: string,
21
+ /** Caller-safe message; never populate this with an unfiltered native cause. */
22
+ message?: string,
23
+ /** Whether the operation’s safe failure policy permits a caller retry. */
24
+ retryable?: boolean,
25
+ /** Native acceptance/completion could not be established; retry may duplicate work. */
26
+ outcomeUnknown?: boolean,
27
+ /** Already-sanitized structured diagnostics supplied by the owning subsystem. */
28
+ details?: Record<string, unknown> | undefined);
29
+ /** Error name used in diagnostics. */
30
+ readonly name = "HarnessSdkError";
31
+ }
32
+ /**
33
+ * Normalize a failure for SDK or adapter callers. Return existing HarnessSdkError instances by
34
+ * identity. For other objects with a code matching A-Z followed by up to 80 uppercase letters,
35
+ * digits or underscores, use the code as the message and copy only literal-true flags. A present
36
+ * outcomeUnknown property on such an object overrides the supplied default. Other causes use
37
+ * fallback (HARNESS_ERROR by default) as both code and message, with retryable false and the
38
+ * supplied outcomeUnknown default. Supply a safe fallback. Property access can throw if the
39
+ * cause has throwing getters or proxy traps.
40
+ *
41
+ * Called by:
42
+ * - Session.onRequestFailed to choose recovery state and publish safe submission failure status.
43
+ * - InteractionRegistry.deliverResponse to classify responder or publication failures for the
44
+ * interaction waiter and, during delivery, for publication of an unknown outcome.
45
+ * - checkRelease in structured-output/errors to throw safe failures after cleanup attempts settle.
46
+ * Public consumers can apply the same normalization; callers own propagation, retries and cleanup.
47
+ */
48
+ export declare function sdkError(cause: unknown, fallback?: string, outcomeUnknown?: boolean): HarnessSdkError;