@axiastudio/aioc 0.1.2 → 0.2.0-next.2
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 +5 -8
- package/dist/harness-descriptor.d.ts +78 -0
- package/dist/harness-descriptor.d.ts.map +1 -0
- package/dist/harness-descriptor.js +277 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +4 -1
- package/dist/approval-helpers.d.ts +0 -24
- package/dist/approval-helpers.d.ts.map +0 -1
- package/dist/approval-helpers.js +0 -94
package/README.md
CHANGED
|
@@ -8,15 +8,12 @@ Project home and documentation: [https://axiastudio.github.io/aioc](https://axia
|
|
|
8
8
|
|
|
9
9
|
## Release Status
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
The stable `0.1.x` line started with `0.1.0`.
|
|
11
|
+
This package is stable as of `0.1.0`.
|
|
13
12
|
The core runtime surface is compatibility-managed. Breaking changes to the stable surface should ship only with explicit migration guidance and release notes.
|
|
14
13
|
|
|
15
14
|
### Stable Scope
|
|
16
15
|
|
|
17
|
-
AIOC `0.1.0`
|
|
18
|
-
AIOC `0.1.1` adds thread-history utilities, the run-output stream adapter, and provider-specific instruction-role documentation without changing the stable governance model.
|
|
19
|
-
AIOC `0.1.2` adds approval evidence helpers for application-owned approval workflows.
|
|
16
|
+
AIOC `0.1.0` stabilizes the core runtime surface, public documentation aligned to the exported contract, `RunRecord` and replay/compare workflows, and the governance-first runtime model validated in real applications beyond toy examples.
|
|
20
17
|
|
|
21
18
|
- Release notes: `CHANGELOG.md`
|
|
22
19
|
- Historical beta contract snapshot: `docs/BETA-CONTRACT.md`
|
|
@@ -97,7 +94,6 @@ console.log(result.finalOutput);
|
|
|
97
94
|
- handoffs via `Agent({ handoffs: [...] })`
|
|
98
95
|
- `run(...)` with streaming support (`stream` defaults to `false`)
|
|
99
96
|
- policy helpers `allow(...)` / `deny(...)` / `requireApproval(...)`
|
|
100
|
-
- approval evidence helpers `createApprovalRequestSeed(...)`, `toApprovedProposalHashes(...)`, `toActiveApprovalGrantMap(...)`
|
|
101
97
|
- provider setup helpers `setupMistral(...)`, `setupOpenAI(...)`, `setupProvider(...)`
|
|
102
98
|
- run logger hook `run(..., { logger })`
|
|
103
99
|
- run record hook `run(..., { record })`
|
|
@@ -266,10 +262,11 @@ AIOC adopts the following non-negotiable principles:
|
|
|
266
262
|
- `docs/RFC-0003-run-record-audit-trail-and-persistence.md` (`Accepted`)
|
|
267
263
|
- `docs/RFC-0004-policy-outcomes-and-approval-model.md` (`Accepted`)
|
|
268
264
|
- `docs/RFC-0005-suspended-proposals-and-approval-lifecycle.md` (`Accepted`)
|
|
269
|
-
- `docs/RFC-0006-approval-evidence-helpers.md` (`
|
|
265
|
+
- `docs/RFC-0006-approval-evidence-helpers.md` (`Draft`)
|
|
270
266
|
- `docs/RFC-0007-thread-state-utilities.md` (`Accepted`)
|
|
271
267
|
- `docs/RFC-0008-run-stream-consumer-utilities.md` (`Accepted`)
|
|
272
|
-
- `docs/RFC-
|
|
268
|
+
- `docs/RFC-0009-governance-events-and-exporters.md` (`Experimental`)
|
|
269
|
+
- `docs/RFC-0011-agent-harness-descriptor.md` (`Experimental`)
|
|
273
270
|
- `docs/PRIVACY-BASELINE.md`
|
|
274
271
|
|
|
275
272
|
## Historical Snapshots
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Agent } from "./agent";
|
|
2
|
+
import type { Tool } from "./tool";
|
|
3
|
+
import type { NonStreamRunOptions } from "./types";
|
|
4
|
+
export interface HarnessDescriptorMetadata {
|
|
5
|
+
name?: string;
|
|
6
|
+
version?: string;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface HarnessRuntimeDescriptor {
|
|
10
|
+
entry_agent: string;
|
|
11
|
+
max_turns?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface HarnessToolDescriptor {
|
|
14
|
+
target: string;
|
|
15
|
+
}
|
|
16
|
+
export interface HarnessAgentDescriptor {
|
|
17
|
+
name?: string;
|
|
18
|
+
handoffDescription?: string;
|
|
19
|
+
instructions?: string;
|
|
20
|
+
model?: string;
|
|
21
|
+
modelSettings?: Record<string, unknown>;
|
|
22
|
+
tools?: string[];
|
|
23
|
+
handoffs?: string[];
|
|
24
|
+
}
|
|
25
|
+
export interface HarnessContextReferenceDescriptor {
|
|
26
|
+
type?: string;
|
|
27
|
+
optional?: boolean;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
}
|
|
30
|
+
export type HarnessContextReferenceEntry = boolean | HarnessContextReferenceDescriptor;
|
|
31
|
+
export interface HarnessContextFieldDescriptor {
|
|
32
|
+
type: string;
|
|
33
|
+
default?: unknown;
|
|
34
|
+
optional?: boolean;
|
|
35
|
+
mutable?: boolean;
|
|
36
|
+
redact?: boolean;
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
export interface HarnessContextDescriptor {
|
|
40
|
+
fields?: Record<string, HarnessContextFieldDescriptor>;
|
|
41
|
+
references?: Record<string, HarnessContextReferenceEntry>;
|
|
42
|
+
}
|
|
43
|
+
export interface AgentHarnessDescriptor {
|
|
44
|
+
descriptor_version?: string;
|
|
45
|
+
metadata?: HarnessDescriptorMetadata;
|
|
46
|
+
runtime: HarnessRuntimeDescriptor;
|
|
47
|
+
context?: HarnessContextDescriptor;
|
|
48
|
+
tools?: Record<string, HarnessToolDescriptor>;
|
|
49
|
+
agent_defaults?: Pick<HarnessAgentDescriptor, "model" | "modelSettings" | "instructions">;
|
|
50
|
+
agents: Record<string, HarnessAgentDescriptor>;
|
|
51
|
+
}
|
|
52
|
+
export interface AgentHarnessRegistry<TContext = unknown> {
|
|
53
|
+
tools?: Record<string, Tool<TContext>>;
|
|
54
|
+
registryVersion?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface CreateHarnessContextInput {
|
|
57
|
+
message?: string;
|
|
58
|
+
now?: string | Date;
|
|
59
|
+
overrides?: unknown;
|
|
60
|
+
}
|
|
61
|
+
export interface AgentHarnessMetadata {
|
|
62
|
+
name?: string;
|
|
63
|
+
version?: string;
|
|
64
|
+
descriptorVersion?: string;
|
|
65
|
+
descriptorHash: string;
|
|
66
|
+
registryVersion?: string;
|
|
67
|
+
}
|
|
68
|
+
export interface AgentHarness<TContext = unknown> {
|
|
69
|
+
entryAgent: Agent<TContext>;
|
|
70
|
+
agents: Map<string, Agent<TContext>>;
|
|
71
|
+
descriptorHash: string;
|
|
72
|
+
metadata: AgentHarnessMetadata;
|
|
73
|
+
runOptions: Omit<NonStreamRunOptions<TContext>, "stream">;
|
|
74
|
+
createContext(input?: CreateHarnessContextInput): TContext;
|
|
75
|
+
}
|
|
76
|
+
export declare function hashAgentHarnessDescriptor(descriptor: AgentHarnessDescriptor): string;
|
|
77
|
+
export declare function buildAgentHarness<TContext = unknown>(descriptor: AgentHarnessDescriptor, registry?: AgentHarnessRegistry<TContext>): AgentHarness<TContext>;
|
|
78
|
+
//# sourceMappingURL=harness-descriptor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness-descriptor.d.ts","sourceRoot":"","sources":["../src/harness-descriptor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hashAgentHarnessDescriptor = hashAgentHarnessDescriptor;
|
|
4
|
+
exports.buildAgentHarness = buildAgentHarness;
|
|
5
|
+
const agent_1 = require("./agent");
|
|
6
|
+
const canonical_json_1 = require("./canonical-json");
|
|
7
|
+
const CONTEXT_PROMPT_PLACEHOLDER = /\{\{\s*context\.([^}]+?)\s*\}\}/g;
|
|
8
|
+
function cloneJsonValue(value) {
|
|
9
|
+
if (typeof value === "undefined") {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
return JSON.parse(JSON.stringify(value));
|
|
13
|
+
}
|
|
14
|
+
function isPlainObject(value) {
|
|
15
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
function assertPlainObject(value, label) {
|
|
18
|
+
if (!isPlainObject(value)) {
|
|
19
|
+
throw new Error(`${label} must be an object.`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function assertOptionalPlainObject(value, label) {
|
|
23
|
+
if (typeof value === "undefined") {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
assertPlainObject(value, label);
|
|
27
|
+
}
|
|
28
|
+
function assertNonEmptyString(value, label) {
|
|
29
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
30
|
+
throw new Error(`${label} must be a non-empty string.`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function validateDescriptorShape(descriptor) {
|
|
34
|
+
assertPlainObject(descriptor, "Harness descriptor");
|
|
35
|
+
assertPlainObject(descriptor.runtime, "Harness descriptor runtime");
|
|
36
|
+
assertNonEmptyString(descriptor.runtime.entry_agent, "Harness descriptor runtime.entry_agent");
|
|
37
|
+
assertPlainObject(descriptor.agents, "Harness descriptor agents");
|
|
38
|
+
if (Object.keys(descriptor.agents).length === 0) {
|
|
39
|
+
throw new Error("Harness descriptor agents must not be empty.");
|
|
40
|
+
}
|
|
41
|
+
assertOptionalPlainObject(descriptor.metadata, "Harness descriptor metadata");
|
|
42
|
+
assertOptionalPlainObject(descriptor.tools, "Harness descriptor tools");
|
|
43
|
+
assertOptionalPlainObject(descriptor.agent_defaults, "Harness descriptor agent_defaults");
|
|
44
|
+
assertOptionalPlainObject(descriptor.context, "Harness descriptor context");
|
|
45
|
+
assertOptionalPlainObject(descriptor.context?.fields, "Harness descriptor context.fields");
|
|
46
|
+
assertOptionalPlainObject(descriptor.context?.references, "Harness descriptor context.references");
|
|
47
|
+
for (const [toolId, toolDescriptor] of Object.entries(descriptor.tools ?? {})) {
|
|
48
|
+
assertPlainObject(toolDescriptor, `Harness descriptor tool "${toolId}"`);
|
|
49
|
+
assertNonEmptyString(toolDescriptor.target, `Harness descriptor tool "${toolId}".target`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function normalizePromptPath(path, label) {
|
|
53
|
+
const segments = path
|
|
54
|
+
.split(".")
|
|
55
|
+
.map((segment) => segment.trim())
|
|
56
|
+
.filter((segment) => segment.length > 0);
|
|
57
|
+
if (segments.length === 0) {
|
|
58
|
+
throw new Error(`${label} cannot be empty.`);
|
|
59
|
+
}
|
|
60
|
+
for (const segment of segments) {
|
|
61
|
+
if (!/^[A-Za-z0-9_-]+$/.test(segment)) {
|
|
62
|
+
throw new Error(`${label} contains invalid segment "${segment}".`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return segments.join(".");
|
|
66
|
+
}
|
|
67
|
+
function createPromptReferenceRules(descriptor) {
|
|
68
|
+
const rules = new Map();
|
|
69
|
+
for (const [path, reference] of Object.entries(descriptor.context?.references ?? {})) {
|
|
70
|
+
if (reference === false) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const normalizedPath = normalizePromptPath(path, "Context reference path");
|
|
74
|
+
if (reference === true) {
|
|
75
|
+
rules.set(normalizedPath, { optional: false });
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (!isPlainObject(reference)) {
|
|
79
|
+
throw new Error(`Context reference "${path}" must be true, false, or an object.`);
|
|
80
|
+
}
|
|
81
|
+
rules.set(normalizedPath, { optional: Boolean(reference.optional) });
|
|
82
|
+
}
|
|
83
|
+
return rules;
|
|
84
|
+
}
|
|
85
|
+
function collectContextPromptPaths(template) {
|
|
86
|
+
const paths = new Set();
|
|
87
|
+
for (const match of template.matchAll(CONTEXT_PROMPT_PLACEHOLDER)) {
|
|
88
|
+
const rawPath = match[1];
|
|
89
|
+
if (!rawPath) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
paths.add(normalizePromptPath(rawPath, `Context prompt placeholder "${match[0]}"`));
|
|
93
|
+
}
|
|
94
|
+
return [...paths];
|
|
95
|
+
}
|
|
96
|
+
function readPromptPath(context, path) {
|
|
97
|
+
let cursor = context;
|
|
98
|
+
for (const segment of normalizePromptPath(path, "Context prompt path").split(".")) {
|
|
99
|
+
if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
|
|
100
|
+
return { exists: false, value: undefined };
|
|
101
|
+
}
|
|
102
|
+
cursor = cursor[segment];
|
|
103
|
+
}
|
|
104
|
+
return { exists: true, value: cursor };
|
|
105
|
+
}
|
|
106
|
+
function stringifyPromptValue(value) {
|
|
107
|
+
if (typeof value === "string") {
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
if (typeof value === "number" ||
|
|
111
|
+
typeof value === "boolean" ||
|
|
112
|
+
typeof value === "bigint") {
|
|
113
|
+
return String(value);
|
|
114
|
+
}
|
|
115
|
+
if (typeof value === "undefined") {
|
|
116
|
+
return "";
|
|
117
|
+
}
|
|
118
|
+
const json = JSON.stringify(value);
|
|
119
|
+
return typeof json === "undefined" ? String(value) : json;
|
|
120
|
+
}
|
|
121
|
+
function renderInstructionTemplate(template, context, promptAccessRules) {
|
|
122
|
+
return template.replace(CONTEXT_PROMPT_PLACEHOLDER, (placeholder, rawPath) => {
|
|
123
|
+
const path = normalizePromptPath(rawPath, `Context prompt placeholder "${placeholder}"`);
|
|
124
|
+
const rule = promptAccessRules.get(path);
|
|
125
|
+
if (!rule) {
|
|
126
|
+
throw new Error(`Harness descriptor instruction references undeclared context path "${path}".`);
|
|
127
|
+
}
|
|
128
|
+
const resolved = readPromptPath(context, path);
|
|
129
|
+
if (!resolved.exists || typeof resolved.value === "undefined") {
|
|
130
|
+
if (rule.optional) {
|
|
131
|
+
return "";
|
|
132
|
+
}
|
|
133
|
+
throw new Error(`Harness descriptor instruction could not resolve required context path "${path}".`);
|
|
134
|
+
}
|
|
135
|
+
return stringifyPromptValue(resolved.value);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function compileAgentInstructions(agentId, instructions, promptAccessRules) {
|
|
139
|
+
if (typeof instructions === "undefined") {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
const promptPaths = collectContextPromptPaths(instructions);
|
|
143
|
+
if (promptPaths.length === 0) {
|
|
144
|
+
return instructions;
|
|
145
|
+
}
|
|
146
|
+
for (const path of promptPaths) {
|
|
147
|
+
if (!promptAccessRules.has(path)) {
|
|
148
|
+
throw new Error(`Agent "${agentId}" instruction references undeclared context path "${path}".`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return (runContext) => renderInstructionTemplate(instructions, runContext.context, promptAccessRules);
|
|
152
|
+
}
|
|
153
|
+
function setPath(target, path, value) {
|
|
154
|
+
const segments = path.split(".").filter((segment) => segment.length > 0);
|
|
155
|
+
if (segments.length === 0) {
|
|
156
|
+
throw new Error("Context field path cannot be empty.");
|
|
157
|
+
}
|
|
158
|
+
let cursor = target;
|
|
159
|
+
for (const segment of segments.slice(0, -1)) {
|
|
160
|
+
const existing = cursor[segment];
|
|
161
|
+
if (typeof existing !== "object" || existing === null) {
|
|
162
|
+
const next = {};
|
|
163
|
+
cursor[segment] = next;
|
|
164
|
+
cursor = next;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
cursor = existing;
|
|
168
|
+
}
|
|
169
|
+
cursor[segments[segments.length - 1]] = value;
|
|
170
|
+
}
|
|
171
|
+
function mergeObject(target, source) {
|
|
172
|
+
for (const [key, value] of Object.entries(source)) {
|
|
173
|
+
const current = target[key];
|
|
174
|
+
if (current &&
|
|
175
|
+
typeof current === "object" &&
|
|
176
|
+
!Array.isArray(current) &&
|
|
177
|
+
value &&
|
|
178
|
+
typeof value === "object" &&
|
|
179
|
+
!Array.isArray(value)) {
|
|
180
|
+
mergeObject(current, value);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
target[key] = cloneJsonValue(value);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function resolveDefaultValue(value, input) {
|
|
187
|
+
if (value === "{{input.message}}") {
|
|
188
|
+
return input.message ?? "";
|
|
189
|
+
}
|
|
190
|
+
if (value === "{{runtime.now_iso}}") {
|
|
191
|
+
const now = input.now ?? new Date();
|
|
192
|
+
return now instanceof Date ? now.toISOString() : now;
|
|
193
|
+
}
|
|
194
|
+
return cloneJsonValue(value);
|
|
195
|
+
}
|
|
196
|
+
function createContextFromDescriptor(descriptor, input = {}) {
|
|
197
|
+
const context = {};
|
|
198
|
+
for (const [path, field] of Object.entries(descriptor.context?.fields ?? {})) {
|
|
199
|
+
if (!("default" in field)) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
setPath(context, path, resolveDefaultValue(field.default, input));
|
|
203
|
+
}
|
|
204
|
+
if (input.overrides &&
|
|
205
|
+
typeof input.overrides === "object" &&
|
|
206
|
+
!Array.isArray(input.overrides)) {
|
|
207
|
+
mergeObject(context, input.overrides);
|
|
208
|
+
}
|
|
209
|
+
return context;
|
|
210
|
+
}
|
|
211
|
+
function resolveTool(toolId, descriptor, registry) {
|
|
212
|
+
const toolDescriptor = descriptor.tools?.[toolId];
|
|
213
|
+
if (!toolDescriptor) {
|
|
214
|
+
throw new Error(`Harness descriptor references unknown tool "${toolId}".`);
|
|
215
|
+
}
|
|
216
|
+
const toolDefinition = registry.tools?.[toolDescriptor.target];
|
|
217
|
+
if (!toolDefinition) {
|
|
218
|
+
throw new Error(`Harness registry is missing target "${toolDescriptor.target}" for tool "${toolId}".`);
|
|
219
|
+
}
|
|
220
|
+
return toolDefinition;
|
|
221
|
+
}
|
|
222
|
+
function hashAgentHarnessDescriptor(descriptor) {
|
|
223
|
+
return `sha256:${(0, canonical_json_1.hashCanonicalJsonValue)(descriptor)}`;
|
|
224
|
+
}
|
|
225
|
+
function buildAgentHarness(descriptor, registry = {}) {
|
|
226
|
+
validateDescriptorShape(descriptor);
|
|
227
|
+
const descriptorHash = hashAgentHarnessDescriptor(descriptor);
|
|
228
|
+
const agents = new Map();
|
|
229
|
+
const promptAccessRules = createPromptReferenceRules(descriptor);
|
|
230
|
+
for (const [agentId, agentDescriptor] of Object.entries(descriptor.agents)) {
|
|
231
|
+
const instructions = agentDescriptor.instructions ?? descriptor.agent_defaults?.instructions;
|
|
232
|
+
const agent = new agent_1.Agent({
|
|
233
|
+
name: agentDescriptor.name ?? agentId,
|
|
234
|
+
handoffDescription: agentDescriptor.handoffDescription,
|
|
235
|
+
instructions: compileAgentInstructions(agentId, instructions, promptAccessRules),
|
|
236
|
+
model: agentDescriptor.model ?? descriptor.agent_defaults?.model,
|
|
237
|
+
modelSettings: agentDescriptor.modelSettings ??
|
|
238
|
+
descriptor.agent_defaults?.modelSettings,
|
|
239
|
+
tools: (agentDescriptor.tools ?? []).map((toolId) => resolveTool(toolId, descriptor, registry)),
|
|
240
|
+
handoffs: [],
|
|
241
|
+
});
|
|
242
|
+
agents.set(agentId, agent);
|
|
243
|
+
}
|
|
244
|
+
for (const [agentId, agentDescriptor] of Object.entries(descriptor.agents)) {
|
|
245
|
+
const agent = agents.get(agentId);
|
|
246
|
+
if (!agent) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
agent.handoffs = (agentDescriptor.handoffs ?? []).map((handoffId) => {
|
|
250
|
+
const handoffAgent = agents.get(handoffId);
|
|
251
|
+
if (!handoffAgent) {
|
|
252
|
+
throw new Error(`Harness descriptor references unknown handoff agent "${handoffId}" from "${agentId}".`);
|
|
253
|
+
}
|
|
254
|
+
return handoffAgent;
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
const entryAgent = agents.get(descriptor.runtime.entry_agent);
|
|
258
|
+
if (!entryAgent) {
|
|
259
|
+
throw new Error(`Harness descriptor entry_agent "${descriptor.runtime.entry_agent}" does not exist.`);
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
entryAgent,
|
|
263
|
+
agents,
|
|
264
|
+
descriptorHash,
|
|
265
|
+
metadata: {
|
|
266
|
+
name: descriptor.metadata?.name,
|
|
267
|
+
version: descriptor.metadata?.version,
|
|
268
|
+
descriptorVersion: descriptor.descriptor_version,
|
|
269
|
+
descriptorHash,
|
|
270
|
+
registryVersion: registry.registryVersion,
|
|
271
|
+
},
|
|
272
|
+
runOptions: {
|
|
273
|
+
maxTurns: descriptor.runtime.max_turns,
|
|
274
|
+
},
|
|
275
|
+
createContext: (input) => createContextFromDescriptor(descriptor, input),
|
|
276
|
+
};
|
|
277
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export * from "./agent";
|
|
2
|
-
export * from "./approval-helpers";
|
|
3
2
|
export * from "./config";
|
|
4
3
|
export * from "./errors";
|
|
5
4
|
export * from "./guardrails";
|
|
5
|
+
export * from "./harness-descriptor";
|
|
6
6
|
export * from "./json";
|
|
7
7
|
export * from "./logger";
|
|
8
8
|
export * from "./messages";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,OAAO,CAAC;AACtB,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -15,10 +15,10 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./agent"), exports);
|
|
18
|
-
__exportStar(require("./approval-helpers"), exports);
|
|
19
18
|
__exportStar(require("./config"), exports);
|
|
20
19
|
__exportStar(require("./errors"), exports);
|
|
21
20
|
__exportStar(require("./guardrails"), exports);
|
|
21
|
+
__exportStar(require("./harness-descriptor"), exports);
|
|
22
22
|
__exportStar(require("./json"), exports);
|
|
23
23
|
__exportStar(require("./logger"), exports);
|
|
24
24
|
__exportStar(require("./messages"), exports);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiastudio/aioc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-next.2",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"example:approval-required": "tsx src/examples/basic/approval-required.ts",
|
|
23
23
|
"example:approval-evidence": "tsx src/examples/basic/approval-evidence.ts",
|
|
24
24
|
"example:tool-policy": "tsx src/examples/basic/tools.ts",
|
|
25
|
+
"example:harness": "tsx src/examples/harness-descriptor/customer-support.ts",
|
|
25
26
|
"example:run-record": "tsx src/examples/basic/run-record-sink.ts",
|
|
26
27
|
"example:rru:01-extract": "tsx src/examples/run-record-utils-minimal/01-extract-tool-calls.ts",
|
|
27
28
|
"example:rru:02-compare": "tsx src/examples/run-record-utils-minimal/02-compare-run-records.ts",
|
|
@@ -69,11 +70,13 @@
|
|
|
69
70
|
},
|
|
70
71
|
"dependencies": {
|
|
71
72
|
"dotenv": "^17.2.3",
|
|
73
|
+
"js-yaml": "^4.1.1",
|
|
72
74
|
"zod": "^3.25.76",
|
|
73
75
|
"zod-to-json-schema": "^3.24.6"
|
|
74
76
|
},
|
|
75
77
|
"devDependencies": {
|
|
76
78
|
"@eslint/js": "^9.35.0",
|
|
79
|
+
"@types/js-yaml": "^4.0.9",
|
|
77
80
|
"@types/node": "^22.15.3",
|
|
78
81
|
"eslint": "^9.35.0",
|
|
79
82
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { SuspendedProposal } from "./run-record";
|
|
2
|
-
export interface ApprovalGrant {
|
|
3
|
-
proposalHash: string;
|
|
4
|
-
approvedAt: string;
|
|
5
|
-
expiresAt?: string;
|
|
6
|
-
revokedAt?: string;
|
|
7
|
-
metadata?: Record<string, unknown>;
|
|
8
|
-
}
|
|
9
|
-
export interface ApprovalRequestSeed {
|
|
10
|
-
proposalHash: string;
|
|
11
|
-
kind: "tool" | "handoff";
|
|
12
|
-
reason: string;
|
|
13
|
-
publicReason?: string;
|
|
14
|
-
policyVersion?: string;
|
|
15
|
-
expiresAt?: string;
|
|
16
|
-
resourceName: string;
|
|
17
|
-
canonicalPayloadJson: string;
|
|
18
|
-
}
|
|
19
|
-
export declare function createApprovalRequestSeed(proposal: SuspendedProposal): ApprovalRequestSeed;
|
|
20
|
-
export declare function isApprovalGrantActive(grant: ApprovalGrant, now?: string): boolean;
|
|
21
|
-
export declare function findActiveApprovalGrant(proposalHash: string, grants: readonly ApprovalGrant[], now?: string): ApprovalGrant | null;
|
|
22
|
-
export declare function toActiveApprovalGrantMap(grants: readonly ApprovalGrant[], now?: string): Record<string, ApprovalGrant>;
|
|
23
|
-
export declare function toApprovedProposalHashes(grants: readonly ApprovalGrant[], now?: string): string[];
|
|
24
|
-
//# sourceMappingURL=approval-helpers.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"approval-helpers.d.ts","sourceRoot":"","sources":["../src/approval-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAsCD,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,iBAAiB,GAC1B,mBAAmB,CAoBrB;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,aAAa,EACpB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAYT;AAED,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,SAAS,aAAa,EAAE,EAChC,GAAG,CAAC,EAAE,MAAM,GACX,aAAa,GAAG,IAAI,CAgBtB;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,SAAS,aAAa,EAAE,EAChC,GAAG,CAAC,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAe/B;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,SAAS,aAAa,EAAE,EAChC,GAAG,CAAC,EAAE,MAAM,GACX,MAAM,EAAE,CAEV"}
|
package/dist/approval-helpers.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createApprovalRequestSeed = createApprovalRequestSeed;
|
|
4
|
-
exports.isApprovalGrantActive = isApprovalGrantActive;
|
|
5
|
-
exports.findActiveApprovalGrant = findActiveApprovalGrant;
|
|
6
|
-
exports.toActiveApprovalGrantMap = toActiveApprovalGrantMap;
|
|
7
|
-
exports.toApprovedProposalHashes = toApprovedProposalHashes;
|
|
8
|
-
function parseTimestamp(value, label) {
|
|
9
|
-
const timestamp = Date.parse(value);
|
|
10
|
-
if (Number.isNaN(timestamp)) {
|
|
11
|
-
throw new Error(`${label} must be a valid timestamp.`);
|
|
12
|
-
}
|
|
13
|
-
return timestamp;
|
|
14
|
-
}
|
|
15
|
-
function getNowTimestamp(now) {
|
|
16
|
-
return parseTimestamp(now ?? new Date().toISOString(), "now");
|
|
17
|
-
}
|
|
18
|
-
function isGrantNewer(candidate, current) {
|
|
19
|
-
return (parseTimestamp(candidate.approvedAt, "grant.approvedAt") >
|
|
20
|
-
parseTimestamp(current.approvedAt, "grant.approvedAt"));
|
|
21
|
-
}
|
|
22
|
-
function getProposalMetadata(proposal) {
|
|
23
|
-
return {
|
|
24
|
-
...(typeof proposal.publicReason !== "undefined"
|
|
25
|
-
? { publicReason: proposal.publicReason }
|
|
26
|
-
: {}),
|
|
27
|
-
...(typeof proposal.policyVersion !== "undefined"
|
|
28
|
-
? { policyVersion: proposal.policyVersion }
|
|
29
|
-
: {}),
|
|
30
|
-
...(typeof proposal.expiresAt !== "undefined"
|
|
31
|
-
? { expiresAt: proposal.expiresAt }
|
|
32
|
-
: {}),
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
function createApprovalRequestSeed(proposal) {
|
|
36
|
-
if (proposal.kind === "tool") {
|
|
37
|
-
return {
|
|
38
|
-
proposalHash: proposal.proposalHash,
|
|
39
|
-
kind: proposal.kind,
|
|
40
|
-
reason: proposal.reason,
|
|
41
|
-
...getProposalMetadata(proposal),
|
|
42
|
-
resourceName: proposal.toolName,
|
|
43
|
-
canonicalPayloadJson: proposal.argsCanonicalJson,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
return {
|
|
47
|
-
proposalHash: proposal.proposalHash,
|
|
48
|
-
kind: proposal.kind,
|
|
49
|
-
reason: proposal.reason,
|
|
50
|
-
...getProposalMetadata(proposal),
|
|
51
|
-
resourceName: proposal.toAgentName,
|
|
52
|
-
canonicalPayloadJson: proposal.payloadCanonicalJson,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
function isApprovalGrantActive(grant, now) {
|
|
56
|
-
if (typeof grant.revokedAt !== "undefined") {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
if (typeof grant.expiresAt === "undefined") {
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
62
|
-
return (parseTimestamp(grant.expiresAt, "grant.expiresAt") >= getNowTimestamp(now));
|
|
63
|
-
}
|
|
64
|
-
function findActiveApprovalGrant(proposalHash, grants, now) {
|
|
65
|
-
let match = null;
|
|
66
|
-
for (const grant of grants) {
|
|
67
|
-
if (grant.proposalHash !== proposalHash) {
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
if (!isApprovalGrantActive(grant, now)) {
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
if (!match || isGrantNewer(grant, match)) {
|
|
74
|
-
match = grant;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return match;
|
|
78
|
-
}
|
|
79
|
-
function toActiveApprovalGrantMap(grants, now) {
|
|
80
|
-
const grantMap = {};
|
|
81
|
-
for (const grant of grants) {
|
|
82
|
-
if (!isApprovalGrantActive(grant, now)) {
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
const current = grantMap[grant.proposalHash];
|
|
86
|
-
if (!current || isGrantNewer(grant, current)) {
|
|
87
|
-
grantMap[grant.proposalHash] = grant;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return grantMap;
|
|
91
|
-
}
|
|
92
|
-
function toApprovedProposalHashes(grants, now) {
|
|
93
|
-
return Object.keys(toActiveApprovalGrantMap(grants, now));
|
|
94
|
-
}
|