@ai-sdk/harness 0.0.0 → 1.0.0-beta.14

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 (67) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/LICENSE +13 -0
  3. package/README.md +142 -0
  4. package/agent/index.ts +47 -0
  5. package/bridge/index.ts +10 -0
  6. package/dist/agent/index.d.ts +1521 -0
  7. package/dist/agent/index.js +2958 -0
  8. package/dist/agent/index.js.map +1 -0
  9. package/dist/bridge/index.d.ts +111 -0
  10. package/dist/bridge/index.js +415 -0
  11. package/dist/bridge/index.js.map +1 -0
  12. package/dist/index.d.ts +1536 -0
  13. package/dist/index.js +15834 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/utils/index.d.ts +225 -0
  16. package/dist/utils/index.js +12148 -0
  17. package/dist/utils/index.js.map +1 -0
  18. package/package.json +99 -1
  19. package/src/agent/harness-agent-session.ts +509 -0
  20. package/src/agent/harness-agent-settings.ts +131 -0
  21. package/src/agent/harness-agent-tool-approval-continuation.ts +94 -0
  22. package/src/agent/harness-agent-types.ts +50 -0
  23. package/src/agent/harness-agent.ts +819 -0
  24. package/src/agent/internal/bootstrap-recipe.ts +124 -0
  25. package/src/agent/internal/bridge-port-registry.ts +52 -0
  26. package/src/agent/internal/harness-stream-text-result.ts +720 -0
  27. package/src/agent/internal/lifecycle-state-validation.ts +95 -0
  28. package/src/agent/internal/permission-mode.ts +50 -0
  29. package/src/agent/internal/resolve-observability.ts +128 -0
  30. package/src/agent/internal/run-prompt.ts +813 -0
  31. package/src/agent/internal/strip-work-dir.ts +68 -0
  32. package/src/agent/internal/to-harness-stream.ts +75 -0
  33. package/src/agent/internal/translate-stream-part.ts +221 -0
  34. package/src/agent/internal/turn-telemetry.ts +359 -0
  35. package/src/agent/observability/file-reporter.ts +206 -0
  36. package/src/agent/observability/index.ts +15 -0
  37. package/src/agent/observability/trace-tree-reporter.ts +122 -0
  38. package/src/agent/observability/types.ts +86 -0
  39. package/src/agent/prewarm.ts +47 -0
  40. package/src/bridge/index.ts +702 -0
  41. package/src/errors/harness-capability-unsupported-error.ts +41 -0
  42. package/src/errors/harness-error.ts +22 -0
  43. package/src/index.ts +3 -0
  44. package/src/utils/bridge-ready.ts +277 -0
  45. package/src/utils/classify-disk-log.ts +43 -0
  46. package/src/utils/index.ts +15 -0
  47. package/src/utils/sandbox-channel.ts +453 -0
  48. package/src/v1/harness-v1-bootstrap.ts +46 -0
  49. package/src/v1/harness-v1-bridge-protocol.ts +310 -0
  50. package/src/v1/harness-v1-builtin-tool.ts +138 -0
  51. package/src/v1/harness-v1-call-warning.ts +22 -0
  52. package/src/v1/harness-v1-diagnostic.ts +66 -0
  53. package/src/v1/harness-v1-lifecycle-state.ts +65 -0
  54. package/src/v1/harness-v1-metadata.ts +13 -0
  55. package/src/v1/harness-v1-network-sandbox-session.ts +123 -0
  56. package/src/v1/harness-v1-observability.ts +20 -0
  57. package/src/v1/harness-v1-permission-mode.ts +11 -0
  58. package/src/v1/harness-v1-prompt-control.ts +41 -0
  59. package/src/v1/harness-v1-prompt.ts +11 -0
  60. package/src/v1/harness-v1-sandbox-provider.ts +76 -0
  61. package/src/v1/harness-v1-session.ts +272 -0
  62. package/src/v1/harness-v1-skill.ts +36 -0
  63. package/src/v1/harness-v1-stream-part.ts +363 -0
  64. package/src/v1/harness-v1-tool-spec.ts +31 -0
  65. package/src/v1/harness-v1.ts +83 -0
  66. package/src/v1/index.ts +93 -0
  67. package/utils/index.ts +1 -0
@@ -0,0 +1,225 @@
1
+ import { FlexibleSchema, Experimental_SandboxProcess, Experimental_SandboxSession } from '@ai-sdk/provider-utils';
2
+ import { WebSocket } from 'ws';
3
+
4
+ /**
5
+ * Diagnostic event surfaced by {@link SandboxChannel} during its connection
6
+ * lifecycle. Silent unless a consumer wires `onDebug`. Reconnects are otherwise
7
+ * invisible — the channel reconnects transparently and the in-flight turn keeps
8
+ * streaming.
9
+ */
10
+ type SandboxChannelDebugEvent = {
11
+ event: 'reconnect-attempt';
12
+ attempt: number;
13
+ lastSeenEventId: number;
14
+ } | {
15
+ event: 'reconnected';
16
+ attempt: number;
17
+ lastSeenEventId: number;
18
+ } | {
19
+ event: 'reconnect-failed';
20
+ attempts: number;
21
+ lastSeenEventId: number;
22
+ cause: unknown;
23
+ };
24
+ interface SandboxChannelReconnectOptions {
25
+ /** Give up reconnecting after this many milliseconds. Default 30_000. */
26
+ readonly maxElapsedMs?: number;
27
+ /** First backoff delay. Default 50. */
28
+ readonly initialDelayMs?: number;
29
+ /** Backoff ceiling. Default 2_000. */
30
+ readonly maxDelayMs?: number;
31
+ }
32
+ interface SandboxChannelOptions<TOut> {
33
+ /**
34
+ * Open a fresh WebSocket to the bridge and resolve once it is ready to carry
35
+ * frames (i.e. after any adapter-specific handshake such as Claude Code's
36
+ * `bridge-hello`). Called once by {@link SandboxChannel.open} and again on
37
+ * every transient reconnect. Must reject if the connection cannot be
38
+ * established.
39
+ */
40
+ connect: () => Promise<WebSocket>;
41
+ /** Schema validating inbound (bridge → host) frames. */
42
+ outboundSchema: FlexibleSchema<TOut>;
43
+ reconnect?: SandboxChannelReconnectOptions;
44
+ onDebug?: (event: SandboxChannelDebugEvent) => void;
45
+ /**
46
+ * Sink for forwarded bridge diagnostics — `sandbox-log` (captured
47
+ * console lines) and `debug-event` (structured) frames. When set, these
48
+ * frame types are routed here instead of the per-type listener dispatch, so
49
+ * they never reach the consumer's stream. Typed to the diagnostic members of
50
+ * `TOut`, so it is a no-op union for channels whose protocol has none.
51
+ */
52
+ onDiagnostic?: (event: Extract<TOut, {
53
+ type: 'sandbox-log' | 'debug-event';
54
+ }>) => void;
55
+ /**
56
+ * Seed the host-side cursor before the first connect. Pass the
57
+ * `lastSeenEventId` persisted from a prior process so the bridge replays only
58
+ * events past it when this channel opens with `{ resume: true }` — the
59
+ * cross-process attach handshake. Defaults to `0` (fresh session).
60
+ */
61
+ initialLastSeenEventId?: number;
62
+ }
63
+ type EventTypeOf<TOut extends {
64
+ type: string;
65
+ }> = TOut['type'];
66
+ type Listener<TOut extends {
67
+ type: string;
68
+ }, T extends EventTypeOf<TOut>> = (event: Extract<TOut, {
69
+ type: T;
70
+ }>) => void;
71
+ /**
72
+ * Host-side typed wrapper around the bridge WebSocket connection.
73
+ *
74
+ * Buffers inbound messages until a listener for their type is registered, so
75
+ * callers that subscribe asynchronously do not miss early frames. Inbound
76
+ * dispatch is serialised through a promise chain so a `close` event that
77
+ * arrives on the same microtask as the final `finish` message does not fire
78
+ * close handlers until the message has been dispatched.
79
+ *
80
+ * Survives transient disconnects. The bridge keeps running and
81
+ * accumulates events in an in-memory log keyed by a monotonic `seq`; on an
82
+ * unexpected socket drop this channel re-invokes `connect`, re-wires the new
83
+ * socket, and asks the bridge to replay everything past `lastSeenEventId`. The
84
+ * in-flight turn never observes the blip — `onClose` fires only after a
85
+ * host-initiated close or once the reconnect budget is exhausted.
86
+ */
87
+ declare class SandboxChannel<TOut extends {
88
+ type: string;
89
+ }, TIn extends {
90
+ type: string;
91
+ } = {
92
+ type: string;
93
+ }> {
94
+ private readonly listeners;
95
+ private readonly buffered;
96
+ private readonly onCloseHandlers;
97
+ private readonly connectThunk;
98
+ private readonly outboundSchema;
99
+ private readonly onDebug;
100
+ private readonly onDiagnostic;
101
+ private readonly maxElapsedMs;
102
+ private readonly initialDelayMs;
103
+ private readonly maxDelayMs;
104
+ private ws;
105
+ private connected;
106
+ /** Host has begun teardown; suppresses reconnect so a bridge-side close finalises. */
107
+ private closing;
108
+ /**
109
+ * Host has gracefully suspended (slice boundary). Inbound frames are ignored
110
+ * from this point so the cursor stops advancing exactly at the last delivered
111
+ * event — the bridge keeps the turn running and the not-yet-delivered tail is
112
+ * replayed to the next process on `resume`.
113
+ */
114
+ private suspended;
115
+ /** Channel is fully torn down; `send` throws and `onClose` has fired. */
116
+ private terminal;
117
+ private _lastSeenEventId;
118
+ private readonly pendingSends;
119
+ private dispatchChain;
120
+ constructor(options: SandboxChannelOptions<TOut>);
121
+ /**
122
+ * Highest bridge event `seq` this channel has observed. Persist it (e.g. via
123
+ * the adapter's resume handle) so a future process can seed
124
+ * {@link SandboxChannelOptions.initialLastSeenEventId} and attach.
125
+ */
126
+ get lastSeenEventId(): number;
127
+ /**
128
+ * Establish the initial connection. A single attempt — startup failures
129
+ * reject so the caller can fail `doStart` cleanly. Reconnect retries apply
130
+ * only to drops after a successful open.
131
+ *
132
+ * Pass `{ resume: true }` to attach to a bridge that is already mid-session:
133
+ * after the socket opens, the channel sends `{ type: 'resume', lastSeenEventId }`
134
+ * so the bridge replays everything past the seeded cursor. This is the
135
+ * cross-process attach handshake — identical to what a transient reconnect
136
+ * does, but triggered by the initial open from a new process.
137
+ */
138
+ open(opts?: {
139
+ resume?: boolean;
140
+ }): Promise<void>;
141
+ on<T extends EventTypeOf<TOut>>(type: T, listener: Listener<TOut, T>): () => void;
142
+ onClose(handler: (code: number, reason: string) => void): void;
143
+ send(message: TIn): void;
144
+ /**
145
+ * Mark that the host is tearing the session down. The next socket close is
146
+ * then treated as terminal rather than triggering a reconnect. Call before
147
+ * sending a `shutdown` / `detach` message whose ack the bridge follows with a
148
+ * socket close.
149
+ */
150
+ beginClose(): void;
151
+ close(): void;
152
+ /**
153
+ * Gracefully suspend at a slice boundary: stop processing inbound frames
154
+ * (so the cursor freezes at the last delivered event), drain any frames
155
+ * already queued for dispatch, then close the socket and finalise with the
156
+ * close reason `'suspended'`. Resolves with the final `lastSeenEventId`.
157
+ *
158
+ * The bridge keeps the in-flight turn running (a host socket close never
159
+ * aborts it) and accumulates events past the cursor for the next process to
160
+ * `resume`. Unlike {@link close}, the consumer's active turn is wound down
161
+ * cleanly — adapters distinguish a suspend from an unexpected drop via the
162
+ * `'suspended'` close reason and resolve `done` successfully.
163
+ */
164
+ suspend(): Promise<number>;
165
+ isClosed(): boolean;
166
+ private wire;
167
+ private reconnectLoop;
168
+ private rawSend;
169
+ private flushPending;
170
+ private enqueue;
171
+ private handleIncoming;
172
+ private dispatch;
173
+ private finalizeClose;
174
+ }
175
+
176
+ /**
177
+ * Recovery rung selected from an on-disk bridge event log when attach is not
178
+ * possible (the bridge process is gone): `'replay'` when the log holds a
179
+ * complete turn the host can resume from its cursor, `'rerun'` otherwise.
180
+ */
181
+ type DiskLogRecoveryMode = 'replay' | 'rerun';
182
+ /**
183
+ * Decide whether a respawned bridge can `replay` a turn from its persisted
184
+ * `event-log.ndjson`, or must `rerun` it from scratch.
185
+ *
186
+ * A turn is replayable only when its log ends in a terminal `finish` event —
187
+ * a log that is missing, empty, or ends mid-turn means the bridge died before
188
+ * completing the turn, so there is no coherent tail to deliver and the runtime
189
+ * must re-run it (continuing its own thread from the sandbox snapshot).
190
+ *
191
+ * @param eventLog Raw contents of `event-log.ndjson` (newline-delimited JSON),
192
+ * or `null`/`undefined`/empty when the file is absent.
193
+ */
194
+ declare function classifyDiskLog(eventLog: string | null | undefined): Promise<DiskLogRecoveryMode>;
195
+
196
+ type BridgeReadySource = 'stdout' | 'metadata';
197
+ type BridgeReadyErrorContext = {
198
+ proc: Experimental_SandboxProcess;
199
+ stdoutTail: string[];
200
+ };
201
+ type WaitForBridgeReadyOptions = {
202
+ proc: Experimental_SandboxProcess;
203
+ sandbox: Experimental_SandboxSession;
204
+ bridgeStateDir: string;
205
+ bridgeType: string;
206
+ timeoutMs: number;
207
+ abortSignal?: AbortSignal;
208
+ pollIntervalMs?: number;
209
+ createTimeoutError?: (context: BridgeReadyErrorContext) => Error | Promise<Error>;
210
+ createExitError?: (context: BridgeReadyErrorContext) => Error | Promise<Error>;
211
+ };
212
+ type WaitForBridgeReadyResult = {
213
+ port: number;
214
+ source: BridgeReadySource;
215
+ stdoutTail: string[];
216
+ };
217
+ declare function markBridgeStarting({ sandbox, bridgeStateDir, bridgeType, abortSignal, }: {
218
+ sandbox: Experimental_SandboxSession;
219
+ bridgeStateDir: string;
220
+ bridgeType: string;
221
+ abortSignal?: AbortSignal;
222
+ }): Promise<void>;
223
+ declare function waitForBridgeReady({ proc, sandbox, bridgeStateDir, bridgeType, timeoutMs, abortSignal, pollIntervalMs, createTimeoutError, createExitError, }: WaitForBridgeReadyOptions): Promise<WaitForBridgeReadyResult>;
224
+
225
+ export { type BridgeReadyErrorContext, type BridgeReadySource, type DiskLogRecoveryMode, SandboxChannel, type SandboxChannelDebugEvent, type SandboxChannelOptions, type SandboxChannelReconnectOptions, type WaitForBridgeReadyOptions, type WaitForBridgeReadyResult, classifyDiskLog, markBridgeStarting, waitForBridgeReady };