@ai-sdk/harness 1.0.71 → 1.0.73
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/CHANGELOG.md +18 -0
- package/README.md +23 -0
- package/dist/agent/index.d.ts +148 -23
- package/dist/agent/index.js +185 -37
- package/dist/agent/index.js.map +1 -1
- package/dist/bridge/index.js +14 -20
- package/dist/bridge/index.js.map +1 -1
- package/dist/index.d.ts +124 -10
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +59 -7
- package/dist/utils/index.js +47 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +3 -3
- package/src/agent/harness-agent-session.ts +27 -7
- package/src/agent/harness-agent-settings.ts +8 -0
- package/src/agent/harness-agent.ts +74 -23
- package/src/agent/internal/harness-stream-text-result.ts +251 -59
- package/src/agent/internal/run-prompt.ts +10 -2
- package/src/agent/prepare-sandbox-for-harness.ts +8 -18
- package/src/bridge/index.ts +41 -37
- package/src/errors/harness-capability-unsupported-error.ts +2 -2
- package/src/utils/index.ts +5 -0
- package/src/utils/sandbox-credential-brokering.ts +41 -0
- package/src/v1/harness-v1-bridge-protocol.ts +13 -0
- package/src/v1/harness-v1-network-sandbox-session.ts +87 -5
- package/src/v1/harness-v1-response-format.ts +32 -0
- package/src/v1/harness-v1-session.ts +17 -2
- package/src/v1/index.ts +10 -0
package/src/bridge/index.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// Shared in-sandbox bridge runtime. Adapter `bridge.mjs` bundles re-bundle
|
|
2
2
|
// this module (tsup inlines it; `ws` stays external and resolves from the
|
|
3
3
|
// sandbox-installed node_modules). It owns everything generic to the bridge
|
|
4
|
-
// transport — the WebSocket server, token auth,
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// transport — the WebSocket server, token auth, the in-memory event log +
|
|
5
|
+
// monotonic `seq`, resume replay, and the lifecycle/meta files. Any number of
|
|
6
|
+
// hosts may be connected; exactly one of them owns the event stream, and
|
|
7
|
+
// `start`/`resume` transfer that ownership. The adapter supplies only `onStart`
|
|
8
|
+
// (drive its CLI/SDK and translate to wire events) and lifecycle cleanup hooks.
|
|
8
9
|
|
|
9
10
|
import { appendFile, mkdir, writeFile } from 'node:fs/promises';
|
|
10
11
|
import { existsSync, readFileSync } from 'node:fs';
|
|
@@ -234,8 +235,14 @@ export async function runBridge<TStart extends { type: 'start' }>(
|
|
|
234
235
|
// ─── mutable runtime state ──────────────────────────────────────────
|
|
235
236
|
let currentBoundPort = 0;
|
|
236
237
|
let currentTurnState: BridgeState = 'init';
|
|
238
|
+
/*
|
|
239
|
+
* The one connection turn events stream to. A socket claims it by asking for
|
|
240
|
+
* work — `start` (a turn) or `resume` (a catch-up) — never by connecting:
|
|
241
|
+
* every event goes here alone, so claiming on connect would silence a turn
|
|
242
|
+
* already streaming to someone else. Any number of sockets may be connected;
|
|
243
|
+
* the others still exchange control frames, they just get no events.
|
|
244
|
+
*/
|
|
237
245
|
let activeSocket: WebSocket | undefined;
|
|
238
|
-
let activeSocketReadyForLiveEvents = false;
|
|
239
246
|
let isFirstTurn = true;
|
|
240
247
|
let turnAbort: AbortController | undefined;
|
|
241
248
|
let currentUserMessages: string[] | undefined;
|
|
@@ -374,26 +381,13 @@ export async function runBridge<TStart extends { type: 'start' }>(
|
|
|
374
381
|
};
|
|
375
382
|
|
|
376
383
|
// ─── wire send + replay ─────────────────────────────────────────────
|
|
377
|
-
const sendControl = (msg: Record<string, unknown>): void => {
|
|
378
|
-
if (activeSocket?.readyState === WS_OPEN) {
|
|
379
|
-
try {
|
|
380
|
-
activeSocket.send(JSON.stringify(msg));
|
|
381
|
-
} catch {
|
|
382
|
-
// best-effort
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
};
|
|
386
|
-
|
|
387
384
|
const emit = (event: BridgeEvent): void => {
|
|
388
385
|
const seq = ++seqCounter;
|
|
389
386
|
const line = JSON.stringify({ ...event, seq });
|
|
390
387
|
eventLog.push({ seq, line });
|
|
391
388
|
diskBuffer += `${line}\n`;
|
|
392
389
|
scheduleEventFlush();
|
|
393
|
-
if (
|
|
394
|
-
activeSocketReadyForLiveEvents &&
|
|
395
|
-
activeSocket?.readyState === WS_OPEN
|
|
396
|
-
) {
|
|
390
|
+
if (activeSocket?.readyState === WS_OPEN) {
|
|
397
391
|
try {
|
|
398
392
|
activeSocket.send(line);
|
|
399
393
|
} catch {
|
|
@@ -526,8 +520,7 @@ export async function runBridge<TStart extends { type: 'start' }>(
|
|
|
526
520
|
): Promise<void> => {
|
|
527
521
|
switch (msg.type) {
|
|
528
522
|
case 'start': {
|
|
529
|
-
|
|
530
|
-
activeSocketReadyForLiveEvents = true;
|
|
523
|
+
activeSocket = ws; // asking for a turn claims the event stream
|
|
531
524
|
const firstTurn = isFirstTurn;
|
|
532
525
|
isFirstTurn = false;
|
|
533
526
|
eventLog = []; // clear previous turn; keep seqCounter monotonic
|
|
@@ -616,9 +609,9 @@ export async function runBridge<TStart extends { type: 'start' }>(
|
|
|
616
609
|
turnAbort?.abort();
|
|
617
610
|
return;
|
|
618
611
|
case 'resume':
|
|
619
|
-
|
|
612
|
+
activeSocket = ws; // asking for a catch-up claims it too
|
|
613
|
+
// Synchronous, so no event can slip out live ahead of the replayed tail.
|
|
620
614
|
replay(ws, msg.lastSeenEventId);
|
|
621
|
-
activeSocketReadyForLiveEvents = true;
|
|
622
615
|
return;
|
|
623
616
|
case 'destroy':
|
|
624
617
|
currentTurnState = 'done';
|
|
@@ -630,7 +623,7 @@ export async function runBridge<TStart extends { type: 'start' }>(
|
|
|
630
623
|
currentTurnState = 'done';
|
|
631
624
|
void writeBridgeMeta('done');
|
|
632
625
|
const data = (await onStop?.()) ?? {};
|
|
633
|
-
sendControl({ type: 'bridge-stop', data });
|
|
626
|
+
sendControl(ws, { type: 'bridge-stop', data });
|
|
634
627
|
drainThenExit(ws, 1000, 'stop');
|
|
635
628
|
return;
|
|
636
629
|
}
|
|
@@ -693,16 +686,10 @@ export async function runBridge<TStart extends { type: 'start' }>(
|
|
|
693
686
|
return;
|
|
694
687
|
}
|
|
695
688
|
|
|
696
|
-
// Single-flight: a fresh authorized connection *replaces* the active one
|
|
697
|
-
// (the host reconnecting after a drop). The previous socket's close is a
|
|
698
|
-
// no-op below because it is no longer `activeSocket`.
|
|
699
|
-
activeSocket = ws;
|
|
700
|
-
activeSocketReadyForLiveEvents = false;
|
|
701
|
-
|
|
702
689
|
// Announce liveness the instant we accept. Some sandbox runtimes complete
|
|
703
690
|
// the host-side WS handshake before the connection is forwarded here; the
|
|
704
691
|
// host waits for this frame before sending `start`/`resume`.
|
|
705
|
-
sendControl({
|
|
692
|
+
sendControl(ws, {
|
|
706
693
|
type: 'bridge-hello',
|
|
707
694
|
state: currentTurnState,
|
|
708
695
|
lastSeq: seqCounter,
|
|
@@ -715,7 +702,7 @@ export async function runBridge<TStart extends { type: 'start' }>(
|
|
|
715
702
|
typeof raw === 'string' ? raw : Buffer.from(raw).toString('utf8');
|
|
716
703
|
parsed = JSON.parse(text) as TStart | InboundControl;
|
|
717
704
|
} catch (err) {
|
|
718
|
-
sendControl({
|
|
705
|
+
sendControl(ws, {
|
|
719
706
|
type: 'error',
|
|
720
707
|
error: `protocol parse error: ${(err as Error).message}`,
|
|
721
708
|
});
|
|
@@ -725,13 +712,12 @@ export async function runBridge<TStart extends { type: 'start' }>(
|
|
|
725
712
|
});
|
|
726
713
|
|
|
727
714
|
ws.on('close', () => {
|
|
728
|
-
// Only the
|
|
729
|
-
//
|
|
730
|
-
// the in-flight turn
|
|
731
|
-
// log for replay
|
|
715
|
+
// Only the stream owner's close matters; a socket that never claimed it,
|
|
716
|
+
// or that a later `start`/`resume` displaced, closes as a no-op.
|
|
717
|
+
// Crucially we do NOT abort the in-flight turn: it keeps running and its
|
|
718
|
+
// events accumulate in the log for replay on reconnect.
|
|
732
719
|
if (activeSocket === ws) {
|
|
733
720
|
activeSocket = undefined;
|
|
734
|
-
activeSocketReadyForLiveEvents = false;
|
|
735
721
|
}
|
|
736
722
|
});
|
|
737
723
|
|
|
@@ -767,6 +753,24 @@ export async function runBridge<TStart extends { type: 'start' }>(
|
|
|
767
753
|
};
|
|
768
754
|
}
|
|
769
755
|
|
|
756
|
+
/*
|
|
757
|
+
* Control frames answer the socket that sent the frame they reply to, so the
|
|
758
|
+
* target is always explicit. Event streaming is the separate, stateful path
|
|
759
|
+
* (`emit` → `activeSocket`); this one carries no state at all.
|
|
760
|
+
*/
|
|
761
|
+
function sendControl(
|
|
762
|
+
socket: WebSocket | undefined,
|
|
763
|
+
message: Record<string, unknown>,
|
|
764
|
+
): void {
|
|
765
|
+
if (socket?.readyState === WS_OPEN) {
|
|
766
|
+
try {
|
|
767
|
+
socket.send(JSON.stringify(message));
|
|
768
|
+
} catch {
|
|
769
|
+
// best-effort
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
770
774
|
function serialiseError(err: unknown): unknown {
|
|
771
775
|
if (err instanceof Error) {
|
|
772
776
|
return { name: err.name, message: err.message, stack: err.stack };
|
|
@@ -8,8 +8,8 @@ const symbol = Symbol.for(marker);
|
|
|
8
8
|
/**
|
|
9
9
|
* Thrown when a caller asks the harness to do something the adapter (or the
|
|
10
10
|
* supplied sandbox) does not support, e.g. requesting manual compaction from
|
|
11
|
-
* an adapter that only auto-compacts, or invoking `
|
|
12
|
-
* that does not expose one.
|
|
11
|
+
* an adapter that only auto-compacts, or invoking `getPortEndpoint` on a
|
|
12
|
+
* sandbox that does not expose one.
|
|
13
13
|
*
|
|
14
14
|
* The caller supplies the full human-readable message. Optional `harnessId`
|
|
15
15
|
* is recorded as structured context for tooling.
|
package/src/utils/index.ts
CHANGED
|
@@ -6,6 +6,11 @@ export {
|
|
|
6
6
|
} from './sandbox-channel';
|
|
7
7
|
export { classifyDiskLog, type DiskLogRecoveryMode } from './classify-disk-log';
|
|
8
8
|
export { getAiGatewayAuthFromEnv } from './ai-gateway-auth';
|
|
9
|
+
export {
|
|
10
|
+
createCredentialRequestTransformation,
|
|
11
|
+
maskSandboxCredentials,
|
|
12
|
+
warnCredentialBrokeringUnavailable,
|
|
13
|
+
} from './sandbox-credential-brokering';
|
|
9
14
|
export { resolveSandboxHomeDir } from './sandbox-home-dir';
|
|
10
15
|
export { shellQuote } from './shell-quote';
|
|
11
16
|
export {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { HarnessV1RequestTransformation } from '../v1';
|
|
2
|
+
|
|
3
|
+
export function warnCredentialBrokeringUnavailable(): void {
|
|
4
|
+
console.warn(
|
|
5
|
+
'The sandbox implementation does not support configuring request transformations, so credential brokering does not work. Falling back to less secure credential forwarding.',
|
|
6
|
+
);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function maskSandboxCredentials({
|
|
10
|
+
environment,
|
|
11
|
+
credentialEnvironmentVariables,
|
|
12
|
+
}: {
|
|
13
|
+
environment: Readonly<Record<string, string>>;
|
|
14
|
+
credentialEnvironmentVariables: ReadonlyArray<string>;
|
|
15
|
+
}): Record<string, string> {
|
|
16
|
+
const maskedEnvironment = { ...environment };
|
|
17
|
+
for (const name of credentialEnvironmentVariables) {
|
|
18
|
+
if (maskedEnvironment[name] != null) {
|
|
19
|
+
maskedEnvironment[name] = name;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return maskedEnvironment;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createCredentialRequestTransformation({
|
|
26
|
+
baseUrl,
|
|
27
|
+
headers,
|
|
28
|
+
}: {
|
|
29
|
+
baseUrl: string;
|
|
30
|
+
headers: Readonly<Record<string, string>>;
|
|
31
|
+
}): HarnessV1RequestTransformation {
|
|
32
|
+
const url = new URL(baseUrl);
|
|
33
|
+
const pathname = url.pathname.replace(/\/+$/, '');
|
|
34
|
+
return {
|
|
35
|
+
match: {
|
|
36
|
+
host: url.hostname,
|
|
37
|
+
...(pathname.length === 0 ? {} : { path: { startsWith: pathname } }),
|
|
38
|
+
},
|
|
39
|
+
transform: { headers },
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
harnessV1DebugLevelSchema,
|
|
5
5
|
type HarnessV1Diagnostic,
|
|
6
6
|
} from './harness-v1-diagnostic';
|
|
7
|
+
import type { HarnessV1ResponseFormat } from './harness-v1-response-format';
|
|
7
8
|
import {
|
|
8
9
|
harnessV1CompactionPartSchema,
|
|
9
10
|
harnessV1ErrorPartSchema,
|
|
@@ -87,6 +88,17 @@ export const harnessV1BridgeBuiltinToolFilteringSchema = z.discriminatedUnion(
|
|
|
87
88
|
],
|
|
88
89
|
);
|
|
89
90
|
|
|
91
|
+
export const harnessV1BridgeResponseFormatSchema: z.ZodType<HarnessV1ResponseFormat> =
|
|
92
|
+
z.discriminatedUnion('type', [
|
|
93
|
+
z.object({ type: z.literal('text') }),
|
|
94
|
+
z.object({
|
|
95
|
+
type: z.literal('json'),
|
|
96
|
+
schema: z.record(z.string(), z.json()).optional(),
|
|
97
|
+
name: z.string().optional(),
|
|
98
|
+
description: z.string().optional(),
|
|
99
|
+
}),
|
|
100
|
+
]);
|
|
101
|
+
|
|
90
102
|
/**
|
|
91
103
|
* Common fields of the inbound `start` message. Each adapter extends this with
|
|
92
104
|
* its runtime-specific configuration (e.g. `thinking`/`continue` for Claude
|
|
@@ -105,6 +117,7 @@ export const harnessV1BridgeStartBaseSchema = z.object({
|
|
|
105
117
|
debug: harnessV1DebugConfigSchema.optional(),
|
|
106
118
|
permissionMode: harnessV1BridgePermissionModeSchema.optional(),
|
|
107
119
|
builtinToolFiltering: harnessV1BridgeBuiltinToolFilteringSchema.optional(),
|
|
120
|
+
responseFormat: harnessV1BridgeResponseFormatSchema.optional(),
|
|
108
121
|
});
|
|
109
122
|
|
|
110
123
|
// --- Transport / control frames (outbound, not consumer events) ---
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import type { Experimental_SandboxSession as SandboxSession } from '@ai-sdk/provider-utils';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Connection details for a sandbox-exposed port. Headers are scoped to the
|
|
5
|
+
* returned URL and must be included when opening the connection.
|
|
6
|
+
*/
|
|
7
|
+
export type HarnessV1PortEndpoint = {
|
|
8
|
+
readonly url: string;
|
|
9
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
10
|
+
};
|
|
11
|
+
|
|
3
12
|
/**
|
|
4
13
|
* Network sandbox session returned by `HarnessV1SandboxProvider.createSession()`. The
|
|
5
14
|
* harness keeps this for the lifetime of a session. It is itself a
|
|
@@ -8,8 +17,8 @@ import type { Experimental_SandboxSession as SandboxSession } from '@ai-sdk/prov
|
|
|
8
17
|
*
|
|
9
18
|
* Code that should only touch the filesystem and spawn processes receives the
|
|
10
19
|
* reduced view from {@link HarnessV1NetworkSandboxSession.restricted}, never the
|
|
11
|
-
* network sandbox session itself — so it cannot stop the sandbox
|
|
12
|
-
* network
|
|
20
|
+
* network sandbox session itself — so it cannot stop the sandbox, change
|
|
21
|
+
* network access, or transform requests.
|
|
13
22
|
*/
|
|
14
23
|
export interface HarnessV1NetworkSandboxSession extends SandboxSession {
|
|
15
24
|
/**
|
|
@@ -36,13 +45,23 @@ export interface HarnessV1NetworkSandboxSession extends SandboxSession {
|
|
|
36
45
|
*/
|
|
37
46
|
readonly defaultWorkingDirectory: string;
|
|
38
47
|
|
|
39
|
-
/** Ports the sandbox exposes; resolvable
|
|
48
|
+
/** Ports the sandbox exposes; resolvable via `getPortEndpoint`. */
|
|
40
49
|
readonly ports: ReadonlyArray<number>;
|
|
41
50
|
|
|
42
51
|
/**
|
|
43
|
-
* Resolve
|
|
52
|
+
* Resolve the connection details for a sandbox-exposed port. Bridge-backed
|
|
44
53
|
* adapters call this to open their WebSocket to the in-sandbox bridge.
|
|
45
54
|
*/
|
|
55
|
+
readonly getPortEndpoint: (options: {
|
|
56
|
+
port: number;
|
|
57
|
+
protocol?: 'http' | 'https' | 'ws';
|
|
58
|
+
}) => PromiseLike<HarnessV1PortEndpoint>;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Resolve a publicly-reachable URL for a sandbox-exposed port.
|
|
62
|
+
*
|
|
63
|
+
* @deprecated Use `getPortEndpoint` instead.
|
|
64
|
+
*/
|
|
46
65
|
readonly getPortUrl: (options: {
|
|
47
66
|
port: number;
|
|
48
67
|
protocol?: 'http' | 'https' | 'ws';
|
|
@@ -68,6 +87,29 @@ export interface HarnessV1NetworkSandboxSession extends SandboxSession {
|
|
|
68
87
|
policy: HarnessV1NetworkPolicy,
|
|
69
88
|
) => PromiseLike<void>;
|
|
70
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Replace the sandbox's outbound request-transformation rules. Optional —
|
|
92
|
+
* implementations expose this only when credentials can be injected outside
|
|
93
|
+
* the sandbox security boundary. Calling this method assumes authority over
|
|
94
|
+
* the complete transformation set; harness adapters should normally use
|
|
95
|
+
* `addRequestTransformations` instead. Adapters may preserve legacy
|
|
96
|
+
* credential-forwarding behavior when additive request transformations are
|
|
97
|
+
* unavailable.
|
|
98
|
+
*/
|
|
99
|
+
readonly setRequestTransformations?: (
|
|
100
|
+
transformations: ReadonlyArray<HarnessV1RequestTransformation>,
|
|
101
|
+
) => PromiseLike<void>;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Add outbound request-transformation rules without replacing rules already
|
|
105
|
+
* managed by the sandbox session. Optional for the same reason as
|
|
106
|
+
* `setRequestTransformations`. Harness adapters should use this additive
|
|
107
|
+
* capability unless they explicitly own the complete transformation set.
|
|
108
|
+
*/
|
|
109
|
+
readonly addRequestTransformations?: (
|
|
110
|
+
transformations: ReadonlyArray<HarnessV1RequestTransformation>,
|
|
111
|
+
) => PromiseLike<void>;
|
|
112
|
+
|
|
71
113
|
/**
|
|
72
114
|
* Replace the set of ports exposed by the sandbox. Full-replacement
|
|
73
115
|
* semantics: ports omitted from the array are deregistered. Optional —
|
|
@@ -86,7 +128,8 @@ export interface HarnessV1NetworkSandboxSession extends SandboxSession {
|
|
|
86
128
|
*
|
|
87
129
|
* The returned object points at exactly the same underlying sandbox
|
|
88
130
|
* resource as the network sandbox session it was produced from; it is only a
|
|
89
|
-
* narrower surface over the same resource, not a separate sandbox.
|
|
131
|
+
* narrower surface over the same resource, not a separate sandbox. In
|
|
132
|
+
* particular, it cannot mutate network access or request transformations.
|
|
90
133
|
*/
|
|
91
134
|
readonly restricted: () => SandboxSession;
|
|
92
135
|
}
|
|
@@ -121,3 +164,42 @@ export type HarnessV1NetworkPolicy =
|
|
|
121
164
|
allowedCIDRs: ReadonlyArray<string>;
|
|
122
165
|
deniedCIDRs?: ReadonlyArray<string>;
|
|
123
166
|
};
|
|
167
|
+
|
|
168
|
+
type HarnessV1RequestTransformationPathMatcher =
|
|
169
|
+
| { exact: string }
|
|
170
|
+
| { startsWith: string }
|
|
171
|
+
| { regex: string };
|
|
172
|
+
|
|
173
|
+
type HarnessV1RequestTransformationKeyValuePartMatcher =
|
|
174
|
+
| { exact: string }
|
|
175
|
+
| { startsWith: string }
|
|
176
|
+
| { regex: string };
|
|
177
|
+
|
|
178
|
+
type HarnessV1RequestTransformationKeyValueMatcher = {
|
|
179
|
+
readonly key?: HarnessV1RequestTransformationKeyValuePartMatcher;
|
|
180
|
+
readonly value?: HarnessV1RequestTransformationKeyValuePartMatcher;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Outbound HTTPS request transformation applied outside the sandbox security
|
|
185
|
+
* boundary. The host is part of the match so each rule is self-contained and
|
|
186
|
+
* several rules, including several for the same host, can be installed at
|
|
187
|
+
* once.
|
|
188
|
+
*
|
|
189
|
+
* Credential values belong in `transform.headers`, while the sandbox process
|
|
190
|
+
* receives only a non-secret placeholder. Implementations must overwrite
|
|
191
|
+
* matching request headers after the request leaves the sandbox rather than
|
|
192
|
+
* making transformed values available inside it.
|
|
193
|
+
*/
|
|
194
|
+
export type HarnessV1RequestTransformation = {
|
|
195
|
+
readonly match: {
|
|
196
|
+
readonly host: string;
|
|
197
|
+
readonly path?: HarnessV1RequestTransformationPathMatcher;
|
|
198
|
+
readonly method?: ReadonlyArray<string>;
|
|
199
|
+
readonly queryString?: ReadonlyArray<HarnessV1RequestTransformationKeyValueMatcher>;
|
|
200
|
+
readonly headers?: ReadonlyArray<HarnessV1RequestTransformationKeyValueMatcher>;
|
|
201
|
+
};
|
|
202
|
+
readonly transform: {
|
|
203
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
204
|
+
};
|
|
205
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type HarnessV1JSONValue =
|
|
2
|
+
| null
|
|
3
|
+
| boolean
|
|
4
|
+
| number
|
|
5
|
+
| string
|
|
6
|
+
| HarnessV1JSONArray
|
|
7
|
+
| HarnessV1JSONObject;
|
|
8
|
+
|
|
9
|
+
export interface HarnessV1JSONArray extends Array<HarnessV1JSONValue> {}
|
|
10
|
+
|
|
11
|
+
export interface HarnessV1JSONObject {
|
|
12
|
+
[key: string]: HarnessV1JSONValue | undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type HarnessV1JSONSchema = HarnessV1JSONObject;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Requested response format for one harness turn.
|
|
19
|
+
*
|
|
20
|
+
* This intentionally mirrors the AI SDK provider response-format shape
|
|
21
|
+
* without depending on `@ai-sdk/provider`. Harness implementations receive
|
|
22
|
+
* JSON Schema rather than the caller's original Zod or Standard Schema so the
|
|
23
|
+
* contract can cross process and package boundaries.
|
|
24
|
+
*/
|
|
25
|
+
export type HarnessV1ResponseFormat =
|
|
26
|
+
| { readonly type: 'text' }
|
|
27
|
+
| {
|
|
28
|
+
readonly type: 'json';
|
|
29
|
+
readonly schema?: HarnessV1JSONSchema;
|
|
30
|
+
readonly name?: string;
|
|
31
|
+
readonly description?: string;
|
|
32
|
+
};
|
|
@@ -3,6 +3,7 @@ import type { HarnessV1Observability } from './harness-v1-observability';
|
|
|
3
3
|
import type { HarnessV1PermissionMode } from './harness-v1-permission-mode';
|
|
4
4
|
import type { HarnessV1Prompt } from './harness-v1-prompt';
|
|
5
5
|
import type { HarnessV1PromptControl } from './harness-v1-prompt-control';
|
|
6
|
+
import type { HarnessV1ResponseFormat } from './harness-v1-response-format';
|
|
6
7
|
import type {
|
|
7
8
|
HarnessV1ContinueTurnState,
|
|
8
9
|
HarnessV1ResumeSessionState,
|
|
@@ -78,8 +79,10 @@ export type HarnessV1StartOptions = {
|
|
|
78
79
|
* Network sandbox session the adapter operates against. It is owned and
|
|
79
80
|
* lifecycled by `HarnessAgent`. Adapters call `restricted()` for the
|
|
80
81
|
* tool-safe filesystem/exec/spawn surface, and use the infra methods
|
|
81
|
-
* (`
|
|
82
|
-
*
|
|
82
|
+
* (`getPortEndpoint`, `ports`, `setNetworkPolicy`,
|
|
83
|
+
* `setRequestTransformations`, `addRequestTransformations`) for bridge
|
|
84
|
+
* wiring. Adapters must not call `stop()` themselves; the agent does that
|
|
85
|
+
* during cleanup.
|
|
83
86
|
*/
|
|
84
87
|
readonly sandboxSession: HarnessV1NetworkSandboxSession;
|
|
85
88
|
|
|
@@ -103,6 +106,12 @@ export type HarnessV1PromptTurnOptions = {
|
|
|
103
106
|
*/
|
|
104
107
|
readonly prompt: HarnessV1Prompt;
|
|
105
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Response format requested for this turn. Adapters that cannot honor a
|
|
111
|
+
* JSON response format must throw `HarnessCapabilityUnsupportedError`.
|
|
112
|
+
*/
|
|
113
|
+
readonly responseFormat?: HarnessV1ResponseFormat;
|
|
114
|
+
|
|
106
115
|
/**
|
|
107
116
|
* Host-defined tools to make available to the underlying runtime for this
|
|
108
117
|
* turn. The harness emits `tool-call` events when the runtime calls one
|
|
@@ -142,6 +151,12 @@ export type HarnessV1PromptTurnOptions = {
|
|
|
142
151
|
* that was previously suspended temporarily, e.g. by the workflow slice loop.
|
|
143
152
|
*/
|
|
144
153
|
export type HarnessV1ContinueTurnOptions = {
|
|
154
|
+
/**
|
|
155
|
+
* Response format of the in-flight turn. Rerun-based adapters use this when
|
|
156
|
+
* reconstructing the turn; attach-based adapters may ignore it.
|
|
157
|
+
*/
|
|
158
|
+
readonly responseFormat?: HarnessV1ResponseFormat;
|
|
159
|
+
|
|
145
160
|
/**
|
|
146
161
|
* Host-defined tools to make available for the continued turn. Same shape
|
|
147
162
|
* as `doPromptTurn`'s `tools`. An adapter that purely attaches to a live turn
|
package/src/v1/index.ts
CHANGED
|
@@ -25,6 +25,13 @@ export {
|
|
|
25
25
|
} from './harness-v1-builtin-tool';
|
|
26
26
|
export type { HarnessV1Metadata } from './harness-v1-metadata';
|
|
27
27
|
export type { HarnessV1Prompt } from './harness-v1-prompt';
|
|
28
|
+
export type {
|
|
29
|
+
HarnessV1JSONSchema,
|
|
30
|
+
HarnessV1JSONArray,
|
|
31
|
+
HarnessV1JSONObject,
|
|
32
|
+
HarnessV1JSONValue,
|
|
33
|
+
HarnessV1ResponseFormat,
|
|
34
|
+
} from './harness-v1-response-format';
|
|
28
35
|
export type { HarnessV1SandboxProvider } from './harness-v1-sandbox-provider';
|
|
29
36
|
export type {
|
|
30
37
|
HarnessV1ContinueTurnState,
|
|
@@ -36,6 +43,8 @@ export type {
|
|
|
36
43
|
export type {
|
|
37
44
|
HarnessV1NetworkPolicy,
|
|
38
45
|
HarnessV1NetworkSandboxSession,
|
|
46
|
+
HarnessV1PortEndpoint,
|
|
47
|
+
HarnessV1RequestTransformation,
|
|
39
48
|
} from './harness-v1-network-sandbox-session';
|
|
40
49
|
export type { HarnessV1Skill } from './harness-v1-skill';
|
|
41
50
|
export type { HarnessV1StreamPart } from './harness-v1-stream-part';
|
|
@@ -66,6 +75,7 @@ export {
|
|
|
66
75
|
harnessV1BridgeInboundCommandSchemas,
|
|
67
76
|
harnessV1BridgeOutboundMessageSchema,
|
|
68
77
|
harnessV1BridgeReadySchema,
|
|
78
|
+
harnessV1BridgeResponseFormatSchema,
|
|
69
79
|
harnessV1BridgeResumeInboundSchema,
|
|
70
80
|
harnessV1BridgeSandboxLogSchema,
|
|
71
81
|
harnessV1BridgeStopInboundSchema,
|