@ai-sdk/harness 1.0.99 → 1.0.101
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 +19 -0
- package/agent/index.ts +2 -8
- package/dist/agent/index.d.ts +65 -46
- package/dist/agent/index.js +93 -71
- package/dist/agent/index.js.map +1 -1
- package/dist/bridge/index.d.ts +11 -1
- package/dist/bridge/index.js +37 -7
- package/dist/bridge/index.js.map +1 -1
- package/dist/index.d.ts +80 -2
- package/dist/index.js +286 -229
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +9 -1
- package/dist/utils/index.js +17 -2
- package/dist/utils/index.js.map +1 -1
- package/package.json +3 -3
- package/src/agent/harness-agent-session.ts +4 -8
- package/src/agent/harness-agent-tool-approval-continuation.ts +6 -32
- package/src/agent/harness-agent-tool-result-continuation.ts +4 -53
- package/src/agent/harness-agent.ts +40 -18
- package/src/agent/internal/run-prompt.ts +100 -35
- package/src/bridge/index.ts +82 -11
- package/src/utils/sandbox-credential-brokering.ts +32 -1
- package/src/v1/harness-v1-bridge-protocol.ts +1 -0
- package/src/v1/harness-v1-builtin-tool.ts +2 -0
- package/src/v1/harness-v1-lifecycle-state.ts +2 -0
- package/src/v1/harness-v1-prompt-control.ts +3 -0
- package/src/v1/harness-v1-questions-tool.ts +72 -0
- package/src/v1/index.ts +9 -0
package/src/bridge/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { appendFile, mkdir, writeFile } from 'node:fs/promises';
|
|
|
11
11
|
import { existsSync, readFileSync } from 'node:fs';
|
|
12
12
|
import { randomUUID } from 'node:crypto';
|
|
13
13
|
import { env as procEnv, pid, stdout } from 'node:process';
|
|
14
|
+
import type { ToolResultPart } from '@ai-sdk/provider-utils';
|
|
14
15
|
import { WebSocketServer, type WebSocket } from 'ws';
|
|
15
16
|
|
|
16
17
|
export { HarnessBridgeCapabilityUnsupportedError } from './harness-bridge-capability-unsupported-error';
|
|
@@ -244,8 +245,21 @@ export interface BridgeTurn {
|
|
|
244
245
|
* itself (via {@link emit}) using the same `toolCallId`.
|
|
245
246
|
*/
|
|
246
247
|
requestToolResult(
|
|
247
|
-
|
|
248
|
-
|
|
248
|
+
input:
|
|
249
|
+
| string
|
|
250
|
+
| {
|
|
251
|
+
toolCallId: string;
|
|
252
|
+
matches?: (result: {
|
|
253
|
+
output: unknown;
|
|
254
|
+
isError?: boolean;
|
|
255
|
+
toolResult?: ToolResultPart;
|
|
256
|
+
}) => boolean;
|
|
257
|
+
},
|
|
258
|
+
): Promise<{
|
|
259
|
+
output: unknown;
|
|
260
|
+
isError?: boolean;
|
|
261
|
+
toolResult?: ToolResultPart;
|
|
262
|
+
}>;
|
|
249
263
|
|
|
250
264
|
/**
|
|
251
265
|
* Register interest in a host approval decision and resolve when the matching
|
|
@@ -342,6 +356,7 @@ type InboundControl =
|
|
|
342
356
|
toolCallId: string;
|
|
343
357
|
output: unknown;
|
|
344
358
|
isError?: boolean;
|
|
359
|
+
toolResult?: ToolResultPart;
|
|
345
360
|
}
|
|
346
361
|
| {
|
|
347
362
|
type: 'tool-approval-response';
|
|
@@ -506,8 +521,27 @@ export async function runBridge<TStart extends { type: 'start' }>(
|
|
|
506
521
|
|
|
507
522
|
const pendingToolResults = new Map<
|
|
508
523
|
string,
|
|
509
|
-
|
|
524
|
+
{
|
|
525
|
+
resolve: (output: {
|
|
526
|
+
output: unknown;
|
|
527
|
+
isError?: boolean;
|
|
528
|
+
toolResult?: ToolResultPart;
|
|
529
|
+
}) => void;
|
|
530
|
+
matches?: (output: {
|
|
531
|
+
output: unknown;
|
|
532
|
+
isError?: boolean;
|
|
533
|
+
toolResult?: ToolResultPart;
|
|
534
|
+
}) => boolean;
|
|
535
|
+
}
|
|
510
536
|
>();
|
|
537
|
+
const bufferedToolResults: Array<{
|
|
538
|
+
toolCallId: string;
|
|
539
|
+
result: {
|
|
540
|
+
output: unknown;
|
|
541
|
+
isError?: boolean;
|
|
542
|
+
toolResult?: ToolResultPart;
|
|
543
|
+
};
|
|
544
|
+
}> = [];
|
|
511
545
|
const pendingToolApprovals = new Map<
|
|
512
546
|
string,
|
|
513
547
|
(response: { approved: boolean; reason?: string }) => void
|
|
@@ -749,10 +783,28 @@ export async function runBridge<TStart extends { type: 'start' }>(
|
|
|
749
783
|
const userMessages = createBridgeUserMessageQueue({ respond: emit });
|
|
750
784
|
const turn: BridgeTurn = {
|
|
751
785
|
emit,
|
|
752
|
-
requestToolResult:
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
786
|
+
requestToolResult: requestInput => {
|
|
787
|
+
const request =
|
|
788
|
+
typeof requestInput === 'string'
|
|
789
|
+
? { toolCallId: requestInput }
|
|
790
|
+
: requestInput;
|
|
791
|
+
const bufferedIndex = bufferedToolResults.findIndex(
|
|
792
|
+
buffered =>
|
|
793
|
+
buffered.toolCallId === request.toolCallId ||
|
|
794
|
+
request.matches?.(buffered.result) === true,
|
|
795
|
+
);
|
|
796
|
+
if (bufferedIndex >= 0) {
|
|
797
|
+
return Promise.resolve(
|
|
798
|
+
bufferedToolResults.splice(bufferedIndex, 1)[0].result,
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
return new Promise(resolve => {
|
|
802
|
+
pendingToolResults.set(request.toolCallId, {
|
|
803
|
+
resolve,
|
|
804
|
+
matches: request.matches,
|
|
805
|
+
});
|
|
806
|
+
});
|
|
807
|
+
},
|
|
756
808
|
requestToolApproval: approvalId =>
|
|
757
809
|
new Promise(resolve => {
|
|
758
810
|
pendingToolApprovals.set(approvalId, resolve);
|
|
@@ -800,10 +852,29 @@ export async function runBridge<TStart extends { type: 'start' }>(
|
|
|
800
852
|
return;
|
|
801
853
|
}
|
|
802
854
|
case 'tool-result': {
|
|
803
|
-
const
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
855
|
+
const result = {
|
|
856
|
+
output: msg.output,
|
|
857
|
+
isError: msg.isError,
|
|
858
|
+
toolResult: msg.toolResult,
|
|
859
|
+
};
|
|
860
|
+
const exactPending = pendingToolResults.get(msg.toolCallId);
|
|
861
|
+
const matchingPending =
|
|
862
|
+
exactPending == null
|
|
863
|
+
? Array.from(pendingToolResults.entries()).find(
|
|
864
|
+
([, pending]) => pending.matches?.(result) === true,
|
|
865
|
+
)
|
|
866
|
+
: undefined;
|
|
867
|
+
const pending = exactPending ?? matchingPending?.[1];
|
|
868
|
+
const pendingId =
|
|
869
|
+
exactPending != null ? msg.toolCallId : matchingPending?.[0];
|
|
870
|
+
if (pending != null && pendingId != null) {
|
|
871
|
+
pendingToolResults.delete(pendingId);
|
|
872
|
+
pending.resolve(result);
|
|
873
|
+
} else {
|
|
874
|
+
bufferedToolResults.push({
|
|
875
|
+
toolCallId: msg.toolCallId,
|
|
876
|
+
result,
|
|
877
|
+
});
|
|
807
878
|
}
|
|
808
879
|
return;
|
|
809
880
|
}
|
|
@@ -11,7 +11,38 @@ export function isSandboxCredentialPlaceholder(value: string): boolean {
|
|
|
11
11
|
return /^aisdkhc_[A-Za-z0-9_-]{43}$/.test(value);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Warns when credential brokering is unavailable, but only if real credentials
|
|
16
|
+
* remain among the credentials forwarded into the sandbox.
|
|
17
|
+
*/
|
|
18
|
+
export function warnCredentialBrokeringUnavailable(options: {
|
|
19
|
+
environment: Readonly<Record<string, string>>;
|
|
20
|
+
forwardedEnvironment: Readonly<Record<string, string>>;
|
|
21
|
+
credentialEnvironmentVariables: ReadonlyArray<string>;
|
|
22
|
+
}): void {
|
|
23
|
+
const credentialEnvironmentVariables = [
|
|
24
|
+
...new Set(options.credentialEnvironmentVariables),
|
|
25
|
+
];
|
|
26
|
+
const credentials = credentialEnvironmentVariables
|
|
27
|
+
.map(name => options.environment[name])
|
|
28
|
+
.filter(
|
|
29
|
+
(credential): credential is string =>
|
|
30
|
+
credential != null && credential.length > 0,
|
|
31
|
+
);
|
|
32
|
+
const forwardedCredentials = credentialEnvironmentVariables
|
|
33
|
+
.map(name => options.forwardedEnvironment[name])
|
|
34
|
+
.filter((credential): credential is string => credential != null);
|
|
35
|
+
|
|
36
|
+
if (
|
|
37
|
+
!credentials.some(credential =>
|
|
38
|
+
forwardedCredentials.some(forwardedCredential =>
|
|
39
|
+
forwardedCredential.includes(credential),
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
15
46
|
console.warn(
|
|
16
47
|
'The sandbox implementation does not support configuring request transformations, so credential brokering does not work. Falling back to less secure credential forwarding.',
|
|
17
48
|
);
|
|
@@ -295,6 +295,7 @@ export const harnessV1BridgeToolResultInboundSchema = z.object({
|
|
|
295
295
|
toolCallId: z.string(),
|
|
296
296
|
output: z.unknown(),
|
|
297
297
|
isError: z.boolean().optional(),
|
|
298
|
+
toolResult: z.unknown().optional(),
|
|
298
299
|
});
|
|
299
300
|
|
|
300
301
|
export const harnessV1BridgeToolApprovalResponseInboundSchema = z.object({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { tool, type FlexibleSchema, type Tool } from '@ai-sdk/provider-utils';
|
|
2
2
|
import { z } from 'zod/v4';
|
|
3
|
+
import { harnessV1QuestionsTool } from './harness-v1-questions-tool';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Cross-harness vocabulary of common built-in tool names with their baseline
|
|
@@ -50,6 +51,7 @@ export const HARNESS_V1_BUILTIN_TOOLS = {
|
|
|
50
51
|
inputSchema: z.object({ query: z.string() }),
|
|
51
52
|
outputSchema: z.unknown(),
|
|
52
53
|
}),
|
|
54
|
+
askUserQuestions: harnessV1QuestionsTool,
|
|
53
55
|
} as const;
|
|
54
56
|
|
|
55
57
|
export type HarnessV1BuiltinToolName = keyof typeof HARNESS_V1_BUILTIN_TOOLS;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { JSONValue } from '@ai-sdk/provider';
|
|
2
|
+
import type { ProviderOptions } from '@ai-sdk/provider-utils';
|
|
2
3
|
import type { HarnessV1Skill } from './harness-v1-skill';
|
|
3
4
|
import type { HarnessV1ToolSpec } from './harness-v1-tool-spec';
|
|
4
5
|
|
|
@@ -16,6 +17,7 @@ export type HarnessV1PendingToolResult = {
|
|
|
16
17
|
readonly toolCallId: string;
|
|
17
18
|
readonly toolName: string;
|
|
18
19
|
readonly input: string;
|
|
20
|
+
readonly providerOptions?: ProviderOptions;
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
/**
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ToolResultPart } from '@ai-sdk/provider-utils';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Bidirectional control surface returned by `doPromptTurn`.
|
|
3
5
|
*
|
|
@@ -16,6 +18,7 @@ export type HarnessV1PromptControl = {
|
|
|
16
18
|
toolCallId: string;
|
|
17
19
|
output: unknown;
|
|
18
20
|
isError?: boolean;
|
|
21
|
+
toolResult?: ToolResultPart;
|
|
19
22
|
}): PromiseLike<void>;
|
|
20
23
|
|
|
21
24
|
/**
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { FunctionTool } from '@ai-sdk/provider-utils';
|
|
2
|
+
import { z } from 'zod/v4';
|
|
3
|
+
|
|
4
|
+
const harnessV1QuestionSchema = z.object({
|
|
5
|
+
id: z.string().min(1),
|
|
6
|
+
question: z.string().min(1),
|
|
7
|
+
header: z.string().optional(),
|
|
8
|
+
options: z
|
|
9
|
+
.array(
|
|
10
|
+
z.object({
|
|
11
|
+
id: z.string().min(1),
|
|
12
|
+
label: z.string().min(1),
|
|
13
|
+
description: z.string().optional(),
|
|
14
|
+
preview: z.string().optional(),
|
|
15
|
+
}),
|
|
16
|
+
)
|
|
17
|
+
.optional(),
|
|
18
|
+
allowMultiple: z.boolean().optional(),
|
|
19
|
+
allowFreeForm: z
|
|
20
|
+
.union([
|
|
21
|
+
z.boolean(),
|
|
22
|
+
z.object({
|
|
23
|
+
secret: z.boolean(),
|
|
24
|
+
}),
|
|
25
|
+
])
|
|
26
|
+
.optional(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export const harnessV1QuestionsToolInputSchema = z.object({
|
|
30
|
+
allowPartialAnswers: z.boolean(),
|
|
31
|
+
questions: z.array(harnessV1QuestionSchema).min(1),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const harnessV1QuestionAnswerSchema = z.object({
|
|
35
|
+
optionIds: z.array(z.string()),
|
|
36
|
+
freeform: z.string().optional(),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const harnessV1QuestionsToolOutputSchema = z.discriminatedUnion(
|
|
40
|
+
'action',
|
|
41
|
+
[
|
|
42
|
+
z.object({
|
|
43
|
+
action: z.literal('answered'),
|
|
44
|
+
answers: z.record(z.string(), harnessV1QuestionAnswerSchema),
|
|
45
|
+
}),
|
|
46
|
+
z.object({
|
|
47
|
+
action: z.literal('partially-answered'),
|
|
48
|
+
answers: z.record(z.string(), harnessV1QuestionAnswerSchema),
|
|
49
|
+
}),
|
|
50
|
+
z.object({ action: z.literal('declined') }),
|
|
51
|
+
z.object({ action: z.literal('cancelled') }),
|
|
52
|
+
],
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
export type HarnessV1QuestionsToolInput = z.infer<
|
|
56
|
+
typeof harnessV1QuestionsToolInputSchema
|
|
57
|
+
>;
|
|
58
|
+
|
|
59
|
+
export type HarnessV1QuestionsToolOutput = z.infer<
|
|
60
|
+
typeof harnessV1QuestionsToolOutputSchema
|
|
61
|
+
>;
|
|
62
|
+
|
|
63
|
+
export const harnessV1QuestionsTool: FunctionTool<
|
|
64
|
+
HarnessV1QuestionsToolInput,
|
|
65
|
+
HarnessV1QuestionsToolOutput
|
|
66
|
+
> = {
|
|
67
|
+
description: 'Ask the user one or more questions',
|
|
68
|
+
inputSchema: harnessV1QuestionsToolInputSchema,
|
|
69
|
+
outputSchema: harnessV1QuestionsToolOutputSchema,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type HarnessV1QuestionsTool = typeof harnessV1QuestionsTool;
|
package/src/v1/index.ts
CHANGED
|
@@ -28,6 +28,15 @@ export {
|
|
|
28
28
|
HARNESS_V1_BUILTIN_TOOLS,
|
|
29
29
|
commonTool,
|
|
30
30
|
} from './harness-v1-builtin-tool';
|
|
31
|
+
export type {
|
|
32
|
+
HarnessV1QuestionsTool,
|
|
33
|
+
HarnessV1QuestionsToolInput,
|
|
34
|
+
HarnessV1QuestionsToolOutput,
|
|
35
|
+
} from './harness-v1-questions-tool';
|
|
36
|
+
export {
|
|
37
|
+
harnessV1QuestionsToolInputSchema,
|
|
38
|
+
harnessV1QuestionsToolOutputSchema,
|
|
39
|
+
} from './harness-v1-questions-tool';
|
|
31
40
|
export type { HarnessV1Metadata } from './harness-v1-metadata';
|
|
32
41
|
export type { HarnessV1Prompt } from './harness-v1-prompt';
|
|
33
42
|
export type {
|