@ai-sdk/harness 0.0.0-6b196531-20260710185421

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 (77) hide show
  1. package/CHANGELOG.md +414 -0
  2. package/LICENSE +13 -0
  3. package/README.md +176 -0
  4. package/agent/index.ts +56 -0
  5. package/bridge/index.ts +10 -0
  6. package/dist/agent/index.d.ts +1631 -0
  7. package/dist/agent/index.js +3491 -0
  8. package/dist/agent/index.js.map +1 -0
  9. package/dist/bridge/index.d.ts +129 -0
  10. package/dist/bridge/index.js +482 -0
  11. package/dist/bridge/index.js.map +1 -0
  12. package/dist/index.d.ts +1587 -0
  13. package/dist/index.js +517 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/utils/index.d.ts +329 -0
  16. package/dist/utils/index.js +1241 -0
  17. package/dist/utils/index.js.map +1 -0
  18. package/package.json +100 -0
  19. package/src/agent/harness-agent-session.ts +518 -0
  20. package/src/agent/harness-agent-settings.ts +187 -0
  21. package/src/agent/harness-agent-tool-approval-continuation.ts +94 -0
  22. package/src/agent/harness-agent-tool-types.ts +15 -0
  23. package/src/agent/harness-agent-types.ts +50 -0
  24. package/src/agent/harness-agent.ts +865 -0
  25. package/src/agent/internal/bootstrap-recipe.ts +124 -0
  26. package/src/agent/internal/bridge-port-registry.ts +52 -0
  27. package/src/agent/internal/harness-stream-text-result.ts +731 -0
  28. package/src/agent/internal/lifecycle-state-validation.ts +95 -0
  29. package/src/agent/internal/permission-mode.ts +50 -0
  30. package/src/agent/internal/resolve-observability.ts +128 -0
  31. package/src/agent/internal/run-prompt.ts +901 -0
  32. package/src/agent/internal/sandbox-bootstrap.ts +266 -0
  33. package/src/agent/internal/strip-work-dir.ts +68 -0
  34. package/src/agent/internal/to-harness-stream.ts +75 -0
  35. package/src/agent/internal/tool-filtering.ts +114 -0
  36. package/src/agent/internal/translate-stream-part.ts +221 -0
  37. package/src/agent/internal/turn-telemetry.ts +361 -0
  38. package/src/agent/observability/file-reporter.ts +206 -0
  39. package/src/agent/observability/index.ts +15 -0
  40. package/src/agent/observability/trace-tree-reporter.ts +122 -0
  41. package/src/agent/observability/types.ts +86 -0
  42. package/src/agent/prepare-harness-sandbox-template.ts +68 -0
  43. package/src/agent/prepare-sandbox-for-harness.ts +165 -0
  44. package/src/bridge/index.ts +797 -0
  45. package/src/errors/harness-capability-unsupported-error.ts +41 -0
  46. package/src/errors/harness-error.ts +22 -0
  47. package/src/index.ts +3 -0
  48. package/src/utils/ai-gateway-auth.ts +15 -0
  49. package/src/utils/bridge-diagnostics.ts +213 -0
  50. package/src/utils/bridge-ready.ts +277 -0
  51. package/src/utils/classify-disk-log.ts +43 -0
  52. package/src/utils/index.ts +31 -0
  53. package/src/utils/sandbox-channel.ts +525 -0
  54. package/src/utils/sandbox-home-dir.ts +22 -0
  55. package/src/utils/shell-quote.ts +3 -0
  56. package/src/utils/write-skills.ts +141 -0
  57. package/src/v1/harness-v1-bootstrap.ts +46 -0
  58. package/src/v1/harness-v1-bridge-protocol.ts +342 -0
  59. package/src/v1/harness-v1-builtin-tool.ts +138 -0
  60. package/src/v1/harness-v1-call-warning.ts +22 -0
  61. package/src/v1/harness-v1-diagnostic.ts +66 -0
  62. package/src/v1/harness-v1-lifecycle-state.ts +65 -0
  63. package/src/v1/harness-v1-metadata.ts +13 -0
  64. package/src/v1/harness-v1-network-sandbox-session.ts +123 -0
  65. package/src/v1/harness-v1-observability.ts +20 -0
  66. package/src/v1/harness-v1-permission-mode.ts +11 -0
  67. package/src/v1/harness-v1-prompt-control.ts +41 -0
  68. package/src/v1/harness-v1-prompt.ts +11 -0
  69. package/src/v1/harness-v1-sandbox-provider.ts +76 -0
  70. package/src/v1/harness-v1-session.ts +280 -0
  71. package/src/v1/harness-v1-skill.ts +36 -0
  72. package/src/v1/harness-v1-stream-part.ts +363 -0
  73. package/src/v1/harness-v1-tool-filtering.ts +25 -0
  74. package/src/v1/harness-v1-tool-spec.ts +31 -0
  75. package/src/v1/harness-v1.ts +94 -0
  76. package/src/v1/index.ts +99 -0
  77. package/utils/index.ts +1 -0
@@ -0,0 +1,46 @@
1
+ /**
2
+ * One file to write into the sandbox as part of an adapter's bootstrap recipe.
3
+ * Paths should live under {@link HarnessV1Bootstrap.bootstrapDir}.
4
+ */
5
+ export interface HarnessV1BootstrapFile {
6
+ readonly path: string;
7
+ readonly content: string;
8
+ }
9
+
10
+ /**
11
+ * One command to run in the sandbox as part of an adapter's bootstrap recipe.
12
+ * Commands run sequentially after all files have been written; a non-zero exit
13
+ * aborts the bootstrap.
14
+ */
15
+ export interface HarnessV1BootstrapCommand {
16
+ readonly command: string;
17
+ readonly workingDirectory?: string;
18
+ }
19
+
20
+ /**
21
+ * Adapter-owned bootstrap recipe. The adapter declares the files and commands
22
+ * needed to set up its bridge inside any sandbox. The harness framework hashes
23
+ * the recipe into an identity used by sandbox providers for snapshot-based
24
+ * reuse, and applies the recipe idempotently before the bridge spawns.
25
+ */
26
+ export interface HarnessV1Bootstrap {
27
+ /**
28
+ * Stable id of the adapter that owns this recipe. Conventionally matches
29
+ * {@link HarnessV1.harnessId}. Contributes to the recipe hash.
30
+ */
31
+ readonly harnessId: string;
32
+
33
+ /**
34
+ * Absolute path inside the sandbox where this recipe writes its state.
35
+ * The marker file lives directly under it. Files declared in {@link files}
36
+ * should also use this prefix so an adapter upgrade can sweep stale state
37
+ * by clearing the directory.
38
+ */
39
+ readonly bootstrapDir: string;
40
+
41
+ /** Files to write into the sandbox before any command runs. */
42
+ readonly files: ReadonlyArray<HarnessV1BootstrapFile>;
43
+
44
+ /** Commands to run after files are written, in order. */
45
+ readonly commands: ReadonlyArray<HarnessV1BootstrapCommand>;
46
+ }
@@ -0,0 +1,342 @@
1
+ import { z } from 'zod/v4';
2
+ import {
3
+ harnessV1DebugConfigSchema,
4
+ harnessV1DebugLevelSchema,
5
+ type HarnessV1Diagnostic,
6
+ } from './harness-v1-diagnostic';
7
+ import {
8
+ harnessV1CompactionPartSchema,
9
+ harnessV1ErrorPartSchema,
10
+ harnessV1FileChangePartSchema,
11
+ harnessV1FinishPartSchema,
12
+ harnessV1FinishStepPartSchema,
13
+ harnessV1RawPartSchema,
14
+ harnessV1ReasoningDeltaPartSchema,
15
+ harnessV1ReasoningEndPartSchema,
16
+ harnessV1ReasoningStartPartSchema,
17
+ harnessV1StreamStartPartSchema,
18
+ harnessV1TextDeltaPartSchema,
19
+ harnessV1TextEndPartSchema,
20
+ harnessV1TextStartPartSchema,
21
+ harnessV1ToolApprovalRequestPartSchema,
22
+ harnessV1ToolCallPartSchema,
23
+ harnessV1ToolResultPartSchema,
24
+ } from './harness-v1-stream-part';
25
+
26
+ /*
27
+ * The bridge wire protocol shared by every bridge-backed harness adapter.
28
+ *
29
+ * This is the serialization of the host<->runtime contract for adapters that
30
+ * run the agent runtime inside the sandbox and talk to the host over a
31
+ * WebSocket. It exists ONLY because of that transport: untrusted JSON frames
32
+ * crossing the sandbox boundary need runtime validation, the connection needs
33
+ * a handshake, and the host drives turns with serialized commands. Every export
34
+ * here is therefore prefixed `harnessV1Bridge…`.
35
+ *
36
+ * It has three tiers:
37
+ *
38
+ * 1. The OUTBOUND events — `HarnessV1StreamPart` re-expressed as Zod (imported
39
+ * member schemas from `harness-v1-stream-part.ts`), because the part type is
40
+ * compile-time only and the frames need runtime validation at the boundary.
41
+ * 2. The transport/control frames that are NOT consumer events — `bridge-hello`
42
+ * (handshake), `bridge-detach` (resume payload), `bridge-thread` (a resume
43
+ * coordinate some runtimes announce). These ride the same socket.
44
+ * 3. The INBOUND command vocabulary the host sends back: the shared commands
45
+ * live here; the per-adapter `start` payload extends
46
+ * `harnessV1BridgeStartBaseSchema` and assembles the final inbound union in
47
+ * the adapter package.
48
+ *
49
+ * Non-bridge adapters (e.g. Pi) do not use this layer at all — they have no
50
+ * serialization boundary and target the universal `HarnessV1StreamPart` type
51
+ * directly. That is the deliberate split: `harness-v1-stream-part.ts` is the
52
+ * transport-agnostic event vocabulary; this file is the bridge transport.
53
+ */
54
+
55
+ /**
56
+ * The subset of a host-defined tool that travels on the `start` message. The
57
+ * runtime only needs the name, description, and JSON-Schema input to surface
58
+ * the tool; `execute` stays on the host.
59
+ */
60
+ export const harnessV1BridgeToolWireSchema = z.object({
61
+ name: z.string(),
62
+ description: z.string().optional(),
63
+ inputSchema: z.unknown().optional(),
64
+ });
65
+
66
+ export type HarnessV1BridgeToolWire = z.infer<
67
+ typeof harnessV1BridgeToolWireSchema
68
+ >;
69
+
70
+ export const harnessV1BridgePermissionModeSchema = z.enum([
71
+ 'allow-reads',
72
+ 'allow-edits',
73
+ 'allow-all',
74
+ ]);
75
+
76
+ export const harnessV1BridgeBuiltinToolFilteringSchema = z.discriminatedUnion(
77
+ 'mode',
78
+ [
79
+ z.object({
80
+ mode: z.literal('allow'),
81
+ toolNames: z.array(z.string()),
82
+ }),
83
+ z.object({
84
+ mode: z.literal('deny'),
85
+ toolNames: z.array(z.string()),
86
+ }),
87
+ ],
88
+ );
89
+
90
+ /**
91
+ * Common fields of the inbound `start` message. Each adapter extends this with
92
+ * its runtime-specific configuration (e.g. `thinking`/`continue` for Claude
93
+ * Code, `reasoningEffort`/`webSearch`/`skills`/`resumeThreadId` for Codex) and
94
+ * assembles the final inbound union from the shared command members below.
95
+ *
96
+ * `debug` carries the general `HarnessV1DebugConfig` — diagnostics config is not
97
+ * a bridge concept, it just happens to ride the `start` frame for bridge-backed
98
+ * adapters.
99
+ */
100
+ export const harnessV1BridgeStartBaseSchema = z.object({
101
+ type: z.literal('start'),
102
+ prompt: z.string(),
103
+ tools: z.array(harnessV1BridgeToolWireSchema).optional(),
104
+ model: z.string().optional(),
105
+ debug: harnessV1DebugConfigSchema.optional(),
106
+ permissionMode: harnessV1BridgePermissionModeSchema.optional(),
107
+ builtinToolFiltering: harnessV1BridgeBuiltinToolFilteringSchema.optional(),
108
+ });
109
+
110
+ // --- Transport / control frames (outbound, not consumer events) ---
111
+
112
+ /**
113
+ * Sent the instant the bridge accepts an authenticated WS connection. The host
114
+ * waits for it before sending `start`/`resume`, because some sandbox runtimes
115
+ * complete the upstream WS handshake before the connection is wired through to
116
+ * the bridge process — anything sent in that gap is dropped. Carries the
117
+ * bridge's lifecycle `state` and highest emitted `seq` for reconnect.
118
+ */
119
+ export const harnessV1BridgeHelloSchema = z.object({
120
+ type: z.literal('bridge-hello'),
121
+ state: z.string().optional(),
122
+ lastSeq: z.number().optional(),
123
+ });
124
+
125
+ /**
126
+ * The bridge's reply to an inbound `detach`. Carries the adapter-specific
127
+ * payload the host serializes into lifecycle state `data`.
128
+ */
129
+ export const harnessV1BridgeDetachSchema = z.object({
130
+ type: z.literal('bridge-detach'),
131
+ data: z.unknown(),
132
+ });
133
+
134
+ /**
135
+ * A resume coordinate the bridge proactively announces (e.g. Codex's thread id)
136
+ * so the host can cache it for a later resume without waiting for `detach`.
137
+ */
138
+ export const harnessV1BridgeThreadSchema = z.object({
139
+ type: z.literal('bridge-thread'),
140
+ threadId: z.string(),
141
+ });
142
+
143
+ /**
144
+ * Acknowledgement for an inbound `interrupt` command. The host waits for this
145
+ * before freezing its replay cursor so the adapter-specific interrupt has
146
+ * actually reached the underlying runtime.
147
+ */
148
+ export const harnessV1BridgeInterruptedSchema = z.object({
149
+ type: z.literal('bridge-interrupted'),
150
+ ok: z.boolean(),
151
+ error: z.unknown().optional(),
152
+ });
153
+
154
+ // --- Diagnostics frames (outbound, not consumer events) ---
155
+
156
+ /**
157
+ * One captured console line from inside the sandbox. The bridge line-buffers
158
+ * `process.stdout`/`process.stderr` and emits one of these per complete line.
159
+ * Routed host-side to the diagnostics sink, never to the consumer stream.
160
+ */
161
+ export const harnessV1BridgeSandboxLogSchema = z.object({
162
+ type: z.literal('sandbox-log'),
163
+ source: z.string(),
164
+ stream: z.enum(['stdout', 'stderr']),
165
+ line: z.string(),
166
+ });
167
+
168
+ /**
169
+ * A structured diagnostic an adapter emits from inside the bridge via
170
+ * `turn.bridgeLog(...)`. Gated by the session's debug level + subsystem filter.
171
+ */
172
+ export const harnessV1BridgeDebugEventSchema = z.object({
173
+ type: z.literal('debug-event'),
174
+ level: harnessV1DebugLevelSchema,
175
+ subsystem: z.string(),
176
+ message: z.string(),
177
+ attrs: z.record(z.string(), z.unknown()).optional(),
178
+ error: z
179
+ .object({
180
+ name: z.string().optional(),
181
+ message: z.string(),
182
+ stack: z.string().optional(),
183
+ })
184
+ .optional(),
185
+ });
186
+
187
+ /**
188
+ * Every frame a bridge can send to the host: the stream-part events plus the
189
+ * transport/control frames. This is the schema the host `SandboxChannel`
190
+ * validates inbound frames against.
191
+ */
192
+ export const harnessV1BridgeOutboundMessageSchema = z.discriminatedUnion(
193
+ 'type',
194
+ [
195
+ harnessV1StreamStartPartSchema,
196
+ harnessV1TextStartPartSchema,
197
+ harnessV1TextDeltaPartSchema,
198
+ harnessV1TextEndPartSchema,
199
+ harnessV1ReasoningStartPartSchema,
200
+ harnessV1ReasoningDeltaPartSchema,
201
+ harnessV1ReasoningEndPartSchema,
202
+ harnessV1ToolCallPartSchema,
203
+ harnessV1ToolApprovalRequestPartSchema,
204
+ harnessV1ToolResultPartSchema,
205
+ harnessV1FinishStepPartSchema,
206
+ harnessV1FinishPartSchema,
207
+ harnessV1FileChangePartSchema,
208
+ harnessV1CompactionPartSchema,
209
+ harnessV1ErrorPartSchema,
210
+ harnessV1RawPartSchema,
211
+ harnessV1BridgeHelloSchema,
212
+ harnessV1BridgeDetachSchema,
213
+ harnessV1BridgeThreadSchema,
214
+ harnessV1BridgeInterruptedSchema,
215
+ harnessV1BridgeSandboxLogSchema,
216
+ harnessV1BridgeDebugEventSchema,
217
+ ],
218
+ );
219
+
220
+ export type HarnessV1BridgeOutboundMessage = z.infer<
221
+ typeof harnessV1BridgeOutboundMessageSchema
222
+ >;
223
+
224
+ export type HarnessV1BridgeSandboxLog = z.infer<
225
+ typeof harnessV1BridgeSandboxLogSchema
226
+ >;
227
+
228
+ export type HarnessV1BridgeDebugEvent = z.infer<
229
+ typeof harnessV1BridgeDebugEventSchema
230
+ >;
231
+
232
+ /**
233
+ * Normalize a bridge diagnostics wire frame into the transport-agnostic
234
+ * `HarnessV1Diagnostic` an adapter reports to the framework. A captured console
235
+ * line maps `stderr` → `warn` and `stdout` → `info`; a structured event passes
236
+ * its fields through. This is the seam where the bridge's serialization is
237
+ * lifted into the general emission shape every harness shares.
238
+ */
239
+ export function harnessV1DiagnosticFromBridgeFrame(
240
+ frame: HarnessV1BridgeSandboxLog | HarnessV1BridgeDebugEvent,
241
+ context: { sessionId?: string; timestamp: number },
242
+ ): HarnessV1Diagnostic {
243
+ if (frame.type === 'sandbox-log') {
244
+ return {
245
+ level: frame.stream === 'stderr' ? 'warn' : 'info',
246
+ message: frame.line,
247
+ subsystem: `sandbox.log.${frame.source}`,
248
+ kind: 'log',
249
+ source: frame.source,
250
+ stream: frame.stream,
251
+ sessionId: context.sessionId,
252
+ timestamp: context.timestamp,
253
+ };
254
+ }
255
+ return {
256
+ level: frame.level,
257
+ message: frame.message,
258
+ subsystem: frame.subsystem,
259
+ kind: 'event',
260
+ attrs: frame.attrs,
261
+ error: frame.error,
262
+ sessionId: context.sessionId,
263
+ timestamp: context.timestamp,
264
+ };
265
+ }
266
+
267
+ // --- Shared inbound command members (host -> bridge) ---
268
+
269
+ export const harnessV1BridgeToolResultInboundSchema = z.object({
270
+ type: z.literal('tool-result'),
271
+ toolCallId: z.string(),
272
+ output: z.unknown(),
273
+ isError: z.boolean().optional(),
274
+ });
275
+
276
+ export const harnessV1BridgeToolApprovalResponseInboundSchema = z.object({
277
+ type: z.literal('tool-approval-response'),
278
+ approvalId: z.string(),
279
+ approved: z.boolean(),
280
+ reason: z.string().optional(),
281
+ });
282
+
283
+ export const harnessV1BridgeUserMessageInboundSchema = z.object({
284
+ type: z.literal('user-message'),
285
+ text: z.string(),
286
+ });
287
+
288
+ export const harnessV1BridgeAbortInboundSchema = z.object({
289
+ type: z.literal('abort'),
290
+ });
291
+
292
+ export const harnessV1BridgeInterruptInboundSchema = z.object({
293
+ type: z.literal('interrupt'),
294
+ });
295
+
296
+ export const harnessV1BridgeShutdownInboundSchema = z.object({
297
+ type: z.literal('shutdown'),
298
+ });
299
+
300
+ /**
301
+ * Reconnect: after re-establishing the socket the host asks the bridge to
302
+ * replay every buffered event with `seq > lastSeenEventId`.
303
+ */
304
+ export const harnessV1BridgeResumeInboundSchema = z.object({
305
+ type: z.literal('resume'),
306
+ lastSeenEventId: z.number(),
307
+ });
308
+
309
+ /**
310
+ * The bridge replies with `bridge-detach` carrying any cached resume payload,
311
+ * then exits.
312
+ */
313
+ export const harnessV1BridgeDetachInboundSchema = z.object({
314
+ type: z.literal('detach'),
315
+ });
316
+
317
+ /**
318
+ * The inbound command members shared by every bridge adapter. Spread these
319
+ * alongside the adapter's own `start` schema to build the final inbound union:
320
+ * `z.discriminatedUnion('type', [adapterStartSchema, ...harnessV1BridgeInboundCommandSchemas])`.
321
+ */
322
+ export const harnessV1BridgeInboundCommandSchemas = [
323
+ harnessV1BridgeToolResultInboundSchema,
324
+ harnessV1BridgeToolApprovalResponseInboundSchema,
325
+ harnessV1BridgeUserMessageInboundSchema,
326
+ harnessV1BridgeAbortInboundSchema,
327
+ harnessV1BridgeInterruptInboundSchema,
328
+ harnessV1BridgeShutdownInboundSchema,
329
+ harnessV1BridgeResumeInboundSchema,
330
+ harnessV1BridgeDetachInboundSchema,
331
+ ] as const;
332
+
333
+ /**
334
+ * The JSON line the bridge writes to stdout once its WebSocket server is bound,
335
+ * announcing the port the host should connect to.
336
+ */
337
+ export const harnessV1BridgeReadySchema = z.object({
338
+ type: z.literal('bridge-ready'),
339
+ port: z.number(),
340
+ });
341
+
342
+ export type HarnessV1BridgeReady = z.infer<typeof harnessV1BridgeReadySchema>;
@@ -0,0 +1,138 @@
1
+ import { tool, type FlexibleSchema, type Tool } from '@ai-sdk/provider-utils';
2
+ import { z } from 'zod/v4';
3
+
4
+ /**
5
+ * Cross-harness vocabulary of common built-in tool names with their baseline
6
+ * input schemas. Adapters that declare a built-in with one of these
7
+ * `commonName`s must accept (at least) every input the baseline schema
8
+ * accepts. Extra optional fields are encouraged.
9
+ *
10
+ * Used both as runtime values (spread into `ToolSet`s for inspection) and as
11
+ * a vocabulary source — `HarnessV1BuiltinToolName` is derived from its keys.
12
+ */
13
+ export const HARNESS_V1_BUILTIN_TOOLS = {
14
+ read: tool({
15
+ description: 'Read file contents',
16
+ inputSchema: z.object({ file_path: z.string() }),
17
+ outputSchema: z.unknown(),
18
+ }),
19
+ write: tool({
20
+ description: 'Write content to a file',
21
+ inputSchema: z.object({ file_path: z.string(), content: z.string() }),
22
+ outputSchema: z.unknown(),
23
+ }),
24
+ edit: tool({
25
+ description: 'Edit a file by replacing text',
26
+ inputSchema: z.object({
27
+ file_path: z.string(),
28
+ old_string: z.string(),
29
+ new_string: z.string(),
30
+ }),
31
+ outputSchema: z.unknown(),
32
+ }),
33
+ bash: tool({
34
+ description: 'Execute a shell command',
35
+ inputSchema: z.object({ command: z.string() }),
36
+ outputSchema: z.unknown(),
37
+ }),
38
+ grep: tool({
39
+ description: 'Search file contents with regex',
40
+ inputSchema: z.object({ pattern: z.string() }),
41
+ outputSchema: z.unknown(),
42
+ }),
43
+ glob: tool({
44
+ description: 'Find files matching a glob pattern',
45
+ inputSchema: z.object({ pattern: z.string() }),
46
+ outputSchema: z.unknown(),
47
+ }),
48
+ webSearch: tool({
49
+ description: 'Search the web',
50
+ inputSchema: z.object({ query: z.string() }),
51
+ outputSchema: z.unknown(),
52
+ }),
53
+ } as const;
54
+
55
+ export type HarnessV1BuiltinToolName = keyof typeof HARNESS_V1_BUILTIN_TOOLS;
56
+
57
+ export const HARNESS_V1_BUILTIN_TOOL_NAMES = Object.keys(
58
+ HARNESS_V1_BUILTIN_TOOLS,
59
+ ) as ReadonlyArray<HarnessV1BuiltinToolName>;
60
+
61
+ export type HarnessV1BuiltinToolUseKind = 'readonly' | 'edit' | 'bash';
62
+
63
+ /**
64
+ * A tool that the adapter's underlying runtime exposes natively. Extends the
65
+ * AI SDK `Tool` shape with two optional harness-specific fields:
66
+ *
67
+ * - `nativeName`: the name as the underlying runtime knows it. Required
68
+ * only when the tool's key in the harness's `builtinTools` is not the
69
+ * native name — i.e. when the tool maps to a `commonName` (e.g. key
70
+ * `'bash'` for Claude Code's native `'Bash'`). Tools without a common
71
+ * equivalent are keyed by their native name directly, so `nativeName`
72
+ * is redundant and omitted.
73
+ * - `commonName`: cross-harness label drawn from
74
+ * `HARNESS_V1_BUILTIN_TOOL_NAMES`. Set when the tool maps to a familiar
75
+ * capability; consumers use it to recognize, e.g., that Claude Code's
76
+ * `Bash` and Codex's `shell` are the same kind of tool.
77
+ *
78
+ * Always set both fields together via the `commonTool` helper, or neither
79
+ * (declare the tool with the AI SDK's `tool()` directly).
80
+ */
81
+ export type HarnessV1BuiltinTool<INPUT = unknown, OUTPUT = unknown> = Tool<
82
+ INPUT,
83
+ OUTPUT,
84
+ any
85
+ > & {
86
+ readonly nativeName?: string;
87
+ readonly commonName?: HarnessV1BuiltinToolName;
88
+ readonly toolUseKind?: HarnessV1BuiltinToolUseKind;
89
+ };
90
+
91
+ type InputOf<T> = T extends Tool<infer I, any, any> ? I : never;
92
+
93
+ type StandardInputOf<N extends HarnessV1BuiltinToolName> = InputOf<
94
+ (typeof HARNESS_V1_BUILTIN_TOOLS)[N]
95
+ >;
96
+
97
+ /*
98
+ * Type-level superset check. If `TStandard` is assignable to `TAdapter`
99
+ * (i.e. the adapter accepts every input the standard accepts), the return
100
+ * type is `TOk`. Otherwise it's a tagged error tuple that surfaces a clear
101
+ * TypeScript error at the call site.
102
+ */
103
+ type SupersetCheck<TStandard, TAdapter, TOk> = TStandard extends TAdapter
104
+ ? TOk
105
+ : [
106
+ 'ERROR: adapter input schema must be a superset of the standard schema',
107
+ { expected: TStandard; got: TAdapter },
108
+ ];
109
+
110
+ /**
111
+ * Declare a built-in tool that maps to a cross-harness common name. The
112
+ * adapter's input schema must accept every input the standard schema for
113
+ * `commonName` accepts. Extra optional fields are encouraged.
114
+ *
115
+ * If the schema is missing a field the standard requires (or has an
116
+ * incompatible type), the return type collapses to a tagged error tuple,
117
+ * which fails the surrounding `as const satisfies ToolSet` assignment and
118
+ * surfaces a readable TypeScript error at the offending entry.
119
+ */
120
+ export function commonTool<TName extends HarnessV1BuiltinToolName, TInput>(
121
+ commonName: TName,
122
+ opts: {
123
+ readonly nativeName: string;
124
+ readonly toolUseKind?: HarnessV1BuiltinToolUseKind;
125
+ readonly description?: string;
126
+ readonly inputSchema: FlexibleSchema<TInput>;
127
+ },
128
+ ): SupersetCheck<StandardInputOf<TName>, TInput, HarnessV1BuiltinTool<TInput>> {
129
+ return {
130
+ ...tool({
131
+ description: opts.description,
132
+ inputSchema: opts.inputSchema as FlexibleSchema<TInput>,
133
+ }),
134
+ nativeName: opts.nativeName,
135
+ commonName,
136
+ toolUseKind: opts.toolUseKind,
137
+ } as never;
138
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Warning emitted by a harness adapter during a call.
3
+ *
4
+ * Surfaces non-fatal issues such as unsupported options or quirks of the
5
+ * underlying agent runtime. Mirrors the shape of `SharedV4Warning` but lives
6
+ * in the harness namespace.
7
+ */
8
+ export type HarnessV1CallWarning =
9
+ | {
10
+ type: 'unsupported-setting';
11
+ setting: string;
12
+ details?: string;
13
+ }
14
+ | {
15
+ type: 'unsupported-tool';
16
+ tool: string;
17
+ details?: string;
18
+ }
19
+ | {
20
+ type: 'other';
21
+ message: string;
22
+ };
@@ -0,0 +1,66 @@
1
+ import { z } from 'zod/v4';
2
+
3
+ /*
4
+ * Diagnostics EMISSION contract — part of the `HarnessV1` spec.
5
+ *
6
+ * These are the types a harness adapter produces and receives: an adapter
7
+ * reports a `HarnessV1Diagnostic` to the framework (a bridge adapter normalizes
8
+ * its wire frames into one; a non-bridge adapter constructs one directly), and
9
+ * receives a `HarnessV1DebugConfig` to gate what it emits. They are distinct
10
+ * from the unaffixed host-facing `HarnessDiagnostic` / `HarnessDebugConfig`
11
+ * (the external/telemetry surface) — the framework maps between the two at the
12
+ * boundary, so the emission and consumption surfaces can evolve independently.
13
+ */
14
+
15
+ /** Severity of a diagnostic, ordered most → least severe. */
16
+ export const harnessV1DebugLevelSchema = z.enum([
17
+ 'error',
18
+ 'warn',
19
+ 'info',
20
+ 'debug',
21
+ 'trace',
22
+ ]);
23
+
24
+ export type HarnessV1DebugLevel = z.infer<typeof harnessV1DebugLevelSchema>;
25
+
26
+ /**
27
+ * Per-session diagnostics configuration the framework hands an adapter (and the
28
+ * host sends on `start.debug`). When absent or `enabled` is false the adapter
29
+ * captures and emits nothing. `subsystems` filters structured events by dotted
30
+ * prefix; console capture is independent of the subsystem filter.
31
+ */
32
+ export const harnessV1DebugConfigSchema = z.object({
33
+ enabled: z.boolean().optional(),
34
+ level: harnessV1DebugLevelSchema.optional(),
35
+ subsystems: z.array(z.string()).optional(),
36
+ });
37
+
38
+ export type HarnessV1DebugConfig = z.infer<typeof harnessV1DebugConfigSchema>;
39
+
40
+ /**
41
+ * A diagnostic as emitted by a harness adapter. Structurally identical to the
42
+ * host-facing `HarnessDiagnostic` today, but kept separate: this is the spec's
43
+ * emission shape, that is the external consumption shape.
44
+ */
45
+ export type HarnessV1Diagnostic = {
46
+ /** Severity. */
47
+ readonly level: HarnessV1DebugLevel;
48
+ /** Human-readable line (console capture) or message (structured event). */
49
+ readonly message: string;
50
+ /** Dotted subsystem (`sandbox.log.<source>` for console capture). */
51
+ readonly subsystem: string;
52
+ /** `'log'` = captured console line; `'event'` = structured emission. */
53
+ readonly kind: 'log' | 'event';
54
+ /** Originating source label (console capture). */
55
+ readonly source?: string;
56
+ /** Which standard stream the line came from (console capture). */
57
+ readonly stream?: 'stdout' | 'stderr';
58
+ /** Structured attributes (structured events only). */
59
+ readonly attrs?: Record<string, unknown>;
60
+ /** Error payload (structured events only). */
61
+ readonly error?: { name?: string; message: string; stack?: string };
62
+ /** The harness session this diagnostic originated from. */
63
+ readonly sessionId?: string;
64
+ /** Emission time (epoch ms). */
65
+ readonly timestamp: number;
66
+ };
@@ -0,0 +1,65 @@
1
+ import type { JSONValue } from '@ai-sdk/provider';
2
+
3
+ export type HarnessV1PendingToolApproval = {
4
+ readonly approvalId: string;
5
+ readonly toolCallId: string;
6
+ readonly toolName: string;
7
+ readonly input: string;
8
+ readonly kind: 'builtin' | 'custom';
9
+ readonly providerExecuted?: boolean;
10
+ readonly nativeName?: string;
11
+ };
12
+
13
+ type HarnessV1LifecycleStateBase = {
14
+ /**
15
+ * Identifier of the harness that produced this state. Used by adapters to
16
+ * refuse mismatched payloads.
17
+ */
18
+ readonly harnessId: string;
19
+
20
+ /**
21
+ * Spec version of the harness that produced this state.
22
+ */
23
+ readonly specificationVersion: 'harness-v1';
24
+
25
+ /**
26
+ * Adapter-defined payload. May be persisted as JSON; the adapter is
27
+ * responsible for any necessary encoding.
28
+ */
29
+ readonly data: JSONValue;
30
+ };
31
+
32
+ /**
33
+ * Opaque payload returned by between-turn session lifecycle methods and
34
+ * accepted by a future `HarnessV1.doStart({ resumeFrom })` to resume the same
35
+ * underlying session before starting a new turn.
36
+ */
37
+ export type HarnessV1ResumeSessionState = HarnessV1LifecycleStateBase & {
38
+ readonly type: 'resume-session';
39
+
40
+ /**
41
+ * Optional unfinished-turn state. When present, the session must be resumed
42
+ * before the turn is continued.
43
+ */
44
+ readonly continueFrom?: HarnessV1ContinueTurnState;
45
+ };
46
+
47
+ /**
48
+ * Opaque payload returned by `doSuspendTurn` and accepted by a future
49
+ * `HarnessV1.doStart({ continueFrom })` to reconnect to the same session before
50
+ * continuing the interrupted turn.
51
+ */
52
+ export type HarnessV1ContinueTurnState = HarnessV1LifecycleStateBase & {
53
+ readonly type: 'continue-turn';
54
+
55
+ /**
56
+ * Framework-owned pending approval records. These are intentionally outside
57
+ * adapter-defined `data` so callers can persist the entire lifecycle payload
58
+ * without the harness framework owning storage.
59
+ */
60
+ readonly pendingToolApprovals?: readonly HarnessV1PendingToolApproval[];
61
+ };
62
+
63
+ export type HarnessV1LifecycleState =
64
+ | HarnessV1ResumeSessionState
65
+ | HarnessV1ContinueTurnState;
@@ -0,0 +1,13 @@
1
+ import type { JSONValue } from '@ai-sdk/provider';
2
+
3
+ /**
4
+ * Adapter-namespaced opaque data attached to harness events.
5
+ *
6
+ * Mirrors the `providerMetadata` pattern from `@ai-sdk/provider`, but lives
7
+ * in the harness namespace because a harness is a peer concept to a
8
+ * provider, not a kind of provider.
9
+ *
10
+ * Keys are harness ids (e.g. `'claude-code'`). Inner values are arbitrary
11
+ * JSON-serializable data the adapter chooses to surface to callers.
12
+ */
13
+ export type HarnessV1Metadata = Record<string, Record<string, JSONValue>>;