@peer-agent/runtime-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 梁音
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # @peer-agent/runtime-sdk
2
+
3
+ Public, host-neutral orchestration SDK for Peer Agent Runtime.
4
+
5
+ The SDK owns execution ordering, session and turn lifecycle, structured cancellation, Runtime Event sequencing, Hook coordination, approval flow, Provider dispatch, and Evidence attachment. Hosts supply environment-specific implementations. The package does not depend on Electron, filesystem APIs, child processes, network clients, or UI frameworks.
6
+
7
+ ## Package family
8
+
9
+ Install the SDK and its public package dependencies:
10
+
11
+ ```bash
12
+ npm install @peer-agent/runtime-sdk
13
+ ```
14
+
15
+ The published Runtime family is:
16
+
17
+ | Package | Responsibility |
18
+ | --- | --- |
19
+ | `@peer-agent/protocol` | Serializable contracts shared by runtimes and clients |
20
+ | `@peer-agent/runtime-core` | Capability, projection, permission, Hook, and Evidence primitives |
21
+ | `@peer-agent/runtime-sdk` | Host-neutral orchestration, events, pipeline, and session lifecycle |
22
+
23
+ `@peer-agent/runtime-node` is a host adapter and is not part of the initial public package family.
24
+
25
+ ## Requirements
26
+
27
+ - Node.js 20 or newer
28
+ - ESM
29
+ - TypeScript 5.x when consuming type declarations
30
+
31
+ Use package root exports only. Deep imports into `src` or `dist` are not public API.
32
+
33
+ ## Runtime Host Adapter
34
+
35
+ A host provides capability execution and failure-result construction. Hook execution, human approval, and Evidence attachment are optional ports.
36
+
37
+ ```ts
38
+ import {
39
+ createRuntimeSdk,
40
+ type RuntimeSdkHostAdapter,
41
+ } from '@peer-agent/runtime-sdk';
42
+
43
+ const host: RuntimeSdkHostAdapter = {
44
+ executeProvider: (request, context) => providerRegistry.execute(request, context),
45
+ createBlockedExecution: ({ request, decision, reason }) =>
46
+ makeBlockedExecution(request, decision, reason),
47
+ hookRunner,
48
+ approvalPort,
49
+ appendHookEvidence,
50
+ };
51
+
52
+ const runtime = createRuntimeSdk({ host });
53
+ runtime.subscribe((event) => renderRuntimeEvent(event));
54
+ const execution = await runtime.execute(request, context);
55
+ ```
56
+
57
+ The governed tool path is:
58
+
59
+ ```text
60
+ tool.started
61
+ -> PreToolUse Hook
62
+ -> optional human approval
63
+ -> Capability Provider
64
+ -> PostToolUse Hook
65
+ -> Evidence attachment
66
+ -> tool.completed
67
+ ```
68
+
69
+ A Hook may tighten a decision from `allow` to `ask` or `deny`; it must not bypass a stricter permission decision. If `ask` has no approval port, execution fails closed.
70
+
71
+ ## Session Controller
72
+
73
+ `RuntimeSessionController` owns stable session identity, monotonic turn indexes, turn IDs, and the AbortSignal used by Provider work.
74
+
75
+ ```ts
76
+ import { createRuntimeSessionController } from '@peer-agent/runtime-sdk';
77
+
78
+ const sessions = createRuntimeSessionController();
79
+
80
+ const firstTurn = sessions.start({
81
+ sessionId: 'conversation-42',
82
+ conversationId: 'conversation-42',
83
+ streamId: 'stream-1',
84
+ });
85
+ await runProvider({ signal: firstTurn.signal });
86
+ firstTurn.complete();
87
+
88
+ const resumedTurn = sessions.resume({
89
+ sessionId: 'conversation-42',
90
+ streamId: 'stream-2',
91
+ });
92
+ resumedTurn.cancel('user_aborted');
93
+ ```
94
+
95
+ Rules:
96
+
97
+ - A session has at most one active turn.
98
+ - `resume` keeps the session identity and increments `turnIndex`.
99
+ - `cancel` and `fail` abort the SDK-owned signal.
100
+ - A late `complete` cannot overwrite an existing `cancelled` or `failed` terminal state.
101
+ - UI stream reattachment and replay remain host concerns; they are not Runtime resume semantics.
102
+
103
+ ## Runtime Pipeline
104
+
105
+ `createRuntimePipeline` runs model turns and projected tool calls while preserving structured cancellation. A host adapter converts `cancelled` into platform-specific behavior such as Desktop `AbortError` or terminal UI state.
106
+
107
+ ```ts
108
+ import { createRuntimePipeline } from '@peer-agent/runtime-sdk';
109
+
110
+ const pipeline = createRuntimePipeline({
111
+ modelTurnRunner,
112
+ toolExecutor,
113
+ eventSink,
114
+ });
115
+
116
+ const result = await pipeline.run(turnContext, { signal });
117
+ if (result.status === 'cancelled') {
118
+ // Convert only at the host boundary.
119
+ }
120
+ ```
121
+
122
+ ## Runtime Event protocol
123
+
124
+ Runtime Event v1 is identified by `RUNTIME_EVENT_PROTOCOL_VERSION`. One Runtime emitter assigns a monotonically increasing `sequence` to all emitted events. Session resume must continue on the same emitter when a host needs cross-turn ordering.
125
+
126
+ Subscribers are observational: a subscriber failure does not interrupt Provider execution or delivery to other subscribers.
127
+
128
+ ## Compatibility policy
129
+
130
+ - `@peer-agent/protocol`, `@peer-agent/runtime-core`, and `@peer-agent/runtime-sdk` follow SemVer independently from the Desktop and TUI products.
131
+ - The initial public package family is versioned `0.1.0`.
132
+ - The SDK declares compatible Protocol and Core ranges as normal package dependencies.
133
+ - Breaking public root-export, event, session, or host-port changes require a SemVer minor bump while the packages remain `0.x`.
134
+ - Runtime Event protocol changes require an explicit protocol version change and compatibility notes.
135
+ - Source file paths, test helpers, Desktop adapters, and Node host internals are not public API.
136
+
137
+ ## Release verification
138
+
139
+ The monorepo validates the exact tarballs rather than relying on workspace symlinks:
140
+
141
+ ```bash
142
+ pnpm runtime:pack-check
143
+ pnpm runtime:pack-smoke
144
+ ```
145
+
146
+ These checks build and pack all three public packages, reject leaked source/test/Desktop files, install the tarballs into an empty offline project, run an ESM consumer, compile an external strict TypeScript consumer, verify dependency ranges, and confirm deep imports are blocked.
147
+
148
+ Package metadata is prepared for public npm access, but registry publication is a separate explicit operation requiring scope permission, credentials, version, and dist-tag confirmation.
@@ -0,0 +1,151 @@
1
+ import type { RuntimeDecision } from '@peer-agent/runtime-core';
2
+ import type { RuntimeExecuteRequest, RuntimeExecutionContext, RuntimeToolCall, RuntimeToolResult } from '@peer-agent/protocol';
3
+ export type { RuntimeExecuteRequest, RuntimeExecutionContext, RuntimeToolCall, RuntimeToolResult, } from '@peer-agent/protocol';
4
+ export declare const RUNTIME_EVENT_PROTOCOL_VERSION: 1;
5
+ export type RuntimeEventProtocolVersion = typeof RUNTIME_EVENT_PROTOCOL_VERSION;
6
+ export type RuntimeSdkEventType = 'session.started' | 'message.delta' | 'reasoning.delta' | 'message.completed' | 'runtime.error' | 'tool.started' | 'hook.completed' | 'permission.requested' | 'permission.resolved' | 'tool.completed';
7
+ export type RuntimeSdkToolCall = RuntimeToolCall;
8
+ export type RuntimeSdkExecuteRequest = RuntimeExecuteRequest;
9
+ export type RuntimeSdkExecutionContext = RuntimeExecutionContext;
10
+ export type RuntimeSdkToolResult = RuntimeToolResult;
11
+ export interface RuntimeSdkProviderExecution {
12
+ readonly result: RuntimeSdkToolResult;
13
+ readonly [key: string]: unknown;
14
+ }
15
+ export interface RuntimeSdkHookRecord {
16
+ readonly hookId?: string;
17
+ readonly decision?: RuntimeDecision;
18
+ readonly reason?: string;
19
+ readonly [key: string]: unknown;
20
+ }
21
+ export interface RuntimeSdkHookPayload {
22
+ readonly sessionId?: string;
23
+ readonly projectionId?: string;
24
+ readonly conversationId?: string;
25
+ readonly call: RuntimeSdkToolCall;
26
+ readonly result?: RuntimeSdkToolResult;
27
+ }
28
+ export interface RuntimeSdkHookRunner {
29
+ runPreToolUse?(payload: RuntimeSdkHookPayload): Promise<readonly RuntimeSdkHookRecord[]> | readonly RuntimeSdkHookRecord[];
30
+ runPostToolUse?(payload: RuntimeSdkHookPayload): Promise<readonly RuntimeSdkHookRecord[]> | readonly RuntimeSdkHookRecord[];
31
+ }
32
+ export interface RuntimeSdkPermissionRequest {
33
+ readonly kind: 'hook';
34
+ readonly hookEvent: 'PreToolUse';
35
+ readonly call: RuntimeSdkToolCall;
36
+ readonly toolCallId: string;
37
+ readonly capabilityId: string;
38
+ readonly args: unknown;
39
+ readonly workspacePath?: string;
40
+ readonly reason: string;
41
+ }
42
+ export interface RuntimeSdkApprovalDecision {
43
+ readonly decision: RuntimeDecision;
44
+ readonly [key: string]: unknown;
45
+ }
46
+ export interface RuntimeSdkApprovalPort {
47
+ requestApproval(request: RuntimeSdkPermissionRequest, context: RuntimeSdkExecutionContext): Promise<RuntimeSdkApprovalDecision> | RuntimeSdkApprovalDecision;
48
+ }
49
+ export interface RuntimeSdkHostAdapter {
50
+ executeProvider(request: RuntimeSdkExecuteRequest, context: RuntimeSdkExecutionContext): Promise<RuntimeSdkProviderExecution> | RuntimeSdkProviderExecution;
51
+ createBlockedExecution(options: {
52
+ readonly request: RuntimeSdkExecuteRequest;
53
+ readonly context: RuntimeSdkExecutionContext;
54
+ readonly decision: RuntimeDecision;
55
+ readonly reason: string;
56
+ readonly approval?: RuntimeSdkApprovalDecision;
57
+ }): RuntimeSdkProviderExecution;
58
+ appendHookEvidence?(result: RuntimeSdkToolResult, records: readonly RuntimeSdkHookRecord[], finalDecision: RuntimeDecision): RuntimeSdkToolResult;
59
+ hookRunner?: RuntimeSdkHookRunner | null;
60
+ approvalPort?: RuntimeSdkApprovalPort | null;
61
+ }
62
+ export interface RuntimeSdkEventBase {
63
+ readonly protocolVersion: RuntimeEventProtocolVersion;
64
+ readonly eventId: string;
65
+ readonly sequence: number;
66
+ readonly occurredAt: string;
67
+ readonly sessionId?: string;
68
+ readonly streamId?: string;
69
+ readonly projectionId?: string;
70
+ readonly conversationId?: string;
71
+ readonly toolCallId?: string;
72
+ readonly capabilityId?: string;
73
+ }
74
+ export interface RuntimeSdkSessionStartedEvent extends RuntimeSdkEventBase {
75
+ readonly type: 'session.started';
76
+ readonly sessionId: string;
77
+ readonly streamId?: string;
78
+ readonly mode?: string;
79
+ readonly providerId?: string;
80
+ readonly model?: string;
81
+ }
82
+ export interface RuntimeSdkMessageDeltaEvent extends RuntimeSdkEventBase {
83
+ readonly type: 'message.delta';
84
+ readonly streamId: string;
85
+ readonly content: string;
86
+ }
87
+ export interface RuntimeSdkReasoningDeltaEvent extends RuntimeSdkEventBase {
88
+ readonly type: 'reasoning.delta';
89
+ readonly streamId: string;
90
+ readonly content: string;
91
+ }
92
+ export interface RuntimeSdkMessageCompletedEvent extends RuntimeSdkEventBase {
93
+ readonly type: 'message.completed';
94
+ readonly streamId: string;
95
+ readonly content?: string;
96
+ readonly usage?: unknown;
97
+ readonly lifetimeUsage?: unknown;
98
+ readonly finishReason?: string;
99
+ }
100
+ export interface RuntimeSdkRuntimeErrorEvent extends RuntimeSdkEventBase {
101
+ readonly type: 'runtime.error';
102
+ readonly code: string;
103
+ readonly message?: string;
104
+ readonly recoverable?: boolean;
105
+ readonly details?: unknown;
106
+ }
107
+ export interface RuntimeSdkToolStartedEvent extends RuntimeSdkEventBase {
108
+ readonly toolCallId: string;
109
+ readonly capabilityId: string;
110
+ readonly type: 'tool.started';
111
+ }
112
+ export interface RuntimeSdkHookCompletedEvent extends RuntimeSdkEventBase {
113
+ readonly toolCallId: string;
114
+ readonly capabilityId: string;
115
+ readonly type: 'hook.completed';
116
+ readonly phase: 'PreToolUse' | 'PostToolUse';
117
+ readonly decision: RuntimeDecision;
118
+ readonly records: readonly RuntimeSdkHookRecord[];
119
+ }
120
+ export interface RuntimeSdkPermissionRequestedEvent extends RuntimeSdkEventBase {
121
+ readonly toolCallId: string;
122
+ readonly capabilityId: string;
123
+ readonly type: 'permission.requested';
124
+ readonly decision: 'ask';
125
+ }
126
+ export interface RuntimeSdkPermissionResolvedEvent extends RuntimeSdkEventBase {
127
+ readonly toolCallId: string;
128
+ readonly capabilityId: string;
129
+ readonly type: 'permission.resolved';
130
+ readonly decision: RuntimeDecision;
131
+ }
132
+ export interface RuntimeSdkToolCompletedEvent extends RuntimeSdkEventBase {
133
+ readonly toolCallId: string;
134
+ readonly capabilityId: string;
135
+ readonly type: 'tool.completed';
136
+ readonly decision: RuntimeDecision;
137
+ readonly result: RuntimeSdkToolResult;
138
+ }
139
+ export type RuntimeSdkEvent = RuntimeSdkSessionStartedEvent | RuntimeSdkMessageDeltaEvent | RuntimeSdkReasoningDeltaEvent | RuntimeSdkMessageCompletedEvent | RuntimeSdkRuntimeErrorEvent | RuntimeSdkToolStartedEvent | RuntimeSdkHookCompletedEvent | RuntimeSdkPermissionRequestedEvent | RuntimeSdkPermissionResolvedEvent | RuntimeSdkToolCompletedEvent;
140
+ export type RuntimeSdkEventInput = RuntimeSdkEvent extends infer Event ? Event extends RuntimeSdkEvent ? Omit<Event, 'protocolVersion' | 'eventId' | 'sequence' | 'occurredAt'> : never : never;
141
+ export type RuntimeSdkEventListener = (event: RuntimeSdkEvent) => void;
142
+ export interface RuntimeSdkOptions {
143
+ readonly host: RuntimeSdkHostAdapter;
144
+ readonly workspaceRoot?: string;
145
+ readonly now?: () => string;
146
+ }
147
+ export interface RuntimeSdk {
148
+ execute(request: RuntimeSdkExecuteRequest, context?: RuntimeSdkExecutionContext): Promise<RuntimeSdkProviderExecution>;
149
+ emit(event: RuntimeSdkEventInput): RuntimeSdkEvent;
150
+ subscribe(listener: RuntimeSdkEventListener): () => void;
151
+ }
@@ -0,0 +1 @@
1
+ export const RUNTIME_EVENT_PROTOCOL_VERSION = 1;
@@ -0,0 +1,7 @@
1
+ export { RUNTIME_EVENT_PROTOCOL_VERSION } from './contracts.ts';
2
+ export type { RuntimeEventProtocolVersion, RuntimeExecuteRequest, RuntimeExecutionContext, RuntimeToolCall, RuntimeToolResult, RuntimeSdk, RuntimeSdkApprovalDecision, RuntimeSdkApprovalPort, RuntimeSdkEvent, RuntimeSdkEventBase, RuntimeSdkEventInput, RuntimeSdkEventListener, RuntimeSdkEventType, RuntimeSdkSessionStartedEvent, RuntimeSdkMessageDeltaEvent, RuntimeSdkReasoningDeltaEvent, RuntimeSdkMessageCompletedEvent, RuntimeSdkRuntimeErrorEvent, RuntimeSdkHookCompletedEvent, RuntimeSdkPermissionRequestedEvent, RuntimeSdkPermissionResolvedEvent, RuntimeSdkToolCompletedEvent, RuntimeSdkToolStartedEvent, RuntimeSdkExecuteRequest, RuntimeSdkExecutionContext, RuntimeSdkHookPayload, RuntimeSdkHookRecord, RuntimeSdkHookRunner, RuntimeSdkHostAdapter, RuntimeSdkOptions, RuntimeSdkPermissionRequest, RuntimeSdkProviderExecution, RuntimeSdkToolCall, RuntimeSdkToolResult, } from './contracts.ts';
3
+ export type { RuntimePipeline, RuntimePipelineCompletedTurn, RuntimePipelineContinueTurn, RuntimePipelineEventSink, RuntimePipelineModelAdapter, RuntimePipelineOptions, RuntimePipelineRunInput, RuntimePipelineRunResult, RuntimePipelineStatus, RuntimePipelineToolCall, RuntimePipelineToolCallsTurn, RuntimePipelineToolExecution, RuntimePipelineToolExecutor, RuntimePipelineTurnContext, RuntimePipelineTurnResult, } from './pipeline-contracts.ts';
4
+ export { createRuntimePipeline } from './pipeline.ts';
5
+ export { createRuntimeSdk } from './runtime.ts';
6
+ export type { RuntimeSessionController, RuntimeSessionControllerOptions, RuntimeSessionResumeOptions, RuntimeSessionSnapshot, RuntimeSessionStartOptions, RuntimeSessionStatus, RuntimeSessionTurnHandle, RuntimeSessionTurnSnapshot, RuntimeTurnStatus, } from './session-contracts.ts';
7
+ export { createRuntimeSessionController } from './session-controller.ts';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { RUNTIME_EVENT_PROTOCOL_VERSION } from "./contracts.js";
2
+ export { createRuntimePipeline } from "./pipeline.js";
3
+ export { createRuntimeSdk } from "./runtime.js";
4
+ export { createRuntimeSessionController } from "./session-controller.js";
@@ -0,0 +1,83 @@
1
+ import type { RuntimeSdkEvent, RuntimeSdkEventInput, RuntimeSdkToolResult } from './contracts.ts';
2
+ export type RuntimePipelineStatus = 'completed' | 'stopped' | 'cancelled' | 'exhausted';
3
+ export interface RuntimePipelineRunInput<TInput = unknown> {
4
+ readonly sessionId: string;
5
+ readonly streamId?: string;
6
+ readonly conversationId?: string;
7
+ readonly mode?: string;
8
+ readonly providerId?: string;
9
+ readonly model?: string;
10
+ readonly input: TInput;
11
+ readonly maxTurns?: number;
12
+ }
13
+ export interface RuntimePipelineToolCall<TPayload = unknown> {
14
+ readonly toolCallId: string;
15
+ readonly capabilityId?: string;
16
+ readonly name?: string;
17
+ readonly arguments?: unknown;
18
+ readonly payload?: TPayload;
19
+ }
20
+ export interface RuntimePipelineToolExecution<TCall extends RuntimePipelineToolCall = RuntimePipelineToolCall, TResult = RuntimeSdkToolResult> {
21
+ readonly call: TCall;
22
+ readonly result: TResult;
23
+ readonly terminal?: boolean;
24
+ readonly terminalReason?: string;
25
+ }
26
+ export interface RuntimePipelineTurnContext<TInput = unknown> {
27
+ readonly run: RuntimePipelineRunInput<TInput>;
28
+ readonly turn: number;
29
+ readonly signal?: AbortSignal;
30
+ readonly emit: (event: RuntimeSdkEventInput) => RuntimeSdkEvent | null;
31
+ }
32
+ export interface RuntimePipelineContinueTurn<TState> {
33
+ readonly kind: 'continue';
34
+ readonly state: TState;
35
+ }
36
+ export interface RuntimePipelineCompletedTurn<TState, TOutput = unknown> {
37
+ readonly kind: 'completed';
38
+ readonly state: TState;
39
+ readonly output?: TOutput;
40
+ readonly reason?: string;
41
+ }
42
+ export interface RuntimePipelineToolCallsTurn<TState, TCall extends RuntimePipelineToolCall = RuntimePipelineToolCall> {
43
+ readonly kind: 'tool_calls';
44
+ readonly state: TState;
45
+ readonly calls: readonly TCall[];
46
+ }
47
+ export type RuntimePipelineTurnResult<TState, TCall extends RuntimePipelineToolCall = RuntimePipelineToolCall, TOutput = unknown> = RuntimePipelineContinueTurn<TState> | RuntimePipelineCompletedTurn<TState, TOutput> | RuntimePipelineToolCallsTurn<TState, TCall>;
48
+ export interface RuntimePipelineModelAdapter<TInput = unknown, TState = unknown, TCall extends RuntimePipelineToolCall = RuntimePipelineToolCall, TToolResult = RuntimeSdkToolResult, TOutput = unknown> {
49
+ initialize(input: RuntimePipelineRunInput<TInput>, context: RuntimePipelineTurnContext<TInput>): TState | Promise<TState>;
50
+ runTurn(state: TState, context: RuntimePipelineTurnContext<TInput>): RuntimePipelineTurnResult<TState, TCall, TOutput> | Promise<RuntimePipelineTurnResult<TState, TCall, TOutput>>;
51
+ applyToolResults(state: TState, executions: readonly RuntimePipelineToolExecution<TCall, TToolResult>[], context: RuntimePipelineTurnContext<TInput>): TState | Promise<TState>;
52
+ onCompleted?(state: TState, output: TOutput | undefined, context: RuntimePipelineTurnContext<TInput>): void | Promise<void>;
53
+ onStopped?(state: TState, executions: readonly RuntimePipelineToolExecution<TCall, TToolResult>[], context: RuntimePipelineTurnContext<TInput>): void | Promise<void>;
54
+ onCancelled?(state: TState | undefined, context: RuntimePipelineTurnContext<TInput>): void | Promise<void>;
55
+ onExhausted?(state: TState, context: RuntimePipelineTurnContext<TInput>): void | Promise<void>;
56
+ }
57
+ export interface RuntimePipelineToolExecutor<TInput = unknown, TCall extends RuntimePipelineToolCall = RuntimePipelineToolCall, TToolResult = RuntimeSdkToolResult> {
58
+ execute(call: TCall, context: RuntimePipelineTurnContext<TInput> & {
59
+ readonly index: number;
60
+ }): RuntimePipelineToolExecution<TCall, TToolResult> | Promise<RuntimePipelineToolExecution<TCall, TToolResult>>;
61
+ }
62
+ export interface RuntimePipelineEventSink {
63
+ emit(event: RuntimeSdkEventInput): RuntimeSdkEvent | null;
64
+ }
65
+ export interface RuntimePipelineOptions<TInput = unknown, TState = unknown, TCall extends RuntimePipelineToolCall = RuntimePipelineToolCall, TToolResult = RuntimeSdkToolResult, TOutput = unknown> {
66
+ readonly model: RuntimePipelineModelAdapter<TInput, TState, TCall, TToolResult, TOutput>;
67
+ readonly tools: RuntimePipelineToolExecutor<TInput, TCall, TToolResult>;
68
+ readonly events?: RuntimePipelineEventSink;
69
+ readonly defaultMaxTurns?: number;
70
+ }
71
+ export interface RuntimePipelineRunResult<TState = unknown, TOutput = unknown> {
72
+ readonly status: RuntimePipelineStatus;
73
+ readonly state?: TState;
74
+ readonly output?: TOutput;
75
+ readonly turns: number;
76
+ readonly toolCalls: number;
77
+ readonly reason?: string;
78
+ }
79
+ export interface RuntimePipeline<TInput = unknown, TState = unknown, TOutput = unknown> {
80
+ run(input: RuntimePipelineRunInput<TInput>, context?: {
81
+ readonly signal?: AbortSignal;
82
+ }): Promise<RuntimePipelineRunResult<TState, TOutput>>;
83
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { RuntimePipeline, RuntimePipelineOptions, RuntimePipelineToolCall } from './pipeline-contracts.ts';
2
+ export declare function createRuntimePipeline<TInput = unknown, TState = unknown, TCall extends RuntimePipelineToolCall = RuntimePipelineToolCall, TToolResult = unknown, TOutput = unknown>(options: RuntimePipelineOptions<TInput, TState, TCall, TToolResult, TOutput>): RuntimePipeline<TInput, TState, TOutput>;
@@ -0,0 +1,165 @@
1
+ const DEFAULT_MAX_TURNS = 64;
2
+ function normalizeMaxTurns(value, fallback) {
3
+ if (value === Number.POSITIVE_INFINITY)
4
+ return value;
5
+ if (!Number.isFinite(value))
6
+ return fallback;
7
+ return Math.max(1, Math.floor(value));
8
+ }
9
+ function isAbortError(error) {
10
+ return error instanceof Error && error.name === 'AbortError';
11
+ }
12
+ function errorMessage(error) {
13
+ if (error instanceof Error)
14
+ return error.message || error.name;
15
+ return String(error || 'runtime_pipeline_error');
16
+ }
17
+ function throwIfAborted(signal) {
18
+ if (signal?.aborted) {
19
+ const error = new Error('Aborted');
20
+ error.name = 'AbortError';
21
+ throw error;
22
+ }
23
+ }
24
+ export function createRuntimePipeline(options) {
25
+ if (!options?.model)
26
+ throw new Error('RuntimePipeline requires a model adapter.');
27
+ if (!options?.tools)
28
+ throw new Error('RuntimePipeline requires a tool executor.');
29
+ const defaultMaxTurns = normalizeMaxTurns(options.defaultMaxTurns, DEFAULT_MAX_TURNS);
30
+ return {
31
+ async run(input, context = {}) {
32
+ if (!input?.sessionId)
33
+ throw new Error('RuntimePipeline run requires sessionId.');
34
+ const signal = context.signal;
35
+ const maxTurns = normalizeMaxTurns(input.maxTurns, defaultMaxTurns);
36
+ let state;
37
+ let turns = 0;
38
+ let toolCalls = 0;
39
+ const emit = (event) => {
40
+ try {
41
+ return options.events?.emit(event) ?? null;
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ };
47
+ const eventBase = {
48
+ sessionId: input.sessionId,
49
+ ...(input.streamId ? { streamId: input.streamId } : {}),
50
+ ...(input.conversationId ? { conversationId: input.conversationId } : {}),
51
+ };
52
+ const turnContext = (turn) => ({
53
+ run: input,
54
+ turn,
55
+ signal,
56
+ emit,
57
+ });
58
+ try {
59
+ throwIfAborted(signal);
60
+ emit({
61
+ type: 'session.started',
62
+ ...eventBase,
63
+ ...(input.mode ? { mode: input.mode } : {}),
64
+ ...(input.providerId ? { providerId: input.providerId } : {}),
65
+ ...(input.model ? { model: input.model } : {}),
66
+ });
67
+ state = await options.model.initialize(input, turnContext(0));
68
+ for (let turn = 0; turn < maxTurns; turn += 1) {
69
+ throwIfAborted(signal);
70
+ turns = turn + 1;
71
+ const currentContext = turnContext(turn);
72
+ const outcome = await options.model.runTurn(state, currentContext);
73
+ state = outcome.state;
74
+ if (outcome.kind === 'continue')
75
+ continue;
76
+ if (outcome.kind === 'completed') {
77
+ await options.model.onCompleted?.(state, outcome.output, currentContext);
78
+ if (input.streamId) {
79
+ emit({
80
+ type: 'message.completed',
81
+ ...eventBase,
82
+ streamId: input.streamId,
83
+ ...(outcome.reason ? { finishReason: outcome.reason } : {}),
84
+ });
85
+ }
86
+ return {
87
+ status: 'completed',
88
+ state,
89
+ output: outcome.output,
90
+ turns,
91
+ toolCalls,
92
+ ...(outcome.reason ? { reason: outcome.reason } : {}),
93
+ };
94
+ }
95
+ const executions = [];
96
+ for (const [index, call] of outcome.calls.entries()) {
97
+ throwIfAborted(signal);
98
+ const execution = await options.tools.execute(call, {
99
+ ...currentContext,
100
+ index,
101
+ });
102
+ executions.push(execution);
103
+ toolCalls += 1;
104
+ }
105
+ state = await options.model.applyToolResults(state, executions, currentContext);
106
+ const terminalExecution = executions.find((execution) => execution.terminal);
107
+ if (terminalExecution) {
108
+ await options.model.onStopped?.(state, executions, currentContext);
109
+ if (input.streamId) {
110
+ emit({
111
+ type: 'message.completed',
112
+ ...eventBase,
113
+ streamId: input.streamId,
114
+ finishReason: terminalExecution.terminalReason || 'tool_requested_stop',
115
+ });
116
+ }
117
+ return {
118
+ status: 'stopped',
119
+ state,
120
+ turns,
121
+ toolCalls,
122
+ reason: terminalExecution.terminalReason || 'tool_requested_stop',
123
+ };
124
+ }
125
+ }
126
+ const exhaustedContext = turnContext(turns);
127
+ await options.model.onExhausted?.(state, exhaustedContext);
128
+ const exhaustedReason = 'max_turns_exceeded';
129
+ emit({
130
+ type: 'runtime.error',
131
+ ...eventBase,
132
+ code: exhaustedReason,
133
+ message: exhaustedReason,
134
+ });
135
+ return {
136
+ status: 'exhausted',
137
+ state,
138
+ turns,
139
+ toolCalls,
140
+ reason: exhaustedReason,
141
+ };
142
+ }
143
+ catch (error) {
144
+ if (signal?.aborted || isAbortError(error)) {
145
+ const cancelledContext = turnContext(turns);
146
+ await options.model.onCancelled?.(state, cancelledContext);
147
+ return {
148
+ status: 'cancelled',
149
+ state,
150
+ turns,
151
+ toolCalls,
152
+ reason: 'aborted',
153
+ };
154
+ }
155
+ emit({
156
+ type: 'runtime.error',
157
+ ...eventBase,
158
+ code: 'runtime_pipeline_error',
159
+ message: errorMessage(error),
160
+ });
161
+ throw error;
162
+ }
163
+ },
164
+ };
165
+ }
@@ -0,0 +1,2 @@
1
+ import { type RuntimeSdk, type RuntimeSdkOptions } from './contracts.ts';
2
+ export declare function createRuntimeSdk(options: RuntimeSdkOptions): RuntimeSdk;
@@ -0,0 +1,141 @@
1
+ import { mostRestrictiveHookDecision } from '@peer-agent/runtime-core';
2
+ import { RUNTIME_EVENT_PROTOCOL_VERSION, } from "./contracts.js";
3
+ function normalizeRecords(records) {
4
+ return Array.isArray(records) ? records : [];
5
+ }
6
+ function createRuntimeEventId(sequence) {
7
+ const randomId = globalThis.crypto?.randomUUID?.();
8
+ return randomId ? `runtime-event-${randomId}` : `runtime-event-${Date.now()}-${sequence}`;
9
+ }
10
+ export function createRuntimeSdk(options) {
11
+ if (!options?.host || typeof options.host.executeProvider !== 'function') {
12
+ throw new TypeError('Runtime SDK requires a host.executeProvider adapter.');
13
+ }
14
+ if (typeof options.host.createBlockedExecution !== 'function') {
15
+ throw new TypeError('Runtime SDK requires a host.createBlockedExecution adapter.');
16
+ }
17
+ const listeners = new Set();
18
+ const now = options.now ?? (() => new Date().toISOString());
19
+ let sequence = 0;
20
+ function emit(event) {
21
+ const nextSequence = ++sequence;
22
+ const nextEvent = {
23
+ ...event,
24
+ protocolVersion: RUNTIME_EVENT_PROTOCOL_VERSION,
25
+ eventId: createRuntimeEventId(nextSequence),
26
+ sequence: nextSequence,
27
+ occurredAt: now(),
28
+ };
29
+ for (const listener of listeners) {
30
+ try {
31
+ listener(nextEvent);
32
+ }
33
+ catch {
34
+ // Runtime events are observational. A broken subscriber must not interrupt execution.
35
+ }
36
+ }
37
+ return nextEvent;
38
+ }
39
+ function subscribe(listener) {
40
+ listeners.add(listener);
41
+ return () => listeners.delete(listener);
42
+ }
43
+ async function execute(request, context = {}) {
44
+ const call = request?.call;
45
+ if (!call?.toolCallId || !call?.capabilityId) {
46
+ throw new TypeError('Runtime SDK execute requires call.toolCallId and call.capabilityId.');
47
+ }
48
+ const eventBase = {
49
+ toolCallId: call.toolCallId,
50
+ capabilityId: call.capabilityId,
51
+ sessionId: request.sessionId,
52
+ projectionId: request.projectionId,
53
+ conversationId: request.conversationId,
54
+ };
55
+ const hookPayload = {
56
+ sessionId: request.sessionId,
57
+ projectionId: request.projectionId,
58
+ conversationId: request.conversationId,
59
+ call,
60
+ };
61
+ emit({ type: 'tool.started', ...eventBase });
62
+ const preRecords = normalizeRecords(options.host.hookRunner?.runPreToolUse
63
+ ? await options.host.hookRunner.runPreToolUse(hookPayload)
64
+ : []);
65
+ const preDecision = mostRestrictiveHookDecision(preRecords);
66
+ emit({
67
+ type: 'hook.completed',
68
+ ...eventBase,
69
+ phase: 'PreToolUse',
70
+ decision: preDecision,
71
+ records: preRecords,
72
+ });
73
+ if (preDecision === 'deny') {
74
+ const blocked = options.host.createBlockedExecution({
75
+ request,
76
+ context,
77
+ decision: 'deny',
78
+ reason: preRecords.find((record) => record.decision === 'deny')?.reason ?? 'hook_denied',
79
+ });
80
+ const result = options.host.appendHookEvidence
81
+ ? options.host.appendHookEvidence(blocked.result, preRecords, preDecision)
82
+ : blocked.result;
83
+ const resolvedBlocked = { ...blocked, result };
84
+ emit({ type: 'tool.completed', ...eventBase, decision: 'deny', result });
85
+ return resolvedBlocked;
86
+ }
87
+ if (preDecision === 'ask') {
88
+ const approvalRequest = {
89
+ kind: 'hook',
90
+ hookEvent: 'PreToolUse',
91
+ call,
92
+ toolCallId: call.toolCallId,
93
+ capabilityId: call.capabilityId,
94
+ args: call.arguments ?? call.argumentsPreview ?? {},
95
+ workspacePath: context.workspaceRoot ?? options.workspaceRoot,
96
+ reason: preRecords.find((record) => record.decision === 'ask')?.reason ?? 'hook_approval_required',
97
+ };
98
+ emit({ type: 'permission.requested', ...eventBase, decision: 'ask' });
99
+ const approval = options.host.approvalPort
100
+ ? await options.host.approvalPort.requestApproval(approvalRequest, context)
101
+ : { decision: 'ask' };
102
+ emit({ type: 'permission.resolved', ...eventBase, decision: approval.decision });
103
+ if (approval.decision !== 'allow') {
104
+ const blocked = options.host.createBlockedExecution({
105
+ request,
106
+ context,
107
+ decision: approval.decision,
108
+ reason: approval.decision === 'deny' ? 'hook_approval_denied' : 'hook_approval_required',
109
+ approval,
110
+ });
111
+ const result = options.host.appendHookEvidence
112
+ ? options.host.appendHookEvidence(blocked.result, preRecords, preDecision)
113
+ : blocked.result;
114
+ const resolvedBlocked = { ...blocked, result };
115
+ emit({ type: 'tool.completed', ...eventBase, decision: approval.decision, result });
116
+ return resolvedBlocked;
117
+ }
118
+ }
119
+ const execution = await options.host.executeProvider(request, context);
120
+ const postRecords = normalizeRecords(options.host.hookRunner?.runPostToolUse
121
+ ? await options.host.hookRunner.runPostToolUse({
122
+ ...hookPayload,
123
+ result: execution.result,
124
+ })
125
+ : []);
126
+ emit({
127
+ type: 'hook.completed',
128
+ ...eventBase,
129
+ phase: 'PostToolUse',
130
+ decision: mostRestrictiveHookDecision(postRecords),
131
+ records: postRecords,
132
+ });
133
+ const result = options.host.appendHookEvidence
134
+ ? options.host.appendHookEvidence(execution.result, [...preRecords, ...postRecords], preDecision)
135
+ : execution.result;
136
+ const resolvedExecution = { ...execution, result };
137
+ emit({ type: 'tool.completed', ...eventBase, decision: preDecision, result });
138
+ return resolvedExecution;
139
+ }
140
+ return { emit, execute, subscribe };
141
+ }
@@ -0,0 +1,57 @@
1
+ export type RuntimeSessionStatus = 'idle' | 'running';
2
+ export type RuntimeTurnStatus = 'running' | 'completed' | 'failed' | 'cancelled';
3
+ export interface RuntimeSessionTurnSnapshot {
4
+ readonly turnId: string;
5
+ readonly turnIndex: number;
6
+ readonly streamId?: string;
7
+ readonly status: RuntimeTurnStatus;
8
+ readonly startedAt: string;
9
+ readonly completedAt?: string;
10
+ readonly reason?: string;
11
+ }
12
+ export interface RuntimeSessionSnapshot {
13
+ readonly sessionId: string;
14
+ readonly conversationId?: string;
15
+ readonly status: RuntimeSessionStatus;
16
+ readonly createdAt: string;
17
+ readonly updatedAt: string;
18
+ readonly nextTurnIndex: number;
19
+ readonly activeTurn?: RuntimeSessionTurnSnapshot;
20
+ readonly lastTurn?: RuntimeSessionTurnSnapshot;
21
+ }
22
+ export interface RuntimeSessionStartOptions {
23
+ readonly sessionId: string;
24
+ readonly conversationId?: string;
25
+ readonly streamId?: string;
26
+ readonly signal?: AbortSignal;
27
+ }
28
+ export interface RuntimeSessionResumeOptions {
29
+ readonly sessionId: string;
30
+ readonly streamId?: string;
31
+ readonly signal?: AbortSignal;
32
+ }
33
+ export interface RuntimeSessionTurnHandle {
34
+ readonly sessionId: string;
35
+ readonly conversationId?: string;
36
+ readonly turnId: string;
37
+ readonly turnIndex: number;
38
+ readonly streamId?: string;
39
+ readonly signal: AbortSignal;
40
+ complete(): RuntimeSessionSnapshot;
41
+ fail(reason?: string): RuntimeSessionSnapshot;
42
+ cancel(reason?: string): RuntimeSessionSnapshot;
43
+ snapshot(): RuntimeSessionSnapshot;
44
+ }
45
+ export interface RuntimeSessionControllerOptions {
46
+ readonly createTurnId?: (sessionId: string, turnIndex: number) => string;
47
+ readonly now?: () => string;
48
+ readonly onChange?: (snapshot: RuntimeSessionSnapshot) => void;
49
+ }
50
+ export interface RuntimeSessionController {
51
+ start(options: RuntimeSessionStartOptions): RuntimeSessionTurnHandle;
52
+ resume(options: RuntimeSessionResumeOptions): RuntimeSessionTurnHandle;
53
+ cancel(sessionId: string, reason?: string): RuntimeSessionSnapshot | null;
54
+ get(sessionId: string): RuntimeSessionSnapshot | null;
55
+ list(): readonly RuntimeSessionSnapshot[];
56
+ delete(sessionId: string): boolean;
57
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { RuntimeSessionController, RuntimeSessionControllerOptions } from './session-contracts.ts';
2
+ export declare function createRuntimeSessionController(options?: RuntimeSessionControllerOptions): RuntimeSessionController;
@@ -0,0 +1,138 @@
1
+ function normalizeReason(reason, fallback = 'aborted') {
2
+ return typeof reason === 'string' && reason.trim() ? reason.trim() : fallback;
3
+ }
4
+ function snapshotSession(record) {
5
+ return {
6
+ sessionId: record.sessionId,
7
+ ...(record.conversationId ? { conversationId: record.conversationId } : {}),
8
+ status: record.activeTurn ? 'running' : 'idle',
9
+ createdAt: record.createdAt,
10
+ updatedAt: record.updatedAt,
11
+ nextTurnIndex: record.nextTurnIndex,
12
+ ...(record.activeTurn ? { activeTurn: { ...record.activeTurn.snapshot } } : {}),
13
+ ...(record.lastTurn ? { lastTurn: { ...record.lastTurn } } : {}),
14
+ };
15
+ }
16
+ export function createRuntimeSessionController(options = {}) {
17
+ const sessions = new Map();
18
+ const now = options.now ?? (() => new Date().toISOString());
19
+ const createTurnId = options.createTurnId
20
+ ?? ((sessionId, turnIndex) => `${sessionId}:turn:${turnIndex}`);
21
+ const publish = (record) => {
22
+ const snapshot = snapshotSession(record);
23
+ options.onChange?.(snapshot);
24
+ return snapshot;
25
+ };
26
+ const finishTurn = (record, activeTurn, status, reason, abortSignal = false) => {
27
+ if (record.activeTurn !== activeTurn)
28
+ return snapshotSession(record);
29
+ activeTurn.detachExternalSignal?.();
30
+ if (abortSignal && !activeTurn.controller.signal.aborted) {
31
+ activeTurn.controller.abort(reason);
32
+ }
33
+ const completedAt = now();
34
+ record.activeTurn = undefined;
35
+ record.lastTurn = {
36
+ ...activeTurn.snapshot,
37
+ status,
38
+ completedAt,
39
+ ...(reason ? { reason } : {}),
40
+ };
41
+ record.updatedAt = completedAt;
42
+ return publish(record);
43
+ };
44
+ const beginTurn = (record, options, startedAt = now()) => {
45
+ if (record.activeTurn) {
46
+ throw new Error(`Runtime session ${record.sessionId} already has an active turn`);
47
+ }
48
+ const turnIndex = record.nextTurnIndex;
49
+ const turnId = createTurnId(record.sessionId, turnIndex);
50
+ const controller = new AbortController();
51
+ const turnSnapshot = {
52
+ turnId,
53
+ turnIndex,
54
+ ...(options.streamId ? { streamId: options.streamId } : {}),
55
+ status: 'running',
56
+ startedAt,
57
+ };
58
+ const activeTurn = { snapshot: turnSnapshot, controller };
59
+ record.activeTurn = activeTurn;
60
+ record.nextTurnIndex = turnIndex + 1;
61
+ record.updatedAt = startedAt;
62
+ const cancelFromExternalSignal = () => {
63
+ finishTurn(record, activeTurn, 'cancelled', normalizeReason(options.signal?.reason, 'host_aborted'), true);
64
+ };
65
+ if (options.signal) {
66
+ if (options.signal.aborted) {
67
+ cancelFromExternalSignal();
68
+ }
69
+ else {
70
+ options.signal.addEventListener('abort', cancelFromExternalSignal, { once: true });
71
+ activeTurn.detachExternalSignal = () => {
72
+ options.signal?.removeEventListener('abort', cancelFromExternalSignal);
73
+ };
74
+ }
75
+ }
76
+ if (record.activeTurn === activeTurn)
77
+ publish(record);
78
+ return {
79
+ sessionId: record.sessionId,
80
+ ...(record.conversationId ? { conversationId: record.conversationId } : {}),
81
+ turnId,
82
+ turnIndex,
83
+ ...(options.streamId ? { streamId: options.streamId } : {}),
84
+ signal: controller.signal,
85
+ complete: () => finishTurn(record, activeTurn, 'completed'),
86
+ fail: (reason) => finishTurn(record, activeTurn, 'failed', reason, true),
87
+ cancel: (reason) => finishTurn(record, activeTurn, 'cancelled', normalizeReason(reason), true),
88
+ snapshot: () => snapshotSession(record),
89
+ };
90
+ };
91
+ return {
92
+ start(startOptions) {
93
+ if (sessions.has(startOptions.sessionId)) {
94
+ throw new Error(`Runtime session ${startOptions.sessionId} already exists`);
95
+ }
96
+ const startedAt = now();
97
+ const record = {
98
+ sessionId: startOptions.sessionId,
99
+ ...(startOptions.conversationId
100
+ ? { conversationId: startOptions.conversationId }
101
+ : {}),
102
+ createdAt: startedAt,
103
+ updatedAt: startedAt,
104
+ nextTurnIndex: 0,
105
+ };
106
+ sessions.set(record.sessionId, record);
107
+ return beginTurn(record, startOptions, startedAt);
108
+ },
109
+ resume(resumeOptions) {
110
+ const record = sessions.get(resumeOptions.sessionId);
111
+ if (!record) {
112
+ throw new Error(`Runtime session ${resumeOptions.sessionId} does not exist`);
113
+ }
114
+ return beginTurn(record, resumeOptions);
115
+ },
116
+ cancel(sessionId, reason = 'aborted') {
117
+ const record = sessions.get(sessionId);
118
+ if (!record)
119
+ return null;
120
+ if (!record.activeTurn)
121
+ return snapshotSession(record);
122
+ return finishTurn(record, record.activeTurn, 'cancelled', normalizeReason(reason), true);
123
+ },
124
+ get(sessionId) {
125
+ const record = sessions.get(sessionId);
126
+ return record ? snapshotSession(record) : null;
127
+ },
128
+ list() {
129
+ return [...sessions.values()].map(snapshotSession);
130
+ },
131
+ delete(sessionId) {
132
+ const record = sessions.get(sessionId);
133
+ if (!record || record.activeTurn)
134
+ return false;
135
+ return sessions.delete(sessionId);
136
+ },
137
+ };
138
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@peer-agent/runtime-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Public host-neutral orchestration SDK for Peer Agent runtimes.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "sideEffects": false,
8
+ "engines": {
9
+ "node": ">=20"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/yinLiangDream/Peer-Agent.git",
14
+ "directory": "packages/runtime-sdk"
15
+ },
16
+ "homepage": "https://github.com/yinLiangDream/Peer-Agent#readme",
17
+ "bugs": {
18
+ "url": "https://github.com/yinLiangDream/Peer-Agent/issues"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "main": "./dist/index.js",
24
+ "types": "./dist/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "default": "./dist/index.js"
29
+ }
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "README.md"
34
+ ],
35
+ "dependencies": {
36
+ "@peer-agent/protocol": "^0.1.0",
37
+ "@peer-agent/runtime-core": "^0.1.0"
38
+ },
39
+ "devDependencies": {
40
+ "typescript": "^5.9.3"
41
+ },
42
+ "scripts": {
43
+ "build": "tsc -p tsconfig.build.json",
44
+ "test": "node --test src/*.test.ts",
45
+ "typecheck": "tsc -p tsconfig.json"
46
+ }
47
+ }