@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.
- package/LICENSE +201 -0
- package/README.md +185 -2
- package/package.json +66 -4
- package/src/adapter/identity.d.ts +41 -0
- package/src/adapter/identity.js +71 -0
- package/src/client-options.d.ts +71 -0
- package/src/client-options.js +0 -0
- package/src/client.d.ts +106 -0
- package/src/client.js +254 -0
- package/src/diagnostics/errors.d.ts +15 -0
- package/src/diagnostics/errors.js +59 -0
- package/src/diagnostics/events.d.ts +46 -0
- package/src/diagnostics/events.js +47 -0
- package/src/diagnostics.d.ts +20 -0
- package/src/diagnostics.js +56 -0
- package/src/errors.d.ts +48 -0
- package/src/errors.js +63 -0
- package/src/index.d.ts +27 -0
- package/src/index.js +23 -0
- package/src/lifecycle/approval.d.ts +16 -0
- package/src/lifecycle/approval.js +18 -0
- package/src/lifecycle/deferred.d.ts +10 -0
- package/src/lifecycle/deferred.js +12 -0
- package/src/lifecycle/errors.d.ts +18 -0
- package/src/lifecycle/errors.js +45 -0
- package/src/lifecycle/events.d.ts +35 -0
- package/src/lifecycle/events.js +141 -0
- package/src/lifecycle/execution-observation.d.ts +33 -0
- package/src/lifecycle/execution-observation.js +0 -0
- package/src/lifecycle/immutable.d.ts +12 -0
- package/src/lifecycle/immutable.js +28 -0
- package/src/lifecycle/input.d.ts +9 -0
- package/src/lifecycle/input.js +19 -0
- package/src/lifecycle/interactions.d.ts +129 -0
- package/src/lifecycle/interactions.js +282 -0
- package/src/lifecycle/limits.d.ts +7 -0
- package/src/lifecycle/limits.js +13 -0
- package/src/lifecycle/result.d.ts +77 -0
- package/src/lifecycle/result.js +213 -0
- package/src/lifecycle/value.d.ts +4 -0
- package/src/lifecycle/value.js +5 -0
- package/src/ports.d.ts +224 -0
- package/src/ports.js +0 -0
- package/src/privacy/file-change-limits.d.ts +20 -0
- package/src/privacy/file-change-limits.js +124 -0
- package/src/privacy/secrets.d.ts +205 -0
- package/src/privacy/secrets.js +564 -0
- package/src/process.d.ts +92 -0
- package/src/process.js +331 -0
- package/src/queue.d.ts +143 -0
- package/src/queue.js +266 -0
- package/src/request.d.ts +28 -0
- package/src/request.js +81 -0
- package/src/requests/guard.d.ts +15 -0
- package/src/requests/guard.js +93 -0
- package/src/session.d.ts +459 -0
- package/src/session.js +1135 -0
- package/src/structured-output/adapter.d.ts +8 -0
- package/src/structured-output/adapter.js +54 -0
- package/src/structured-output/bridge.d.ts +81 -0
- package/src/structured-output/bridge.js +274 -0
- package/src/structured-output/errors.d.ts +17 -0
- package/src/structured-output/errors.js +31 -0
- package/src/structured-output/limits.d.ts +19 -0
- package/src/structured-output/limits.js +0 -0
- package/src/structured-output/schema.d.ts +20 -0
- package/src/structured-output/schema.js +122 -0
- package/src/structured-output/session.d.ts +329 -0
- package/src/structured-output/session.js +846 -0
- package/src/structured-output/stdio.mjs +83 -0
- package/src/structured-output/tool.d.ts +38 -0
- package/src/structured-output/tool.js +34 -0
- package/src/turn.d.ts +75 -0
- package/src/turn.js +95 -0
- package/src/types.d.ts +62 -0
- package/src/types.js +0 -0
package/src/errors.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
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 class HarnessSdkError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
retryable;
|
|
8
|
+
outcomeUnknown;
|
|
9
|
+
details;
|
|
10
|
+
/**
|
|
11
|
+
* Create a failure with an already-sanitized code, message and diagnostics. The message defaults
|
|
12
|
+
* to the code, both flags default to false, and details are retained by reference.
|
|
13
|
+
*/
|
|
14
|
+
constructor(
|
|
15
|
+
/** Safe machine-readable failure code selected by the owning subsystem. */
|
|
16
|
+
code,
|
|
17
|
+
/** Caller-safe message; never populate this with an unfiltered native cause. */
|
|
18
|
+
message = code,
|
|
19
|
+
/** Whether the operation’s safe failure policy permits a caller retry. */
|
|
20
|
+
retryable = false,
|
|
21
|
+
/** Native acceptance/completion could not be established; retry may duplicate work. */
|
|
22
|
+
outcomeUnknown = false,
|
|
23
|
+
/** Already-sanitized structured diagnostics supplied by the owning subsystem. */
|
|
24
|
+
details) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.code = code;
|
|
27
|
+
this.retryable = retryable;
|
|
28
|
+
this.outcomeUnknown = outcomeUnknown;
|
|
29
|
+
this.details = details;
|
|
30
|
+
}
|
|
31
|
+
/** Error name used in diagnostics. */
|
|
32
|
+
name = "HarnessSdkError";
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Normalize a failure for SDK or adapter callers. Return existing HarnessSdkError instances by
|
|
36
|
+
* identity. For other objects with a code matching A-Z followed by up to 80 uppercase letters,
|
|
37
|
+
* digits or underscores, use the code as the message and copy only literal-true flags. A present
|
|
38
|
+
* outcomeUnknown property on such an object overrides the supplied default. Other causes use
|
|
39
|
+
* fallback (HARNESS_ERROR by default) as both code and message, with retryable false and the
|
|
40
|
+
* supplied outcomeUnknown default. Supply a safe fallback. Property access can throw if the
|
|
41
|
+
* cause has throwing getters or proxy traps.
|
|
42
|
+
*
|
|
43
|
+
* Called by:
|
|
44
|
+
* - Session.onRequestFailed to choose recovery state and publish safe submission failure status.
|
|
45
|
+
* - InteractionRegistry.deliverResponse to classify responder or publication failures for the
|
|
46
|
+
* interaction waiter and, during delivery, for publication of an unknown outcome.
|
|
47
|
+
* - checkRelease in structured-output/errors to throw safe failures after cleanup attempts settle.
|
|
48
|
+
* Public consumers can apply the same normalization; callers own propagation, retries and cleanup.
|
|
49
|
+
*/
|
|
50
|
+
export function sdkError(cause, fallback = "HARNESS_ERROR", outcomeUnknown = false) {
|
|
51
|
+
if (cause instanceof HarnessSdkError)
|
|
52
|
+
return cause;
|
|
53
|
+
if (cause && typeof cause === "object" && "code" in cause
|
|
54
|
+
&& typeof cause.code === "string" && /^[A-Z][A-Z0-9_]{0,80}$/.test(cause.code)) {
|
|
55
|
+
const code = cause.code;
|
|
56
|
+
const message = cause.code;
|
|
57
|
+
const retryable = "retryable" in cause && cause.retryable === true;
|
|
58
|
+
const resolvedOutcomeUnknown = "outcomeUnknown" in cause ? cause.outcomeUnknown === true : outcomeUnknown;
|
|
59
|
+
return new HarnessSdkError(code, message, retryable, resolvedOutcomeUnknown);
|
|
60
|
+
}
|
|
61
|
+
// Do not include raw backend errors: they may contain credentials or private answers.
|
|
62
|
+
return new HarnessSdkError(fallback, fallback, false, outcomeUnknown);
|
|
63
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export { createClient, Client } from "./client.js";
|
|
2
|
+
export { Session } from "./session.js";
|
|
3
|
+
export type { ClientOptions, InteractionHandlers, SubmitOptions } from "./client-options.js";
|
|
4
|
+
export { Turn } from "./turn.js";
|
|
5
|
+
export type { EventSubscriptionOptions, Input, RequestOptions, TurnResult } from "./types.js";
|
|
6
|
+
export type * from "./ports.js";
|
|
7
|
+
export * from "./errors.js";
|
|
8
|
+
export { waitForRequest } from "./request.js";
|
|
9
|
+
/** Adapter/observer buffering primitives, also available from the package's queue entry point. */
|
|
10
|
+
export * from "./queue.js";
|
|
11
|
+
/** Session-local native ID mapping implementation. */
|
|
12
|
+
export * from "./adapter/identity.js";
|
|
13
|
+
/** Interaction settlement and replay implementation used by Session. */
|
|
14
|
+
export * from "./lifecycle/interactions.js";
|
|
15
|
+
/** Adapter subprocess handles and launch helpers, also available from the process entry point; callers drain output and await handle disposal. */
|
|
16
|
+
export * from "./process.js";
|
|
17
|
+
/** Marked-secret projection implementation used by Session. */
|
|
18
|
+
export * from "./privacy/secrets.js";
|
|
19
|
+
/**
|
|
20
|
+
* Forward LogTape's host logging controls without adding SDK ownership. The exported dispose is
|
|
21
|
+
* LogTape's logging teardown; native resources are released through Client.dispose separately.
|
|
22
|
+
*/
|
|
23
|
+
export { configure, configureSync, dispose, disposeSync, drain, getLogger, reset, resetSync } from '@logtape/logtape';
|
|
24
|
+
/** LogTape types for hosts configuring logging through the forwarded functions above. */
|
|
25
|
+
export type { Config, LogLevel, LogRecord, Logger, Sink } from '@logtape/logtape';
|
|
26
|
+
/** Explicit unattended approval policy for hosts; never enabled by Client automatically. */
|
|
27
|
+
export { approveWithNarrowestScope } from "./lifecycle/approval.js";
|
package/src/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Root entry point for the application API, adapter contracts and host utilities.
|
|
2
|
+
export { createClient, Client } from "./client.js";
|
|
3
|
+
export { Session } from "./session.js";
|
|
4
|
+
export { Turn } from "./turn.js";
|
|
5
|
+
export * from "./errors.js";
|
|
6
|
+
export { waitForRequest } from "./request.js";
|
|
7
|
+
/** Adapter/observer buffering primitives, also available from the package's queue entry point. */
|
|
8
|
+
export * from "./queue.js";
|
|
9
|
+
/** Session-local native ID mapping implementation. */
|
|
10
|
+
export * from "./adapter/identity.js";
|
|
11
|
+
/** Interaction settlement and replay implementation used by Session. */
|
|
12
|
+
export * from "./lifecycle/interactions.js";
|
|
13
|
+
/** Adapter subprocess handles and launch helpers, also available from the process entry point; callers drain output and await handle disposal. */
|
|
14
|
+
export * from "./process.js";
|
|
15
|
+
/** Marked-secret projection implementation used by Session. */
|
|
16
|
+
export * from "./privacy/secrets.js";
|
|
17
|
+
/**
|
|
18
|
+
* Forward LogTape's host logging controls without adding SDK ownership. The exported dispose is
|
|
19
|
+
* LogTape's logging teardown; native resources are released through Client.dispose separately.
|
|
20
|
+
*/
|
|
21
|
+
export { configure, configureSync, dispose, disposeSync, drain, getLogger, reset, resetSync } from '@logtape/logtape';
|
|
22
|
+
/** Explicit unattended approval policy for hosts; never enabled by Client automatically. */
|
|
23
|
+
export { approveWithNarrowestScope } from "./lifecycle/approval.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { InteractionRequested, InteractionResponse } from '@anureo/protocol';
|
|
2
|
+
/**
|
|
3
|
+
* Select an advertised grant for unattended approval. Prefer allow-once, then session, then
|
|
4
|
+
* rule grants. When several options have the same scope, return the first advertised option.
|
|
5
|
+
* Reject with Error when the request offers none of these grants.
|
|
6
|
+
*
|
|
7
|
+
* Opt in through ClientOptions.interactions.approval, as createReviewClient and
|
|
8
|
+
* createRefactorClient do. The returned promise completes when the answer is selected.
|
|
9
|
+
* Session.startInteractionHandler invokes the registered policy and awaits native delivery
|
|
10
|
+
* separately. If selection rejects while the signal is active, Session attempts cancellation.
|
|
11
|
+
*/
|
|
12
|
+
export declare function approveWithNarrowestScope(request: Extract<InteractionRequested, {
|
|
13
|
+
kind: 'approval';
|
|
14
|
+
}>): Promise<Extract<InteractionResponse, {
|
|
15
|
+
kind: 'approval';
|
|
16
|
+
}>>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Select an advertised grant for unattended approval. Prefer allow-once, then session, then
|
|
3
|
+
* rule grants. When several options have the same scope, return the first advertised option.
|
|
4
|
+
* Reject with Error when the request offers none of these grants.
|
|
5
|
+
*
|
|
6
|
+
* Opt in through ClientOptions.interactions.approval, as createReviewClient and
|
|
7
|
+
* createRefactorClient do. The returned promise completes when the answer is selected.
|
|
8
|
+
* Session.startInteractionHandler invokes the registered policy and awaits native delivery
|
|
9
|
+
* separately. If selection rejects while the signal is active, Session attempts cancellation.
|
|
10
|
+
*/
|
|
11
|
+
export async function approveWithNarrowestScope(request) {
|
|
12
|
+
const allow = request.options.find(option => option.semantic === 'allowOnce')
|
|
13
|
+
?? request.options.find(option => option.semantic === 'allowSession')
|
|
14
|
+
?? request.options.find(option => option.semantic === 'allowWithRule');
|
|
15
|
+
if (!allow)
|
|
16
|
+
throw new Error('Approval request has no allow option');
|
|
17
|
+
return { kind: 'approval', optionId: allow.id };
|
|
18
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a package-internal promise with caller-owned settlement. Session uses it for observation
|
|
3
|
+
* closure, retained execution results and submission binding. A preinstalled rejection observer
|
|
4
|
+
* allows callers to attach later and still receive the original rejection.
|
|
5
|
+
*/
|
|
6
|
+
export declare function deferred<T>(): {
|
|
7
|
+
promise: Promise<T>;
|
|
8
|
+
resolve: (value: T) => void;
|
|
9
|
+
reject: (cause: unknown) => void;
|
|
10
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a package-internal promise with caller-owned settlement. Session uses it for observation
|
|
3
|
+
* closure, retained execution results and submission binding. A preinstalled rejection observer
|
|
4
|
+
* allows callers to attach later and still receive the original rejection.
|
|
5
|
+
*/
|
|
6
|
+
export function deferred() {
|
|
7
|
+
let resolve;
|
|
8
|
+
let reject;
|
|
9
|
+
const promise = new Promise((yes, no) => { resolve = yes; reject = no; });
|
|
10
|
+
void promise.catch(() => { });
|
|
11
|
+
return { promise, resolve, reject };
|
|
12
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { HarnessSdkError } from "../errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Construct the package-internal error used to reject unfinished Turn results and streams.
|
|
4
|
+
* Session.finishClose passes native stop confirmation; Session.dispose uses the unconfirmed default.
|
|
5
|
+
* Even confirmed stop supplies no terminal result, so outcomeUnknown stays true. Callers still own
|
|
6
|
+
* interaction cancellation and backend release; constructing this error performs neither.
|
|
7
|
+
*/
|
|
8
|
+
export declare function observationClosed({ executionStopped }?: {
|
|
9
|
+
executionStopped?: 'confirmed' | 'unconfirmed';
|
|
10
|
+
}): HarnessSdkError;
|
|
11
|
+
/**
|
|
12
|
+
* Construct a package-internal aggregate of cleanup failures without releasing resources.
|
|
13
|
+
* Client.dispose and Session.finishClose throw this after their release attempts. The client's
|
|
14
|
+
* rethrowAfterCleanup uses it when setup and cleanup both fail. Deduplicate input causes and
|
|
15
|
+
* flatten nested release diagnostics in order. The returned error contains codes and unknown-outcome
|
|
16
|
+
* flags so raw failure messages stay out of aggregate diagnostics.
|
|
17
|
+
*/
|
|
18
|
+
export declare function combined(causes: unknown[]): HarnessSdkError;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { HarnessSdkError, sdkError } from "../errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Construct the package-internal error used to reject unfinished Turn results and streams.
|
|
4
|
+
* Session.finishClose passes native stop confirmation; Session.dispose uses the unconfirmed default.
|
|
5
|
+
* Even confirmed stop supplies no terminal result, so outcomeUnknown stays true. Callers still own
|
|
6
|
+
* interaction cancellation and backend release; constructing this error performs neither.
|
|
7
|
+
*/
|
|
8
|
+
export function observationClosed({ executionStopped = 'unconfirmed' } = {}) {
|
|
9
|
+
const message = executionStopped === 'confirmed'
|
|
10
|
+
? 'Observation closed before a native terminal result'
|
|
11
|
+
: 'Observation closed; execution stop is unconfirmed';
|
|
12
|
+
return new HarnessSdkError('OBSERVATION_CLOSED', message, false, true, { source: 'sdk', executionStopped });
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Construct a package-internal aggregate of cleanup failures without releasing resources.
|
|
16
|
+
* Client.dispose and Session.finishClose throw this after their release attempts. The client's
|
|
17
|
+
* rethrowAfterCleanup uses it when setup and cleanup both fail. Deduplicate input causes and
|
|
18
|
+
* flatten nested release diagnostics in order. The returned error contains codes and unknown-outcome
|
|
19
|
+
* flags so raw failure messages stay out of aggregate diagnostics.
|
|
20
|
+
*/
|
|
21
|
+
export function combined(causes) {
|
|
22
|
+
const errors = [...new Set(causes)].flatMap(releaseDiagnostics);
|
|
23
|
+
return new HarnessSdkError('RESOURCE_RELEASE_FAILED', 'One or more owned resources could not be released', false, true, { source: 'sdk', errors });
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* releaseDiagnostics filters nested aggregates with this guard before copying their codes. Accept
|
|
27
|
+
* only bounded machine-readable codes; do not trust nested messages or arbitrary error details.
|
|
28
|
+
*/
|
|
29
|
+
function isReleaseDiagnostic(value) {
|
|
30
|
+
return !!value && typeof value === 'object' && 'code' in value
|
|
31
|
+
&& typeof value.code === 'string' && /^[A-Z][A-Z0-9_]{0,80}$/.test(value.code);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* combined flat-maps each distinct cause through this normalization. Flatten an existing release
|
|
35
|
+
* aggregate or return one safe code/unknown flag, preserving input order and discarding raw causes.
|
|
36
|
+
* No cleanup is attempted; callers decide when to throw the aggregate.
|
|
37
|
+
*/
|
|
38
|
+
function releaseDiagnostics(cause) {
|
|
39
|
+
const error = sdkError(cause);
|
|
40
|
+
if (error.code === 'RESOURCE_RELEASE_FAILED' && Array.isArray(error.details?.errors)) {
|
|
41
|
+
return error.details.errors.filter(isReleaseDiagnostic)
|
|
42
|
+
.map(value => ({ code: value.code, outcomeUnknown: value.outcomeUnknown === true }));
|
|
43
|
+
}
|
|
44
|
+
return [{ code: error.code, outcomeUnknown: error.outcomeUnknown }];
|
|
45
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { EventSubscriptionOptions } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Package-internal fan-out for bounded live subscriptions without replay or producer backpressure.
|
|
4
|
+
* Session.publish feeds the session hub and Session.retainEvent feeds each execution hub.
|
|
5
|
+
* Session orders and ends those streams. Consumers release their own subscriptions.
|
|
6
|
+
*/
|
|
7
|
+
export declare class EventHub<T> {
|
|
8
|
+
/**
|
|
9
|
+
* Subscribe to future events with an independent queue. Session.events and Turn.events return
|
|
10
|
+
* this iterator to host consumers, who exhaust or return it, or abort the signal to release it.
|
|
11
|
+
* Invalid budgets throw synchronously. The optional filter defaults to accepting all events. An
|
|
12
|
+
* overlapping blocked read rejects with CONCURRENT_EVENT_READ and ends the subscription.
|
|
13
|
+
* Cancelling a live subscription completes its blocked and future reads and skips any
|
|
14
|
+
* buffered events. Once detached by termination or failure, abort no longer affects the reader.
|
|
15
|
+
* Late subscribers observe the stored termination even if their signal is already aborted.
|
|
16
|
+
*/
|
|
17
|
+
subscribe(options?: EventSubscriptionOptions, accepts?: (value: T) => boolean): AsyncIterableIterator<T>;
|
|
18
|
+
/**
|
|
19
|
+
* Session.publish sends session events here; Session.retainEvent sends execution events here before
|
|
20
|
+
* ending terminal streams. Offer the event synchronously to each subscriber in registration
|
|
21
|
+
* order. Each subscriber filters and clones it independently. A filter, clone or queue admission
|
|
22
|
+
* failure closes that subscription and lets delivery continue. Ignore publication after hub end.
|
|
23
|
+
*/
|
|
24
|
+
publish(value: T): void;
|
|
25
|
+
/**
|
|
26
|
+
* Session.retainEvent ends terminal execution streams; Session.endObservation ends remaining
|
|
27
|
+
* execution and session streams with a failure. Idempotently end all observers with an optional read
|
|
28
|
+
* failure and remove their abort listeners. Queued events drain before a truthy cause rejects reads
|
|
29
|
+
* (otherwise reads complete). Later subscribers observe the first termination without replay.
|
|
30
|
+
* Consumers still own returning or exhausting their readers.
|
|
31
|
+
*/
|
|
32
|
+
end(cause?: unknown): void;
|
|
33
|
+
private readonly subscribers;
|
|
34
|
+
private termination?;
|
|
35
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Owns isolated live event fan-out and each observer's queue, reader and cancellation lifetime.
|
|
2
|
+
import { BoundedEventQueue } from "../queue.js";
|
|
3
|
+
/**
|
|
4
|
+
* Package-internal fan-out for bounded live subscriptions without replay or producer backpressure.
|
|
5
|
+
* Session.publish feeds the session hub and Session.retainEvent feeds each execution hub.
|
|
6
|
+
* Session orders and ends those streams. Consumers release their own subscriptions.
|
|
7
|
+
*/
|
|
8
|
+
export class EventHub {
|
|
9
|
+
/**
|
|
10
|
+
* Subscribe to future events with an independent queue. Session.events and Turn.events return
|
|
11
|
+
* this iterator to host consumers, who exhaust or return it, or abort the signal to release it.
|
|
12
|
+
* Invalid budgets throw synchronously. The optional filter defaults to accepting all events. An
|
|
13
|
+
* overlapping blocked read rejects with CONCURRENT_EVENT_READ and ends the subscription.
|
|
14
|
+
* Cancelling a live subscription completes its blocked and future reads and skips any
|
|
15
|
+
* buffered events. Once detached by termination or failure, abort no longer affects the reader.
|
|
16
|
+
* Late subscribers observe the stored termination even if their signal is already aborted.
|
|
17
|
+
*/
|
|
18
|
+
subscribe(options = {}, accepts = () => true) {
|
|
19
|
+
const subscription = createSubscription({ options, accepts, remove: observer => this.subscribers.delete(observer) });
|
|
20
|
+
if (this.termination) {
|
|
21
|
+
subscription.queue.end(this.termination.cause);
|
|
22
|
+
}
|
|
23
|
+
else if (options.signal?.aborted) {
|
|
24
|
+
subscription.cancel();
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
this.subscribers.add(subscription);
|
|
28
|
+
options.signal?.addEventListener('abort', subscription.cancel, { once: true });
|
|
29
|
+
}
|
|
30
|
+
return subscription.iterator;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Session.publish sends session events here; Session.retainEvent sends execution events here before
|
|
34
|
+
* ending terminal streams. Offer the event synchronously to each subscriber in registration
|
|
35
|
+
* order. Each subscriber filters and clones it independently. A filter, clone or queue admission
|
|
36
|
+
* failure closes that subscription and lets delivery continue. Ignore publication after hub end.
|
|
37
|
+
*/
|
|
38
|
+
publish(value) {
|
|
39
|
+
if (this.termination)
|
|
40
|
+
return;
|
|
41
|
+
for (const subscriber of this.subscribers)
|
|
42
|
+
deliverToSubscriber(subscriber, value);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Session.retainEvent ends terminal execution streams; Session.endObservation ends remaining
|
|
46
|
+
* execution and session streams with a failure. Idempotently end all observers with an optional read
|
|
47
|
+
* failure and remove their abort listeners. Queued events drain before a truthy cause rejects reads
|
|
48
|
+
* (otherwise reads complete). Later subscribers observe the first termination without replay.
|
|
49
|
+
* Consumers still own returning or exhausting their readers.
|
|
50
|
+
*/
|
|
51
|
+
end(cause) {
|
|
52
|
+
if (this.termination)
|
|
53
|
+
return;
|
|
54
|
+
this.termination = { cause };
|
|
55
|
+
for (const subscriber of this.subscribers) {
|
|
56
|
+
subscriber.end(cause);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
subscribers = new Set();
|
|
60
|
+
termination;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* EventHub.subscribe allocates an observer here before deciding whether to register it or close it.
|
|
64
|
+
* Queue construction can throw before registration. Closure calls remove to release hub membership
|
|
65
|
+
* and leaves the reader available to drain. Cancellation also returns the reader to release ownership.
|
|
66
|
+
*/
|
|
67
|
+
function createSubscription({ options, accepts, remove }) {
|
|
68
|
+
const queue = createSubscriptionQueue(options);
|
|
69
|
+
const reader = queue[Symbol.asyncIterator]();
|
|
70
|
+
let cancelled = false;
|
|
71
|
+
/** Remove hub membership and the listener registered by subscribe, leaving the reader available to drain. */
|
|
72
|
+
const detach = () => {
|
|
73
|
+
remove(subscription);
|
|
74
|
+
options.signal?.removeEventListener('abort', cancelSubscription);
|
|
75
|
+
};
|
|
76
|
+
/** Hub termination and delivery/read failures detach before closing admission, preserving buffered drain. */
|
|
77
|
+
const endSubscription = (cause) => {
|
|
78
|
+
detach();
|
|
79
|
+
queue.end(cause);
|
|
80
|
+
};
|
|
81
|
+
/** subscribe registers this abort listener; early iterator exit also skips retained data and releases ownership. */
|
|
82
|
+
const cancelSubscription = () => {
|
|
83
|
+
cancelled = true;
|
|
84
|
+
endSubscription();
|
|
85
|
+
void reader.return?.();
|
|
86
|
+
};
|
|
87
|
+
const iterator = {
|
|
88
|
+
/** Reuse the claimed reader for for-await, without acquiring a second queue reader. */
|
|
89
|
+
[Symbol.asyncIterator]() { return this; },
|
|
90
|
+
/** Await queue delivery/termination and propagate read failures after detaching; cancellation skips retained data. */
|
|
91
|
+
async next() {
|
|
92
|
+
if (cancelled)
|
|
93
|
+
return { done: true, value: undefined };
|
|
94
|
+
try {
|
|
95
|
+
const result = await reader.next();
|
|
96
|
+
if (result.done)
|
|
97
|
+
detach();
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
catch (cause) {
|
|
101
|
+
endSubscription(cause);
|
|
102
|
+
throw cause;
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
/** Early exit completes this observer's blocked reads without stopping the shared producer. */
|
|
106
|
+
async return() {
|
|
107
|
+
cancelSubscription();
|
|
108
|
+
return { done: true, value: undefined };
|
|
109
|
+
},
|
|
110
|
+
/** Release this observer before rejecting with the consumer's supplied cause. */
|
|
111
|
+
async throw(cause) {
|
|
112
|
+
cancelSubscription();
|
|
113
|
+
throw cause;
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
const subscription = { queue, accepts, end: endSubscription, cancel: cancelSubscription, iterator };
|
|
117
|
+
return subscription;
|
|
118
|
+
}
|
|
119
|
+
/** Allocate an observer's queue, capping individual events at 8 MiB or the smaller total byte budget. Invalid limits throw before registration. */
|
|
120
|
+
function createSubscriptionQueue(options) {
|
|
121
|
+
const maxBytes = options.queue?.maxBytes ?? 16 * 1024 * 1024;
|
|
122
|
+
return new BoundedEventQueue({
|
|
123
|
+
...options.queue,
|
|
124
|
+
maxBytes,
|
|
125
|
+
maxFrameBytes: Math.min(maxBytes, 8 * 1024 * 1024),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* EventHub.publish offers each event here. Filter before cloning so each accepting subscriber gets
|
|
130
|
+
* its own copy. On failure, close only this subscriber and preserve its buffered events before a
|
|
131
|
+
* truthy cause rejects reads or a falsy cause completes them.
|
|
132
|
+
*/
|
|
133
|
+
function deliverToSubscriber(subscriber, value) {
|
|
134
|
+
try {
|
|
135
|
+
if (subscriber.accepts(value))
|
|
136
|
+
subscriber.queue.push(structuredClone(value));
|
|
137
|
+
}
|
|
138
|
+
catch (cause) {
|
|
139
|
+
subscriber.end(cause);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AhpEvent } from '@anureo/protocol';
|
|
2
|
+
import type { EventHub } from "./events.js";
|
|
3
|
+
import type { Input, RequestOptions, TurnResult } from "../types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Internal port allocated by Session.context and passed to Turn by Session.submitExecution when
|
|
6
|
+
* association resolves. Turn uses it for live subscriptions, terminal results and explicit controls.
|
|
7
|
+
* Session owns event ordering, shared settlement and native resources. Each Turn owns its wait
|
|
8
|
+
* and subscription cancellation.
|
|
9
|
+
*/
|
|
10
|
+
export interface ExecutionObservation {
|
|
11
|
+
/** Stable execution ID reported by the adapter and shared by submissions bound to the same turn. */
|
|
12
|
+
readonly id: string;
|
|
13
|
+
/** Session-owned live producer hub; Turn.events creates an isolated subscription without replay. */
|
|
14
|
+
readonly events: EventHub<AhpEvent>;
|
|
15
|
+
/** Session.retainEvent resolves the frozen snapshot at native completion. Session.endObservation rejects unfinished results on closure. */
|
|
16
|
+
readonly result: Promise<TurnResult>;
|
|
17
|
+
/** Session.retainEvent sets this on turn/completed before publishing the terminal event and ending its stream. */
|
|
18
|
+
readonly terminal: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Turn.steer explicitly invokes the callback installed by Session.context. It normalizes input and
|
|
21
|
+
* awaits Session.request('turn/steer') with this execution as the atomic target. It discards the
|
|
22
|
+
* validated receipt and propagates normalization, admission, dispatch and wait rejections. Native
|
|
23
|
+
* work continues if the caller ends its wait after admission.
|
|
24
|
+
*/
|
|
25
|
+
steer(input: Input, wait?: RequestOptions): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Turn.interrupt explicitly invokes the callback installed by Session.context. It awaits
|
|
28
|
+
* Session.request('turn/interrupt') for this execution, discarding the validated receipt or
|
|
29
|
+
* propagating admission, dispatch and wait rejections. Session.retainEvent settles result
|
|
30
|
+
* separately when the terminal event arrives.
|
|
31
|
+
*/
|
|
32
|
+
interrupt(wait?: RequestOptions): Promise<void>;
|
|
33
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clone acyclic protocol data and freeze enumerable object and array descendants. Cloning or
|
|
3
|
+
* freezing failures throw and leave the original value untouched.
|
|
4
|
+
*
|
|
5
|
+
* Called by:
|
|
6
|
+
* - Client.initialize to store negotiated capabilities; failure rejects initialization.
|
|
7
|
+
* - Session's constructor/attach and binding/reference/capabilities/snapshot getters to detach stored
|
|
8
|
+
* or exposed state. Session.executeRequest returns frozen receipts; failure rejects its request.
|
|
9
|
+
* - ResultAccumulator.finish to return a terminal snapshot. Session.retainEvent settles the shared
|
|
10
|
+
* Turn result with that snapshot, or lets a failure propagate through ordered publication.
|
|
11
|
+
*/
|
|
12
|
+
export declare function immutable<T>(value: T): T;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clone acyclic protocol data and freeze enumerable object and array descendants. Cloning or
|
|
3
|
+
* freezing failures throw and leave the original value untouched.
|
|
4
|
+
*
|
|
5
|
+
* Called by:
|
|
6
|
+
* - Client.initialize to store negotiated capabilities; failure rejects initialization.
|
|
7
|
+
* - Session's constructor/attach and binding/reference/capabilities/snapshot getters to detach stored
|
|
8
|
+
* or exposed state. Session.executeRequest returns frozen receipts; failure rejects its request.
|
|
9
|
+
* - ResultAccumulator.finish to return a terminal snapshot. Session.retainEvent settles the shared
|
|
10
|
+
* Turn result with that snapshot, or lets a failure propagate through ordered publication.
|
|
11
|
+
*/
|
|
12
|
+
export function immutable(value) {
|
|
13
|
+
const copy = structuredClone(value);
|
|
14
|
+
/**
|
|
15
|
+
* immutable invokes this recursive walk on its detached clone. Freeze descendants first so no
|
|
16
|
+
* enumerable object/array child of a returned protocol snapshot remains writable; failures propagate.
|
|
17
|
+
*/
|
|
18
|
+
const freeze = (entry) => {
|
|
19
|
+
if (entry && typeof entry === 'object') {
|
|
20
|
+
for (const child of Object.values(entry)) {
|
|
21
|
+
freeze(child);
|
|
22
|
+
}
|
|
23
|
+
Object.freeze(entry);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
freeze(copy);
|
|
27
|
+
return copy;
|
|
28
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { InputBlock } from '@anureo/protocol';
|
|
2
|
+
import type { Input } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Normalize host input into one text block for a string or a detached copy of a block array.
|
|
5
|
+
* Throw INVALID_PARAMS for other values or clone failure. Session.submit and the callback installed
|
|
6
|
+
* by Session.context for Turn.steer pass the resulting blocks to request admission, which validates
|
|
7
|
+
* their contents and capabilities. Both expose normalization failures as promise rejections.
|
|
8
|
+
*/
|
|
9
|
+
export declare function inputBlocks(input: Input): InputBlock[];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { HarnessSdkError } from "../errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Normalize host input into one text block for a string or a detached copy of a block array.
|
|
4
|
+
* Throw INVALID_PARAMS for other values or clone failure. Session.submit and the callback installed
|
|
5
|
+
* by Session.context for Turn.steer pass the resulting blocks to request admission, which validates
|
|
6
|
+
* their contents and capabilities. Both expose normalization failures as promise rejections.
|
|
7
|
+
*/
|
|
8
|
+
export function inputBlocks(input) {
|
|
9
|
+
if (typeof input === 'string')
|
|
10
|
+
return [{ type: 'text', text: input }];
|
|
11
|
+
if (!Array.isArray(input))
|
|
12
|
+
throw new HarnessSdkError('INVALID_PARAMS');
|
|
13
|
+
try {
|
|
14
|
+
return structuredClone(input);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
throw new HarnessSdkError('INVALID_PARAMS');
|
|
18
|
+
}
|
|
19
|
+
}
|