@lunora/testing 1.0.0-alpha.50 → 1.0.0-alpha.52

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/dist/index.d.mts CHANGED
@@ -110,6 +110,53 @@ declare const agentHarness: (agent: AgentDefinition, options: AgentHarnessOption
110
110
  declare const finalTurn: (text: string, extra?: Partial<AgentGenerateResult>) => AgentGenerateResult;
111
111
  /** A single-tool-call turn: the loop runs the named tool with `input`. */
112
112
  declare const toolCallTurn: (id: string, name: string, input: unknown, text?: string) => AgentGenerateResult;
113
+ /** The primitive an eval attaches to a span: a numeric score or a string label. */
114
+ type EvaluationAttributeValue = number | string;
115
+ /**
116
+ * Structural slice of the post-hoc span handle `ctx.trace` hands its body (see the
117
+ * server `SpanHandle`) — enough to attach an eval's attributes. Declared here
118
+ * rather than imported so `@lunora/testing` takes no dependency on `@lunora/server`
119
+ * or `@lunora/do`; the real handle is assignable to it.
120
+ */
121
+ interface EvaluationSpanHandle {
122
+ setAttributes: (fields: Record<string, EvaluationAttributeValue>) => void;
123
+ }
124
+ /** One eval verdict to emit. */
125
+ interface RecordEvaluationInput {
126
+ /**
127
+ * Optional categorical label (e.g. `"pass"` / `"fail"` / a rubric bucket),
128
+ * emitted as the `.label` attribute. Omitted → no label attribute.
129
+ */
130
+ label?: string;
131
+ /**
132
+ * The scorer/evaluation name — becomes the key's name segment. Any character
133
+ * outside `[A-Za-z0-9._-]` is replaced with `_` so a scorer name carrying a
134
+ * colon (e.g. `"contains:shipped"`) still yields a well-formed attribute key.
135
+ */
136
+ name: string;
137
+ /** The numeric score (typically `[0, 1]`), emitted as the `.score` attribute. */
138
+ score: number;
139
+ /**
140
+ * Optional generation span to attach the attributes to — the post-hoc
141
+ * `SpanHandle` a `ctx.trace` body receives. Omitted → nothing is attached and
142
+ * the caller uses the returned bag for a standalone event/metric.
143
+ */
144
+ span?: EvaluationSpanHandle;
145
+ }
146
+ /**
147
+ * Build the `gen_ai.evaluation.NAME.*` attribute bag for one eval verdict — the
148
+ * `.score` (number) always, the `.label` (string) when a label is given. Exported
149
+ * so a caller can emit the score as a standalone event/metric without a span.
150
+ */
151
+ declare const evaluationAttributes: (input: Pick<RecordEvaluationInput, "label" | "name" | "score">) => Record<string, EvaluationAttributeValue>;
152
+ /**
153
+ * Emit one eval verdict as `gen_ai.evaluation.NAME.*` attributes. Attaches them to
154
+ * `input.span` when supplied (the post-hoc generation-span handle), and always
155
+ * returns the attribute bag so a span-less caller can ship it as an eval
156
+ * event/metric. Privacy-safe: only the name, score, and optional label are
157
+ * emitted — never the graded prompt or output.
158
+ */
159
+ declare const recordEvaluation: (input: RecordEvaluationInput) => Record<string, EvaluationAttributeValue>;
113
160
  /** A pending job entry in the fake scheduler queue. */
114
161
  interface FakeScheduledJob extends ScheduledJob {
115
162
  /** The args the job was scheduled with. */
@@ -460,4 +507,4 @@ declare const scoreSample: (sample: ScorerSample, scorers: ReadonlyArray<Scorer>
460
507
  * Returns per-case results plus the mean of their averages.
461
508
  */
462
509
  declare const evaluate: (cases: ReadonlyArray<EvalCase>, produce: (input: string) => Promise<string> | string, scorers: ReadonlyArray<Scorer>) => Promise<EvalResult>;
463
- export { type AgentHarness, type AgentHarnessOptions, type AgentRunOverrides, type EvalCase, type EvalItemResult, type EvalResult, type FakeScheduledJob, type FakeSchedulerControls, type FunctionRegistry, type HarnessDispatch, type HarnessMessage, type HarnessThread, type LunoraTestOptions, type ScheduledJobFailure, type ScoreResult, type Scorer, type ScorerSample, type SweepOptions, type TestHarness, type TestIdentity, type TestSubscription, agentHarness, containsScorer, evaluate, exactMatchScorer, finalTurn, keywordScorer, llmScorer, lunoraTest, regexScorer, scoreSample, toolCallTurn };
510
+ export { type AgentHarness, type AgentHarnessOptions, type AgentRunOverrides, type EvalCase, type EvalItemResult, type EvalResult, type EvaluationAttributeValue, type EvaluationSpanHandle, type FakeScheduledJob, type FakeSchedulerControls, type FunctionRegistry, type HarnessDispatch, type HarnessMessage, type HarnessThread, type LunoraTestOptions, type RecordEvaluationInput, type ScheduledJobFailure, type ScoreResult, type Scorer, type ScorerSample, type SweepOptions, type TestHarness, type TestIdentity, type TestSubscription, agentHarness, containsScorer, evaluate, evaluationAttributes, exactMatchScorer, finalTurn, keywordScorer, llmScorer, lunoraTest, recordEvaluation, regexScorer, scoreSample, toolCallTurn };
package/dist/index.d.ts CHANGED
@@ -110,6 +110,53 @@ declare const agentHarness: (agent: AgentDefinition, options: AgentHarnessOption
110
110
  declare const finalTurn: (text: string, extra?: Partial<AgentGenerateResult>) => AgentGenerateResult;
111
111
  /** A single-tool-call turn: the loop runs the named tool with `input`. */
112
112
  declare const toolCallTurn: (id: string, name: string, input: unknown, text?: string) => AgentGenerateResult;
113
+ /** The primitive an eval attaches to a span: a numeric score or a string label. */
114
+ type EvaluationAttributeValue = number | string;
115
+ /**
116
+ * Structural slice of the post-hoc span handle `ctx.trace` hands its body (see the
117
+ * server `SpanHandle`) — enough to attach an eval's attributes. Declared here
118
+ * rather than imported so `@lunora/testing` takes no dependency on `@lunora/server`
119
+ * or `@lunora/do`; the real handle is assignable to it.
120
+ */
121
+ interface EvaluationSpanHandle {
122
+ setAttributes: (fields: Record<string, EvaluationAttributeValue>) => void;
123
+ }
124
+ /** One eval verdict to emit. */
125
+ interface RecordEvaluationInput {
126
+ /**
127
+ * Optional categorical label (e.g. `"pass"` / `"fail"` / a rubric bucket),
128
+ * emitted as the `.label` attribute. Omitted → no label attribute.
129
+ */
130
+ label?: string;
131
+ /**
132
+ * The scorer/evaluation name — becomes the key's name segment. Any character
133
+ * outside `[A-Za-z0-9._-]` is replaced with `_` so a scorer name carrying a
134
+ * colon (e.g. `"contains:shipped"`) still yields a well-formed attribute key.
135
+ */
136
+ name: string;
137
+ /** The numeric score (typically `[0, 1]`), emitted as the `.score` attribute. */
138
+ score: number;
139
+ /**
140
+ * Optional generation span to attach the attributes to — the post-hoc
141
+ * `SpanHandle` a `ctx.trace` body receives. Omitted → nothing is attached and
142
+ * the caller uses the returned bag for a standalone event/metric.
143
+ */
144
+ span?: EvaluationSpanHandle;
145
+ }
146
+ /**
147
+ * Build the `gen_ai.evaluation.NAME.*` attribute bag for one eval verdict — the
148
+ * `.score` (number) always, the `.label` (string) when a label is given. Exported
149
+ * so a caller can emit the score as a standalone event/metric without a span.
150
+ */
151
+ declare const evaluationAttributes: (input: Pick<RecordEvaluationInput, "label" | "name" | "score">) => Record<string, EvaluationAttributeValue>;
152
+ /**
153
+ * Emit one eval verdict as `gen_ai.evaluation.NAME.*` attributes. Attaches them to
154
+ * `input.span` when supplied (the post-hoc generation-span handle), and always
155
+ * returns the attribute bag so a span-less caller can ship it as an eval
156
+ * event/metric. Privacy-safe: only the name, score, and optional label are
157
+ * emitted — never the graded prompt or output.
158
+ */
159
+ declare const recordEvaluation: (input: RecordEvaluationInput) => Record<string, EvaluationAttributeValue>;
113
160
  /** A pending job entry in the fake scheduler queue. */
114
161
  interface FakeScheduledJob extends ScheduledJob {
115
162
  /** The args the job was scheduled with. */
@@ -460,4 +507,4 @@ declare const scoreSample: (sample: ScorerSample, scorers: ReadonlyArray<Scorer>
460
507
  * Returns per-case results plus the mean of their averages.
461
508
  */
462
509
  declare const evaluate: (cases: ReadonlyArray<EvalCase>, produce: (input: string) => Promise<string> | string, scorers: ReadonlyArray<Scorer>) => Promise<EvalResult>;
463
- export { type AgentHarness, type AgentHarnessOptions, type AgentRunOverrides, type EvalCase, type EvalItemResult, type EvalResult, type FakeScheduledJob, type FakeSchedulerControls, type FunctionRegistry, type HarnessDispatch, type HarnessMessage, type HarnessThread, type LunoraTestOptions, type ScheduledJobFailure, type ScoreResult, type Scorer, type ScorerSample, type SweepOptions, type TestHarness, type TestIdentity, type TestSubscription, agentHarness, containsScorer, evaluate, exactMatchScorer, finalTurn, keywordScorer, llmScorer, lunoraTest, regexScorer, scoreSample, toolCallTurn };
510
+ export { type AgentHarness, type AgentHarnessOptions, type AgentRunOverrides, type EvalCase, type EvalItemResult, type EvalResult, type EvaluationAttributeValue, type EvaluationSpanHandle, type FakeScheduledJob, type FakeSchedulerControls, type FunctionRegistry, type HarnessDispatch, type HarnessMessage, type HarnessThread, type LunoraTestOptions, type RecordEvaluationInput, type ScheduledJobFailure, type ScoreResult, type Scorer, type ScorerSample, type SweepOptions, type TestHarness, type TestIdentity, type TestSubscription, agentHarness, containsScorer, evaluate, evaluationAttributes, exactMatchScorer, finalTurn, keywordScorer, llmScorer, lunoraTest, recordEvaluation, regexScorer, scoreSample, toolCallTurn };
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  export { agentHarness, finalTurn, toolCallTurn } from './packem_shared/agentHarness-GTPhWdQ8.mjs';
2
- export { lunoraTest } from './packem_shared/lunoraTest-CHix6VWB.mjs';
2
+ export { evaluationAttributes, recordEvaluation } from './packem_shared/evaluationAttributes-C08Q5MOX.mjs';
3
+ export { lunoraTest } from './packem_shared/lunoraTest-Dv_w_v9z.mjs';
3
4
  export { containsScorer, evaluate, exactMatchScorer, keywordScorer, llmScorer, regexScorer, scoreSample } from './packem_shared/containsScorer-DGwtvUNp.mjs';
4
5
  export { extractLink, listCapturedMail, waitForMail } from '@lunora/mail/testing';
@@ -0,0 +1,31 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+
3
+ const SAFE_NAME_CHAR = /[\w.-]/u;
4
+ const sanitizeName = (name) => {
5
+ let out = "";
6
+ for (const char of name) {
7
+ out += SAFE_NAME_CHAR.test(char) ? char : "_";
8
+ }
9
+ return out;
10
+ };
11
+ const evaluationAttributes = (input) => {
12
+ if (typeof input.name !== "string" || input.name.length === 0) {
13
+ throw new LunoraError("BAD_REQUEST", "@lunora/testing: recordEvaluation requires a non-empty `name`");
14
+ }
15
+ if (typeof input.score !== "number" || !Number.isFinite(input.score)) {
16
+ throw new LunoraError("BAD_REQUEST", "@lunora/testing: recordEvaluation `score` must be a finite number");
17
+ }
18
+ const key = sanitizeName(input.name);
19
+ const attributes = { [`gen_ai.evaluation.${key}.score`]: input.score };
20
+ if (input.label !== void 0) {
21
+ attributes[`gen_ai.evaluation.${key}.label`] = input.label;
22
+ }
23
+ return attributes;
24
+ };
25
+ const recordEvaluation = (input) => {
26
+ const attributes = evaluationAttributes(input);
27
+ input.span?.setAttributes(attributes);
28
+ return attributes;
29
+ };
30
+
31
+ export { evaluationAttributes, recordEvaluation };
@@ -151,7 +151,11 @@ const stubProxy = (surface) => /* @__PURE__ */ new Proxy((..._args) => unavailab
151
151
  apply: () => unavailable(surface),
152
152
  get: () => unavailable(surface)
153
153
  });
154
- const passthroughTrace = async (_name, function_) => await function_(passthroughTrace);
154
+ const noopSpan = {
155
+ setAttribute: () => void 0,
156
+ setAttributes: () => void 0
157
+ };
158
+ const passthroughTrace = async (_name, function_) => await function_(passthroughTrace, noopSpan);
155
159
  const noopMetrics = {
156
160
  count: () => void 0,
157
161
  gauge: () => void 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/testing",
3
- "version": "1.0.0-alpha.50",
3
+ "version": "1.0.0-alpha.52",
4
4
  "description": "Testing toolkit for Lunora: an in-memory harness for queries, mutations, and actions",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -46,11 +46,11 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@lunora/agent": "1.0.0-alpha.7",
50
- "@lunora/do": "1.0.0-alpha.37",
51
- "@lunora/errors": "1.0.0-alpha.6",
52
- "@lunora/mail": "1.0.0-alpha.16",
53
- "@lunora/server": "1.0.0-alpha.29"
49
+ "@lunora/agent": "1.0.0-alpha.9",
50
+ "@lunora/do": "1.0.0-alpha.41",
51
+ "@lunora/errors": "1.0.0-alpha.7",
52
+ "@lunora/mail": "1.0.0-alpha.17",
53
+ "@lunora/server": "1.0.0-alpha.30"
54
54
  },
55
55
  "engines": {
56
56
  "node": "^22.15.0 || >=24.11.0"