agentfootprint 9.47.0 → 9.49.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/CLAUDE.md +12 -3
- package/dist/adapters/observability/githubBugReporter.js +16 -2
- package/dist/adapters/observability/githubBugReporter.js.map +1 -1
- package/dist/conventions.js +60 -1
- package/dist/conventions.js.map +1 -1
- package/dist/core/Agent.js +14 -1
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/agent/AgentBuilder.js +147 -3
- package/dist/core/agent/AgentBuilder.js.map +1 -1
- package/dist/core/agent/runManifest.js +7 -0
- package/dist/core/agent/runManifest.js.map +1 -1
- package/dist/core-flow/Conditional.js +6 -0
- package/dist/core-flow/Conditional.js.map +1 -1
- package/dist/core-flow/Graph.js +4 -0
- package/dist/core-flow/Graph.js.map +1 -1
- package/dist/core-flow/Parallel.js +6 -0
- package/dist/core-flow/Parallel.js.map +1 -1
- package/dist/doors/recipes.js +55 -0
- package/dist/doors/recipes.js.map +1 -0
- package/dist/esm/adapters/observability/githubBugReporter.js +16 -2
- package/dist/esm/adapters/observability/githubBugReporter.js.map +1 -1
- package/dist/esm/conventions.d.ts +45 -0
- package/dist/esm/conventions.js +57 -0
- package/dist/esm/conventions.js.map +1 -1
- package/dist/esm/core/Agent.d.ts +9 -1
- package/dist/esm/core/Agent.js +14 -1
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/agent/AgentBuilder.d.ts +68 -0
- package/dist/esm/core/agent/AgentBuilder.js +147 -3
- package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
- package/dist/esm/core/agent/runManifest.d.ts +18 -0
- package/dist/esm/core/agent/runManifest.js +7 -0
- package/dist/esm/core/agent/runManifest.js.map +1 -1
- package/dist/esm/core-flow/Conditional.js +6 -0
- package/dist/esm/core-flow/Conditional.js.map +1 -1
- package/dist/esm/core-flow/Graph.js +4 -0
- package/dist/esm/core-flow/Graph.js.map +1 -1
- package/dist/esm/core-flow/Parallel.js +6 -0
- package/dist/esm/core-flow/Parallel.js.map +1 -1
- package/dist/esm/doors/recipes.d.ts +38 -0
- package/dist/esm/doors/recipes.js +39 -0
- package/dist/esm/doors/recipes.js.map +1 -0
- package/dist/esm/events/payloads.d.ts +29 -0
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +8 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/bug-report/build.d.ts +18 -8
- package/dist/esm/lib/bug-report/build.js +111 -26
- package/dist/esm/lib/bug-report/build.js.map +1 -1
- package/dist/esm/lib/bug-report/envelope.d.ts +79 -0
- package/dist/esm/lib/bug-report/envelope.js +74 -0
- package/dist/esm/lib/bug-report/envelope.js.map +1 -0
- package/dist/esm/lib/bug-report/index.d.ts +1 -1
- package/dist/esm/lib/bug-report/types.d.ts +87 -3
- package/dist/esm/observe.d.ts +3 -1
- package/dist/esm/observe.js +18 -0
- package/dist/esm/observe.js.map +1 -1
- package/dist/esm/recipes/apply.d.ts +67 -0
- package/dist/esm/recipes/apply.js +117 -0
- package/dist/esm/recipes/apply.js.map +1 -0
- package/dist/esm/recipes/defineAgentRecipe.d.ts +68 -0
- package/dist/esm/recipes/defineAgentRecipe.js +112 -0
- package/dist/esm/recipes/defineAgentRecipe.js.map +1 -0
- package/dist/esm/recipes/identifier.d.ts +40 -0
- package/dist/esm/recipes/identifier.js +95 -0
- package/dist/esm/recipes/identifier.js.map +1 -0
- package/dist/esm/recipes/index.d.ts +19 -0
- package/dist/esm/recipes/index.js +19 -0
- package/dist/esm/recipes/index.js.map +1 -0
- package/dist/esm/recipes/provenance.d.ts +57 -0
- package/dist/esm/recipes/provenance.js +53 -0
- package/dist/esm/recipes/provenance.js.map +1 -0
- package/dist/esm/recipes/types.d.ts +134 -0
- package/dist/esm/recipes/types.js +63 -0
- package/dist/esm/recipes/types.js.map +1 -0
- package/dist/esm/recipes/version.d.ts +38 -0
- package/dist/esm/recipes/version.js +84 -0
- package/dist/esm/recipes/version.js.map +1 -0
- package/dist/esm/recorders/observability/fileRecordingSink.d.ts +104 -0
- package/dist/esm/recorders/observability/fileRecordingSink.js +195 -0
- package/dist/esm/recorders/observability/fileRecordingSink.js.map +1 -0
- package/dist/esm/recorders/observability/recordingEnvelope.d.ts +292 -0
- package/dist/esm/recorders/observability/recordingEnvelope.js +375 -0
- package/dist/esm/recorders/observability/recordingEnvelope.js.map +1 -0
- package/dist/index.js +12 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/bug-report/build.js +111 -26
- package/dist/lib/bug-report/build.js.map +1 -1
- package/dist/lib/bug-report/envelope.js +78 -0
- package/dist/lib/bug-report/envelope.js.map +1 -0
- package/dist/observe.js +29 -1
- package/dist/observe.js.map +1 -1
- package/dist/recipes/apply.js +125 -0
- package/dist/recipes/apply.js.map +1 -0
- package/dist/recipes/defineAgentRecipe.js +118 -0
- package/dist/recipes/defineAgentRecipe.js.map +1 -0
- package/dist/recipes/identifier.js +100 -0
- package/dist/recipes/identifier.js.map +1 -0
- package/dist/recipes/index.js +24 -0
- package/dist/recipes/index.js.map +1 -0
- package/dist/recipes/provenance.js +57 -0
- package/dist/recipes/provenance.js.map +1 -0
- package/dist/recipes/types.js +64 -0
- package/dist/recipes/types.js.map +1 -0
- package/dist/recipes/version.js +89 -0
- package/dist/recipes/version.js.map +1 -0
- package/dist/recorders/observability/fileRecordingSink.js +201 -0
- package/dist/recorders/observability/fileRecordingSink.js.map +1 -0
- package/dist/recorders/observability/recordingEnvelope.js +382 -0
- package/dist/recorders/observability/recordingEnvelope.js.map +1 -0
- package/dist/types/conventions.d.ts +45 -0
- package/dist/types/conventions.d.ts.map +1 -1
- package/dist/types/core/Agent.d.ts +9 -1
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/agent/AgentBuilder.d.ts +68 -0
- package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
- package/dist/types/core/agent/runManifest.d.ts +18 -0
- package/dist/types/core/agent/runManifest.d.ts.map +1 -1
- package/dist/types/core-flow/Conditional.d.ts.map +1 -1
- package/dist/types/core-flow/Graph.d.ts.map +1 -1
- package/dist/types/core-flow/Parallel.d.ts.map +1 -1
- package/dist/types/doors/recipes.d.ts +39 -0
- package/dist/types/doors/recipes.d.ts.map +1 -0
- package/dist/types/events/payloads.d.ts +29 -0
- package/dist/types/events/payloads.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/bug-report/build.d.ts +18 -8
- package/dist/types/lib/bug-report/build.d.ts.map +1 -1
- package/dist/types/lib/bug-report/envelope.d.ts +80 -0
- package/dist/types/lib/bug-report/envelope.d.ts.map +1 -0
- package/dist/types/lib/bug-report/index.d.ts +1 -1
- package/dist/types/lib/bug-report/index.d.ts.map +1 -1
- package/dist/types/lib/bug-report/types.d.ts +87 -3
- package/dist/types/lib/bug-report/types.d.ts.map +1 -1
- package/dist/types/observe.d.ts +3 -1
- package/dist/types/observe.d.ts.map +1 -1
- package/dist/types/recipes/apply.d.ts +68 -0
- package/dist/types/recipes/apply.d.ts.map +1 -0
- package/dist/types/recipes/defineAgentRecipe.d.ts +69 -0
- package/dist/types/recipes/defineAgentRecipe.d.ts.map +1 -0
- package/dist/types/recipes/identifier.d.ts +41 -0
- package/dist/types/recipes/identifier.d.ts.map +1 -0
- package/dist/types/recipes/index.d.ts +20 -0
- package/dist/types/recipes/index.d.ts.map +1 -0
- package/dist/types/recipes/provenance.d.ts +58 -0
- package/dist/types/recipes/provenance.d.ts.map +1 -0
- package/dist/types/recipes/types.d.ts +135 -0
- package/dist/types/recipes/types.d.ts.map +1 -0
- package/dist/types/recipes/version.d.ts +39 -0
- package/dist/types/recipes/version.d.ts.map +1 -0
- package/dist/types/recorders/observability/fileRecordingSink.d.ts +105 -0
- package/dist/types/recorders/observability/fileRecordingSink.d.ts.map +1 -0
- package/dist/types/recorders/observability/recordingEnvelope.d.ts +293 -0
- package/dist/types/recorders/observability/recordingEnvelope.d.ts.map +1 -0
- package/package.json +14 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stampConversation — put the bundle's evidence in the archive contract, or
|
|
3
|
+
* say in one sentence why it could not be.
|
|
4
|
+
*
|
|
5
|
+
* The repo has ONE archive contract (`RecordingEnvelope`) and several
|
|
6
|
+
* presentations over it. This bundle used to be an exception: it packed a bare
|
|
7
|
+
* `recording.json` plus an `environment.json` that repeated the producer facts
|
|
8
|
+
* the envelope already stamps, so the same two versions were written by two
|
|
9
|
+
* different pieces of code with no relationship between them. This is the fold
|
|
10
|
+
* — the envelope is BUILT here, never re-implemented, so every rule it enforces
|
|
11
|
+
* (identity is never invented, `droppedEvents` is proven or refused, an
|
|
12
|
+
* incomplete recording gets no `endedAt`) arrives whole.
|
|
13
|
+
*
|
|
14
|
+
* ## Why a refusal is a return value here, not a throw
|
|
15
|
+
*
|
|
16
|
+
* `persistRecording` throws when a run fact is indeterminate, and it is right
|
|
17
|
+
* to: its caller asked for an ARCHIVE, and an archive stamped with a guess is
|
|
18
|
+
* worse than none. A bug report's caller asked for something else — the
|
|
19
|
+
* evidence, in a zip, so a maintainer can open it. Throwing would leave that
|
|
20
|
+
* person with no bundle at all because the library could not name a start time.
|
|
21
|
+
*
|
|
22
|
+
* So the refusal travels instead of stopping the export: this returns the
|
|
23
|
+
* REASON, the bundle carries the bare recording under its own honest name, and
|
|
24
|
+
* the manifest states which fact was missing and the one line that supplies it.
|
|
25
|
+
* Nothing is stamped that was not known — which is the rule the throw exists to
|
|
26
|
+
* keep — and the report still gets filed.
|
|
27
|
+
*
|
|
28
|
+
* @internal Not a public export; `exportBugReport` is the door.
|
|
29
|
+
*/
|
|
30
|
+
import { buildRecordingEnvelope, IndeterminateRunFactError, } from '../../recorders/observability/recordingEnvelope.js';
|
|
31
|
+
/**
|
|
32
|
+
* Envelope every recording of one conversation.
|
|
33
|
+
*
|
|
34
|
+
* @param sources the conversation's recordings, in order, each with whether it
|
|
35
|
+
* can prove its own drop count.
|
|
36
|
+
* @param facts what the reporter stated. `undefined` — the default, since
|
|
37
|
+
* `run` is optional on both entry points — is itself a refusal
|
|
38
|
+
* reason, and a named one.
|
|
39
|
+
*/
|
|
40
|
+
export function stampConversation(sources, facts) {
|
|
41
|
+
if (facts === undefined || typeof facts.complete !== 'boolean') {
|
|
42
|
+
return {
|
|
43
|
+
field: 'complete',
|
|
44
|
+
refusal: 'run.complete was not stated. Nothing in a frozen recording says whether it captured ' +
|
|
45
|
+
'the run through to its end — a crash-handler snapshot and a finished run look ' +
|
|
46
|
+
'identical — so the library asks rather than guessing, and defaulting it would make ' +
|
|
47
|
+
'every partial recording claim to be whole. Pass run: { complete: true } (or false) ' +
|
|
48
|
+
'to exportBugReport.',
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const envelopes = [];
|
|
52
|
+
for (const entry of sources) {
|
|
53
|
+
try {
|
|
54
|
+
envelopes.push(buildRecordingEnvelope(entry.source, {
|
|
55
|
+
run: {
|
|
56
|
+
complete: facts.complete,
|
|
57
|
+
// A count the library can CHECK is never overridden by one it
|
|
58
|
+
// cannot: a live handle knows what the cap discarded, so a stated
|
|
59
|
+
// number is only offered for the sources that carry no count.
|
|
60
|
+
...(!entry.countsDrops &&
|
|
61
|
+
facts.droppedEvents !== undefined && { droppedEvents: facts.droppedEvents }),
|
|
62
|
+
},
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (error instanceof IndeterminateRunFactError) {
|
|
67
|
+
return { refusal: error.message, field: error.field };
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { envelopes };
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=envelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../../../src/lib/bug-report/envelope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EACL,sBAAsB,EACtB,yBAAyB,GAG1B,MAAM,oDAAoD,CAAC;AA2C5D;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAkC,EAClC,KAAoC;IAEpC,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC/D,OAAO;YACL,KAAK,EAAE,UAAU;YACjB,OAAO,EACL,sFAAsF;gBACtF,gFAAgF;gBAChF,qFAAqF;gBACrF,qFAAqF;gBACrF,qBAAqB;SACxB,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAwB,EAAE,CAAC;IAC1C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC;YACH,SAAS,CAAC,IAAI,CACZ,sBAAsB,CAAC,KAAK,CAAC,MAAM,EAAE;gBACnC,GAAG,EAAE;oBACH,QAAQ,EAAE,KAAK,CAAC,QAAQ;oBACxB,8DAA8D;oBAC9D,kEAAkE;oBAClE,8DAA8D;oBAC9D,GAAG,CAAC,CAAC,KAAK,CAAC,WAAW;wBACpB,KAAK,CAAC,aAAa,KAAK,SAAS,IAAI,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,EAAE,CAAC;iBAC/E;aACF,CAAC,CACH,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,yBAAyB,EAAE,CAAC;gBAC/C,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;YACxD,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,CAAC;AACvB,CAAC"}
|
|
@@ -10,5 +10,5 @@
|
|
|
10
10
|
* promise this library wants to keep.
|
|
11
11
|
*/
|
|
12
12
|
export { describeBugReport, exportBugReport } from './build.js';
|
|
13
|
-
export type { BugReport, BugReportEnvironment, BugReportExcluded, BugReportFields, BugReportFile, BugReportFileSummary, BugReportInput, BugReportManifest, BugReportOversize, BugReportSource, BugReportUnit, DescribeBugReportOptions, ExportBugReportOptions, } from './types.js';
|
|
13
|
+
export type { BugReport, BugReportEnvironment, BugReportExcluded, BugReportFields, BugReportFile, BugReportFileSummary, BugReportInput, BugReportManifest, BugReportOversize, BugReportRunFacts, BugReportSource, BugReportUnit, DescribeBugReportOptions, ExportBugReportOptions, } from './types.js';
|
|
14
14
|
export type { Transcript, TranscriptStep, TranscriptTurn } from './transcript.js';
|
|
@@ -70,6 +70,18 @@ export interface BugReportUnit {
|
|
|
70
70
|
readonly runCount?: number;
|
|
71
71
|
/** The hosting session id, when the runs carried one. */
|
|
72
72
|
readonly sessionId?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Conversation units only. `true` when this conversation's evidence rides as
|
|
75
|
+
* a `RecordingEnvelope` — the archive contract, with the recording under its
|
|
76
|
+
* `recording` field; `false` when the envelope's run facts were not available
|
|
77
|
+
* and it rides as the bare recording instead.
|
|
78
|
+
*
|
|
79
|
+
* Stated per unit rather than per bundle because a bundle can be mixed: a
|
|
80
|
+
* live `recordRun` handle proves its own dropped-event count and a bare
|
|
81
|
+
* `Recording` alongside it cannot, so one conversation can be stamped while
|
|
82
|
+
* its neighbour is not. The manifest's notes name the missing fact.
|
|
83
|
+
*/
|
|
84
|
+
readonly enveloped?: boolean;
|
|
73
85
|
/** Files this unit puts in the bundle (a `file` unit has exactly one). */
|
|
74
86
|
readonly files: readonly string[];
|
|
75
87
|
}
|
|
@@ -92,7 +104,15 @@ export interface BugReportExcluded {
|
|
|
92
104
|
/** The unit ids that were offered and not included. */
|
|
93
105
|
readonly unitIds: readonly string[];
|
|
94
106
|
}
|
|
95
|
-
/**
|
|
107
|
+
/**
|
|
108
|
+
* Versions, and deliberately nothing that identifies a machine or a person.
|
|
109
|
+
*
|
|
110
|
+
* This is the MANIFEST's summary block — the one a consent dialog and the issue
|
|
111
|
+
* body print. The bundled `environment.json` is deliberately NARROWER: since
|
|
112
|
+
* the bundle carries a {@link BugReportManifest.manifestVersion} 2 envelope,
|
|
113
|
+
* the producer versions are stamped there (`envelope.json` → `producer`) and
|
|
114
|
+
* the file keeps only the host half. One archive fact, one stamping place.
|
|
115
|
+
*/
|
|
96
116
|
export interface BugReportEnvironment {
|
|
97
117
|
/** This library's version, read from its own package manifest. */
|
|
98
118
|
readonly agentfootprint: string;
|
|
@@ -106,6 +126,45 @@ export interface BugReportEnvironment {
|
|
|
106
126
|
/** The reporting application's own version, when it told us. */
|
|
107
127
|
readonly appVersion?: string;
|
|
108
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* The run facts an archive envelope needs and a frozen recording cannot supply.
|
|
131
|
+
*
|
|
132
|
+
* The bundle's evidence rides as a `RecordingEnvelope` — the one archive
|
|
133
|
+
* contract this repo has — and that envelope refuses to stamp a fact it had to
|
|
134
|
+
* guess. Two of its fields have no derivation:
|
|
135
|
+
*
|
|
136
|
+
* `complete` nothing in a frozen recording says whether it reached the
|
|
137
|
+
* run's end; a crash-handler snapshot and a finished run
|
|
138
|
+
* look identical. Stated, or the envelope is not built.
|
|
139
|
+
* `droppedEvents` only the live `recordRun` handle counts what the
|
|
140
|
+
* `maxEvents` cap discarded.
|
|
141
|
+
*
|
|
142
|
+
* Everything else the envelope needs — `runId`, `sessionId`, `principal`,
|
|
143
|
+
* `tenant`, `startedAt`, `endedAt` — is derived per recording from that
|
|
144
|
+
* recording's OWN events, and is deliberately not settable here: a bundle may
|
|
145
|
+
* carry several runs, and one run id stated once cannot be true of all of them.
|
|
146
|
+
*/
|
|
147
|
+
export interface BugReportRunFacts {
|
|
148
|
+
/**
|
|
149
|
+
* Did each recording in this bundle capture its run through to the end?
|
|
150
|
+
*
|
|
151
|
+
* Say `false` for a recording frozen from a crash handler, a timeout or
|
|
152
|
+
* mid-stream. Leave the whole `run` option off and the bundle still carries
|
|
153
|
+
* the evidence — as the bare recording, with the manifest stating in a note
|
|
154
|
+
* which fact was missing and how to supply it.
|
|
155
|
+
*/
|
|
156
|
+
readonly complete: boolean;
|
|
157
|
+
/**
|
|
158
|
+
* Events lost to the recorder's `maxEvents` cap, for sources that cannot
|
|
159
|
+
* prove their own count.
|
|
160
|
+
*
|
|
161
|
+
* A live `recordRun` handle counts them, and that PROVEN count wins over
|
|
162
|
+
* anything stated here — a number the library can check is never overridden
|
|
163
|
+
* by a number it cannot. State this when the bundle is built from bare
|
|
164
|
+
* `Recording` objects, whose shape carries no count.
|
|
165
|
+
*/
|
|
166
|
+
readonly droppedEvents?: number;
|
|
167
|
+
}
|
|
109
168
|
/** The oversize verdict, with hints that name real, droppable unit ids. */
|
|
110
169
|
export interface BugReportOversize {
|
|
111
170
|
readonly totalBytes: number;
|
|
@@ -122,7 +181,19 @@ export interface BugReportOversize {
|
|
|
122
181
|
* the reporter's fields, and states the exclusions.
|
|
123
182
|
*/
|
|
124
183
|
export interface BugReportManifest {
|
|
125
|
-
|
|
184
|
+
/**
|
|
185
|
+
* The BUNDLE LAYOUT version — bumped when the file set or this manifest's own
|
|
186
|
+
* shape changes, so a reader can tell which archive it is holding instead of
|
|
187
|
+
* inferring it from which names happen to be present.
|
|
188
|
+
*
|
|
189
|
+
* 1 — the evidence rode as a bare `recording.json`, and `environment.json`
|
|
190
|
+
* repeated the producer versions the archive contract stamps.
|
|
191
|
+
* 2 — the evidence rides as `envelope.json`, a full `RecordingEnvelope`;
|
|
192
|
+
* `environment.json` keeps only the host facts the envelope does not
|
|
193
|
+
* hold. A conversation whose run facts could not be stamped falls back
|
|
194
|
+
* to `recording.json` and the manifest says which fact was missing.
|
|
195
|
+
*/
|
|
196
|
+
readonly manifestVersion: 2;
|
|
126
197
|
/** ISO 8601, UTC. Also the timestamp stamped on every zip entry. */
|
|
127
198
|
readonly createdAt: string;
|
|
128
199
|
/** Present on an export manifest; absent on a description. */
|
|
@@ -198,9 +269,22 @@ export interface ExportBugReportOptions extends BugReportFields {
|
|
|
198
269
|
readonly warnOverBytes?: number;
|
|
199
270
|
/** Override the timestamp — the only thing that makes the zip deterministic. */
|
|
200
271
|
readonly now?: Date;
|
|
272
|
+
/**
|
|
273
|
+
* The two run facts the archive envelope cannot derive. Supply them and the
|
|
274
|
+
* bundle's evidence rides as `envelope.json`; leave them off and it rides as
|
|
275
|
+
* the bare `recording.json`, with the manifest naming the missing fact.
|
|
276
|
+
*/
|
|
277
|
+
readonly run?: BugReportRunFacts;
|
|
201
278
|
}
|
|
202
|
-
/** `describeBugReport` takes nothing but the input, and the same
|
|
279
|
+
/** `describeBugReport` takes nothing but the input, and the same dials. */
|
|
203
280
|
export interface DescribeBugReportOptions {
|
|
204
281
|
readonly warnOverBytes?: number;
|
|
205
282
|
readonly now?: Date;
|
|
283
|
+
/**
|
|
284
|
+
* The same run facts {@link ExportBugReportOptions.run} takes — pass the same
|
|
285
|
+
* value to both calls. The offer measures the files the export will write, so
|
|
286
|
+
* stating the facts to one call and not the other would size the bundle from
|
|
287
|
+
* a different set of files than the one that leaves.
|
|
288
|
+
*/
|
|
289
|
+
readonly run?: BugReportRunFacts;
|
|
206
290
|
}
|
package/dist/esm/observe.d.ts
CHANGED
|
@@ -39,7 +39,9 @@ export { boundaryRecorder, BoundaryRecorder, type ActorArrow, type BoundaryAggre
|
|
|
39
39
|
export { buildRunSteps, RunStepRecorder, runStepRecorder, type BuildRunStepsOptions, type RunStep, type RunStepGraph, type RunStepKind, type RunStepMeta, type RunStepRecorderOptions, type RunStepTransition, } from './recorders/observability/RunStepRecorder.js';
|
|
40
40
|
export { attachFlowchart, buildStepGraph, buildStepGraphFromEvents, type StepGraph, type StepNode, type StepEdge, type SlotBoundary, type ContextInjection, type FlowchartOptions, type FlowchartHandle, } from './recorders/observability/FlowchartRecorder.js';
|
|
41
41
|
export { recordRun, type Recording, type RecordRunOptions, type RunRecorder, } from './recorders/observability/recordRun.js';
|
|
42
|
-
export {
|
|
42
|
+
export { buildRecordingEnvelope, persistRecording, FULL_PRIVACY_POLICY_ID, RECORDING_ENVELOPE_FORMAT, IndeterminateRunFactError, UnsupportedPrivacyModeError, type BuildRecordingEnvelopeOptions, type PersistRecordingOptions, type RecordingConfiguration, type RecordingEnvelope, type RecordingPrivacy, type RecordingPrivacyMode, type RecordingProducer, type RecordingRun, type RecordingRunFacts, type RecordingSink, type RecordingSource, type RecordingTimestamp, } from './recorders/observability/recordingEnvelope.js';
|
|
43
|
+
export { fileRecordingSink, recordingFileName, UnsafeRecordingIdError, type FileRecordingSinkOptions, } from './recorders/observability/fileRecordingSink.js';
|
|
44
|
+
export { describeBugReport, exportBugReport, type BugReport, type BugReportEnvironment, type BugReportExcluded, type BugReportFields, type BugReportFile, type BugReportFileSummary, type BugReportInput, type BugReportManifest, type BugReportOversize, type BugReportRunFacts, type BugReportSource, type BugReportUnit, type DescribeBugReportOptions, type ExportBugReportOptions, type Transcript, type TranscriptStep, type TranscriptTurn, } from './lib/bug-report/index.js';
|
|
43
45
|
export { summarizeEmbeddings, summarizeVector, type EmbeddingSummary, } from './recorders/observability/embeddingSummary.js';
|
|
44
46
|
export { serializeTrace, redactContent, traceToStepGraph, type Trace, type TraceSummary, type TraceRedaction, type SerializeTraceOptions, } from './recorders/observability/trace.js';
|
|
45
47
|
export { attachLocalObservability, type LocalObservabilityHandle, type LocalObservabilityOptions, } from './recorders/observability/localObservability.js';
|
package/dist/esm/observe.js
CHANGED
|
@@ -45,11 +45,29 @@ export { attachFlowchart, buildStepGraph, buildStepGraphFromEvents, } from './re
|
|
|
45
45
|
// which is the shape the UIs consume (lens's `observeRecording`) and the one
|
|
46
46
|
// every integration used to assemble by hand, each missing a different piece.
|
|
47
47
|
export { recordRun, } from './recorders/observability/recordRun.js';
|
|
48
|
+
// The recording ENVELOPE — the versioned contract that makes a recording
|
|
49
|
+
// archivable. `recordRun` freezes a run; this states which run it is, how much
|
|
50
|
+
// of it this is, who produced it and under what privacy policy, so a saved run
|
|
51
|
+
// can be filed, attached to a bug report, or read by an analysis tool without
|
|
52
|
+
// every consumer inventing its own wrapper. Every field is derived from the
|
|
53
|
+
// run's own events or stated by the caller — never guessed.
|
|
54
|
+
export { buildRecordingEnvelope, persistRecording, FULL_PRIVACY_POLICY_ID, RECORDING_ENVELOPE_FORMAT, IndeterminateRunFactError, UnsupportedPrivacyModeError, } from './recorders/observability/recordingEnvelope.js';
|
|
55
|
+
// The reference sink: one archived run per JSON file, written atomically
|
|
56
|
+
// (tmp + rename) so a crash never leaves a half-parsed archive. The file name
|
|
57
|
+
// derives from the run id, which makes it a key — hence the asserted safe
|
|
58
|
+
// charset and `UnsafeRecordingIdError` rather than a hopeful `${runId}.json`.
|
|
59
|
+
export { fileRecordingSink, recordingFileName, UnsafeRecordingIdError, } from './recorders/observability/fileRecordingSink.js';
|
|
48
60
|
// exportBugReport — a bug report IS the evidence. `describeBugReport` measures
|
|
49
61
|
// the run first (selectable units, sizes, the redacted keys by NAME) so a human
|
|
50
62
|
// can consent to exactly what leaves; `exportBugReport` bundles the units they
|
|
51
63
|
// kept as named files plus a real (stored) zip. `githubBugReporter` — in this
|
|
52
64
|
// same door, from the providers barrel — files that bundle.
|
|
65
|
+
//
|
|
66
|
+
// The evidence in that zip is a RecordingEnvelope (bundle layout 2), built by
|
|
67
|
+
// the same `buildRecordingEnvelope` above rather than a second wrapper — so the
|
|
68
|
+
// producer facts are stamped once. `BugReportRunFacts` is the pair the envelope
|
|
69
|
+
// cannot derive; without it the bundle still ships, as the bare recording, with
|
|
70
|
+
// the manifest naming the fact that was missing.
|
|
53
71
|
export { describeBugReport, exportBugReport, } from './lib/bug-report/index.js';
|
|
54
72
|
// What a recording keeps of a vector: `{ dims, norm }`, not the bytes (8.20.0).
|
|
55
73
|
// Applied by BoundaryRecorder and recordRun unless `recordEmbeddings: true`;
|
package/dist/esm/observe.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observe.js","sourceRoot":"","sources":["../../src/observe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,4BAA4B;AAC5B,OAAO,EAAE,eAAe,EAA+B,MAAM,qCAAqC,CAAC;AACnG,OAAO,EAAE,cAAc,EAA8B,MAAM,oCAAoC,CAAC;AAEhG,+BAA+B;AAC/B,OAAO,EACL,mBAAmB,GAEpB,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,aAAa,EAA6B,MAAM,mCAAmC,CAAC;AAC7F,OAAO,EACL,gBAAgB,EAChB,gBAAgB,GAkBjB,MAAM,+CAA+C,CAAC;AACvD,OAAO,EACL,aAAa,EACb,eAAe,EACf,eAAe,GAQhB,MAAM,8CAA8C,CAAC;AACtD,OAAO,EACL,eAAe,EACf,cAAc,EACd,wBAAwB,GAQzB,MAAM,gDAAgD,CAAC;AAExD,yEAAyE;AACzE,6EAA6E;AAC7E,6EAA6E;AAC7E,8EAA8E;AAC9E,OAAO,EACL,SAAS,GAIV,MAAM,wCAAwC,CAAC;AAEhD,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAC/E,8EAA8E;AAC9E,4DAA4D;AAC5D,OAAO,EACL,iBAAiB,EACjB,eAAe,
|
|
1
|
+
{"version":3,"file":"observe.js","sourceRoot":"","sources":["../../src/observe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* apply — the pure half of applying a recipe.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: policy resolution + the sentences the applier raises. Pure, so
|
|
5
|
+
* every refusal can be read and tested without building an agent.
|
|
6
|
+
* Role: recipes/ layer. `AgentBuilder.recipe()` is the only caller; it owns
|
|
7
|
+
* the mutation, this file owns the words.
|
|
8
|
+
* Emits: N/A.
|
|
9
|
+
*/
|
|
10
|
+
import { type RecipeSource } from './provenance.js';
|
|
11
|
+
import type { AppliedRecipe, RecipeConflictPolicy } from './types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the conflict policy, or refuse the requested one BY NAME.
|
|
14
|
+
*
|
|
15
|
+
* The three a reader reaches for are named in the refusal because each is a
|
|
16
|
+
* real design and none is implemented: every one of them has to answer where
|
|
17
|
+
* the dropped registration is RECORDED, and until it does, running it as
|
|
18
|
+
* `'error'`'s quiet cousin would be the accepted-and-silently-wrong shape this
|
|
19
|
+
* library refuses. So the unimplemented policy is refused rather than
|
|
20
|
+
* approximated by the one that ships.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveRecipeConflictPolicy(value: unknown, callSite: string): RecipeConflictPolicy;
|
|
23
|
+
/**
|
|
24
|
+
* The refusal for a name two sources both registered.
|
|
25
|
+
*
|
|
26
|
+
* Raised only when at least one side came from a recipe. A collision between
|
|
27
|
+
* two direct builder calls keeps the sentence it has always had — the message
|
|
28
|
+
* an app already reads in its tests should not change because a feature it does
|
|
29
|
+
* not use shipped.
|
|
30
|
+
*
|
|
31
|
+
* `what` is the word the reader uses (`'tool name'` / `'injection id'`);
|
|
32
|
+
* `existing` is who registered it first and `incoming` who is registering it
|
|
33
|
+
* now, either of which may be `undefined` for the unattributed case;
|
|
34
|
+
* `callSite` is the API being called, e.g. `Agent.tool()`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function duplicateRegistrationRefusal(params: {
|
|
37
|
+
readonly what: 'tool name' | 'injection id';
|
|
38
|
+
readonly name: string;
|
|
39
|
+
readonly existing: RecipeSource | undefined;
|
|
40
|
+
readonly incoming: RecipeSource | undefined;
|
|
41
|
+
readonly callSite: string;
|
|
42
|
+
}): string;
|
|
43
|
+
/** The refusal for one composition applied twice to one agent. */
|
|
44
|
+
export declare function duplicateRecipeRefusal(params: {
|
|
45
|
+
readonly existing: AppliedRecipe;
|
|
46
|
+
readonly incoming: AppliedRecipe;
|
|
47
|
+
readonly callSite: string;
|
|
48
|
+
}): string;
|
|
49
|
+
/**
|
|
50
|
+
* The refusal for a recipe that applies ITSELF, directly or through another.
|
|
51
|
+
*
|
|
52
|
+
* A SEPARATE sentence from {@link duplicateRecipeRefusal}, because these are two
|
|
53
|
+
* different facts and the fix is different for each: "already applied" means the
|
|
54
|
+
* chain names one composition twice, and "currently applying" means the
|
|
55
|
+
* composition is its own ancestor and would never terminate. Telling the author
|
|
56
|
+
* their recursion is a duplicate would send them to look at the wrong line.
|
|
57
|
+
*
|
|
58
|
+
* `stack` is the application chain, outermost first, so the message can show the
|
|
59
|
+
* cycle rather than assert one.
|
|
60
|
+
*/
|
|
61
|
+
export declare function recursiveRecipeRefusal(params: {
|
|
62
|
+
readonly stack: readonly AppliedRecipe[];
|
|
63
|
+
readonly incoming: AppliedRecipe;
|
|
64
|
+
readonly callSite: string;
|
|
65
|
+
}): string;
|
|
66
|
+
/** The refusal for `configure` returning a promise. */
|
|
67
|
+
export declare function asyncConfigureRefusal(recipe: AppliedRecipe, callSite: string): string;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* apply — the pure half of applying a recipe.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: policy resolution + the sentences the applier raises. Pure, so
|
|
5
|
+
* every refusal can be read and tested without building an agent.
|
|
6
|
+
* Role: recipes/ layer. `AgentBuilder.recipe()` is the only caller; it owns
|
|
7
|
+
* the mutation, this file owns the words.
|
|
8
|
+
* Emits: N/A.
|
|
9
|
+
*/
|
|
10
|
+
import { describeRecipeSource } from './provenance.js';
|
|
11
|
+
/** The policies that exist. One, today — see {@link RecipeConflictPolicy}. */
|
|
12
|
+
const CONFLICT_POLICIES = ['error'];
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the conflict policy, or refuse the requested one BY NAME.
|
|
15
|
+
*
|
|
16
|
+
* The three a reader reaches for are named in the refusal because each is a
|
|
17
|
+
* real design and none is implemented: every one of them has to answer where
|
|
18
|
+
* the dropped registration is RECORDED, and until it does, running it as
|
|
19
|
+
* `'error'`'s quiet cousin would be the accepted-and-silently-wrong shape this
|
|
20
|
+
* library refuses. So the unimplemented policy is refused rather than
|
|
21
|
+
* approximated by the one that ships.
|
|
22
|
+
*/
|
|
23
|
+
export function resolveRecipeConflictPolicy(value, callSite) {
|
|
24
|
+
if (value === undefined)
|
|
25
|
+
return 'error';
|
|
26
|
+
if (CONFLICT_POLICIES.includes(value)) {
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
throw new Error(`${callSite}: conflict policy ${typeof value === 'string' ? `'${value}'` : String(value)} is not implemented. The only policy this library has is 'error' (the default): a tool ` +
|
|
30
|
+
`name or injection id a recipe introduces that is already taken refuses at build, naming ` +
|
|
31
|
+
`both sources.\n\n` +
|
|
32
|
+
`'skip', 'replace' and automatic renaming are each a real design, and each one has to ` +
|
|
33
|
+
`answer the same question first — where the dropped or overridden registration is ` +
|
|
34
|
+
`RECORDED. A composition that silently loses a tool answers a turn without it and says ` +
|
|
35
|
+
`nothing, which is the failure this option exists to prevent, so an unimplemented policy ` +
|
|
36
|
+
`is refused here rather than quietly run as 'error'.\n\n` +
|
|
37
|
+
`To compose recipes that overlap today: rename one of the colliding registrations, or ` +
|
|
38
|
+
`have the recipe export the piece so the app registers it once itself.`);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The refusal for a name two sources both registered.
|
|
42
|
+
*
|
|
43
|
+
* Raised only when at least one side came from a recipe. A collision between
|
|
44
|
+
* two direct builder calls keeps the sentence it has always had — the message
|
|
45
|
+
* an app already reads in its tests should not change because a feature it does
|
|
46
|
+
* not use shipped.
|
|
47
|
+
*
|
|
48
|
+
* `what` is the word the reader uses (`'tool name'` / `'injection id'`);
|
|
49
|
+
* `existing` is who registered it first and `incoming` who is registering it
|
|
50
|
+
* now, either of which may be `undefined` for the unattributed case;
|
|
51
|
+
* `callSite` is the API being called, e.g. `Agent.tool()`.
|
|
52
|
+
*/
|
|
53
|
+
export function duplicateRegistrationRefusal(params) {
|
|
54
|
+
const { what, name, existing, incoming, callSite } = params;
|
|
55
|
+
const consequence = what === 'tool name'
|
|
56
|
+
? `The model dispatches tools BY NAME, so two tools under one name is a coin flip whose ` +
|
|
57
|
+
`loser is never called and never mentioned.`
|
|
58
|
+
: `An injection id is how the engine addresses one piece of context — activation, ` +
|
|
59
|
+
`caching, the trace and every recorder key on it — so two under one name is one of ` +
|
|
60
|
+
`them silently never reaching the model.`;
|
|
61
|
+
return (`${callSite}: duplicate ${what} '${name}' — already registered by ` +
|
|
62
|
+
`${describeRecipeSource(existing)}, and now by ${describeRecipeSource(incoming)}.\n\n` +
|
|
63
|
+
`${consequence}\n\n` +
|
|
64
|
+
`Pick one: rename one of them, or have the recipe export the piece so the app registers it ` +
|
|
65
|
+
`once itself. There is no conflict policy that picks a winner for you — see ` +
|
|
66
|
+
`.recipe(recipe, { conflict }).`);
|
|
67
|
+
}
|
|
68
|
+
/** The refusal for one composition applied twice to one agent. */
|
|
69
|
+
export function duplicateRecipeRefusal(params) {
|
|
70
|
+
const { existing, incoming, callSite } = params;
|
|
71
|
+
const sameVersion = existing.version === incoming.version;
|
|
72
|
+
return (`${callSite}: recipe '${incoming.id}' is already applied to this agent ` +
|
|
73
|
+
`${sameVersion
|
|
74
|
+
? `(both at ${existing.version})`
|
|
75
|
+
: `at ${existing.version}, and this one is ${incoming.version}`}.\n\n` +
|
|
76
|
+
`${sameVersion
|
|
77
|
+
? `Applying it twice would run its builder calls twice — which for anything that ` +
|
|
78
|
+
`refuses a duplicate (a tool, an injection) fails on the second pass, and for ` +
|
|
79
|
+
`anything that does not would silently double it.`
|
|
80
|
+
: `One agent runs ONE version of a composition. Two would each apply their builder ` +
|
|
81
|
+
`calls over the other, and the manifest would carry two rows for a composition that ` +
|
|
82
|
+
`cannot be two things at once — nothing downstream could say which one shaped the ` +
|
|
83
|
+
`answer.`}\n\n` +
|
|
84
|
+
`Apply it once${sameVersion ? '' : `, at the version you mean`}.`);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The refusal for a recipe that applies ITSELF, directly or through another.
|
|
88
|
+
*
|
|
89
|
+
* A SEPARATE sentence from {@link duplicateRecipeRefusal}, because these are two
|
|
90
|
+
* different facts and the fix is different for each: "already applied" means the
|
|
91
|
+
* chain names one composition twice, and "currently applying" means the
|
|
92
|
+
* composition is its own ancestor and would never terminate. Telling the author
|
|
93
|
+
* their recursion is a duplicate would send them to look at the wrong line.
|
|
94
|
+
*
|
|
95
|
+
* `stack` is the application chain, outermost first, so the message can show the
|
|
96
|
+
* cycle rather than assert one.
|
|
97
|
+
*/
|
|
98
|
+
export function recursiveRecipeRefusal(params) {
|
|
99
|
+
const { stack, incoming, callSite } = params;
|
|
100
|
+
const cycle = [...stack, incoming].map((r) => `'${r.id}' ${r.version}`).join(' → ');
|
|
101
|
+
return (`${callSite}: recipe '${incoming.id}' ${incoming.version} is applying itself — ${cycle}.\n\n` +
|
|
102
|
+
`\`configure\` runs immediately, so this would recurse until the stack ran out rather than ` +
|
|
103
|
+
`converge on a configured agent.\n\n` +
|
|
104
|
+
`Pull the shared part into a THIRD recipe and have both apply that one, or drop the ` +
|
|
105
|
+
`self-application.`);
|
|
106
|
+
}
|
|
107
|
+
/** The refusal for `configure` returning a promise. */
|
|
108
|
+
export function asyncConfigureRefusal(recipe, callSite) {
|
|
109
|
+
return (`${callSite}: recipe '${recipe.id}' ${recipe.version} returned a promise from \`configure\`. ` +
|
|
110
|
+
`A recipe composes CONFIGURATION only, and \`build()\` is synchronous — there is no phase ` +
|
|
111
|
+
`in which this library would await it, so the work would run after the agent was already ` +
|
|
112
|
+
`built and land on nothing.\n\n` +
|
|
113
|
+
`Do the async part before you build (\`const tools = await mcpClient(…).tools()\`), and ` +
|
|
114
|
+
`have the recipe take what it needs: a recipe can be a plain function of its inputs that ` +
|
|
115
|
+
`RETURNS a recipe (\`export const crm = (tools) => defineAgentRecipe({ … }))\`.`);
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=apply.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply.js","sourceRoot":"","sources":["../../../src/recipes/apply.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* defineAgentRecipe — declare a named, versioned composition.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: a validating factory that freezes. No class, no registry, no
|
|
5
|
+
* instance state — the "recipes over primitives" instruction taken
|
|
6
|
+
* literally.
|
|
7
|
+
* Role: recipes/ layer, pure. The validation it runs is the SAME function
|
|
8
|
+
* `AgentBuilder.recipe()` runs, so a hand-written literal cannot get
|
|
9
|
+
* past the checks the factory makes; the factory only moves the
|
|
10
|
+
* refusal to the declaration, which is where the fix is.
|
|
11
|
+
* Emits: N/A.
|
|
12
|
+
*
|
|
13
|
+
* ## Why it freezes
|
|
14
|
+
*
|
|
15
|
+
* A recipe is handed to `.recipe()` on one agent and, typically, to `.recipe()`
|
|
16
|
+
* on several more. A mutable one is a shared object that a single consumer can
|
|
17
|
+
* edit for everybody — and the edit would be invisible on the record, because
|
|
18
|
+
* the manifest reports the id and the version, both of which would still say
|
|
19
|
+
* what they always said. `Object.freeze` is shallow, which is exactly the
|
|
20
|
+
* depth that matters here: the four fields are three strings and a function.
|
|
21
|
+
*/
|
|
22
|
+
import type { AgentRecipe } from './types.js';
|
|
23
|
+
/**
|
|
24
|
+
* A recipe declaration that cannot be honoured. Thrown by
|
|
25
|
+
* {@link defineAgentRecipe} and by `AgentBuilder.recipe()` — the same class
|
|
26
|
+
* from both doors, because it is the same mistake wherever it is caught.
|
|
27
|
+
*/
|
|
28
|
+
export declare class InvalidAgentRecipeError extends Error {
|
|
29
|
+
readonly code: "ERR_INVALID_AGENT_RECIPE";
|
|
30
|
+
/** Which field was refused (`'id'`, `'version'`, `'configure'`, `'shape'`). */
|
|
31
|
+
readonly field: string;
|
|
32
|
+
constructor(field: string, message: string);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Validate a recipe declaration, or refuse it by name.
|
|
36
|
+
*
|
|
37
|
+
* Total over `unknown`: this is the one gate, and it is called from the
|
|
38
|
+
* factory AND from `.recipe()`, so no recipe reaches an agent unvalidated.
|
|
39
|
+
*
|
|
40
|
+
* @param value - the candidate declaration.
|
|
41
|
+
* @param callSite - the API the author called, named in every refusal.
|
|
42
|
+
*/
|
|
43
|
+
export declare function assertAgentRecipe(value: unknown, callSite: string): asserts value is AgentRecipe;
|
|
44
|
+
/**
|
|
45
|
+
* Declare a recipe: a name, a version, and the builder calls it stands for.
|
|
46
|
+
*
|
|
47
|
+
* Validates every field and returns a frozen object. Refusals name the field
|
|
48
|
+
* and the fix — `defineAgentRecipe` is where a bad id or version costs one
|
|
49
|
+
* line, and `.recipe()` is where the same mistake costs a stack trace through
|
|
50
|
+
* somebody else's app.
|
|
51
|
+
*
|
|
52
|
+
* @example the composition an app imports and applies
|
|
53
|
+
* ```ts
|
|
54
|
+
* import { defineAgentRecipe } from 'agentfootprint/recipes';
|
|
55
|
+
*
|
|
56
|
+
* export const supportDesk = defineAgentRecipe({
|
|
57
|
+
* id: 'support-desk',
|
|
58
|
+
* version: '1.2.0',
|
|
59
|
+
* description: 'Order lookup + refund policy, the way support runs it.',
|
|
60
|
+
* configure: (agent) => {
|
|
61
|
+
* agent.system('You answer support questions.').tool(lookupOrder);
|
|
62
|
+
* },
|
|
63
|
+
* });
|
|
64
|
+
*
|
|
65
|
+
* const agent = Agent.create({ provider, model }).recipe(supportDesk).build();
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export declare function defineAgentRecipe(recipe: AgentRecipe): AgentRecipe;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* defineAgentRecipe — declare a named, versioned composition.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: a validating factory that freezes. No class, no registry, no
|
|
5
|
+
* instance state — the "recipes over primitives" instruction taken
|
|
6
|
+
* literally.
|
|
7
|
+
* Role: recipes/ layer, pure. The validation it runs is the SAME function
|
|
8
|
+
* `AgentBuilder.recipe()` runs, so a hand-written literal cannot get
|
|
9
|
+
* past the checks the factory makes; the factory only moves the
|
|
10
|
+
* refusal to the declaration, which is where the fix is.
|
|
11
|
+
* Emits: N/A.
|
|
12
|
+
*
|
|
13
|
+
* ## Why it freezes
|
|
14
|
+
*
|
|
15
|
+
* A recipe is handed to `.recipe()` on one agent and, typically, to `.recipe()`
|
|
16
|
+
* on several more. A mutable one is a shared object that a single consumer can
|
|
17
|
+
* edit for everybody — and the edit would be invisible on the record, because
|
|
18
|
+
* the manifest reports the id and the version, both of which would still say
|
|
19
|
+
* what they always said. `Object.freeze` is shallow, which is exactly the
|
|
20
|
+
* depth that matters here: the four fields are three strings and a function.
|
|
21
|
+
*/
|
|
22
|
+
import { isPlainRecipeId, recipeIdRefusal } from './identifier.js';
|
|
23
|
+
import { isSemverVersion, versionRefusal } from './version.js';
|
|
24
|
+
/** The fields a recipe declares. Anything else is a typo — see the refusal. */
|
|
25
|
+
const RECIPE_KEYS = ['id', 'version', 'description', 'configure'];
|
|
26
|
+
/**
|
|
27
|
+
* A recipe declaration that cannot be honoured. Thrown by
|
|
28
|
+
* {@link defineAgentRecipe} and by `AgentBuilder.recipe()` — the same class
|
|
29
|
+
* from both doors, because it is the same mistake wherever it is caught.
|
|
30
|
+
*/
|
|
31
|
+
export class InvalidAgentRecipeError extends Error {
|
|
32
|
+
code = 'ERR_INVALID_AGENT_RECIPE';
|
|
33
|
+
/** Which field was refused (`'id'`, `'version'`, `'configure'`, `'shape'`). */
|
|
34
|
+
field;
|
|
35
|
+
constructor(field, message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = 'InvalidAgentRecipeError';
|
|
38
|
+
this.field = field;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Validate a recipe declaration, or refuse it by name.
|
|
43
|
+
*
|
|
44
|
+
* Total over `unknown`: this is the one gate, and it is called from the
|
|
45
|
+
* factory AND from `.recipe()`, so no recipe reaches an agent unvalidated.
|
|
46
|
+
*
|
|
47
|
+
* @param value - the candidate declaration.
|
|
48
|
+
* @param callSite - the API the author called, named in every refusal.
|
|
49
|
+
*/
|
|
50
|
+
export function assertAgentRecipe(value, callSite) {
|
|
51
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
52
|
+
throw new InvalidAgentRecipeError('shape', `${callSite}: a recipe is an object { id, version, description?, configure }, not ` +
|
|
53
|
+
`${value === null ? 'null' : Array.isArray(value) ? 'an array' : typeof value}.`);
|
|
54
|
+
}
|
|
55
|
+
const record = value;
|
|
56
|
+
// Unknown keys first: `name:` instead of `id:` fails every later check with a
|
|
57
|
+
// message about the field that is MISSING, which sends the reader looking for
|
|
58
|
+
// a field they can plainly see they wrote.
|
|
59
|
+
const unknown = Object.keys(record).filter((key) => !RECIPE_KEYS.includes(key));
|
|
60
|
+
if (unknown.length > 0) {
|
|
61
|
+
throw new InvalidAgentRecipeError('shape', `${callSite}: unknown field${unknown.length > 1 ? 's' : ''} ${unknown
|
|
62
|
+
.map((k) => `'${k}'`)
|
|
63
|
+
.join(', ')}. A recipe declares exactly ${RECIPE_KEYS.map((k) => `\`${k}\``).join(', ')} ` +
|
|
64
|
+
`— everything else about the agent is expressed by the builder calls \`configure\` ` +
|
|
65
|
+
`makes, which is the whole point of composing over the builder instead of inventing a ` +
|
|
66
|
+
`second configuration format.`);
|
|
67
|
+
}
|
|
68
|
+
if (!isPlainRecipeId(record.id)) {
|
|
69
|
+
throw new InvalidAgentRecipeError('id', recipeIdRefusal(callSite, record.id));
|
|
70
|
+
}
|
|
71
|
+
if (!isSemverVersion(record.version)) {
|
|
72
|
+
throw new InvalidAgentRecipeError('version', versionRefusal(callSite, record.version));
|
|
73
|
+
}
|
|
74
|
+
if (record.description !== undefined && typeof record.description !== 'string') {
|
|
75
|
+
throw new InvalidAgentRecipeError('description', `${callSite}: description must be a string (one sentence saying what this composition is ` +
|
|
76
|
+
`for), or omitted. Got ${typeof record.description}.`);
|
|
77
|
+
}
|
|
78
|
+
if (typeof record.configure !== 'function') {
|
|
79
|
+
throw new InvalidAgentRecipeError('configure', `${callSite}: configure must be a function (builder) => void — the builder calls this ` +
|
|
80
|
+
`composition stands for. Got ${record.configure === undefined ? 'nothing' : typeof record.configure}. A recipe with no \`configure\` configures nothing: it would apply cleanly, change ` +
|
|
81
|
+
`no behaviour, and still put a row on the run manifest claiming it shaped the agent.`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Declare a recipe: a name, a version, and the builder calls it stands for.
|
|
86
|
+
*
|
|
87
|
+
* Validates every field and returns a frozen object. Refusals name the field
|
|
88
|
+
* and the fix — `defineAgentRecipe` is where a bad id or version costs one
|
|
89
|
+
* line, and `.recipe()` is where the same mistake costs a stack trace through
|
|
90
|
+
* somebody else's app.
|
|
91
|
+
*
|
|
92
|
+
* @example the composition an app imports and applies
|
|
93
|
+
* ```ts
|
|
94
|
+
* import { defineAgentRecipe } from 'agentfootprint/recipes';
|
|
95
|
+
*
|
|
96
|
+
* export const supportDesk = defineAgentRecipe({
|
|
97
|
+
* id: 'support-desk',
|
|
98
|
+
* version: '1.2.0',
|
|
99
|
+
* description: 'Order lookup + refund policy, the way support runs it.',
|
|
100
|
+
* configure: (agent) => {
|
|
101
|
+
* agent.system('You answer support questions.').tool(lookupOrder);
|
|
102
|
+
* },
|
|
103
|
+
* });
|
|
104
|
+
*
|
|
105
|
+
* const agent = Agent.create({ provider, model }).recipe(supportDesk).build();
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export function defineAgentRecipe(recipe) {
|
|
109
|
+
assertAgentRecipe(recipe, 'defineAgentRecipe');
|
|
110
|
+
return Object.freeze({ ...recipe });
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=defineAgentRecipe.js.map
|