@purista/harness 1.0.0 → 1.2.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/README.md +15 -0
- package/dist/agents/index.d.ts +5 -3
- package/dist/agents/index.js +84 -8
- package/dist/errors/catalog.d.ts +45 -5
- package/dist/errors/catalog.js +19 -0
- package/dist/errors/harness-error.d.ts +2 -0
- package/dist/eval/index.d.ts +57 -0
- package/dist/eval/index.js +181 -0
- package/dist/harness/defineHarness.d.ts +96 -20
- package/dist/harness/defineHarness.js +59 -2
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/memory/sandbox/index.d.ts +17 -0
- package/dist/memory/sandbox/index.js +122 -0
- package/dist/models/registry.js +32 -7
- package/dist/ports/capabilities.d.ts +46 -2
- package/dist/ports/harness-context.d.ts +4 -1
- package/dist/ports/index.d.ts +2 -0
- package/dist/ports/index.js +2 -0
- package/dist/ports/memory/facade.d.ts +5 -0
- package/dist/ports/memory/facade.js +123 -0
- package/dist/ports/memory/telemetry.d.ts +16 -0
- package/dist/ports/memory/telemetry.js +77 -0
- package/dist/ports/memory/types.d.ts +204 -0
- package/dist/ports/memory/types.js +1 -0
- package/dist/ports/memory/validation.d.ts +19 -0
- package/dist/ports/memory/validation.js +160 -0
- package/dist/ports/memory.d.ts +3 -0
- package/dist/ports/memory.js +3 -0
- package/dist/ports/workspace.d.ts +177 -0
- package/dist/ports/workspace.js +32 -0
- package/dist/runtime/durable.d.ts +3 -0
- package/dist/runtime/durable.js +2 -1
- package/dist/sessions/index.d.ts +2 -0
- package/dist/sessions/index.js +275 -68
- package/dist/skills/index.d.ts +2 -1
- package/dist/skills/index.js +263 -35
- package/dist/telemetry/shim.d.ts +20 -0
- package/dist/telemetry/shim.js +28 -0
- package/dist/testing/durableWorkspaceStoreContract.d.ts +3 -0
- package/dist/testing/durableWorkspaceStoreContract.js +41 -0
- package/dist/testing/fakeMemoryAdapter.d.ts +16 -0
- package/dist/testing/fakeMemoryAdapter.js +110 -0
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/index.js +4 -0
- package/dist/workspace/in-memory.d.ts +35 -0
- package/dist/workspace/in-memory.js +142 -0
- package/dist/workspace/index.d.ts +1 -0
- package/dist/workspace/index.js +1 -0
- package/package.json +12 -6
|
@@ -2,10 +2,13 @@ import { z } from 'zod';
|
|
|
2
2
|
import { type Logger } from '../logger/index.js';
|
|
3
3
|
import type { ModelAlias, ModelCapability, TokenUsage } from '../ports/model-provider.js';
|
|
4
4
|
import type { StateStore } from '../ports/state.js';
|
|
5
|
-
import type { TelemetryShim } from '../telemetry/index.js';
|
|
5
|
+
import type { Metrics, TelemetryShim } from '../telemetry/index.js';
|
|
6
6
|
import type { HarnessAdapterContext } from '../ports/harness-context.js';
|
|
7
|
+
import type { MemoryAdapter, MemoryFacade, SessionMemory } from '../ports/memory.js';
|
|
8
|
+
import type { DurableWorkspaceStore } from '../ports/workspace.js';
|
|
7
9
|
import type { JsonValue } from '../models/json.js';
|
|
8
10
|
import type { Message } from '../models/state.js';
|
|
11
|
+
import type { RunStatus } from '../models/state.js';
|
|
9
12
|
import type { HarnessError } from '../errors/harness-error.js';
|
|
10
13
|
import { type Sandbox } from '../sandbox/index.js';
|
|
11
14
|
import type { ModelHandle } from '../models/registry.js';
|
|
@@ -13,12 +16,13 @@ import { type AdapterCapability, type DurableRuntimeAdapter, type HarnessInspect
|
|
|
13
16
|
/** Stable harness version string for diagnostics and generated documentation. */
|
|
14
17
|
export declare const HARNESS_VERSION = "0.0.0";
|
|
15
18
|
/** OpenTelemetry capture controls used by the harness. */
|
|
19
|
+
export type TelemetryFlavor = 'dual' | 'gen_ai_only' | 'openinference_only';
|
|
20
|
+
export type ContentCaptureMode = 'NO_CONTENT' | 'SPAN_ONLY' | 'EVENT_ONLY' | 'SPAN_AND_EVENT';
|
|
16
21
|
export interface TelemetryOptions {
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
captureContent?: boolean;
|
|
22
|
+
/** Backend emission shape. */
|
|
23
|
+
flavor?: TelemetryFlavor;
|
|
24
|
+
/** Span/event content capture mode. */
|
|
25
|
+
contentCaptureMode?: ContentCaptureMode;
|
|
22
26
|
}
|
|
23
27
|
/** Default harness budgets and execution behavior. */
|
|
24
28
|
export interface HarnessDefaults {
|
|
@@ -51,6 +55,12 @@ export interface InvokeOptions {
|
|
|
51
55
|
timeoutMs?: number;
|
|
52
56
|
/** Optional history-window override for this call only. */
|
|
53
57
|
historyWindow?: number;
|
|
58
|
+
/** Optional W3C Trace Context parent. */
|
|
59
|
+
traceparent?: string;
|
|
60
|
+
/** Optional W3C Trace Context state. */
|
|
61
|
+
tracestate?: string;
|
|
62
|
+
/** Scalar metadata exposed to handlers and telemetry sanitizers. */
|
|
63
|
+
metadata?: Record<string, JsonValue>;
|
|
54
64
|
}
|
|
55
65
|
/** Canonical built-in tool names provided by the harness. */
|
|
56
66
|
export type BuiltinToolName = 'bash' | 'read' | 'write' | 'edit' | 'glob' | 'grep' | 'list';
|
|
@@ -91,27 +101,47 @@ export interface PermissionContext {
|
|
|
91
101
|
export type PermissionDecision = 'allow' | 'deny';
|
|
92
102
|
/** Async permission hook used for interactive approvals or custom policy engines. */
|
|
93
103
|
export type OnPermission = (ctx: PermissionContext) => Promise<PermissionDecision>;
|
|
104
|
+
/** Skill frontmatter parsed from `SKILL.md`. */
|
|
105
|
+
export interface SkillFrontmatter {
|
|
106
|
+
name: string;
|
|
107
|
+
description: string;
|
|
108
|
+
license?: string;
|
|
109
|
+
compatibility?: string;
|
|
110
|
+
metadata?: Record<string, string>;
|
|
111
|
+
'allowed-tools'?: string;
|
|
112
|
+
}
|
|
113
|
+
/** Validation mode for `SKILL.md` frontmatter. */
|
|
114
|
+
export type SkillValidationMode = 'strict' | 'lenient';
|
|
115
|
+
/** Diagnostic produced while parsing or discovering skills. */
|
|
116
|
+
export interface SkillDiagnostic {
|
|
117
|
+
level: 'warn' | 'error';
|
|
118
|
+
code: 'missing_skill_md' | 'invalid_frontmatter' | 'missing_description' | 'invalid_name' | 'name_mismatch' | 'directory_missing' | 'collision_shadowed' | 'untrusted_project_skill' | 'scan_limit_reached';
|
|
119
|
+
message: string;
|
|
120
|
+
skillName?: string;
|
|
121
|
+
directory?: string;
|
|
122
|
+
source?: string;
|
|
123
|
+
}
|
|
94
124
|
/** Mounted skill metadata after frontmatter parsing. */
|
|
95
125
|
export interface ResolvedSkill {
|
|
96
126
|
/** Public skill id. */
|
|
97
127
|
name: string;
|
|
98
128
|
/** Short user-facing description from frontmatter. */
|
|
99
129
|
description: string;
|
|
100
|
-
/** Optional skill version. */
|
|
101
|
-
version?: string;
|
|
102
130
|
/** Absolute directory mounted into `/skills/<name>`. */
|
|
103
131
|
directory: string;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
132
|
+
/** Absolute path to the parsed `SKILL.md`. */
|
|
133
|
+
skillPath: string;
|
|
134
|
+
/** Absolute path exposed as the skill instruction file location. */
|
|
135
|
+
location: string;
|
|
136
|
+
/** Sandbox mount path for this skill. */
|
|
137
|
+
mountPath: `/skills/${string}`;
|
|
138
|
+
license?: string;
|
|
139
|
+
compatibility?: string;
|
|
140
|
+
metadata?: Record<string, string>;
|
|
141
|
+
allowedTools?: string;
|
|
142
|
+
trust: 'trusted' | 'project' | 'user';
|
|
143
|
+
source?: string;
|
|
144
|
+
diagnostics: readonly SkillDiagnostic[];
|
|
115
145
|
}
|
|
116
146
|
/** Conversation history accessor for a single session thread. */
|
|
117
147
|
export interface ConversationHistory {
|
|
@@ -127,6 +157,8 @@ export interface ToolHandlerContext {
|
|
|
127
157
|
sandbox: import('../sandbox/index.js').SandboxSession;
|
|
128
158
|
logger: Logger;
|
|
129
159
|
telemetry: TelemetryShim;
|
|
160
|
+
metrics: Metrics;
|
|
161
|
+
memory: MemoryFacade;
|
|
130
162
|
runId: string;
|
|
131
163
|
sessionId: string;
|
|
132
164
|
agentId: string;
|
|
@@ -214,9 +246,32 @@ export type ToolsConfig = Record<string, ToolDefinition>;
|
|
|
214
246
|
export interface SkillDefinition {
|
|
215
247
|
/** Absolute path to the directory containing `SKILL.md`. */
|
|
216
248
|
directory: string;
|
|
249
|
+
validationMode?: SkillValidationMode;
|
|
250
|
+
trust?: 'trusted' | 'project' | 'user';
|
|
251
|
+
source?: string;
|
|
217
252
|
}
|
|
218
253
|
/** Full skill registry shape. */
|
|
219
254
|
export type SkillsConfig = Record<string, SkillDefinition>;
|
|
255
|
+
/** Options for local Agent Skills discovery. */
|
|
256
|
+
export interface DiscoverSkillsOptions {
|
|
257
|
+
projectRoot?: string;
|
|
258
|
+
clientName?: string;
|
|
259
|
+
includeProjectAgentsDir?: boolean;
|
|
260
|
+
includeProjectClientDir?: boolean;
|
|
261
|
+
includeUserAgentsDir?: boolean;
|
|
262
|
+
includeUserClientDir?: boolean;
|
|
263
|
+
includeClaudeCompatDir?: boolean;
|
|
264
|
+
includeAncestorProjectDirs?: boolean;
|
|
265
|
+
trustedProjectRoots?: readonly string[];
|
|
266
|
+
validationMode?: SkillValidationMode;
|
|
267
|
+
maxDepth?: number;
|
|
268
|
+
maxDirectories?: number;
|
|
269
|
+
}
|
|
270
|
+
/** Result of local Agent Skills discovery. */
|
|
271
|
+
export interface DiscoveredSkills {
|
|
272
|
+
skills: SkillsConfig;
|
|
273
|
+
diagnostics: readonly SkillDiagnostic[];
|
|
274
|
+
}
|
|
220
275
|
/** Alias map passed to `.models(...)`. */
|
|
221
276
|
export type ModelsConfig = Record<string, ModelAlias>;
|
|
222
277
|
/** Builder-state accumulator used for type propagation across the fluent harness builder. */
|
|
@@ -258,7 +313,9 @@ export interface AgentContextMinimal<S extends BuilderState, I> {
|
|
|
258
313
|
sessionId: string;
|
|
259
314
|
runId: string;
|
|
260
315
|
history: ConversationHistory;
|
|
261
|
-
memory:
|
|
316
|
+
memory: MemoryFacade;
|
|
317
|
+
metadata: Readonly<Record<string, JsonValue>>;
|
|
318
|
+
metrics: Metrics;
|
|
262
319
|
}
|
|
263
320
|
/** Full context passed to workflow handlers. */
|
|
264
321
|
export interface WorkflowContext<S extends BuilderState, I, O> {
|
|
@@ -270,6 +327,9 @@ export interface WorkflowContext<S extends BuilderState, I, O> {
|
|
|
270
327
|
signal: AbortSignal;
|
|
271
328
|
runId: string;
|
|
272
329
|
sessionId: string;
|
|
330
|
+
metadata: Readonly<Record<string, JsonValue>>;
|
|
331
|
+
memory: MemoryFacade;
|
|
332
|
+
metrics: Metrics;
|
|
273
333
|
output?: O;
|
|
274
334
|
}
|
|
275
335
|
/** Full context passed to custom agent handlers. */
|
|
@@ -442,6 +502,7 @@ export interface Session<S extends BuilderState> {
|
|
|
442
502
|
};
|
|
443
503
|
memory: SessionMemory;
|
|
444
504
|
history: ConversationHistory;
|
|
505
|
+
getRunSummary(runId: string): Promise<RunSummary | undefined>;
|
|
445
506
|
clearHistory(): Promise<void>;
|
|
446
507
|
replaceHistory(messages: ReadonlyArray<Omit<Message, 'id' | 'timestamp'>>): Promise<void>;
|
|
447
508
|
close(): Promise<void>;
|
|
@@ -454,6 +515,18 @@ export interface SerializedError {
|
|
|
454
515
|
message: string;
|
|
455
516
|
meta?: Record<string, unknown>;
|
|
456
517
|
}
|
|
518
|
+
export interface RunSummary {
|
|
519
|
+
runId: string;
|
|
520
|
+
sessionId: string;
|
|
521
|
+
status: RunStatus;
|
|
522
|
+
startedAt: string;
|
|
523
|
+
finishedAt?: string;
|
|
524
|
+
tokenTotals: TokenUsage;
|
|
525
|
+
modelCalls: number;
|
|
526
|
+
toolCalls: number;
|
|
527
|
+
agentCalls: number;
|
|
528
|
+
error?: SerializedError;
|
|
529
|
+
}
|
|
457
530
|
/** Harness streaming events emitted from `session.workflows.<id>.stream(...)`. */
|
|
458
531
|
export type RunEvent = {
|
|
459
532
|
type: 'run.started';
|
|
@@ -512,6 +585,7 @@ export type RunEvent = {
|
|
|
512
585
|
runId: string;
|
|
513
586
|
agentId?: string;
|
|
514
587
|
object: JsonValue;
|
|
588
|
+
usage?: TokenUsage;
|
|
515
589
|
} | {
|
|
516
590
|
type: 'model.embedding.completed';
|
|
517
591
|
runId: string;
|
|
@@ -538,7 +612,9 @@ export interface HarnessBuilder<S extends BuilderState = {}> {
|
|
|
538
612
|
logger(logger: Logger): HarnessBuilder<S>;
|
|
539
613
|
state(store: StateStore): HarnessBuilder<S>;
|
|
540
614
|
sandbox(sandbox?: Sandbox<any>): HarnessBuilder<S>;
|
|
615
|
+
memory(adapter: MemoryAdapter): HarnessBuilder<S>;
|
|
541
616
|
runtime(runtime: DurableRuntimeAdapter): HarnessBuilder<S>;
|
|
617
|
+
workspaceStore(store: DurableWorkspaceStore): HarnessBuilder<S>;
|
|
542
618
|
requires(capabilities: readonly AdapterCapability[]): HarnessBuilder<S>;
|
|
543
619
|
defaults(defaults: HarnessDefaults): HarnessBuilder<S>;
|
|
544
620
|
models<const M extends ModelsConfig>(models: M): HarnessBuilder<S & {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { JsonLogger } from '../logger/index.js';
|
|
3
|
+
import { sandboxMemory } from '../memory/sandbox/index.js';
|
|
4
|
+
import { validateMemoryAdapter } from '../ports/memory.js';
|
|
5
|
+
import { validateDurableWorkspaceStore } from '../ports/workspace.js';
|
|
3
6
|
import { InMemoryStateStore } from '../state/in-memory.js';
|
|
4
7
|
import { HarnessConfigError } from '../errors/catalog.js';
|
|
5
8
|
import { autoDetectSandbox } from '../sandbox/index.js';
|
|
@@ -26,9 +29,23 @@ class Builder {
|
|
|
26
29
|
sandbox(sandbox = autoDetectSandbox()) {
|
|
27
30
|
return this.clone({ sandbox });
|
|
28
31
|
}
|
|
32
|
+
memory(memory) {
|
|
33
|
+
if (this.configured.memory) {
|
|
34
|
+
throw new HarnessConfigError('Memory adapter is already configured.', { reason: 'duplicate_adapter', path: 'memory' });
|
|
35
|
+
}
|
|
36
|
+
validateMemoryAdapter(memory);
|
|
37
|
+
return this.clone({ memory });
|
|
38
|
+
}
|
|
29
39
|
runtime(runtime) {
|
|
30
40
|
return this.clone({ runtime });
|
|
31
41
|
}
|
|
42
|
+
workspaceStore(workspaceStore) {
|
|
43
|
+
if (this.configured.workspaceStore) {
|
|
44
|
+
throw new HarnessConfigError('Workspace store is already configured.', { reason: 'duplicate_adapter', path: 'workspaceStore' });
|
|
45
|
+
}
|
|
46
|
+
validateDurableWorkspaceStore(workspaceStore);
|
|
47
|
+
return this.clone({ workspaceStore });
|
|
48
|
+
}
|
|
32
49
|
requires(capabilities) {
|
|
33
50
|
return this.clone({ requiredCapabilities: uniqueCapabilities(capabilities) });
|
|
34
51
|
}
|
|
@@ -54,6 +71,7 @@ class Builder {
|
|
|
54
71
|
const resolved = typeof agents === 'function'
|
|
55
72
|
? agents({ agent: (definition) => definition })
|
|
56
73
|
: agents;
|
|
74
|
+
this.validateAgentSkillReferences(resolved);
|
|
57
75
|
return this.clone({ agents: resolved });
|
|
58
76
|
}
|
|
59
77
|
workflows(workflows) {
|
|
@@ -68,7 +86,11 @@ class Builder {
|
|
|
68
86
|
throw new HarnessConfigError('At least one model alias is required.', { reason: 'missing_models', path: 'models' });
|
|
69
87
|
}
|
|
70
88
|
const sandbox = this.configured.sandbox ?? autoDetectSandbox();
|
|
71
|
-
const
|
|
89
|
+
const memory = this.configured.memory ?? sandboxMemory();
|
|
90
|
+
validateMemoryAdapter(memory);
|
|
91
|
+
if (this.configured.workspaceStore)
|
|
92
|
+
validateDurableWorkspaceStore(this.configured.workspaceStore);
|
|
93
|
+
const inspection = this.resolveInspection(this.options.name ?? 'agent-harness', sandbox, memory, models);
|
|
72
94
|
const missing = missingCapabilities(inspection.requiredCapabilities, inspection.capabilities);
|
|
73
95
|
if (missing.length > 0) {
|
|
74
96
|
throw new HarnessConfigError('Required adapter capabilities are not available.', {
|
|
@@ -83,6 +105,7 @@ class Builder {
|
|
|
83
105
|
...(this.configured.telemetry ? { telemetry: this.configured.telemetry } : {}),
|
|
84
106
|
state: this.configured.state ?? new InMemoryStateStore(),
|
|
85
107
|
sandbox,
|
|
108
|
+
memory,
|
|
86
109
|
defaults: {
|
|
87
110
|
agentMaxIterations: this.configured.defaults?.agentMaxIterations ?? 16,
|
|
88
111
|
runTimeoutMs: this.configured.defaults?.runTimeoutMs ?? 600_000,
|
|
@@ -103,7 +126,21 @@ class Builder {
|
|
|
103
126
|
clone(patch) {
|
|
104
127
|
return new Builder(this.options, { ...this.configured, ...patch });
|
|
105
128
|
}
|
|
106
|
-
|
|
129
|
+
validateAgentSkillReferences(agents) {
|
|
130
|
+
const configuredSkills = new Set(Object.keys(this.configured.skills ?? {}));
|
|
131
|
+
for (const [agentId, agent] of Object.entries(agents)) {
|
|
132
|
+
for (const skillId of agent.skills ?? []) {
|
|
133
|
+
if (!configuredSkills.has(skillId)) {
|
|
134
|
+
throw new HarnessConfigError('Agent references an unknown skill.', {
|
|
135
|
+
reason: 'invalid_agent',
|
|
136
|
+
path: `agents.${agentId}.skills`,
|
|
137
|
+
id: skillId
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
resolveInspection(name, sandbox, memory, models) {
|
|
107
144
|
const adapters = [];
|
|
108
145
|
const sandboxCapabilities = hasAdapterCapabilities(sandbox) ? uniqueCapabilities(sandbox.capabilities) : [];
|
|
109
146
|
adapters.push({
|
|
@@ -111,6 +148,15 @@ class Builder {
|
|
|
111
148
|
id: getAdapterId(sandbox, 'sandbox'),
|
|
112
149
|
capabilities: sandboxCapabilities
|
|
113
150
|
});
|
|
151
|
+
adapters.push({
|
|
152
|
+
kind: 'memory',
|
|
153
|
+
id: memory.info.id,
|
|
154
|
+
capabilities: uniqueCapabilities(memory.info.capabilities),
|
|
155
|
+
metadata: {
|
|
156
|
+
packageName: memory.info.packageName,
|
|
157
|
+
...(memory.info.version ? { version: memory.info.version } : {})
|
|
158
|
+
}
|
|
159
|
+
});
|
|
114
160
|
if (this.configured.runtime) {
|
|
115
161
|
adapters.push({
|
|
116
162
|
kind: 'runtime',
|
|
@@ -118,6 +164,17 @@ class Builder {
|
|
|
118
164
|
capabilities: uniqueCapabilities(this.configured.runtime.capabilities)
|
|
119
165
|
});
|
|
120
166
|
}
|
|
167
|
+
if (this.configured.workspaceStore) {
|
|
168
|
+
adapters.push({
|
|
169
|
+
kind: 'workspace_store',
|
|
170
|
+
id: this.configured.workspaceStore.info.id,
|
|
171
|
+
capabilities: uniqueCapabilities(this.configured.workspaceStore.info.capabilities),
|
|
172
|
+
metadata: {
|
|
173
|
+
packageName: this.configured.workspaceStore.info.packageName,
|
|
174
|
+
policy: this.configured.workspaceStore.info.policy
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
121
178
|
for (const [alias, model] of Object.entries(models)) {
|
|
122
179
|
adapters.push({
|
|
123
180
|
kind: 'model',
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,10 @@ export * from './state/in-memory.js';
|
|
|
9
9
|
export * from './models/json.js';
|
|
10
10
|
export type { SessionRecord, Message, RunRecord, PersistedRunEvent, RunStatus } from './models/state.js';
|
|
11
11
|
export * from './models/registry.js';
|
|
12
|
+
export * from './eval/index.js';
|
|
13
|
+
export * from './memory/sandbox/index.js';
|
|
14
|
+
export * from './skills/index.js';
|
|
12
15
|
export * from './sandbox/index.js';
|
|
16
|
+
export * from './workspace/index.js';
|
|
13
17
|
export * from './tools/mcp/index.js';
|
|
14
18
|
export * from './harness/defineHarness.js';
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,10 @@ export { createDurableWorkflowContext, DurableStepError, DurableRunLeaseError, D
|
|
|
7
7
|
export * from './state/in-memory.js';
|
|
8
8
|
export * from './models/json.js';
|
|
9
9
|
export * from './models/registry.js';
|
|
10
|
+
export * from './eval/index.js';
|
|
11
|
+
export * from './memory/sandbox/index.js';
|
|
12
|
+
export * from './skills/index.js';
|
|
10
13
|
export * from './sandbox/index.js';
|
|
14
|
+
export * from './workspace/index.js';
|
|
11
15
|
export * from './tools/mcp/index.js';
|
|
12
16
|
export * from './harness/defineHarness.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { MemoryAdapter } from '../../ports/memory.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates the built-in memory adapter backed by the current session sandbox.
|
|
4
|
+
*
|
|
5
|
+
* It is intentionally simple and local: session memory is stored below
|
|
6
|
+
* `/memory/session/`, run memory below `/memory/runs/<runId>/`.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* const harness = defineHarness()
|
|
11
|
+
* .memory(sandboxMemory())
|
|
12
|
+
* .models({ fast: model })
|
|
13
|
+
* .agents(({ agent }) => ({ assistant: agent({ model: 'fast', instructions: 'Help.' }) }))
|
|
14
|
+
* .build()
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function sandboxMemory(): MemoryAdapter;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { StateError } from '../../errors/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates the built-in memory adapter backed by the current session sandbox.
|
|
4
|
+
*
|
|
5
|
+
* It is intentionally simple and local: session memory is stored below
|
|
6
|
+
* `/memory/session/`, run memory below `/memory/runs/<runId>/`.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* const harness = defineHarness()
|
|
11
|
+
* .memory(sandboxMemory())
|
|
12
|
+
* .models({ fast: model })
|
|
13
|
+
* .agents(({ agent }) => ({ assistant: agent({ model: 'fast', instructions: 'Help.' }) }))
|
|
14
|
+
* .build()
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function sandboxMemory() {
|
|
18
|
+
return new SandboxMemoryAdapter();
|
|
19
|
+
}
|
|
20
|
+
class SandboxMemoryAdapter {
|
|
21
|
+
info = {
|
|
22
|
+
id: 'sandbox_memory',
|
|
23
|
+
packageName: '@purista/harness',
|
|
24
|
+
capabilities: ['memory.kv', 'memory.list', 'memory.delete', 'memory.run', 'memory.session']
|
|
25
|
+
};
|
|
26
|
+
capabilities = this.info.capabilities;
|
|
27
|
+
configureHarnessContext(_context) {
|
|
28
|
+
// The sandbox adapter receives runtime context through each `open(...)` call.
|
|
29
|
+
}
|
|
30
|
+
async open(scope, ctx) {
|
|
31
|
+
const sandbox = ctx.sandbox;
|
|
32
|
+
if (!sandbox) {
|
|
33
|
+
throw new StateError('sandboxMemory requires an active sandbox session.', {
|
|
34
|
+
op: 'memory.get',
|
|
35
|
+
adapter: 'memory',
|
|
36
|
+
memory_provider: this.info.id,
|
|
37
|
+
reason: 'missing_sandbox'
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const root = scopeRoot(scope);
|
|
41
|
+
const metaRoot = scopeMetaRoot(scope);
|
|
42
|
+
return {
|
|
43
|
+
get: async (key, op) => {
|
|
44
|
+
op.signal.throwIfAborted();
|
|
45
|
+
const path = `${root}/${key}.json`;
|
|
46
|
+
if (!(await sandbox.exists(path)))
|
|
47
|
+
return undefined;
|
|
48
|
+
return JSON.parse(await sandbox.readText(path));
|
|
49
|
+
},
|
|
50
|
+
set: async (key, value, op) => {
|
|
51
|
+
op.signal.throwIfAborted();
|
|
52
|
+
const existing = await readMetadata(sandbox, metaRoot, key);
|
|
53
|
+
const now = new Date().toISOString();
|
|
54
|
+
await sandbox.write(`${root}/${key}.json`, JSON.stringify(value));
|
|
55
|
+
await sandbox.write(`${metaRoot}/${key}.json`, JSON.stringify({
|
|
56
|
+
createdAt: existing?.createdAt ?? now,
|
|
57
|
+
updatedAt: now,
|
|
58
|
+
...(op.opts?.tags ? { tags: op.opts.tags } : existing?.tags ? { tags: existing.tags } : {}),
|
|
59
|
+
...(op.opts?.metadata ? { metadata: op.opts.metadata } : existing?.metadata ? { metadata: existing.metadata } : {})
|
|
60
|
+
}));
|
|
61
|
+
},
|
|
62
|
+
delete: async (key, op) => {
|
|
63
|
+
op.signal.throwIfAborted();
|
|
64
|
+
await sandbox.remove(`${root}/${key}.json`).catch(() => undefined);
|
|
65
|
+
await sandbox.remove(`${metaRoot}/${key}.json`).catch(() => undefined);
|
|
66
|
+
},
|
|
67
|
+
list: async (op) => {
|
|
68
|
+
op.signal.throwIfAborted();
|
|
69
|
+
const entries = await sandbox.list(root).catch(() => []);
|
|
70
|
+
const opts = op.opts ?? {};
|
|
71
|
+
const keys = entries
|
|
72
|
+
.filter((entry) => entry.kind === 'file' && entry.name.endsWith('.json'))
|
|
73
|
+
.map((entry) => entry.name.slice(0, -5))
|
|
74
|
+
.filter((key) => !opts.prefix || key.startsWith(opts.prefix))
|
|
75
|
+
.filter((key) => !opts.cursor || key > opts.cursor)
|
|
76
|
+
.sort()
|
|
77
|
+
.slice(0, opts.limit);
|
|
78
|
+
const out = [];
|
|
79
|
+
for (const key of keys) {
|
|
80
|
+
const metadata = await readMetadata(sandbox, metaRoot, key);
|
|
81
|
+
out.push({ key, ...(metadata ?? {}) });
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function scopeRoot(scope) {
|
|
89
|
+
if (scope.kind === 'session')
|
|
90
|
+
return '/memory/session';
|
|
91
|
+
if (scope.kind === 'run' && scope.runId)
|
|
92
|
+
return `/memory/runs/${scope.runId}`;
|
|
93
|
+
throw new StateError('Unsupported sandbox memory scope.', {
|
|
94
|
+
op: 'memory.get',
|
|
95
|
+
adapter: 'memory',
|
|
96
|
+
memory_provider: 'sandbox_memory',
|
|
97
|
+
reason: `unsupported_scope:${scope.kind}`
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function scopeMetaRoot(scope) {
|
|
101
|
+
if (scope.kind === 'session')
|
|
102
|
+
return '/memory/.meta/session';
|
|
103
|
+
if (scope.kind === 'run' && scope.runId)
|
|
104
|
+
return `/memory/.meta/runs/${scope.runId}`;
|
|
105
|
+
throw new StateError('Unsupported sandbox memory scope.', {
|
|
106
|
+
op: 'memory.list',
|
|
107
|
+
adapter: 'memory',
|
|
108
|
+
memory_provider: 'sandbox_memory',
|
|
109
|
+
reason: `unsupported_scope:${scope.kind}`
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async function readMetadata(sandbox, metaRoot, key) {
|
|
113
|
+
const path = `${metaRoot}/${key}.json`;
|
|
114
|
+
if (!(await sandbox.exists(path).catch(() => false)))
|
|
115
|
+
return undefined;
|
|
116
|
+
try {
|
|
117
|
+
return JSON.parse(await sandbox.readText(path));
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
}
|
package/dist/models/registry.js
CHANGED
|
@@ -131,10 +131,13 @@ function withModelStreamSpan(options, aliasKey, alias, method, ctx, fn) {
|
|
|
131
131
|
span.setAttributes({
|
|
132
132
|
[ATTR_GEN_AI_USAGE_INPUT_TOKENS]: lastUsage.inputTokens,
|
|
133
133
|
[ATTR_GEN_AI_USAGE_OUTPUT_TOKENS]: lastUsage.outputTokens,
|
|
134
|
-
'gen_ai.usage.total_tokens': lastUsage.totalTokens
|
|
134
|
+
'gen_ai.usage.total_tokens': lastUsage.totalTokens,
|
|
135
|
+
'llm.token_count.prompt': lastUsage.inputTokens,
|
|
136
|
+
'llm.token_count.completion': lastUsage.outputTokens,
|
|
137
|
+
'llm.token_count.total': lastUsage.totalTokens
|
|
135
138
|
});
|
|
136
|
-
options.telemetry?.
|
|
137
|
-
options.telemetry?.
|
|
139
|
+
options.telemetry?.recordHistogram('gen_ai.client.token.usage', lastUsage.inputTokens, { ...attrs, [ATTR_GEN_AI_TOKEN_TYPE]: GEN_AI_TOKEN_TYPE_VALUE_INPUT });
|
|
140
|
+
options.telemetry?.recordHistogram('gen_ai.client.token.usage', lastUsage.outputTokens, { ...attrs, [ATTR_GEN_AI_TOKEN_TYPE]: GEN_AI_TOKEN_TYPE_VALUE_OUTPUT });
|
|
138
141
|
}
|
|
139
142
|
if (lastFinishReason)
|
|
140
143
|
span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [lastFinishReason]);
|
|
@@ -188,10 +191,13 @@ async function withModelSpan(options, aliasKey, alias, method, ctx, fn) {
|
|
|
188
191
|
span.setAttributes({
|
|
189
192
|
[ATTR_GEN_AI_USAGE_INPUT_TOKENS]: usage.inputTokens,
|
|
190
193
|
[ATTR_GEN_AI_USAGE_OUTPUT_TOKENS]: usage.outputTokens,
|
|
191
|
-
'gen_ai.usage.total_tokens': usage.totalTokens
|
|
194
|
+
'gen_ai.usage.total_tokens': usage.totalTokens,
|
|
195
|
+
'llm.token_count.prompt': usage.inputTokens,
|
|
196
|
+
'llm.token_count.completion': usage.outputTokens,
|
|
197
|
+
'llm.token_count.total': usage.totalTokens
|
|
192
198
|
});
|
|
193
|
-
options.telemetry?.
|
|
194
|
-
options.telemetry?.
|
|
199
|
+
options.telemetry?.recordHistogram('gen_ai.client.token.usage', usage.inputTokens, { ...attrs, [ATTR_GEN_AI_TOKEN_TYPE]: GEN_AI_TOKEN_TYPE_VALUE_INPUT });
|
|
200
|
+
options.telemetry?.recordHistogram('gen_ai.client.token.usage', usage.outputTokens, { ...attrs, [ATTR_GEN_AI_TOKEN_TYPE]: GEN_AI_TOKEN_TYPE_VALUE_OUTPUT });
|
|
195
201
|
}
|
|
196
202
|
if (finishReason)
|
|
197
203
|
span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [finishReason]);
|
|
@@ -208,11 +214,30 @@ function modelSpanAttrs(options, aliasKey, alias, method, ctx) {
|
|
|
208
214
|
'harness.agent.id': ctx?.agentId,
|
|
209
215
|
'harness.model.alias': aliasKey,
|
|
210
216
|
'harness.model.method': method,
|
|
217
|
+
'gen_ai.operation.name': genAiOperationName(method),
|
|
218
|
+
'openinference.span.kind': openInferenceSpanKind(method),
|
|
211
219
|
[ATTR_GEN_AI_SYSTEM]: alias.provider.genAiSystem,
|
|
220
|
+
'gen_ai.provider.name': alias.provider.genAiSystem,
|
|
212
221
|
[ATTR_GEN_AI_REQUEST_MODEL]: alias.model,
|
|
213
|
-
'model.provider': alias.provider.id
|
|
222
|
+
'model.provider': alias.provider.id,
|
|
223
|
+
'llm.provider': alias.provider.genAiSystem,
|
|
224
|
+
'llm.model_name': alias.model
|
|
214
225
|
};
|
|
215
226
|
}
|
|
227
|
+
function genAiOperationName(method) {
|
|
228
|
+
if (method === 'embeddings')
|
|
229
|
+
return 'embeddings';
|
|
230
|
+
if (method === 'rerank')
|
|
231
|
+
return undefined;
|
|
232
|
+
return 'chat';
|
|
233
|
+
}
|
|
234
|
+
function openInferenceSpanKind(method) {
|
|
235
|
+
if (method === 'embeddings')
|
|
236
|
+
return 'EMBEDDING';
|
|
237
|
+
if (method === 'rerank')
|
|
238
|
+
return 'RERANKER';
|
|
239
|
+
return 'LLM';
|
|
240
|
+
}
|
|
216
241
|
/**
|
|
217
242
|
* Validates alias capabilities for the requested operation.
|
|
218
243
|
*
|
|
@@ -25,15 +25,59 @@ export type AdapterCapability =
|
|
|
25
25
|
| 'runtime.distributed_lock'
|
|
26
26
|
/** Runtime can resume from committed checkpoints. */
|
|
27
27
|
| 'runtime.resume_from_checkpoint'
|
|
28
|
+
/** Runtime checkpoint records can carry durable workspace references. */
|
|
29
|
+
| 'runtime.workspace_checkpoint'
|
|
30
|
+
/** Runtime exposes checkpoint retention and expiry metadata. */
|
|
31
|
+
| 'runtime.checkpoint_retention'
|
|
32
|
+
/** Workspace store persists state beyond process exit. */
|
|
33
|
+
| 'workspace_store.durable'
|
|
34
|
+
/** Workspace store can produce stable checkpoints. */
|
|
35
|
+
| 'workspace_store.checkpoint'
|
|
36
|
+
/** Workspace store can resume committed checkpoints. */
|
|
37
|
+
| 'workspace_store.resume'
|
|
38
|
+
/** Workspace store can abort active or paused workspaces. */
|
|
39
|
+
| 'workspace_store.abort'
|
|
40
|
+
/** Workspace store supports idempotent cleanup. */
|
|
41
|
+
| 'workspace_store.cleanup'
|
|
42
|
+
/** Workspace store supports read-only inspection. */
|
|
43
|
+
| 'workspace_store.inspect'
|
|
44
|
+
/** Workspace store exposes retention policy and expiry metadata. */
|
|
45
|
+
| 'workspace_store.retention'
|
|
46
|
+
/** Workspace store enforces and reports quota policy. */
|
|
47
|
+
| 'workspace_store.quota'
|
|
48
|
+
/** Workspace store encrypts checkpoint, snapshot, file, and metadata storage. */
|
|
49
|
+
| 'workspace_store.encrypted_storage'
|
|
28
50
|
/** Adapter can record feedback. */
|
|
29
|
-
| 'feedback.record'
|
|
51
|
+
| 'feedback.record'
|
|
52
|
+
/** Memory adapter supports key/value reads and writes. */
|
|
53
|
+
| 'memory.kv'
|
|
54
|
+
/** Memory adapter supports key listing. */
|
|
55
|
+
| 'memory.list'
|
|
56
|
+
/** Memory adapter supports key deletion. */
|
|
57
|
+
| 'memory.delete'
|
|
58
|
+
/** Memory adapter supports text search over stored memory. */
|
|
59
|
+
| 'memory.search'
|
|
60
|
+
/** Memory adapter supports entry expiration. */
|
|
61
|
+
| 'memory.ttl'
|
|
62
|
+
/** Memory adapter supports run-scoped memory. */
|
|
63
|
+
| 'memory.run'
|
|
64
|
+
/** Memory adapter supports session-scoped memory. */
|
|
65
|
+
| 'memory.session'
|
|
66
|
+
/** Memory adapter supports agent-scoped memory. */
|
|
67
|
+
| 'memory.agent'
|
|
68
|
+
/** Memory adapter supports user-scoped memory. */
|
|
69
|
+
| 'memory.user'
|
|
70
|
+
/** Memory adapter supports tenant-scoped memory. */
|
|
71
|
+
| 'memory.tenant'
|
|
72
|
+
/** Memory survives adapter close/reopen for the same logical scope. */
|
|
73
|
+
| 'memory.persistent';
|
|
30
74
|
/** Data-only descriptor implemented by adapters that expose capability metadata. */
|
|
31
75
|
export interface AdapterCapabilities {
|
|
32
76
|
readonly capabilities: readonly AdapterCapability[];
|
|
33
77
|
}
|
|
34
78
|
/** Adapter descriptor surfaced through `harness.inspect()`. */
|
|
35
79
|
export interface AdapterInspection {
|
|
36
|
-
readonly kind: 'state' | 'sandbox' | 'runtime' | 'feedback' | 'model';
|
|
80
|
+
readonly kind: 'state' | 'sandbox' | 'runtime' | 'workspace_store' | 'feedback' | 'model' | 'memory';
|
|
37
81
|
readonly id: string;
|
|
38
82
|
readonly capabilities: readonly AdapterCapability[];
|
|
39
83
|
readonly metadata?: Record<string, unknown>;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { Logger } from '../logger/index.js';
|
|
2
|
-
import type { TelemetryShim } from '../telemetry/index.js';
|
|
2
|
+
import type { Metrics, TelemetryShim } from '../telemetry/index.js';
|
|
3
|
+
import type { ContentCaptureMode } from '../harness/defineHarness.js';
|
|
3
4
|
/** Harness-level context inherited by adapters registered with the harness. */
|
|
4
5
|
export interface HarnessAdapterContext {
|
|
5
6
|
harnessName: string;
|
|
6
7
|
logger: Logger;
|
|
7
8
|
telemetry: TelemetryShim;
|
|
9
|
+
metrics: Metrics;
|
|
10
|
+
contentCaptureMode: ContentCaptureMode;
|
|
8
11
|
defaults: {
|
|
9
12
|
agentMaxIterations: number;
|
|
10
13
|
runTimeoutMs: number;
|
package/dist/ports/index.d.ts
CHANGED
package/dist/ports/index.js
CHANGED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CreateMemoryFacadeOptions, MemoryFacade, MemoryScope, SessionMemory } from './types.js';
|
|
2
|
+
/** Creates scoped memory helpers for a concrete session/run context. */
|
|
3
|
+
export declare function createMemoryFacade(opts: CreateMemoryFacadeOptions): MemoryFacade;
|
|
4
|
+
/** Creates a key/value memory facade bound to one normalized scope. */
|
|
5
|
+
export declare function createSessionMemory(opts: CreateMemoryFacadeOptions, scope: MemoryScope): SessionMemory;
|