@ai-sdk/harness 0.0.0 → 1.0.0-canary.3
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 +24 -0
- package/LICENSE +13 -0
- package/README.md +142 -0
- package/agent/index.ts +17 -0
- package/bridge/index.ts +10 -0
- package/dist/agent/index.d.ts +1480 -0
- package/dist/agent/index.js +2554 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/bridge/index.d.ts +111 -0
- package/dist/bridge/index.js +414 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/index.d.ts +1510 -0
- package/dist/index.js +15834 -0
- package/dist/index.js.map +1 -0
- package/dist/observability/index.d.ts +97 -0
- package/dist/observability/index.js +225 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/utils/index.d.ts +196 -0
- package/dist/utils/index.js +327 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +104 -1
- package/src/agent/harness-agent-session.ts +352 -0
- package/src/agent/harness-agent-settings.ts +131 -0
- package/src/agent/harness-agent-tool-approval-continuation.ts +94 -0
- package/src/agent/harness-agent.ts +750 -0
- package/src/agent/harness-diagnostics.ts +88 -0
- package/src/agent/internal/bootstrap-recipe.ts +124 -0
- package/src/agent/internal/bridge-port-registry.ts +52 -0
- package/src/agent/internal/harness-stream-text-result.ts +720 -0
- package/src/agent/internal/permission-mode.ts +50 -0
- package/src/agent/internal/resolve-observability.ts +128 -0
- package/src/agent/internal/resume-state-validation.ts +51 -0
- package/src/agent/internal/run-prompt.ts +811 -0
- package/src/agent/internal/strip-work-dir.ts +68 -0
- package/src/agent/internal/to-harness-stream.ts +75 -0
- package/src/agent/internal/translate-stream-part.ts +221 -0
- package/src/agent/internal/turn-telemetry.ts +359 -0
- package/src/agent/prewarm.ts +46 -0
- package/src/bridge/index.ts +700 -0
- package/src/errors/harness-capability-unsupported-error.ts +41 -0
- package/src/errors/harness-error.ts +22 -0
- package/src/index.ts +3 -0
- package/src/observability/file-reporter.ts +209 -0
- package/src/observability/index.ts +13 -0
- package/src/observability/trace-tree-reporter.ts +122 -0
- package/src/utils/classify-disk-log.ts +43 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/sandbox-channel.ts +453 -0
- package/src/v1/harness-v1-bootstrap.ts +46 -0
- package/src/v1/harness-v1-bridge-protocol.ts +310 -0
- package/src/v1/harness-v1-builtin-tool.ts +138 -0
- package/src/v1/harness-v1-call-warning.ts +22 -0
- package/src/v1/harness-v1-diagnostic.ts +66 -0
- package/src/v1/harness-v1-metadata.ts +13 -0
- package/src/v1/harness-v1-network-sandbox-session.ts +123 -0
- package/src/v1/harness-v1-observability.ts +20 -0
- package/src/v1/harness-v1-permission-mode.ts +11 -0
- package/src/v1/harness-v1-prompt-control.ts +41 -0
- package/src/v1/harness-v1-prompt.ts +11 -0
- package/src/v1/harness-v1-resume-state.ts +46 -0
- package/src/v1/harness-v1-sandbox-provider.ts +76 -0
- package/src/v1/harness-v1-session.ts +268 -0
- package/src/v1/harness-v1-skill.ts +22 -0
- package/src/v1/harness-v1-stream-part.ts +363 -0
- package/src/v1/harness-v1-tool-spec.ts +31 -0
- package/src/v1/harness-v1.ts +83 -0
- package/src/v1/index.ts +93 -0
- package/utils/index.ts +1 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ToolApprovalStatus } from 'ai';
|
|
2
|
+
import type { HarnessV1PermissionMode } from '../../v1';
|
|
3
|
+
import type { HarnessAgentToolApprovalConfiguration } from '../harness-agent-settings';
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_PERMISSION_MODE: HarnessV1PermissionMode =
|
|
6
|
+
'allow-all' as const;
|
|
7
|
+
|
|
8
|
+
export function resolvePermissionMode(input: {
|
|
9
|
+
permissionMode: HarnessV1PermissionMode | undefined;
|
|
10
|
+
}): HarnessV1PermissionMode {
|
|
11
|
+
return input.permissionMode ?? DEFAULT_PERMISSION_MODE;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function permissionModeNeedsBuiltinSupport(input: {
|
|
15
|
+
permissionMode: HarnessV1PermissionMode;
|
|
16
|
+
}): boolean {
|
|
17
|
+
return input.permissionMode !== 'allow-all';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type CustomToolApprovalDecision =
|
|
21
|
+
| { readonly type: 'allow'; readonly reason?: string }
|
|
22
|
+
| { readonly type: 'deny'; readonly reason?: string }
|
|
23
|
+
| { readonly type: 'request' };
|
|
24
|
+
|
|
25
|
+
export function resolveCustomToolApproval(input: {
|
|
26
|
+
toolName: string;
|
|
27
|
+
toolApproval: HarnessAgentToolApprovalConfiguration | undefined;
|
|
28
|
+
}): CustomToolApprovalDecision {
|
|
29
|
+
const status = normalizeToolApprovalStatus({
|
|
30
|
+
status: input.toolApproval?.[input.toolName],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
switch (status.type) {
|
|
34
|
+
case 'not-applicable':
|
|
35
|
+
case 'approved':
|
|
36
|
+
return { type: 'allow', reason: status.reason };
|
|
37
|
+
case 'denied':
|
|
38
|
+
return { type: 'deny', reason: status.reason };
|
|
39
|
+
case 'user-approval':
|
|
40
|
+
return { type: 'request' };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeToolApprovalStatus(input: {
|
|
45
|
+
status: ToolApprovalStatus | undefined;
|
|
46
|
+
}): Exclude<ToolApprovalStatus, string | undefined> {
|
|
47
|
+
if (input.status === undefined) return { type: 'not-applicable' };
|
|
48
|
+
if (typeof input.status === 'string') return { type: input.status };
|
|
49
|
+
return input.status;
|
|
50
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { asArray } from '@ai-sdk/provider-utils';
|
|
2
|
+
import type { Telemetry } from 'ai';
|
|
3
|
+
import type { HarnessV1Diagnostic, HarnessV1Observability } from '../../v1';
|
|
4
|
+
import type {
|
|
5
|
+
HarnessDebugConfig,
|
|
6
|
+
HarnessDebugLevel,
|
|
7
|
+
HarnessDiagnostic,
|
|
8
|
+
HarnessDiagnosticConsumer,
|
|
9
|
+
} from '../harness-diagnostics';
|
|
10
|
+
import type { HarnessAgentSettings } from '../harness-agent-settings';
|
|
11
|
+
|
|
12
|
+
const ENV_TRUTHY = new Set(['1', 'true', 'yes', 'on']);
|
|
13
|
+
|
|
14
|
+
function parseList(value: string | undefined): string[] | undefined {
|
|
15
|
+
if (!value) return undefined;
|
|
16
|
+
const items = value
|
|
17
|
+
.split(',')
|
|
18
|
+
.map(item => item.trim())
|
|
19
|
+
.filter(Boolean);
|
|
20
|
+
return items.length > 0 ? items : undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the effective debug config from explicit settings with env-var
|
|
25
|
+
* fallbacks. `HARNESS_DEBUG*` only fill fields the consumer left unset — code is
|
|
26
|
+
* always authoritative.
|
|
27
|
+
*/
|
|
28
|
+
export function resolveDebugConfig(
|
|
29
|
+
debug: HarnessDebugConfig | undefined,
|
|
30
|
+
env: Record<string, string | undefined> = process.env,
|
|
31
|
+
): {
|
|
32
|
+
enabled: boolean;
|
|
33
|
+
level: HarnessDebugLevel;
|
|
34
|
+
subsystems: string[] | undefined;
|
|
35
|
+
} {
|
|
36
|
+
const enabled =
|
|
37
|
+
debug?.enabled ?? ENV_TRUTHY.has((env.HARNESS_DEBUG ?? '').toLowerCase());
|
|
38
|
+
const level =
|
|
39
|
+
debug?.level ??
|
|
40
|
+
(env.HARNESS_DEBUG_LEVEL as HarnessDebugLevel | undefined) ??
|
|
41
|
+
'debug';
|
|
42
|
+
const subsystems = debug?.subsystems
|
|
43
|
+
? [...debug.subsystems]
|
|
44
|
+
: parseList(env.HARNESS_DEBUG_SUBSYSTEMS);
|
|
45
|
+
return { enabled, level, subsystems };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Map an adapter-emitted `HarnessV1Diagnostic` to the host-facing
|
|
50
|
+
* `HarnessDiagnostic` (the external/telemetry surface). Identity-shaped today,
|
|
51
|
+
* but an explicit boundary so the emission and consumption types can diverge.
|
|
52
|
+
*/
|
|
53
|
+
function toHarnessDiagnostic(d: HarnessV1Diagnostic): HarnessDiagnostic {
|
|
54
|
+
return {
|
|
55
|
+
level: d.level,
|
|
56
|
+
message: d.message,
|
|
57
|
+
subsystem: d.subsystem,
|
|
58
|
+
kind: d.kind,
|
|
59
|
+
source: d.source,
|
|
60
|
+
stream: d.stream,
|
|
61
|
+
attrs: d.attrs,
|
|
62
|
+
error: d.error,
|
|
63
|
+
sessionId: d.sessionId,
|
|
64
|
+
timestamp: d.timestamp,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function formatForStderr(d: HarnessDiagnostic): string {
|
|
69
|
+
const parts = [`[harness:${d.level}]`, d.subsystem, d.message].filter(
|
|
70
|
+
Boolean,
|
|
71
|
+
);
|
|
72
|
+
let line = parts.join(' ');
|
|
73
|
+
if (d.error) {
|
|
74
|
+
line += ` (${d.error.name ?? 'Error'}: ${d.error.message})`;
|
|
75
|
+
}
|
|
76
|
+
return `${line}\n`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Build the per-session observability handle the framework hands to `doStart`.
|
|
81
|
+
* Returns `undefined` when diagnostics are disabled. When enabled, the `report`
|
|
82
|
+
* sink maps each adapter-emitted `HarnessV1Diagnostic` to the host
|
|
83
|
+
* `HarnessDiagnostic` and fans it out, non-lossy, to: the `HARNESS_DEBUG` stderr
|
|
84
|
+
* default, the consumer's `onLog`, and any telemetry integration that
|
|
85
|
+
* implements `ingestDiagnostic`. Adapters normalize their own source (bridge
|
|
86
|
+
* wire frames, host logs) into `HarnessV1Diagnostic` before calling `report`.
|
|
87
|
+
*/
|
|
88
|
+
export function buildObservability(options: {
|
|
89
|
+
settings: Pick<
|
|
90
|
+
HarnessAgentSettings<any, any>,
|
|
91
|
+
'debug' | 'onLog' | 'telemetry'
|
|
92
|
+
>;
|
|
93
|
+
}): HarnessV1Observability | undefined {
|
|
94
|
+
const resolved = resolveDebugConfig(options.settings.debug);
|
|
95
|
+
if (!resolved.enabled) return undefined;
|
|
96
|
+
|
|
97
|
+
const onLog = options.settings.onLog;
|
|
98
|
+
const integrations = options.settings.telemetry?.integrations
|
|
99
|
+
? asArray(options.settings.telemetry.integrations)
|
|
100
|
+
: [];
|
|
101
|
+
const diagnosticConsumers = integrations.filter(
|
|
102
|
+
(integration): integration is Telemetry & HarnessDiagnosticConsumer =>
|
|
103
|
+
typeof (integration as HarnessDiagnosticConsumer).ingestDiagnostic ===
|
|
104
|
+
'function',
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const report = (emitted: HarnessV1Diagnostic): void => {
|
|
108
|
+
const diagnostic = toHarnessDiagnostic(emitted);
|
|
109
|
+
try {
|
|
110
|
+
process.stderr.write(formatForStderr(diagnostic));
|
|
111
|
+
} catch {
|
|
112
|
+
// Never let the stderr default break the turn.
|
|
113
|
+
}
|
|
114
|
+
onLog?.(diagnostic);
|
|
115
|
+
for (const consumer of diagnosticConsumers) {
|
|
116
|
+
consumer.ingestDiagnostic?.(diagnostic);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
debug: {
|
|
122
|
+
enabled: true,
|
|
123
|
+
level: resolved.level,
|
|
124
|
+
...(resolved.subsystems ? { subsystems: resolved.subsystems } : {}),
|
|
125
|
+
},
|
|
126
|
+
report,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { safeValidateTypes } from '@ai-sdk/provider-utils';
|
|
2
|
+
import { HarnessError } from '../../errors/harness-error';
|
|
3
|
+
import type { HarnessV1, HarnessV1ResumeState } from '../../v1';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Validate a `HarnessV1ResumeState` against the harness's contract:
|
|
7
|
+
* - `specificationVersion` must be `'harness-v1'`.
|
|
8
|
+
* - `harnessId` must match the harness producing/consuming the payload.
|
|
9
|
+
* - When the harness declares a `resumeStateSchema`, `data` is parsed
|
|
10
|
+
* against it.
|
|
11
|
+
*
|
|
12
|
+
* Returns the payload with `data` replaced by the parsed value when a
|
|
13
|
+
* schema is present, so callers downstream see a canonical shape.
|
|
14
|
+
*/
|
|
15
|
+
export async function validateResumeStateData(input: {
|
|
16
|
+
harness: HarnessV1;
|
|
17
|
+
state: HarnessV1ResumeState;
|
|
18
|
+
}): Promise<HarnessV1ResumeState> {
|
|
19
|
+
const { harness, state } = input;
|
|
20
|
+
if (state.specificationVersion !== 'harness-v1') {
|
|
21
|
+
throw new HarnessError({
|
|
22
|
+
message: `Resume state has unexpected specificationVersion '${state.specificationVersion}'; expected 'harness-v1'.`,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (state.harnessId !== harness.harnessId) {
|
|
26
|
+
throw new HarnessError({
|
|
27
|
+
message: `Resume state was produced by harness '${state.harnessId}' but this agent uses '${harness.harnessId}'.`,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (harness.resumeStateSchema == null) {
|
|
31
|
+
return state;
|
|
32
|
+
}
|
|
33
|
+
const result = await safeValidateTypes({
|
|
34
|
+
value: state.data,
|
|
35
|
+
schema: harness.resumeStateSchema,
|
|
36
|
+
});
|
|
37
|
+
if (!result.success) {
|
|
38
|
+
throw new HarnessError({
|
|
39
|
+
message: `Resume state failed schema validation for harness '${harness.harnessId}': ${result.error.message}`,
|
|
40
|
+
cause: result.error,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
harnessId: state.harnessId,
|
|
45
|
+
specificationVersion: state.specificationVersion,
|
|
46
|
+
data: result.value as HarnessV1ResumeState['data'],
|
|
47
|
+
...(state.pendingToolApprovals !== undefined
|
|
48
|
+
? { pendingToolApprovals: state.pendingToolApprovals }
|
|
49
|
+
: {}),
|
|
50
|
+
};
|
|
51
|
+
}
|