@purista/harness 1.0.0
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/LICENSE +201 -0
- package/README.md +23 -0
- package/dist/agents/index.d.ts +34 -0
- package/dist/agents/index.js +301 -0
- package/dist/errors/catalog.d.ts +185 -0
- package/dist/errors/catalog.js +144 -0
- package/dist/errors/harness-error.d.ts +64 -0
- package/dist/errors/harness-error.js +58 -0
- package/dist/errors/index.d.ts +3 -0
- package/dist/errors/index.js +3 -0
- package/dist/errors/redaction.d.ts +5 -0
- package/dist/errors/redaction.js +64 -0
- package/dist/harness/defineHarness.d.ts +640 -0
- package/dist/harness/defineHarness.js +176 -0
- package/dist/harness/errors.d.ts +62 -0
- package/dist/harness/errors.js +67 -0
- package/dist/harness/types.d.ts +27 -0
- package/dist/harness/types.js +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +12 -0
- package/dist/logger/index.d.ts +2 -0
- package/dist/logger/index.js +2 -0
- package/dist/logger/json-logger.d.ts +31 -0
- package/dist/logger/json-logger.js +65 -0
- package/dist/logger/logger.d.ts +31 -0
- package/dist/logger/logger.js +1 -0
- package/dist/models/json.d.ts +6 -0
- package/dist/models/json.js +1 -0
- package/dist/models/registry.d.ts +112 -0
- package/dist/models/registry.js +286 -0
- package/dist/models/state.d.ts +64 -0
- package/dist/models/state.js +1 -0
- package/dist/ports/base-model-provider.d.ts +56 -0
- package/dist/ports/base-model-provider.js +343 -0
- package/dist/ports/capabilities.d.ts +70 -0
- package/dist/ports/capabilities.js +38 -0
- package/dist/ports/feedback.d.ts +29 -0
- package/dist/ports/feedback.js +1 -0
- package/dist/ports/harness-context.d.ts +20 -0
- package/dist/ports/harness-context.js +1 -0
- package/dist/ports/index.d.ts +6 -0
- package/dist/ports/index.js +6 -0
- package/dist/ports/model-provider.d.ts +280 -0
- package/dist/ports/model-provider.js +1 -0
- package/dist/ports/state.d.ts +72 -0
- package/dist/ports/state.js +24 -0
- package/dist/runtime/durable.d.ts +134 -0
- package/dist/runtime/durable.js +185 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/steps.d.ts +22 -0
- package/dist/runtime/steps.js +51 -0
- package/dist/sandbox/index.d.ts +111 -0
- package/dist/sandbox/index.js +165 -0
- package/dist/sessions/index.d.ts +23 -0
- package/dist/sessions/index.js +718 -0
- package/dist/skills/index.d.ts +8 -0
- package/dist/skills/index.js +88 -0
- package/dist/state/in-memory.d.ts +35 -0
- package/dist/state/in-memory.js +140 -0
- package/dist/telemetry/index.d.ts +1 -0
- package/dist/telemetry/index.js +1 -0
- package/dist/telemetry/shim.d.ts +26 -0
- package/dist/telemetry/shim.js +120 -0
- package/dist/testing/capabilities.d.ts +11 -0
- package/dist/testing/capabilities.js +20 -0
- package/dist/testing/fakeModelProvider.d.ts +25 -0
- package/dist/testing/fakeModelProvider.js +79 -0
- package/dist/testing/feedback.d.ts +10 -0
- package/dist/testing/feedback.js +24 -0
- package/dist/testing/fixtures/mcp/fake-http-server.d.ts +8 -0
- package/dist/testing/fixtures/mcp/fake-http-server.js +95 -0
- package/dist/testing/index.d.ts +8 -0
- package/dist/testing/index.js +11 -0
- package/dist/testing/sandboxContract.d.ts +4 -0
- package/dist/testing/sandboxContract.js +74 -0
- package/dist/testing/sandboxSnapshot.d.ts +7 -0
- package/dist/testing/sandboxSnapshot.js +201 -0
- package/dist/testing/stateStoreContract.d.ts +2 -0
- package/dist/testing/stateStoreContract.js +109 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +123 -0
- package/dist/tools/mcp/http.d.ts +2 -0
- package/dist/tools/mcp/http.js +109 -0
- package/dist/tools/mcp/index.d.ts +2 -0
- package/dist/tools/mcp/index.js +2 -0
- package/dist/tools/mcp/runner.d.ts +74 -0
- package/dist/tools/mcp/runner.js +238 -0
- package/dist/tools/mcp/schema.d.ts +41 -0
- package/dist/tools/mcp/schema.js +251 -0
- package/dist/tools/mcp/stdio.d.ts +2 -0
- package/dist/tools/mcp/stdio.js +122 -0
- package/dist/ulid/index.d.ts +6 -0
- package/dist/ulid/index.js +35 -0
- package/dist/workflows/index.d.ts +8 -0
- package/dist/workflows/index.js +26 -0
- package/package.json +75 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { JsonLogger } from '../logger/index.js';
|
|
3
|
+
import { InMemoryStateStore } from '../state/in-memory.js';
|
|
4
|
+
import { HarnessConfigError } from '../errors/catalog.js';
|
|
5
|
+
import { autoDetectSandbox } from '../sandbox/index.js';
|
|
6
|
+
import { createSessionHarness } from '../sessions/index.js';
|
|
7
|
+
import { hasAdapterCapabilities, missingCapabilities, uniqueCapabilities } from '../ports/capabilities.js';
|
|
8
|
+
/** Stable harness version string for diagnostics and generated documentation. */
|
|
9
|
+
export const HARNESS_VERSION = '0.0.0';
|
|
10
|
+
class Builder {
|
|
11
|
+
options;
|
|
12
|
+
configured;
|
|
13
|
+
constructor(options, configured = {}) {
|
|
14
|
+
this.options = options;
|
|
15
|
+
this.configured = configured;
|
|
16
|
+
}
|
|
17
|
+
telemetry(opts) {
|
|
18
|
+
return this.clone({ telemetry: opts });
|
|
19
|
+
}
|
|
20
|
+
logger(logger) {
|
|
21
|
+
return this.clone({ logger });
|
|
22
|
+
}
|
|
23
|
+
state(store) {
|
|
24
|
+
return this.clone({ state: store });
|
|
25
|
+
}
|
|
26
|
+
sandbox(sandbox = autoDetectSandbox()) {
|
|
27
|
+
return this.clone({ sandbox });
|
|
28
|
+
}
|
|
29
|
+
runtime(runtime) {
|
|
30
|
+
return this.clone({ runtime });
|
|
31
|
+
}
|
|
32
|
+
requires(capabilities) {
|
|
33
|
+
return this.clone({ requiredCapabilities: uniqueCapabilities(capabilities) });
|
|
34
|
+
}
|
|
35
|
+
defaults(defaults) {
|
|
36
|
+
if (defaults.historyWindow !== undefined && defaults.historyWindow < 0) {
|
|
37
|
+
throw new HarnessConfigError('historyWindow must be >= 0', { reason: 'invalid_defaults', path: 'defaults.historyWindow' });
|
|
38
|
+
}
|
|
39
|
+
return this.clone({ defaults });
|
|
40
|
+
}
|
|
41
|
+
models(models) {
|
|
42
|
+
if (Object.keys(models).length === 0) {
|
|
43
|
+
throw new HarnessConfigError('At least one model alias is required.', { reason: 'missing_models', path: 'models' });
|
|
44
|
+
}
|
|
45
|
+
return this.clone({ models });
|
|
46
|
+
}
|
|
47
|
+
tools(tools) {
|
|
48
|
+
return this.clone({ tools });
|
|
49
|
+
}
|
|
50
|
+
skills(skills) {
|
|
51
|
+
return this.clone({ skills });
|
|
52
|
+
}
|
|
53
|
+
agents(agents) {
|
|
54
|
+
const resolved = typeof agents === 'function'
|
|
55
|
+
? agents({ agent: (definition) => definition })
|
|
56
|
+
: agents;
|
|
57
|
+
return this.clone({ agents: resolved });
|
|
58
|
+
}
|
|
59
|
+
workflows(workflows) {
|
|
60
|
+
const resolved = typeof workflows === 'function'
|
|
61
|
+
? workflows({ workflow: (definition) => definition })
|
|
62
|
+
: workflows;
|
|
63
|
+
return this.clone({ workflows: resolved });
|
|
64
|
+
}
|
|
65
|
+
build() {
|
|
66
|
+
const models = this.configured.models;
|
|
67
|
+
if (!models || Object.keys(models).length === 0) {
|
|
68
|
+
throw new HarnessConfigError('At least one model alias is required.', { reason: 'missing_models', path: 'models' });
|
|
69
|
+
}
|
|
70
|
+
const sandbox = this.configured.sandbox ?? autoDetectSandbox();
|
|
71
|
+
const inspection = this.resolveInspection(this.options.name ?? 'agent-harness', sandbox, models);
|
|
72
|
+
const missing = missingCapabilities(inspection.requiredCapabilities, inspection.capabilities);
|
|
73
|
+
if (missing.length > 0) {
|
|
74
|
+
throw new HarnessConfigError('Required adapter capabilities are not available.', {
|
|
75
|
+
reason: 'missing_required_capability',
|
|
76
|
+
path: 'requires',
|
|
77
|
+
id: missing.join(',')
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const harness = createSessionHarness({
|
|
81
|
+
name: this.options.name ?? 'agent-harness',
|
|
82
|
+
logger: this.configured.logger ?? new JsonLogger(),
|
|
83
|
+
...(this.configured.telemetry ? { telemetry: this.configured.telemetry } : {}),
|
|
84
|
+
state: this.configured.state ?? new InMemoryStateStore(),
|
|
85
|
+
sandbox,
|
|
86
|
+
defaults: {
|
|
87
|
+
agentMaxIterations: this.configured.defaults?.agentMaxIterations ?? 16,
|
|
88
|
+
runTimeoutMs: this.configured.defaults?.runTimeoutMs ?? 600_000,
|
|
89
|
+
toolTimeoutMs: this.configured.defaults?.toolTimeoutMs ?? 120_000,
|
|
90
|
+
skillTimeoutMs: this.configured.defaults?.skillTimeoutMs ?? 60_000,
|
|
91
|
+
modelTimeoutMs: this.configured.defaults?.modelTimeoutMs ?? 300_000,
|
|
92
|
+
...(this.configured.defaults?.historyWindow !== undefined ? { historyWindow: this.configured.defaults.historyWindow } : {})
|
|
93
|
+
},
|
|
94
|
+
models,
|
|
95
|
+
tools: (this.configured.tools ?? {}),
|
|
96
|
+
skills: (this.configured.skills ?? {}),
|
|
97
|
+
agents: (this.configured.agents ?? {}),
|
|
98
|
+
workflows: (this.configured.workflows ?? {}),
|
|
99
|
+
inspection
|
|
100
|
+
});
|
|
101
|
+
return harness;
|
|
102
|
+
}
|
|
103
|
+
clone(patch) {
|
|
104
|
+
return new Builder(this.options, { ...this.configured, ...patch });
|
|
105
|
+
}
|
|
106
|
+
resolveInspection(name, sandbox, models) {
|
|
107
|
+
const adapters = [];
|
|
108
|
+
const sandboxCapabilities = hasAdapterCapabilities(sandbox) ? uniqueCapabilities(sandbox.capabilities) : [];
|
|
109
|
+
adapters.push({
|
|
110
|
+
kind: 'sandbox',
|
|
111
|
+
id: getAdapterId(sandbox, 'sandbox'),
|
|
112
|
+
capabilities: sandboxCapabilities
|
|
113
|
+
});
|
|
114
|
+
if (this.configured.runtime) {
|
|
115
|
+
adapters.push({
|
|
116
|
+
kind: 'runtime',
|
|
117
|
+
id: this.configured.runtime.id ?? 'runtime',
|
|
118
|
+
capabilities: uniqueCapabilities(this.configured.runtime.capabilities)
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
for (const [alias, model] of Object.entries(models)) {
|
|
122
|
+
adapters.push({
|
|
123
|
+
kind: 'model',
|
|
124
|
+
id: alias,
|
|
125
|
+
capabilities: [],
|
|
126
|
+
metadata: {
|
|
127
|
+
providerId: model.provider.id,
|
|
128
|
+
genAiSystem: model.provider.genAiSystem,
|
|
129
|
+
model: model.model,
|
|
130
|
+
modelCapabilities: model.capabilities,
|
|
131
|
+
...(model.provider.info ? { providerInfo: model.provider.info } : {})
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const capabilities = uniqueCapabilities(adapters.flatMap((adapter) => adapter.capabilities));
|
|
136
|
+
return {
|
|
137
|
+
name,
|
|
138
|
+
capabilities,
|
|
139
|
+
requiredCapabilities: uniqueCapabilities(this.configured.requiredCapabilities ?? []),
|
|
140
|
+
adapters
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function getAdapterId(adapter, fallback) {
|
|
145
|
+
if (adapter && typeof adapter === 'object' && typeof adapter.id === 'string') {
|
|
146
|
+
return adapter.id;
|
|
147
|
+
}
|
|
148
|
+
return fallback;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Creates the chainable harness builder used to define a harness system.
|
|
152
|
+
*
|
|
153
|
+
* Application code should compose models, tools, skills, agents, and workflows here,
|
|
154
|
+
* build the harness, and then execute work exclusively through `harness.getSession(...)`.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```ts
|
|
158
|
+
* const harness = defineHarness()
|
|
159
|
+
* .models({ fast: { provider, model: 'gpt-4.1-mini', capabilities: ['object'] } })
|
|
160
|
+
* .agents({ summarize: { model: 'fast', instructions: 'Summarize the input.' } })
|
|
161
|
+
* .workflows({
|
|
162
|
+
* summarize_ticket: {
|
|
163
|
+
* input: z.object({ ticket: z.string() }),
|
|
164
|
+
* output: z.string(),
|
|
165
|
+
* handler: (ctx) => ctx.agents.summarize(ctx.input.ticket)
|
|
166
|
+
* }
|
|
167
|
+
* })
|
|
168
|
+
* .build()
|
|
169
|
+
*
|
|
170
|
+
* const session = await harness.getSession('ticket-123')
|
|
171
|
+
* const summary = await session.workflows.summarize_ticket.prompt({ ticket: 'Cannot log in' })
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
export function defineHarness(opts = {}) {
|
|
175
|
+
return new Builder(opts);
|
|
176
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { JsonValue } from './types.js';
|
|
2
|
+
export declare class HarnessError extends Error {
|
|
3
|
+
code: string;
|
|
4
|
+
category: string;
|
|
5
|
+
retriable: boolean;
|
|
6
|
+
meta?: Record<string, JsonValue>;
|
|
7
|
+
constructor(message: string, opts: {
|
|
8
|
+
code: string;
|
|
9
|
+
category: string;
|
|
10
|
+
retriable: boolean;
|
|
11
|
+
meta?: Record<string, JsonValue>;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export declare class HarnessConfigError extends HarnessError {
|
|
15
|
+
constructor(message: string, meta?: Record<string, JsonValue>);
|
|
16
|
+
}
|
|
17
|
+
export declare class ValidationError extends HarnessError {
|
|
18
|
+
constructor(message: string, meta?: Record<string, JsonValue>);
|
|
19
|
+
}
|
|
20
|
+
export declare class PermissionDeniedError extends HarnessError {
|
|
21
|
+
constructor(message: string, meta?: Record<string, JsonValue>);
|
|
22
|
+
}
|
|
23
|
+
export declare class SandboxError extends HarnessError {
|
|
24
|
+
constructor(message: string, meta?: Record<string, JsonValue>);
|
|
25
|
+
}
|
|
26
|
+
export declare class SandboxNoExecutorError extends HarnessError {
|
|
27
|
+
constructor();
|
|
28
|
+
}
|
|
29
|
+
export declare class ModelError extends HarnessError {
|
|
30
|
+
constructor(message: string, meta?: Record<string, JsonValue>);
|
|
31
|
+
}
|
|
32
|
+
export declare class ToolError extends HarnessError {
|
|
33
|
+
constructor(message: string, meta?: Record<string, JsonValue>);
|
|
34
|
+
}
|
|
35
|
+
export declare class ToolNotFoundError extends HarnessError {
|
|
36
|
+
constructor(tool: string);
|
|
37
|
+
}
|
|
38
|
+
export declare class SkillManifestError extends HarnessError {
|
|
39
|
+
constructor(message: string, meta?: Record<string, JsonValue>);
|
|
40
|
+
}
|
|
41
|
+
export declare class AgentLoopBudgetError extends HarnessError {
|
|
42
|
+
constructor();
|
|
43
|
+
}
|
|
44
|
+
export declare class SessionBusyError extends HarnessError {
|
|
45
|
+
constructor(reason?: string);
|
|
46
|
+
}
|
|
47
|
+
export declare class OperationTimeoutError extends HarnessError {
|
|
48
|
+
constructor(scope: string);
|
|
49
|
+
}
|
|
50
|
+
export declare class OperationCancelledError extends HarnessError {
|
|
51
|
+
constructor(scope: string);
|
|
52
|
+
}
|
|
53
|
+
export declare class InternalError extends HarnessError {
|
|
54
|
+
constructor(message: string, meta?: Record<string, JsonValue>);
|
|
55
|
+
}
|
|
56
|
+
export declare function serializeError(error: unknown): {
|
|
57
|
+
code: string;
|
|
58
|
+
category: string;
|
|
59
|
+
retriable: boolean;
|
|
60
|
+
message: string;
|
|
61
|
+
meta?: Record<string, JsonValue>;
|
|
62
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export class HarnessError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
category;
|
|
4
|
+
retriable;
|
|
5
|
+
meta;
|
|
6
|
+
constructor(message, opts) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = new.target.name;
|
|
9
|
+
this.code = opts.code;
|
|
10
|
+
this.category = opts.category;
|
|
11
|
+
this.retriable = opts.retriable;
|
|
12
|
+
if (opts.meta !== undefined)
|
|
13
|
+
this.meta = opts.meta;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function errorOptions(opts, meta) {
|
|
17
|
+
return meta === undefined ? opts : { ...opts, meta };
|
|
18
|
+
}
|
|
19
|
+
export class HarnessConfigError extends HarnessError {
|
|
20
|
+
constructor(message, meta) { super(message, errorOptions({ code: 'HARNESS_CONFIG_ERROR', category: 'config', retriable: false }, meta)); }
|
|
21
|
+
}
|
|
22
|
+
export class ValidationError extends HarnessError {
|
|
23
|
+
constructor(message, meta) { super(message, errorOptions({ code: 'VALIDATION_ERROR', category: 'validation', retriable: false }, meta)); }
|
|
24
|
+
}
|
|
25
|
+
export class PermissionDeniedError extends HarnessError {
|
|
26
|
+
constructor(message, meta) { super(message, errorOptions({ code: 'PERMISSION_DENIED', category: 'permission', retriable: false }, meta)); }
|
|
27
|
+
}
|
|
28
|
+
export class SandboxError extends HarnessError {
|
|
29
|
+
constructor(message, meta) { super(message, errorOptions({ code: 'SANDBOX_ERROR', category: 'sandbox', retriable: false }, meta)); }
|
|
30
|
+
}
|
|
31
|
+
export class SandboxNoExecutorError extends HarnessError {
|
|
32
|
+
constructor() { super('Sandbox executor unavailable', { code: 'SANDBOX_NO_EXECUTOR', category: 'sandbox', retriable: false }); }
|
|
33
|
+
}
|
|
34
|
+
export class ModelError extends HarnessError {
|
|
35
|
+
constructor(message, meta) { super(message, errorOptions({ code: 'MODEL_ERROR', category: 'model', retriable: true }, meta)); }
|
|
36
|
+
}
|
|
37
|
+
export class ToolError extends HarnessError {
|
|
38
|
+
constructor(message, meta) { super(message, errorOptions({ code: 'TOOL_ERROR', category: 'tool', retriable: true }, meta)); }
|
|
39
|
+
}
|
|
40
|
+
export class ToolNotFoundError extends HarnessError {
|
|
41
|
+
constructor(tool) { super(`Tool not found: ${tool}`, { code: 'TOOL_NOT_FOUND', category: 'tool', retriable: false, meta: { tool } }); }
|
|
42
|
+
}
|
|
43
|
+
export class SkillManifestError extends HarnessError {
|
|
44
|
+
constructor(message, meta) { super(message, errorOptions({ code: 'SKILL_MANIFEST_ERROR', category: 'config', retriable: false }, meta)); }
|
|
45
|
+
}
|
|
46
|
+
export class AgentLoopBudgetError extends HarnessError {
|
|
47
|
+
constructor() { super('Agent maxSteps exceeded', { code: 'AGENT_LOOP_BUDGET', category: 'agent', retriable: false, meta: { reason: 'iterations_exceeded' } }); }
|
|
48
|
+
}
|
|
49
|
+
export class SessionBusyError extends HarnessError {
|
|
50
|
+
constructor(reason = 'run_in_flight') { super('Session is busy', { code: 'SESSION_BUSY', category: 'session', retriable: true, meta: { reason } }); }
|
|
51
|
+
}
|
|
52
|
+
export class OperationTimeoutError extends HarnessError {
|
|
53
|
+
constructor(scope) { super('Operation timed out', { code: 'OPERATION_TIMEOUT', category: 'harness', retriable: true, meta: { scope } }); }
|
|
54
|
+
}
|
|
55
|
+
export class OperationCancelledError extends HarnessError {
|
|
56
|
+
constructor(scope) { super('Operation cancelled', { code: 'OPERATION_CANCELLED', category: 'harness', retriable: false, meta: { scope } }); }
|
|
57
|
+
}
|
|
58
|
+
export class InternalError extends HarnessError {
|
|
59
|
+
constructor(message, meta) { super(message, errorOptions({ code: 'INTERNAL_ERROR', category: 'internal', retriable: false }, meta)); }
|
|
60
|
+
}
|
|
61
|
+
export function serializeError(error) {
|
|
62
|
+
if (error instanceof HarnessError) {
|
|
63
|
+
const serialized = { code: error.code, category: error.category, retriable: error.retriable, message: error.message };
|
|
64
|
+
return error.meta === undefined ? serialized : { ...serialized, meta: error.meta };
|
|
65
|
+
}
|
|
66
|
+
return { code: 'INTERNAL_ERROR', category: 'internal', retriable: false, message: error instanceof Error ? error.message : String(error) };
|
|
67
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
2
|
+
[key: string]: JsonValue;
|
|
3
|
+
};
|
|
4
|
+
export interface ExecOptions {
|
|
5
|
+
cwd?: string;
|
|
6
|
+
env?: Record<string, string>;
|
|
7
|
+
stdin?: string;
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
}
|
|
11
|
+
export interface ExecResult {
|
|
12
|
+
stdout: string;
|
|
13
|
+
stderr: string;
|
|
14
|
+
exitCode: number;
|
|
15
|
+
durationSeconds: number;
|
|
16
|
+
}
|
|
17
|
+
export interface DirEntry {
|
|
18
|
+
name: string;
|
|
19
|
+
path: string;
|
|
20
|
+
kind: 'file' | 'directory';
|
|
21
|
+
size?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface FileStat {
|
|
24
|
+
kind: 'file' | 'directory';
|
|
25
|
+
size: number;
|
|
26
|
+
modifiedAt: string;
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from './errors/index.js';
|
|
2
|
+
export * from './logger/index.js';
|
|
3
|
+
export * from './telemetry/index.js';
|
|
4
|
+
export * from './ulid/index.js';
|
|
5
|
+
export * from './ports/index.js';
|
|
6
|
+
export { createDurableWorkflowContext, DurableStepError, DurableRunLeaseError, DurableTerminalRunError, inMemoryDurableRuntime, isTerminalRunStatus } from './runtime/index.js';
|
|
7
|
+
export type { DurableActiveRunStatus, DurableWorkflowContext, DurableRunLease, DurableRunStart, DurableRunStatus, DurableRuntime, DurableTerminalRunStatus, FinishRunPatch, InMemoryDurableRuntimeOptions, RunCheckpoint } from './runtime/index.js';
|
|
8
|
+
export * from './state/in-memory.js';
|
|
9
|
+
export * from './models/json.js';
|
|
10
|
+
export type { SessionRecord, Message, RunRecord, PersistedRunEvent, RunStatus } from './models/state.js';
|
|
11
|
+
export * from './models/registry.js';
|
|
12
|
+
export * from './sandbox/index.js';
|
|
13
|
+
export * from './tools/mcp/index.js';
|
|
14
|
+
export * from './harness/defineHarness.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from './errors/index.js';
|
|
2
|
+
export * from './logger/index.js';
|
|
3
|
+
export * from './telemetry/index.js';
|
|
4
|
+
export * from './ulid/index.js';
|
|
5
|
+
export * from './ports/index.js';
|
|
6
|
+
export { createDurableWorkflowContext, DurableStepError, DurableRunLeaseError, DurableTerminalRunError, inMemoryDurableRuntime, isTerminalRunStatus } from './runtime/index.js';
|
|
7
|
+
export * from './state/in-memory.js';
|
|
8
|
+
export * from './models/json.js';
|
|
9
|
+
export * from './models/registry.js';
|
|
10
|
+
export * from './sandbox/index.js';
|
|
11
|
+
export * from './tools/mcp/index.js';
|
|
12
|
+
export * from './harness/defineHarness.js';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Logger, LogLevel } from './logger.js';
|
|
2
|
+
/**
|
|
3
|
+
* Options for {@link JsonLogger}.
|
|
4
|
+
*/
|
|
5
|
+
export interface JsonLoggerOptions {
|
|
6
|
+
/** Minimum emitted level. Defaults to env `PURISTA_HARNESS_LOG_LEVEL` or `info`. */
|
|
7
|
+
level?: LogLevel;
|
|
8
|
+
/** Writable destination for newline-delimited JSON records. Defaults to `process.stdout`. */
|
|
9
|
+
out?: {
|
|
10
|
+
write(chunk: string): unknown;
|
|
11
|
+
};
|
|
12
|
+
/** Static bindings added to every emitted log record. */
|
|
13
|
+
bindings?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Default structured logger emitting one JSON object per line.
|
|
17
|
+
*/
|
|
18
|
+
export declare class JsonLogger implements Logger {
|
|
19
|
+
private readonly minLevel;
|
|
20
|
+
private readonly out;
|
|
21
|
+
private readonly bindings;
|
|
22
|
+
constructor(opts?: JsonLoggerOptions);
|
|
23
|
+
trace(msg: string, fields?: Record<string, unknown>): void;
|
|
24
|
+
debug(msg: string, fields?: Record<string, unknown>): void;
|
|
25
|
+
info(msg: string, fields?: Record<string, unknown>): void;
|
|
26
|
+
warn(msg: string, fields?: Record<string, unknown>): void;
|
|
27
|
+
error(msg: string, fields?: Record<string, unknown>): void;
|
|
28
|
+
fatal(msg: string, fields?: Record<string, unknown>): void;
|
|
29
|
+
child(bindings: Record<string, unknown>): Logger;
|
|
30
|
+
private write;
|
|
31
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { context, trace } from '@opentelemetry/api';
|
|
2
|
+
import { sanitizeForLog } from '../errors/redaction.js';
|
|
3
|
+
const ORDERED_LEVELS = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
|
|
4
|
+
const LEVEL_INDEX = new Map(ORDERED_LEVELS.map((level, index) => [level, index]));
|
|
5
|
+
const ENV_NAME = 'PURISTA_HARNESS_LOG_LEVEL';
|
|
6
|
+
function isLogLevel(value) {
|
|
7
|
+
return LEVEL_INDEX.has(value);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Default structured logger emitting one JSON object per line.
|
|
11
|
+
*/
|
|
12
|
+
export class JsonLogger {
|
|
13
|
+
minLevel;
|
|
14
|
+
out;
|
|
15
|
+
bindings;
|
|
16
|
+
constructor(opts = {}) {
|
|
17
|
+
this.out = opts.out ?? process.stdout;
|
|
18
|
+
this.bindings = { ...(opts.bindings ?? {}) };
|
|
19
|
+
if (opts.level) {
|
|
20
|
+
this.minLevel = opts.level;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const envLevel = process.env[ENV_NAME];
|
|
24
|
+
if (envLevel && !isLogLevel(envLevel)) {
|
|
25
|
+
this.minLevel = 'info';
|
|
26
|
+
this.write('warn', `Invalid ${ENV_NAME} value. Falling back to info.`, { invalid_level: envLevel });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
this.minLevel = envLevel && isLogLevel(envLevel) ? envLevel : 'info';
|
|
30
|
+
}
|
|
31
|
+
trace(msg, fields) { this.write('trace', msg, fields); }
|
|
32
|
+
debug(msg, fields) { this.write('debug', msg, fields); }
|
|
33
|
+
info(msg, fields) { this.write('info', msg, fields); }
|
|
34
|
+
warn(msg, fields) { this.write('warn', msg, fields); }
|
|
35
|
+
error(msg, fields) { this.write('error', msg, fields); }
|
|
36
|
+
fatal(msg, fields) { this.write('fatal', msg, fields); }
|
|
37
|
+
child(bindings) {
|
|
38
|
+
return new JsonLogger({ level: this.minLevel, out: this.out, bindings: { ...this.bindings, ...bindings } });
|
|
39
|
+
}
|
|
40
|
+
write(level, msg, fields = {}) {
|
|
41
|
+
if ((LEVEL_INDEX.get(level) ?? 0) < (LEVEL_INDEX.get(this.minLevel) ?? 0)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const activeContext = context.active();
|
|
45
|
+
const spanContext = trace.getSpan(activeContext)?.spanContext() ?? trace.getSpanContext(activeContext);
|
|
46
|
+
const sanitized = sanitizeForLog({ ...this.bindings, ...fields });
|
|
47
|
+
const safeFields = sanitized && typeof sanitized === 'object' && !Array.isArray(sanitized)
|
|
48
|
+
? sanitized
|
|
49
|
+
: {};
|
|
50
|
+
const line = {
|
|
51
|
+
level,
|
|
52
|
+
time: new Date().toISOString(),
|
|
53
|
+
msg,
|
|
54
|
+
...(spanContext?.traceId ? { trace_id: spanContext.traceId } : {}),
|
|
55
|
+
...(spanContext?.spanId ? { span_id: spanContext.spanId } : {}),
|
|
56
|
+
...safeFields
|
|
57
|
+
};
|
|
58
|
+
try {
|
|
59
|
+
this.out.write(`${JSON.stringify(line)}\n`);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Logging must not affect harness execution.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported structured log levels, ordered from most to least verbose.
|
|
3
|
+
*/
|
|
4
|
+
export type LogLevel =
|
|
5
|
+
/** Fine-grained diagnostic tracing. */
|
|
6
|
+
'trace'
|
|
7
|
+
/** Developer-oriented debugging details. */
|
|
8
|
+
| 'debug'
|
|
9
|
+
/** Standard operational information. */
|
|
10
|
+
| 'info'
|
|
11
|
+
/** Recoverable warnings requiring visibility. */
|
|
12
|
+
| 'warn'
|
|
13
|
+
/** Errors that impacted a request/run. */
|
|
14
|
+
| 'error'
|
|
15
|
+
/** Fatal errors that may terminate process health. */
|
|
16
|
+
| 'fatal';
|
|
17
|
+
/**
|
|
18
|
+
* Logger contract used by the harness.
|
|
19
|
+
*
|
|
20
|
+
* All methods must be synchronous and non-throwing from the perspective of
|
|
21
|
+
* harness callers.
|
|
22
|
+
*/
|
|
23
|
+
export interface Logger {
|
|
24
|
+
trace(msg: string, fields?: Record<string, unknown>): void;
|
|
25
|
+
debug(msg: string, fields?: Record<string, unknown>): void;
|
|
26
|
+
info(msg: string, fields?: Record<string, unknown>): void;
|
|
27
|
+
warn(msg: string, fields?: Record<string, unknown>): void;
|
|
28
|
+
error(msg: string, fields?: Record<string, unknown>): void;
|
|
29
|
+
fatal(msg: string, fields?: Record<string, unknown>): void;
|
|
30
|
+
child(bindings: Record<string, unknown>): Logger;
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { EmbeddingRequest, EmbeddingResponse, ContentPart, ModelAlias, ModelCapability, ModelToolSpec, ObjectRequest, ObjectResponse, ObjectStreamChunk, RerankRequest, RerankResponse, TextRequest, TextResponse, TextStreamChunk, ToolCallSpec } from '../ports/model-provider.js';
|
|
2
|
+
import type { TelemetryShim } from '../telemetry/index.js';
|
|
3
|
+
import type { JsonValue } from './json.js';
|
|
4
|
+
export interface ModelInvokeContext {
|
|
5
|
+
harnessName: string;
|
|
6
|
+
sessionId: string;
|
|
7
|
+
runId: string;
|
|
8
|
+
workflowId?: string;
|
|
9
|
+
agentId?: string;
|
|
10
|
+
}
|
|
11
|
+
type TextPart = Extract<ContentPart, {
|
|
12
|
+
kind: 'text';
|
|
13
|
+
}>;
|
|
14
|
+
type VisionPart = Extract<ContentPart, {
|
|
15
|
+
kind: 'image' | 'image_url';
|
|
16
|
+
}>;
|
|
17
|
+
type AudioPart = Extract<ContentPart, {
|
|
18
|
+
kind: 'audio';
|
|
19
|
+
}>;
|
|
20
|
+
type FilePart = Extract<ContentPart, {
|
|
21
|
+
kind: 'file' | 'file_url';
|
|
22
|
+
}>;
|
|
23
|
+
type EmbeddingRequestInput = Omit<EmbeddingRequest, 'model' | 'signal'>;
|
|
24
|
+
type RerankRequestInput = Omit<RerankRequest, 'model' | 'signal'>;
|
|
25
|
+
type AliasCapabilities<A> = A extends {
|
|
26
|
+
capabilities: readonly (infer C)[];
|
|
27
|
+
} ? C : never;
|
|
28
|
+
type HasCapability<A, C extends ModelCapability> = C extends AliasCapabilities<A> ? true : false;
|
|
29
|
+
type ContentPartFor<A> = TextPart | (HasCapability<A, 'vision_input'> extends true ? VisionPart : never) | (HasCapability<A, 'audio_input'> extends true ? AudioPart : never) | (HasCapability<A, 'file_input'> extends true ? FilePart : never);
|
|
30
|
+
type ToolCallsFor<A> = HasCapability<A, 'tool_use'> extends true ? {
|
|
31
|
+
toolCalls?: ToolCallSpec[];
|
|
32
|
+
} : {
|
|
33
|
+
toolCalls?: never;
|
|
34
|
+
};
|
|
35
|
+
type ToolInputFor<A> = HasCapability<A, 'tool_use'> extends true ? {
|
|
36
|
+
tools?: ModelToolSpec[] | undefined;
|
|
37
|
+
} : {
|
|
38
|
+
tools?: never;
|
|
39
|
+
};
|
|
40
|
+
type ModelMessageFor<A> = {
|
|
41
|
+
role: 'system';
|
|
42
|
+
content: string;
|
|
43
|
+
} | {
|
|
44
|
+
role: 'user';
|
|
45
|
+
content: string | ContentPartFor<A>[];
|
|
46
|
+
} | ({
|
|
47
|
+
role: 'assistant';
|
|
48
|
+
content: string | ContentPartFor<A>[];
|
|
49
|
+
} & ToolCallsFor<A>) | (HasCapability<A, 'tool_use'> extends true ? {
|
|
50
|
+
role: 'tool';
|
|
51
|
+
toolCallId: string;
|
|
52
|
+
content: string;
|
|
53
|
+
} : never);
|
|
54
|
+
type TextRequestInputFor<A> = Omit<TextRequest, 'model' | 'signal' | 'defaults' | 'messages' | 'tools'> & {
|
|
55
|
+
messages: ModelMessageFor<A>[];
|
|
56
|
+
} & ToolInputFor<A>;
|
|
57
|
+
type ObjectRequestInputFor<A, T extends JsonValue = JsonValue> = Omit<ObjectRequest<T>, 'model' | 'signal' | 'defaults' | 'messages' | 'tools'> & {
|
|
58
|
+
messages: ModelMessageFor<A>[];
|
|
59
|
+
} & ToolInputFor<A>;
|
|
60
|
+
type TextModelMethods<A> = {
|
|
61
|
+
/** Executes a single text generation request. */
|
|
62
|
+
text(req: TextRequestInputFor<A>, signal: AbortSignal, ctx?: ModelInvokeContext): Promise<TextResponse>;
|
|
63
|
+
};
|
|
64
|
+
type TextStreamModelMethods<A> = {
|
|
65
|
+
/** Executes a streaming text generation request. */
|
|
66
|
+
textStream(req: TextRequestInputFor<A>, signal: AbortSignal, ctx?: ModelInvokeContext): AsyncIterable<TextStreamChunk>;
|
|
67
|
+
};
|
|
68
|
+
type ObjectModelMethods<A> = {
|
|
69
|
+
/** Executes a single structured object generation request. */
|
|
70
|
+
object<T extends JsonValue = JsonValue>(req: ObjectRequestInputFor<A, T>, signal: AbortSignal, ctx?: ModelInvokeContext): Promise<ObjectResponse<T>>;
|
|
71
|
+
};
|
|
72
|
+
type ObjectStreamModelMethods<A> = {
|
|
73
|
+
/** Executes a streaming structured object generation request. */
|
|
74
|
+
objectStream<T extends JsonValue = JsonValue>(req: ObjectRequestInputFor<A, T>, signal: AbortSignal, ctx?: ModelInvokeContext): AsyncIterable<ObjectStreamChunk<T>>;
|
|
75
|
+
};
|
|
76
|
+
type EmbeddingModelMethods = {
|
|
77
|
+
/** Generates embeddings for one or more input strings. */
|
|
78
|
+
embed(req: EmbeddingRequestInput, signal: AbortSignal, ctx?: ModelInvokeContext): Promise<EmbeddingResponse>;
|
|
79
|
+
};
|
|
80
|
+
type RerankModelMethods = {
|
|
81
|
+
/** Reranks documents for a query. */
|
|
82
|
+
rerank(req: RerankRequestInput, signal: AbortSignal, ctx?: ModelInvokeContext): Promise<RerankResponse>;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Bound model handle produced by {@link createModelRegistry}.
|
|
86
|
+
*
|
|
87
|
+
* The visible methods are a type-level projection of the alias capability
|
|
88
|
+
* policy. For example, aliases without `'embeddings'` do not expose `embed`.
|
|
89
|
+
*/
|
|
90
|
+
export type ModelHandle<A extends {
|
|
91
|
+
capabilities: readonly ModelCapability[];
|
|
92
|
+
} = {
|
|
93
|
+
capabilities: readonly ModelCapability[];
|
|
94
|
+
}> = (HasCapability<A, 'text'> extends true ? TextModelMethods<A> : {}) & (HasCapability<A, 'text_stream'> extends true ? TextStreamModelMethods<A> : {}) & (HasCapability<A, 'object'> extends true ? ObjectModelMethods<A> : {}) & (HasCapability<A, 'object_stream'> extends true ? ObjectStreamModelMethods<A> : {}) & (HasCapability<A, 'embeddings'> extends true ? EmbeddingModelMethods : {}) & (HasCapability<A, 'rerank'> extends true ? RerankModelMethods : {});
|
|
95
|
+
/**
|
|
96
|
+
* Creates per-alias model handles that enforce capability gates before provider invocation.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* const registry = createModelRegistry({
|
|
101
|
+
* assistant: { provider, model: 'gpt-4.1-mini', capabilities: ['text'] }
|
|
102
|
+
* })
|
|
103
|
+
* const out = await registry.assistant.text({ messages: [{ role: 'user', content: 'hi' }] }, new AbortController().signal)
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export declare function createModelRegistry<const M extends Record<string, ModelAlias>>(aliases: M, options?: {
|
|
107
|
+
telemetry?: TelemetryShim;
|
|
108
|
+
harnessName?: string;
|
|
109
|
+
}): {
|
|
110
|
+
readonly [K in keyof M]: ModelHandle<M[K]>;
|
|
111
|
+
};
|
|
112
|
+
export {};
|