@ethogram/core 0.1.0-alpha.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 +23 -0
- package/dist/index.cjs +110 -0
- package/dist/index.d.ts +134 -0
- package/dist/index.js +105 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Ethogram core
|
|
2
|
+
|
|
3
|
+
The code-first authoring contract for Ethogram Agents, Stories, behavioral matchers, and consumer-owned local execution profiles.
|
|
4
|
+
|
|
5
|
+
This package is prepared as `@ethogram/core@0.1.0-alpha.0` but has not been published yet.
|
|
6
|
+
|
|
7
|
+
## Public API
|
|
8
|
+
|
|
9
|
+
Runtime values:
|
|
10
|
+
|
|
11
|
+
- `defineAgent`
|
|
12
|
+
- `defineStory`
|
|
13
|
+
- `defineExecutionProfile`
|
|
14
|
+
|
|
15
|
+
Public types cover Agent and Story authoring, legacy and structured GIVEN values, `tool-called` and `tool-not-called` matchers, the generic external execution-profile/tool contract, and framework-neutral verdict-free external execution evidence.
|
|
16
|
+
|
|
17
|
+
Stories use `given`, `when`, and canonical `expectations`. `then` remains a backward-compatible alias for the alpha.
|
|
18
|
+
|
|
19
|
+
Execution profiles always declare `tools`, including `tools: {}` when a third-party framework owns tool dispatch. A completed profile may return optional `ExternalExecutionEvidence`; Ethogram retains ownership of canonical observation normalization and behavioral evaluation.
|
|
20
|
+
|
|
21
|
+
Story expectations declare required behavior. They never contain behavioral verdicts.
|
|
22
|
+
|
|
23
|
+
The current matchers are `tool-called` and `tool-not-called`. Node.js 20.9 or newer is required. See the repository documentation for the framework-owned evidence contract and alpha limitations.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defineAgent = defineAgent;
|
|
4
|
+
exports.defineStory = defineStory;
|
|
5
|
+
exports.defineExecutionProfile = defineExecutionProfile;
|
|
6
|
+
const forbiddenExpectationVerdictKeys = ['passed', 'failed', 'status', 'verdict'];
|
|
7
|
+
function requiredText(value, field) {
|
|
8
|
+
if (!value.trim())
|
|
9
|
+
throw new Error(`${field} is required.`);
|
|
10
|
+
}
|
|
11
|
+
function assertVerdictFreeExpectations(expectations) {
|
|
12
|
+
for (const expectation of expectations) {
|
|
13
|
+
for (const key of forbiddenExpectationVerdictKeys) {
|
|
14
|
+
if (Object.prototype.hasOwnProperty.call(expectation, key)) {
|
|
15
|
+
throw new Error(`Story expectation "${expectation.id}" must not contain behavioral verdict field "${key}".`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function assertStructuredGivenValue(value, location, ancestors) {
|
|
21
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
22
|
+
return;
|
|
23
|
+
if (typeof value === 'number') {
|
|
24
|
+
if (!Number.isFinite(value))
|
|
25
|
+
throw new Error(`${location} must contain only finite numbers.`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (typeof value !== 'object') {
|
|
29
|
+
throw new Error(`${location} contains unsupported value type "${typeof value}".`);
|
|
30
|
+
}
|
|
31
|
+
if (ancestors.has(value))
|
|
32
|
+
throw new Error(`${location} must not contain cyclic values.`);
|
|
33
|
+
ancestors.add(value);
|
|
34
|
+
try {
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
37
|
+
if (!Object.prototype.hasOwnProperty.call(value, index)) {
|
|
38
|
+
throw new Error(`${location}[${index}] must not be undefined or sparse.`);
|
|
39
|
+
}
|
|
40
|
+
assertStructuredGivenValue(value[index], `${location}[${index}]`, ancestors);
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const prototype = Object.getPrototypeOf(value);
|
|
45
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
46
|
+
throw new Error(`${location} must contain only plain records and arrays.`);
|
|
47
|
+
}
|
|
48
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
49
|
+
if (typeof key !== 'string')
|
|
50
|
+
throw new Error(`${location} must not contain symbol keys.`);
|
|
51
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
52
|
+
if (!descriptor || descriptor.get || descriptor.set) {
|
|
53
|
+
throw new Error(`${location}.${key} must be a plain data property.`);
|
|
54
|
+
}
|
|
55
|
+
assertStructuredGivenValue(descriptor.value, `${location}.${key}`, ancestors);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
ancestors.delete(value);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function deepFreeze(value) {
|
|
63
|
+
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
64
|
+
Object.freeze(value);
|
|
65
|
+
for (const nested of Object.values(value))
|
|
66
|
+
deepFreeze(nested);
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
function validatedGiven(given) {
|
|
71
|
+
if (given === undefined)
|
|
72
|
+
return [];
|
|
73
|
+
if (Array.isArray(given)) {
|
|
74
|
+
for (const [index, value] of given.entries()) {
|
|
75
|
+
if (typeof value !== 'string')
|
|
76
|
+
throw new Error(`Story given[${index}] must be a string.`);
|
|
77
|
+
}
|
|
78
|
+
return given;
|
|
79
|
+
}
|
|
80
|
+
assertStructuredGivenValue(given, 'Story given', new Set());
|
|
81
|
+
return deepFreeze(structuredClone(given));
|
|
82
|
+
}
|
|
83
|
+
function defineAgent(agent) {
|
|
84
|
+
requiredText(agent.id, 'Agent id');
|
|
85
|
+
requiredText(agent.name, 'Agent name');
|
|
86
|
+
return agent;
|
|
87
|
+
}
|
|
88
|
+
function defineStory(input) {
|
|
89
|
+
requiredText(input.id, 'Story id');
|
|
90
|
+
requiredText(input.name, 'Story name');
|
|
91
|
+
const prompt = (input.prompt ?? input.when);
|
|
92
|
+
const expectations = (input.expectations ?? input.then);
|
|
93
|
+
requiredText(prompt, 'Story prompt');
|
|
94
|
+
assertVerdictFreeExpectations(expectations);
|
|
95
|
+
return {
|
|
96
|
+
__ethogramType: 'story',
|
|
97
|
+
id: input.id,
|
|
98
|
+
name: input.name,
|
|
99
|
+
agent: input.agent,
|
|
100
|
+
description: input.description,
|
|
101
|
+
given: validatedGiven(input.given),
|
|
102
|
+
prompt,
|
|
103
|
+
expectations,
|
|
104
|
+
...(input.execution === undefined ? {} : { execution: input.execution }),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function defineExecutionProfile(profile) {
|
|
108
|
+
requiredText(profile.id, 'Execution profile id');
|
|
109
|
+
return { __ethogramType: 'execution-profile', ...profile };
|
|
110
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
type AgentIcon = 'headset' | 'target' | 'search';
|
|
2
|
+
export type Agent = {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
icon: AgentIcon;
|
|
7
|
+
};
|
|
8
|
+
export type ToolCalledMatcher = {
|
|
9
|
+
kind: 'tool-called';
|
|
10
|
+
tool: string;
|
|
11
|
+
};
|
|
12
|
+
export type ToolNotCalledMatcher = {
|
|
13
|
+
kind: 'tool-not-called';
|
|
14
|
+
tool: string;
|
|
15
|
+
};
|
|
16
|
+
export type ExpectationMatcher = ToolCalledMatcher | ToolNotCalledMatcher;
|
|
17
|
+
export type StoryExpectation = {
|
|
18
|
+
id: string;
|
|
19
|
+
description: string;
|
|
20
|
+
failureDescription?: string;
|
|
21
|
+
matcher: ExpectationMatcher;
|
|
22
|
+
passed?: never;
|
|
23
|
+
failed?: never;
|
|
24
|
+
status?: never;
|
|
25
|
+
verdict?: never;
|
|
26
|
+
};
|
|
27
|
+
type StoryExecutionCapability = {
|
|
28
|
+
kind: 'external-profile';
|
|
29
|
+
profile: string;
|
|
30
|
+
};
|
|
31
|
+
export type StoryGivenValue = string | number | boolean | null | readonly StoryGivenValue[] | {
|
|
32
|
+
readonly [key: string]: StoryGivenValue;
|
|
33
|
+
};
|
|
34
|
+
export type StoryGiven = string[] | Readonly<Record<string, StoryGivenValue>>;
|
|
35
|
+
export type Story = {
|
|
36
|
+
readonly __ethogramType: 'story';
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
agent: Agent;
|
|
40
|
+
description: string;
|
|
41
|
+
given: StoryGiven;
|
|
42
|
+
prompt: string;
|
|
43
|
+
expectations: StoryExpectation[];
|
|
44
|
+
execution?: StoryExecutionCapability;
|
|
45
|
+
};
|
|
46
|
+
type PromptInput = {
|
|
47
|
+
prompt: string;
|
|
48
|
+
when?: never;
|
|
49
|
+
} | {
|
|
50
|
+
when: string;
|
|
51
|
+
prompt?: never;
|
|
52
|
+
};
|
|
53
|
+
type ExpectationsInput = {
|
|
54
|
+
expectations: StoryExpectation[];
|
|
55
|
+
then?: never;
|
|
56
|
+
} | {
|
|
57
|
+
then: StoryExpectation[];
|
|
58
|
+
expectations?: never;
|
|
59
|
+
};
|
|
60
|
+
export type StoryInput = {
|
|
61
|
+
id: string;
|
|
62
|
+
name: string;
|
|
63
|
+
agent: Agent;
|
|
64
|
+
description: string;
|
|
65
|
+
given?: StoryGiven;
|
|
66
|
+
execution?: StoryExecutionCapability;
|
|
67
|
+
} & PromptInput & ExpectationsInput;
|
|
68
|
+
type ExternalToolInput = Readonly<Record<string, unknown>>;
|
|
69
|
+
type ExternalToolOutput = Readonly<Record<string, unknown>>;
|
|
70
|
+
export type ExternalExecutionEvidenceValue = string | number | boolean | null | readonly ExternalExecutionEvidenceValue[] | {
|
|
71
|
+
readonly [key: string]: ExternalExecutionEvidenceValue;
|
|
72
|
+
};
|
|
73
|
+
type ExternalToolCallEvidenceBase = {
|
|
74
|
+
callId: string;
|
|
75
|
+
name: string;
|
|
76
|
+
input: ExternalExecutionEvidenceValue;
|
|
77
|
+
sequence: number;
|
|
78
|
+
step?: number;
|
|
79
|
+
startedAt?: string;
|
|
80
|
+
endedAt?: string;
|
|
81
|
+
durationMs?: number;
|
|
82
|
+
};
|
|
83
|
+
export type ExternalToolCallEvidence = (ExternalToolCallEvidenceBase & {
|
|
84
|
+
status: 'success';
|
|
85
|
+
output?: ExternalExecutionEvidenceValue;
|
|
86
|
+
error?: never;
|
|
87
|
+
}) | (ExternalToolCallEvidenceBase & {
|
|
88
|
+
status: 'error';
|
|
89
|
+
output?: never;
|
|
90
|
+
error?: {
|
|
91
|
+
name?: string;
|
|
92
|
+
message: string;
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
export type ExternalExecutionEvidence = {
|
|
96
|
+
source: string;
|
|
97
|
+
toolCalls: readonly ExternalToolCallEvidence[];
|
|
98
|
+
provider?: string;
|
|
99
|
+
model?: string;
|
|
100
|
+
startedAt?: string;
|
|
101
|
+
endedAt?: string;
|
|
102
|
+
latencyMs?: number;
|
|
103
|
+
finishReason?: string;
|
|
104
|
+
tokenUsage?: {
|
|
105
|
+
inputTokens?: number;
|
|
106
|
+
outputTokens?: number;
|
|
107
|
+
totalTokens?: number;
|
|
108
|
+
reasoningTokens?: number;
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
export type ExternalToolDefinition = {
|
|
112
|
+
description: string;
|
|
113
|
+
execute(input: ExternalToolInput): ExternalToolOutput | Promise<ExternalToolOutput>;
|
|
114
|
+
};
|
|
115
|
+
export type ExternalToolSet = Readonly<Record<string, ExternalToolDefinition>>;
|
|
116
|
+
export type ExternalExecutionOutcome = {
|
|
117
|
+
decision: string;
|
|
118
|
+
finalResponse: string;
|
|
119
|
+
evidence?: ExternalExecutionEvidence;
|
|
120
|
+
};
|
|
121
|
+
export type ExternalExecutionContext = {
|
|
122
|
+
story: Story;
|
|
123
|
+
callTool(toolName: string, input: ExternalToolInput): Promise<ExternalToolOutput>;
|
|
124
|
+
};
|
|
125
|
+
export type ExternalExecutionProfile = {
|
|
126
|
+
readonly __ethogramType: 'execution-profile';
|
|
127
|
+
id: string;
|
|
128
|
+
tools: ExternalToolSet;
|
|
129
|
+
execute(context: ExternalExecutionContext): Promise<ExternalExecutionOutcome>;
|
|
130
|
+
};
|
|
131
|
+
export declare function defineAgent<const TAgent extends Agent>(agent: TAgent): TAgent;
|
|
132
|
+
export declare function defineStory(input: StoryInput): Story;
|
|
133
|
+
export declare function defineExecutionProfile(profile: Omit<ExternalExecutionProfile, '__ethogramType'>): ExternalExecutionProfile;
|
|
134
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const forbiddenExpectationVerdictKeys = ['passed', 'failed', 'status', 'verdict'];
|
|
2
|
+
function requiredText(value, field) {
|
|
3
|
+
if (!value.trim())
|
|
4
|
+
throw new Error(`${field} is required.`);
|
|
5
|
+
}
|
|
6
|
+
function assertVerdictFreeExpectations(expectations) {
|
|
7
|
+
for (const expectation of expectations) {
|
|
8
|
+
for (const key of forbiddenExpectationVerdictKeys) {
|
|
9
|
+
if (Object.prototype.hasOwnProperty.call(expectation, key)) {
|
|
10
|
+
throw new Error(`Story expectation "${expectation.id}" must not contain behavioral verdict field "${key}".`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function assertStructuredGivenValue(value, location, ancestors) {
|
|
16
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
17
|
+
return;
|
|
18
|
+
if (typeof value === 'number') {
|
|
19
|
+
if (!Number.isFinite(value))
|
|
20
|
+
throw new Error(`${location} must contain only finite numbers.`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (typeof value !== 'object') {
|
|
24
|
+
throw new Error(`${location} contains unsupported value type "${typeof value}".`);
|
|
25
|
+
}
|
|
26
|
+
if (ancestors.has(value))
|
|
27
|
+
throw new Error(`${location} must not contain cyclic values.`);
|
|
28
|
+
ancestors.add(value);
|
|
29
|
+
try {
|
|
30
|
+
if (Array.isArray(value)) {
|
|
31
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
32
|
+
if (!Object.prototype.hasOwnProperty.call(value, index)) {
|
|
33
|
+
throw new Error(`${location}[${index}] must not be undefined or sparse.`);
|
|
34
|
+
}
|
|
35
|
+
assertStructuredGivenValue(value[index], `${location}[${index}]`, ancestors);
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const prototype = Object.getPrototypeOf(value);
|
|
40
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
41
|
+
throw new Error(`${location} must contain only plain records and arrays.`);
|
|
42
|
+
}
|
|
43
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
44
|
+
if (typeof key !== 'string')
|
|
45
|
+
throw new Error(`${location} must not contain symbol keys.`);
|
|
46
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
47
|
+
if (!descriptor || descriptor.get || descriptor.set) {
|
|
48
|
+
throw new Error(`${location}.${key} must be a plain data property.`);
|
|
49
|
+
}
|
|
50
|
+
assertStructuredGivenValue(descriptor.value, `${location}.${key}`, ancestors);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
ancestors.delete(value);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function deepFreeze(value) {
|
|
58
|
+
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
59
|
+
Object.freeze(value);
|
|
60
|
+
for (const nested of Object.values(value))
|
|
61
|
+
deepFreeze(nested);
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
function validatedGiven(given) {
|
|
66
|
+
if (given === undefined)
|
|
67
|
+
return [];
|
|
68
|
+
if (Array.isArray(given)) {
|
|
69
|
+
for (const [index, value] of given.entries()) {
|
|
70
|
+
if (typeof value !== 'string')
|
|
71
|
+
throw new Error(`Story given[${index}] must be a string.`);
|
|
72
|
+
}
|
|
73
|
+
return given;
|
|
74
|
+
}
|
|
75
|
+
assertStructuredGivenValue(given, 'Story given', new Set());
|
|
76
|
+
return deepFreeze(structuredClone(given));
|
|
77
|
+
}
|
|
78
|
+
export function defineAgent(agent) {
|
|
79
|
+
requiredText(agent.id, 'Agent id');
|
|
80
|
+
requiredText(agent.name, 'Agent name');
|
|
81
|
+
return agent;
|
|
82
|
+
}
|
|
83
|
+
export function defineStory(input) {
|
|
84
|
+
requiredText(input.id, 'Story id');
|
|
85
|
+
requiredText(input.name, 'Story name');
|
|
86
|
+
const prompt = (input.prompt ?? input.when);
|
|
87
|
+
const expectations = (input.expectations ?? input.then);
|
|
88
|
+
requiredText(prompt, 'Story prompt');
|
|
89
|
+
assertVerdictFreeExpectations(expectations);
|
|
90
|
+
return {
|
|
91
|
+
__ethogramType: 'story',
|
|
92
|
+
id: input.id,
|
|
93
|
+
name: input.name,
|
|
94
|
+
agent: input.agent,
|
|
95
|
+
description: input.description,
|
|
96
|
+
given: validatedGiven(input.given),
|
|
97
|
+
prompt,
|
|
98
|
+
expectations,
|
|
99
|
+
...(input.execution === undefined ? {} : { execution: input.execution }),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
export function defineExecutionProfile(profile) {
|
|
103
|
+
requiredText(profile.id, 'Execution profile id');
|
|
104
|
+
return { __ethogramType: 'execution-profile', ...profile };
|
|
105
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ethogram/core",
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
4
|
+
"description": "Code-first Agent and Story authoring contracts for Ethogram.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/leonardocamacho1983/ethogram.git",
|
|
9
|
+
"directory": "packages/agentbook"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/leonardocamacho1983/ethogram#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/leonardocamacho1983/ethogram/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": ["ai-agents", "behavioral-testing", "typescript", "developer-tools"],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js",
|
|
21
|
+
"require": "./dist/index.cjs"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=20.9"
|
|
34
|
+
}
|
|
35
|
+
}
|