@tangle-network/agent-app 0.2.0 → 0.3.1
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/skills/eval-architect/SKILL.md +44 -0
- package/.claude/skills/eval-bootstrap/SKILL.md +46 -0
- package/.claude/skills/eval-campaign/SKILL.md +116 -0
- package/.claude/skills/improve-conductor/SKILL.md +45 -0
- package/.claude/skills/measurement-validation/SKILL.md +38 -0
- package/.claude/skills/skill-evolution/SKILL.md +42 -0
- package/.claude/skills/surface-evolution/SKILL.md +35 -0
- package/dist/{chunk-3LP6PEWS.js → chunk-SVCJYRVM.js} +97 -1
- package/dist/chunk-SVCJYRVM.js.map +1 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/eval-campaign/index.d.ts +81 -1
- package/dist/eval-campaign/index.js +94 -1
- package/dist/eval-campaign/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +19 -1
- package/dist/knowledge-loop/index.d.ts +1 -1
- package/dist/model-CKzniMMr.d.ts +110 -0
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +19 -1
- package/package.json +4 -3
- package/dist/chunk-3LP6PEWS.js.map +0 -1
- package/dist/model-BOP69mVu.d.ts +0 -35
|
@@ -4,6 +4,86 @@ import { Scenario, JudgeConfig } from '@tangle-network/agent-eval/campaign';
|
|
|
4
4
|
export { CampaignResult, DispatchContext, Gate, ImprovementDriver, JudgeConfig, JudgeDimension, JudgeScore, LabeledScenarioStore, MutableSurface, Mutator, Scenario, defaultProductionGate, evolutionaryDriver, gepaDriver, paretoSignificanceGate, runCampaign } from '@tangle-network/agent-eval/campaign';
|
|
5
5
|
export { SelfImproveBudget, SelfImproveOptions, SelfImproveResult, selfImprove } from '@tangle-network/agent-eval/contract';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Trust gate — decides whether an ensemble's scores are allowed to be BELIEVED,
|
|
9
|
+
* one level up from {@link aggregateJudgeVerdicts} (which only reduces ONE
|
|
10
|
+
* artifact's raters to a composite). A composite is a number; this is the check
|
|
11
|
+
* that the number means anything. It is the code "Enforced by" for the
|
|
12
|
+
* measurement-validation skill's after-gate ("is this result allowed to be
|
|
13
|
+
* believed").
|
|
14
|
+
*
|
|
15
|
+
* Three checks, each fail-loud and named in `trustReasons`:
|
|
16
|
+
* (1) inter-rater reliability over the corpus ≥ `irrFloor` — raters that
|
|
17
|
+
* disagree no better than chance carry no signal to optimize against.
|
|
18
|
+
* (2) per-item rater spread ≤ `spreadCeiling` — for EACH item, raters must
|
|
19
|
+
* converge on THAT item.
|
|
20
|
+
* (3) surviving raters per item ≥ `minSurvivors` — a mean over one or two
|
|
21
|
+
* raters is an anecdote, not an ensemble.
|
|
22
|
+
*
|
|
23
|
+
* CRITICAL metric semantics — per-item spread is rater disagreement about the
|
|
24
|
+
* SAME item: `max(score) − min(score)` across the raters that scored THAT item
|
|
25
|
+
* (max over its dimensions), never pooled across different items or across the
|
|
26
|
+
* baseline/candidate sides. Pooling reads a genuine quality gap BETWEEN items as
|
|
27
|
+
* "the raters split" and so trips the gate exactly when the finding is largest —
|
|
28
|
+
* the failure mode the after-gate exists to prevent. The corpus IRR (check 1)
|
|
29
|
+
* leans on the substrate's `interRaterReliability`, whose expected-disagreement
|
|
30
|
+
* denominator already pools across items, so genuine item-to-item variation
|
|
31
|
+
* RAISES reliability rather than lowering it.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/** One item's raters: the per-judge verdicts {@link aggregateJudgeVerdicts}
|
|
35
|
+
* reduces, tagged with the item they scored so spread stays within-item. */
|
|
36
|
+
interface TrustItem<D extends string = string> {
|
|
37
|
+
/** Stable item identifier — surfaces in `perItemSpread` and `trustReasons`. */
|
|
38
|
+
itemId: string;
|
|
39
|
+
/** The raters' verdicts for THIS item (one per judge call). A failed judge
|
|
40
|
+
* (`perDimension: null`) is dropped before spread/IRR, never folded as 0. */
|
|
41
|
+
verdicts: readonly JudgeVerdict<D>[];
|
|
42
|
+
}
|
|
43
|
+
/** Thresholds for {@link trustVerdicts}. All overridable; defaults are the
|
|
44
|
+
* conservative after-gate bar. */
|
|
45
|
+
interface TrustThresholds {
|
|
46
|
+
/** Minimum corpus inter-rater reliability (Krippendorff-style α). Below this
|
|
47
|
+
* the raters agree no better than chance. Default 0.2. */
|
|
48
|
+
irrFloor?: number;
|
|
49
|
+
/** Maximum per-item rater spread (`max − min` over a single item's surviving
|
|
50
|
+
* raters, across its dimensions). Above this the raters split ON THAT ITEM.
|
|
51
|
+
* Default 0.5. */
|
|
52
|
+
spreadCeiling?: number;
|
|
53
|
+
/** Minimum surviving (non-failed) raters required per item. Default 3. */
|
|
54
|
+
minSurvivors?: number;
|
|
55
|
+
}
|
|
56
|
+
/** Result of the trust gate. `trustworthy` iff every check passed; `trustReasons`
|
|
57
|
+
* is empty iff `trustworthy`. */
|
|
58
|
+
interface TrustVerdict {
|
|
59
|
+
/** True iff IRR ≥ floor AND every item's spread ≤ ceiling AND every item has
|
|
60
|
+
* ≥ `minSurvivors` surviving raters. */
|
|
61
|
+
trustworthy: boolean;
|
|
62
|
+
/** One entry per FAILED check, each naming its number + the offending value.
|
|
63
|
+
* Empty iff `trustworthy`. */
|
|
64
|
+
trustReasons: string[];
|
|
65
|
+
/** Corpus inter-rater reliability actually measured (the check-1 value). */
|
|
66
|
+
interRaterReliability: number;
|
|
67
|
+
/** Per-item spread (`max − min` over surviving raters, max over dimensions),
|
|
68
|
+
* keyed by `itemId`. The check-2 input, surfaced for drill-down. */
|
|
69
|
+
perItemSpread: Record<string, number>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Decide whether an ensemble's per-item verdicts are trustworthy enough to
|
|
73
|
+
* believe a lift computed from them. Pure: no LLM, no I/O, no clock, no random —
|
|
74
|
+
* the same `items` + `thresholds` always yield the same verdict.
|
|
75
|
+
*
|
|
76
|
+
* Sibling to {@link aggregateJudgeVerdicts}: that reduces ONE item's raters to a
|
|
77
|
+
* composite; this audits the raters ACROSS items and reports whether the
|
|
78
|
+
* composites are believable. Run it on the corpus of held-out items before
|
|
79
|
+
* reporting any lift over their scores.
|
|
80
|
+
*
|
|
81
|
+
* @throws if `items` is empty — an empty corpus has no measurable trust, and a
|
|
82
|
+
* silent `trustworthy: true` over zero evidence is the exact lie the gate
|
|
83
|
+
* exists to refuse.
|
|
84
|
+
*/
|
|
85
|
+
declare function trustVerdicts<D extends string>(items: readonly TrustItem<D>[], thresholds?: TrustThresholds): TrustVerdict;
|
|
86
|
+
|
|
7
87
|
/**
|
|
8
88
|
* Eval-campaign — the app-shell's curated surface for a product's
|
|
9
89
|
* self-improvement loop, NOT a reimplementation.
|
|
@@ -72,4 +152,4 @@ interface EnsembleJudgeConfig<TArtifact, TScenario extends Scenario, D extends s
|
|
|
72
152
|
*/
|
|
73
153
|
declare function buildEnsembleJudge<TArtifact, TScenario extends Scenario, D extends string>(cfg: EnsembleJudgeConfig<TArtifact, TScenario, D>): JudgeConfig<TArtifact, TScenario>;
|
|
74
154
|
|
|
75
|
-
export { type EnsembleJudgeConfig, buildEnsembleJudge };
|
|
155
|
+
export { type EnsembleJudgeConfig, type TrustItem, type TrustThresholds, type TrustVerdict, buildEnsembleJudge, trustVerdicts };
|
|
@@ -2,6 +2,98 @@
|
|
|
2
2
|
import {
|
|
3
3
|
aggregateJudgeVerdicts
|
|
4
4
|
} from "@tangle-network/agent-eval";
|
|
5
|
+
|
|
6
|
+
// src/eval-campaign/trust-gate.ts
|
|
7
|
+
import {
|
|
8
|
+
interRaterReliability
|
|
9
|
+
} from "@tangle-network/agent-eval";
|
|
10
|
+
var DEFAULT_IRR_FLOOR = 0.2;
|
|
11
|
+
var DEFAULT_SPREAD_CEILING = 0.5;
|
|
12
|
+
var DEFAULT_MIN_SURVIVORS = 3;
|
|
13
|
+
function survivors(item) {
|
|
14
|
+
return item.verdicts.filter((v) => v.perDimension !== null);
|
|
15
|
+
}
|
|
16
|
+
function itemSpread(survivorVerdicts) {
|
|
17
|
+
if (survivorVerdicts.length < 2) return 0;
|
|
18
|
+
const dims = /* @__PURE__ */ new Set();
|
|
19
|
+
for (const v of survivorVerdicts) {
|
|
20
|
+
for (const d of Object.keys(v.perDimension)) dims.add(d);
|
|
21
|
+
}
|
|
22
|
+
let worst = 0;
|
|
23
|
+
for (const d of dims) {
|
|
24
|
+
let min = Infinity;
|
|
25
|
+
let max = -Infinity;
|
|
26
|
+
for (const v of survivorVerdicts) {
|
|
27
|
+
const score = v.perDimension[d];
|
|
28
|
+
if (score === void 0) continue;
|
|
29
|
+
if (score < min) min = score;
|
|
30
|
+
if (score > max) max = score;
|
|
31
|
+
}
|
|
32
|
+
if (max > -Infinity && max - min > worst) worst = max - min;
|
|
33
|
+
}
|
|
34
|
+
return worst;
|
|
35
|
+
}
|
|
36
|
+
function trustVerdicts(items, thresholds = {}) {
|
|
37
|
+
if (items.length === 0) {
|
|
38
|
+
throw new Error("trustVerdicts: items is empty \u2014 no evidence to trust");
|
|
39
|
+
}
|
|
40
|
+
const irrFloor = thresholds.irrFloor ?? DEFAULT_IRR_FLOOR;
|
|
41
|
+
const spreadCeiling = thresholds.spreadCeiling ?? DEFAULT_SPREAD_CEILING;
|
|
42
|
+
const minSurvivors = thresholds.minSurvivors ?? DEFAULT_MIN_SURVIVORS;
|
|
43
|
+
const maxRaters = items.reduce((m, it) => Math.max(m, survivors(it).length), 0);
|
|
44
|
+
const raterSeries = Array.from({ length: maxRaters }, () => []);
|
|
45
|
+
const perItemSpread = {};
|
|
46
|
+
const splitItems = [];
|
|
47
|
+
const starvedItems = [];
|
|
48
|
+
for (const item of items) {
|
|
49
|
+
const surv = survivors(item);
|
|
50
|
+
if (surv.length < minSurvivors) starvedItems.push({ itemId: item.itemId, n: surv.length });
|
|
51
|
+
const spread = itemSpread(surv);
|
|
52
|
+
perItemSpread[item.itemId] = spread;
|
|
53
|
+
if (spread > spreadCeiling) splitItems.push({ itemId: item.itemId, spread });
|
|
54
|
+
if (surv.length >= 2) {
|
|
55
|
+
const dims = Array.from(
|
|
56
|
+
new Set(surv.flatMap((v) => Object.keys(v.perDimension)))
|
|
57
|
+
).sort();
|
|
58
|
+
surv.forEach((v, raterIdx) => {
|
|
59
|
+
const column = raterSeries[raterIdx] ??= [];
|
|
60
|
+
const pd = v.perDimension;
|
|
61
|
+
for (const d of dims) {
|
|
62
|
+
const score = pd[d];
|
|
63
|
+
if (score === void 0) continue;
|
|
64
|
+
column.push({
|
|
65
|
+
judgeName: v.model,
|
|
66
|
+
dimension: `${item.itemId}::${d}`,
|
|
67
|
+
score,
|
|
68
|
+
reasoning: v.rationale ?? ""
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const irr = interRaterReliability(raterSeries);
|
|
75
|
+
const trustReasons = [];
|
|
76
|
+
if (irr < irrFloor) {
|
|
77
|
+
trustReasons.push(`(1) IRR ${round(irr)} < ${irrFloor}`);
|
|
78
|
+
}
|
|
79
|
+
for (const { itemId, spread } of splitItems) {
|
|
80
|
+
trustReasons.push(`(2) item ${itemId} spread ${round(spread)} > ${spreadCeiling} \u2014 raters split`);
|
|
81
|
+
}
|
|
82
|
+
for (const { itemId, n } of starvedItems) {
|
|
83
|
+
trustReasons.push(`(3) item ${itemId}: ${n} surviving raters < ${minSurvivors}`);
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
trustworthy: trustReasons.length === 0,
|
|
87
|
+
trustReasons,
|
|
88
|
+
interRaterReliability: irr,
|
|
89
|
+
perItemSpread
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function round(n) {
|
|
93
|
+
return Math.round(n * 100) / 100;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/eval-campaign/index.ts
|
|
5
97
|
import { aggregateJudgeVerdicts as aggregateJudgeVerdicts2 } from "@tangle-network/agent-eval";
|
|
6
98
|
import {
|
|
7
99
|
defaultProductionGate,
|
|
@@ -42,6 +134,7 @@ export {
|
|
|
42
134
|
gepaDriver,
|
|
43
135
|
paretoSignificanceGate,
|
|
44
136
|
runCampaign,
|
|
45
|
-
selfImprove
|
|
137
|
+
selfImprove,
|
|
138
|
+
trustVerdicts
|
|
46
139
|
};
|
|
47
140
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/eval-campaign/index.ts"],"sourcesContent":["/**\n * Eval-campaign — the app-shell's curated surface for a product's\n * self-improvement loop, NOT a reimplementation.\n *\n * The loop ENGINE lives in `@tangle-network/agent-eval` (a peer dependency):\n * `selfImprove` already owns the whole cycle — train/holdout split, the GEPA\n * driver, the held-out production gate, durable provenance + hosted ingest, and\n * every default. A product should NOT hand-roll `runImprovementLoop` +\n * `emitLoopProvenance` around it (that is the boilerplate this surface exists to\n * delete). It should call `selfImprove` with three things it actually owns:\n * scenarios, an `agent` dispatch, and a `judge`.\n *\n * This module adds the one piece `selfImprove` does not own and which every\n * multi-model product re-hand-rolls — the ensemble judge:\n *\n * {@link buildEnsembleJudge} — turn a per-rubric `scoreOne` into a\n * `JudgeConfig` that fans out N uncorrelated judge calls and reduces them via\n * the substrate's `aggregateJudgeVerdicts` (survivor-mean, inter-rater spread,\n * fail-loud on all-failed). A product writes its rubric + one judge call; the\n * fan-out, partial-failure handling, and composite are the scaffold's.\n *\n * Everything else is a curated re-export so a product has ONE eval import:\n * `selfImprove` + the gates + the drivers + the types. See\n * `.claude/skills/eval-campaign/SKILL.md` for the wiring contract.\n */\n\nimport {\n aggregateJudgeVerdicts,\n type JudgeVerdict,\n} from '@tangle-network/agent-eval'\nimport type {\n JudgeConfig,\n JudgeScore,\n Scenario,\n} from '@tangle-network/agent-eval/campaign'\n\n/** Config for {@link buildEnsembleJudge}. `D` = the rubric's dimension union. */\nexport interface EnsembleJudgeConfig<TArtifact, TScenario extends Scenario, D extends string> {\n /** Judge name — appears in traces and scorecards. */\n name: string\n /** Stable-ordered rubric dimensions. Drives the `JudgeDimension` list AND the\n * reducer keys, so a judge that omits a dimension scores it 0 (never silently\n * dropped). */\n rubric: readonly D[]\n /**\n * Score ONE artifact on the rubric → a raw per-dimension verdict. Called\n * `judgeReps` times per artifact; vary the model by `rep` for an uncorrelated\n * ensemble (judges that share a base model share its bias). Return\n * `{ model, perDimension: null }` to record a judge failure WITHOUT killing\n * the ensemble; throw only on an unrecoverable error (the whole rep is then\n * treated as a failed judge).\n */\n scoreOne: (input: {\n artifact: TArtifact\n scenario: TScenario\n signal: AbortSignal\n rep: number\n }) => Promise<JudgeVerdict<D>>\n /** Independent judge calls per artifact, reduced by `aggregateJudgeVerdicts`.\n * Default 1. Raise (with model variety in `scoreOne`) for inter-rater bands. */\n judgeReps?: number\n /** Per-dimension composite weights. Default: uniform over `rubric`. A partial\n * map selects-and-weights exactly the named dimensions. */\n weights?: Partial<Record<D, number>>\n /** Optional human-readable dimension descriptions. Default: the key itself. */\n describe?: (dim: D) => string\n}\n\n/**\n * Build a `JudgeConfig` whose `score()` fans out `judgeReps` independent\n * `scoreOne` calls and reduces them with the substrate's\n * `aggregateJudgeVerdicts`. A single judge call failing does NOT fail the cell\n * (it is recorded and dropped); only ALL judges failing throws — which the\n * campaign records as a failed cell, never a silent zero.\n *\n * Pass the result straight to `selfImprove({ judge })` (or `runCampaign`).\n */\nexport function buildEnsembleJudge<TArtifact, TScenario extends Scenario, D extends string>(\n cfg: EnsembleJudgeConfig<TArtifact, TScenario, D>,\n): JudgeConfig<TArtifact, TScenario> {\n const reps = cfg.judgeReps ?? 1\n if (reps < 1) {\n throw new Error(`buildEnsembleJudge: judgeReps must be >= 1 (got ${reps})`)\n }\n if (cfg.rubric.length === 0) {\n throw new Error('buildEnsembleJudge: rubric is empty')\n }\n return {\n name: cfg.name,\n dimensions: cfg.rubric.map((key) => ({ key, description: cfg.describe?.(key) ?? key })),\n async score({ artifact, scenario, signal }): Promise<JudgeScore> {\n const settled = await Promise.allSettled(\n Array.from({ length: reps }, (_, rep) => cfg.scoreOne({ artifact, scenario, signal, rep })),\n )\n const verdicts: JudgeVerdict<D>[] = settled.map((r, rep) =>\n r.status === 'fulfilled'\n ? r.value\n : { model: `${cfg.name}-rep${rep}`, perDimension: null, rationale: String(r.reason) },\n )\n // Throws iff EVERY rep failed → the campaign records a failed cell.\n const agg = aggregateJudgeVerdicts(verdicts, cfg.rubric, cfg.weights)\n return { composite: agg.composite, dimensions: agg.perDimension, notes: agg.rationale }\n },\n }\n}\n\n// ── Curated re-exports — the one eval import for a product loop ──────────────\n// The loop engine + gates + drivers + the ensemble reducer, so a product wires\n// its self-improvement loop from a single module instead of reaching across\n// three agent-eval subpaths. All DOWNWARD imports (agent-app consumes the\n// substrate); the layering rule is preserved.\n\nexport { aggregateJudgeVerdicts } from '@tangle-network/agent-eval'\nexport type {\n EnsembleAggregate,\n JudgeVerdict,\n RunRecord,\n} from '@tangle-network/agent-eval'\nexport {\n defaultProductionGate,\n evolutionaryDriver,\n gepaDriver,\n paretoSignificanceGate,\n runCampaign,\n} from '@tangle-network/agent-eval/campaign'\nexport type {\n CampaignResult,\n DispatchContext,\n Gate,\n ImprovementDriver,\n JudgeConfig,\n JudgeDimension,\n JudgeScore,\n LabeledScenarioStore,\n MutableSurface,\n Mutator,\n Scenario,\n} from '@tangle-network/agent-eval/campaign'\nexport { selfImprove } from '@tangle-network/agent-eval/contract'\nexport type {\n SelfImproveBudget,\n SelfImproveOptions,\n SelfImproveResult,\n} from '@tangle-network/agent-eval/contract'\n"],"mappings":";AA0BA;AAAA,EACE;AAAA,OAEK;AAmFP,SAAS,0BAAAA,+BAA8B;AAMvC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAcP,SAAS,mBAAmB;AA7DrB,SAAS,mBACd,KACmC;AACnC,QAAM,OAAO,IAAI,aAAa;AAC9B,MAAI,OAAO,GAAG;AACZ,UAAM,IAAI,MAAM,mDAAmD,IAAI,GAAG;AAAA,EAC5E;AACA,MAAI,IAAI,OAAO,WAAW,GAAG;AAC3B,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AACA,SAAO;AAAA,IACL,MAAM,IAAI;AAAA,IACV,YAAY,IAAI,OAAO,IAAI,CAAC,SAAS,EAAE,KAAK,aAAa,IAAI,WAAW,GAAG,KAAK,IAAI,EAAE;AAAA,IACtF,MAAM,MAAM,EAAE,UAAU,UAAU,OAAO,GAAwB;AAC/D,YAAM,UAAU,MAAM,QAAQ;AAAA,QAC5B,MAAM,KAAK,EAAE,QAAQ,KAAK,GAAG,CAAC,GAAG,QAAQ,IAAI,SAAS,EAAE,UAAU,UAAU,QAAQ,IAAI,CAAC,CAAC;AAAA,MAC5F;AACA,YAAM,WAA8B,QAAQ;AAAA,QAAI,CAAC,GAAG,QAClD,EAAE,WAAW,cACT,EAAE,QACF,EAAE,OAAO,GAAG,IAAI,IAAI,OAAO,GAAG,IAAI,cAAc,MAAM,WAAW,OAAO,EAAE,MAAM,EAAE;AAAA,MACxF;AAEA,YAAM,MAAM,uBAAuB,UAAU,IAAI,QAAQ,IAAI,OAAO;AACpE,aAAO,EAAE,WAAW,IAAI,WAAW,YAAY,IAAI,cAAc,OAAO,IAAI,UAAU;AAAA,IACxF;AAAA,EACF;AACF;","names":["aggregateJudgeVerdicts"]}
|
|
1
|
+
{"version":3,"sources":["../../src/eval-campaign/index.ts","../../src/eval-campaign/trust-gate.ts"],"sourcesContent":["/**\n * Eval-campaign — the app-shell's curated surface for a product's\n * self-improvement loop, NOT a reimplementation.\n *\n * The loop ENGINE lives in `@tangle-network/agent-eval` (a peer dependency):\n * `selfImprove` already owns the whole cycle — train/holdout split, the GEPA\n * driver, the held-out production gate, durable provenance + hosted ingest, and\n * every default. A product should NOT hand-roll `runImprovementLoop` +\n * `emitLoopProvenance` around it (that is the boilerplate this surface exists to\n * delete). It should call `selfImprove` with three things it actually owns:\n * scenarios, an `agent` dispatch, and a `judge`.\n *\n * This module adds the one piece `selfImprove` does not own and which every\n * multi-model product re-hand-rolls — the ensemble judge:\n *\n * {@link buildEnsembleJudge} — turn a per-rubric `scoreOne` into a\n * `JudgeConfig` that fans out N uncorrelated judge calls and reduces them via\n * the substrate's `aggregateJudgeVerdicts` (survivor-mean, inter-rater spread,\n * fail-loud on all-failed). A product writes its rubric + one judge call; the\n * fan-out, partial-failure handling, and composite are the scaffold's.\n *\n * Everything else is a curated re-export so a product has ONE eval import:\n * `selfImprove` + the gates + the drivers + the types. See\n * `.claude/skills/eval-campaign/SKILL.md` for the wiring contract.\n */\n\nimport {\n aggregateJudgeVerdicts,\n type JudgeVerdict,\n} from '@tangle-network/agent-eval'\nimport type {\n JudgeConfig,\n JudgeScore,\n Scenario,\n} from '@tangle-network/agent-eval/campaign'\n\n/** Config for {@link buildEnsembleJudge}. `D` = the rubric's dimension union. */\nexport interface EnsembleJudgeConfig<TArtifact, TScenario extends Scenario, D extends string> {\n /** Judge name — appears in traces and scorecards. */\n name: string\n /** Stable-ordered rubric dimensions. Drives the `JudgeDimension` list AND the\n * reducer keys, so a judge that omits a dimension scores it 0 (never silently\n * dropped). */\n rubric: readonly D[]\n /**\n * Score ONE artifact on the rubric → a raw per-dimension verdict. Called\n * `judgeReps` times per artifact; vary the model by `rep` for an uncorrelated\n * ensemble (judges that share a base model share its bias). Return\n * `{ model, perDimension: null }` to record a judge failure WITHOUT killing\n * the ensemble; throw only on an unrecoverable error (the whole rep is then\n * treated as a failed judge).\n */\n scoreOne: (input: {\n artifact: TArtifact\n scenario: TScenario\n signal: AbortSignal\n rep: number\n }) => Promise<JudgeVerdict<D>>\n /** Independent judge calls per artifact, reduced by `aggregateJudgeVerdicts`.\n * Default 1. Raise (with model variety in `scoreOne`) for inter-rater bands. */\n judgeReps?: number\n /** Per-dimension composite weights. Default: uniform over `rubric`. A partial\n * map selects-and-weights exactly the named dimensions. */\n weights?: Partial<Record<D, number>>\n /** Optional human-readable dimension descriptions. Default: the key itself. */\n describe?: (dim: D) => string\n}\n\n/**\n * Build a `JudgeConfig` whose `score()` fans out `judgeReps` independent\n * `scoreOne` calls and reduces them with the substrate's\n * `aggregateJudgeVerdicts`. A single judge call failing does NOT fail the cell\n * (it is recorded and dropped); only ALL judges failing throws — which the\n * campaign records as a failed cell, never a silent zero.\n *\n * Pass the result straight to `selfImprove({ judge })` (or `runCampaign`).\n */\nexport function buildEnsembleJudge<TArtifact, TScenario extends Scenario, D extends string>(\n cfg: EnsembleJudgeConfig<TArtifact, TScenario, D>,\n): JudgeConfig<TArtifact, TScenario> {\n const reps = cfg.judgeReps ?? 1\n if (reps < 1) {\n throw new Error(`buildEnsembleJudge: judgeReps must be >= 1 (got ${reps})`)\n }\n if (cfg.rubric.length === 0) {\n throw new Error('buildEnsembleJudge: rubric is empty')\n }\n return {\n name: cfg.name,\n dimensions: cfg.rubric.map((key) => ({ key, description: cfg.describe?.(key) ?? key })),\n async score({ artifact, scenario, signal }): Promise<JudgeScore> {\n const settled = await Promise.allSettled(\n Array.from({ length: reps }, (_, rep) => cfg.scoreOne({ artifact, scenario, signal, rep })),\n )\n const verdicts: JudgeVerdict<D>[] = settled.map((r, rep) =>\n r.status === 'fulfilled'\n ? r.value\n : { model: `${cfg.name}-rep${rep}`, perDimension: null, rationale: String(r.reason) },\n )\n // Throws iff EVERY rep failed → the campaign records a failed cell.\n const agg = aggregateJudgeVerdicts(verdicts, cfg.rubric, cfg.weights)\n return { composite: agg.composite, dimensions: agg.perDimension, notes: agg.rationale }\n },\n }\n}\n\n// ── Trust gate — the after-gate (\"is this result allowed to be believed\") ────\n// One level up from `aggregateJudgeVerdicts`: it audits the raters ACROSS items\n// and reports whether the composites are believable before a lift is reported.\nexport {\n trustVerdicts,\n type TrustItem,\n type TrustThresholds,\n type TrustVerdict,\n} from './trust-gate'\n\n// ── Curated re-exports — the one eval import for a product loop ──────────────\n// The loop engine + gates + drivers + the ensemble reducer, so a product wires\n// its self-improvement loop from a single module instead of reaching across\n// three agent-eval subpaths. All DOWNWARD imports (agent-app consumes the\n// substrate); the layering rule is preserved.\n\nexport { aggregateJudgeVerdicts } from '@tangle-network/agent-eval'\nexport type {\n EnsembleAggregate,\n JudgeVerdict,\n RunRecord,\n} from '@tangle-network/agent-eval'\nexport {\n defaultProductionGate,\n evolutionaryDriver,\n gepaDriver,\n paretoSignificanceGate,\n runCampaign,\n} from '@tangle-network/agent-eval/campaign'\nexport type {\n CampaignResult,\n DispatchContext,\n Gate,\n ImprovementDriver,\n JudgeConfig,\n JudgeDimension,\n JudgeScore,\n LabeledScenarioStore,\n MutableSurface,\n Mutator,\n Scenario,\n} from '@tangle-network/agent-eval/campaign'\nexport { selfImprove } from '@tangle-network/agent-eval/contract'\nexport type {\n SelfImproveBudget,\n SelfImproveOptions,\n SelfImproveResult,\n} from '@tangle-network/agent-eval/contract'\n","/**\n * Trust gate — decides whether an ensemble's scores are allowed to be BELIEVED,\n * one level up from {@link aggregateJudgeVerdicts} (which only reduces ONE\n * artifact's raters to a composite). A composite is a number; this is the check\n * that the number means anything. It is the code \"Enforced by\" for the\n * measurement-validation skill's after-gate (\"is this result allowed to be\n * believed\").\n *\n * Three checks, each fail-loud and named in `trustReasons`:\n * (1) inter-rater reliability over the corpus ≥ `irrFloor` — raters that\n * disagree no better than chance carry no signal to optimize against.\n * (2) per-item rater spread ≤ `spreadCeiling` — for EACH item, raters must\n * converge on THAT item.\n * (3) surviving raters per item ≥ `minSurvivors` — a mean over one or two\n * raters is an anecdote, not an ensemble.\n *\n * CRITICAL metric semantics — per-item spread is rater disagreement about the\n * SAME item: `max(score) − min(score)` across the raters that scored THAT item\n * (max over its dimensions), never pooled across different items or across the\n * baseline/candidate sides. Pooling reads a genuine quality gap BETWEEN items as\n * \"the raters split\" and so trips the gate exactly when the finding is largest —\n * the failure mode the after-gate exists to prevent. The corpus IRR (check 1)\n * leans on the substrate's `interRaterReliability`, whose expected-disagreement\n * denominator already pools across items, so genuine item-to-item variation\n * RAISES reliability rather than lowering it.\n */\n\nimport {\n interRaterReliability,\n type JudgeScore,\n type JudgeVerdict,\n} from '@tangle-network/agent-eval'\n\n/** One item's raters: the per-judge verdicts {@link aggregateJudgeVerdicts}\n * reduces, tagged with the item they scored so spread stays within-item. */\nexport interface TrustItem<D extends string = string> {\n /** Stable item identifier — surfaces in `perItemSpread` and `trustReasons`. */\n itemId: string\n /** The raters' verdicts for THIS item (one per judge call). A failed judge\n * (`perDimension: null`) is dropped before spread/IRR, never folded as 0. */\n verdicts: readonly JudgeVerdict<D>[]\n}\n\n/** Thresholds for {@link trustVerdicts}. All overridable; defaults are the\n * conservative after-gate bar. */\nexport interface TrustThresholds {\n /** Minimum corpus inter-rater reliability (Krippendorff-style α). Below this\n * the raters agree no better than chance. Default 0.2. */\n irrFloor?: number\n /** Maximum per-item rater spread (`max − min` over a single item's surviving\n * raters, across its dimensions). Above this the raters split ON THAT ITEM.\n * Default 0.5. */\n spreadCeiling?: number\n /** Minimum surviving (non-failed) raters required per item. Default 3. */\n minSurvivors?: number\n}\n\n/** Result of the trust gate. `trustworthy` iff every check passed; `trustReasons`\n * is empty iff `trustworthy`. */\nexport interface TrustVerdict {\n /** True iff IRR ≥ floor AND every item's spread ≤ ceiling AND every item has\n * ≥ `minSurvivors` surviving raters. */\n trustworthy: boolean\n /** One entry per FAILED check, each naming its number + the offending value.\n * Empty iff `trustworthy`. */\n trustReasons: string[]\n /** Corpus inter-rater reliability actually measured (the check-1 value). */\n interRaterReliability: number\n /** Per-item spread (`max − min` over surviving raters, max over dimensions),\n * keyed by `itemId`. The check-2 input, surfaced for drill-down. */\n perItemSpread: Record<string, number>\n}\n\nconst DEFAULT_IRR_FLOOR = 0.2\nconst DEFAULT_SPREAD_CEILING = 0.5\nconst DEFAULT_MIN_SURVIVORS = 3\n\n/** Surviving (non-failed) verdicts for an item — those with a real\n * `perDimension` map. A failed judge carries no scores and is excluded from\n * every statistic (it is NOT a zero rater). */\nfunction survivors<D extends string>(item: TrustItem<D>): JudgeVerdict<D>[] {\n return item.verdicts.filter((v) => v.perDimension !== null)\n}\n\n/**\n * Within-item rater spread: for each dimension, `max − min` across the item's\n * surviving raters; the item's spread is the max over its dimensions (the worst\n * dimension the raters split on). Pooled ONLY within this one item — never\n * across items — so a quality gap between items cannot inflate it.\n */\nfunction itemSpread<D extends string>(survivorVerdicts: JudgeVerdict<D>[]): number {\n if (survivorVerdicts.length < 2) return 0\n const dims = new Set<string>()\n for (const v of survivorVerdicts) {\n for (const d of Object.keys(v.perDimension as Record<string, number>)) dims.add(d)\n }\n let worst = 0\n for (const d of dims) {\n let min = Infinity\n let max = -Infinity\n for (const v of survivorVerdicts) {\n const score = (v.perDimension as Record<string, number>)[d]\n if (score === undefined) continue\n if (score < min) min = score\n if (score > max) max = score\n }\n if (max > -Infinity && max - min > worst) worst = max - min\n }\n return worst\n}\n\n/**\n * Decide whether an ensemble's per-item verdicts are trustworthy enough to\n * believe a lift computed from them. Pure: no LLM, no I/O, no clock, no random —\n * the same `items` + `thresholds` always yield the same verdict.\n *\n * Sibling to {@link aggregateJudgeVerdicts}: that reduces ONE item's raters to a\n * composite; this audits the raters ACROSS items and reports whether the\n * composites are believable. Run it on the corpus of held-out items before\n * reporting any lift over their scores.\n *\n * @throws if `items` is empty — an empty corpus has no measurable trust, and a\n * silent `trustworthy: true` over zero evidence is the exact lie the gate\n * exists to refuse.\n */\nexport function trustVerdicts<D extends string>(\n items: readonly TrustItem<D>[],\n thresholds: TrustThresholds = {},\n): TrustVerdict {\n if (items.length === 0) {\n throw new Error('trustVerdicts: items is empty — no evidence to trust')\n }\n const irrFloor = thresholds.irrFloor ?? DEFAULT_IRR_FLOOR\n const spreadCeiling = thresholds.spreadCeiling ?? DEFAULT_SPREAD_CEILING\n const minSurvivors = thresholds.minSurvivors ?? DEFAULT_MIN_SURVIVORS\n\n // Rater-major JudgeScore series for the substrate's IRR. Each item's surviving\n // raters are assigned a stable column index so the same rater across items\n // lines up; per (item, dimension) one JudgeScore per rater, in item-then-\n // dimension order — the layout interRaterReliability chunks back into items.\n const maxRaters = items.reduce((m, it) => Math.max(m, survivors(it).length), 0)\n const raterSeries: JudgeScore[][] = Array.from({ length: maxRaters }, () => [])\n const perItemSpread: Record<string, number> = {}\n const splitItems: Array<{ itemId: string; spread: number }> = []\n const starvedItems: Array<{ itemId: string; n: number }> = []\n\n for (const item of items) {\n const surv = survivors(item)\n if (surv.length < minSurvivors) starvedItems.push({ itemId: item.itemId, n: surv.length })\n\n const spread = itemSpread(surv)\n perItemSpread[item.itemId] = spread\n if (spread > spreadCeiling) splitItems.push({ itemId: item.itemId, spread })\n\n if (surv.length >= 2) {\n const dims = Array.from(\n new Set(surv.flatMap((v) => Object.keys(v.perDimension as Record<string, number>))),\n ).sort()\n surv.forEach((v, raterIdx) => {\n // raterIdx < surv.length ≤ maxRaters = raterSeries.length, so the column\n // always exists; the ??= keeps the access provably defined for the type.\n const column = (raterSeries[raterIdx] ??= [])\n const pd = v.perDimension as Record<string, number>\n for (const d of dims) {\n const score = pd[d]\n if (score === undefined) continue\n column.push({\n judgeName: v.model,\n dimension: `${item.itemId}::${d}`,\n score,\n reasoning: v.rationale ?? '',\n })\n }\n })\n }\n }\n\n const irr = interRaterReliability(raterSeries)\n\n const trustReasons: string[] = []\n if (irr < irrFloor) {\n trustReasons.push(`(1) IRR ${round(irr)} < ${irrFloor}`)\n }\n for (const { itemId, spread } of splitItems) {\n trustReasons.push(`(2) item ${itemId} spread ${round(spread)} > ${spreadCeiling} — raters split`)\n }\n for (const { itemId, n } of starvedItems) {\n trustReasons.push(`(3) item ${itemId}: ${n} surviving raters < ${minSurvivors}`)\n }\n\n return {\n trustworthy: trustReasons.length === 0,\n trustReasons,\n interRaterReliability: irr,\n perItemSpread,\n }\n}\n\n/** Round to 2 decimals for stable, readable reason strings. */\nfunction round(n: number): number {\n return Math.round(n * 100) / 100\n}\n"],"mappings":"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","names":["aggregateJudgeVerdicts"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -16,5 +16,5 @@ export { HubExecClient, HubExecClientOptions, HubExecErrorCode, HubExecResult, H
|
|
|
16
16
|
export { JsonObject, KvLike, RateLimitResult, RequestContext, SecurityHeaderOptions, addSecurityHeaders, checkRateLimit, extractRequestContext, parseJsonObjectBody, requireString } from './web/index.js';
|
|
17
17
|
export { BuildRedactedDocumentOptions, DEFAULT_REDACTION_PATTERNS, RedactForIngestionOptions, RedactedDocSegment, RedactedDocument, RedactionPattern, RedactionSpan, RevealResult, RevealSpanOptions, buildRedactedDocument, detectSpans, maskSpans, redactForIngestion, revealSpan } from './redact/index.js';
|
|
18
18
|
export { CompletionRequirement, CompletionVerdict, CorrectnessChecker, ProducedState, RuntimeEventLike, SatisfiedBy, TaskGold, createLlmCorrectnessChecker, extractProducedState, verifyCompletion, weightedComposite } from '@tangle-network/agent-eval';
|
|
19
|
-
export { D as DEFAULT_TANGLE_ROUTER_BASE_URL, R as ResolveModelOptions, T as TangleModelConfig, r as resolveTangleModelConfig } from './model-
|
|
19
|
+
export { C as CreateTangleRouterModelConfigOptions, D as DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR, a as DEFAULT_TANGLE_ROUTER_BASE_URL, R as ResolveModelOptions, b as ResolveUserTangleExecutionKeyForUserOptions, c as ResolveUserTangleExecutionKeyOptions, d as ResolvedTangleExecutionKey, T as TangleBillingEnforcementOptions, e as TangleExecutionEnvironment, f as TangleExecutionKeyError, g as TangleExecutionKeyErrorCode, h as TangleExecutionKeyHttpError, i as TangleExecutionKeySource, j as TangleModelConfig, k as createTangleRouterModelConfig, l as isTangleBillingEnforcementDisabled, m as isTangleExecutionKeyError, r as resolveTangleExecutionEnvironment, n as resolveTangleModelConfig, o as resolveUserTangleExecutionKey, p as resolveUserTangleExecutionKeyForUser, t as tangleExecutionKeyHttpError } from './model-CKzniMMr.js';
|
|
20
20
|
import '@tangle-network/agent-knowledge';
|
package/dist/index.js
CHANGED
|
@@ -99,14 +99,23 @@ import {
|
|
|
99
99
|
createBrokerTokenProvider
|
|
100
100
|
} from "./chunk-YGUNTIT5.js";
|
|
101
101
|
import {
|
|
102
|
+
DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR,
|
|
102
103
|
DEFAULT_TANGLE_ROUTER_BASE_URL,
|
|
104
|
+
TangleExecutionKeyError,
|
|
103
105
|
createAgentRuntime,
|
|
104
106
|
createOpenAICompatStreamTurn,
|
|
107
|
+
createTangleRouterModelConfig,
|
|
108
|
+
isTangleBillingEnforcementDisabled,
|
|
109
|
+
isTangleExecutionKeyError,
|
|
110
|
+
resolveTangleExecutionEnvironment,
|
|
105
111
|
resolveTangleModelConfig,
|
|
112
|
+
resolveUserTangleExecutionKey,
|
|
113
|
+
resolveUserTangleExecutionKeyForUser,
|
|
106
114
|
runAppToolLoop,
|
|
107
115
|
streamAppToolLoop,
|
|
116
|
+
tangleExecutionKeyHttpError,
|
|
108
117
|
toLoopEvents
|
|
109
|
-
} from "./chunk-
|
|
118
|
+
} from "./chunk-SVCJYRVM.js";
|
|
110
119
|
import {
|
|
111
120
|
APP_TOOL_NAMES,
|
|
112
121
|
ToolInputError,
|
|
@@ -134,6 +143,7 @@ export {
|
|
|
134
143
|
DEFAULT_HARNESS,
|
|
135
144
|
DEFAULT_HEADER_NAMES,
|
|
136
145
|
DEFAULT_REDACTION_PATTERNS,
|
|
146
|
+
DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR,
|
|
137
147
|
DEFAULT_TANGLE_ROUTER_BASE_URL,
|
|
138
148
|
DELEGATION_MCP_SERVER_KEY,
|
|
139
149
|
DELEGATION_TOOLS,
|
|
@@ -141,6 +151,7 @@ export {
|
|
|
141
151
|
KNOWN_HARNESSES,
|
|
142
152
|
PRESET_MIGRATION_SQL,
|
|
143
153
|
PRESET_TABLES,
|
|
154
|
+
TangleExecutionKeyError,
|
|
144
155
|
ToolInputError,
|
|
145
156
|
addSecurityHeaders,
|
|
146
157
|
agentAppConfigJsonSchema,
|
|
@@ -173,6 +184,7 @@ export {
|
|
|
173
184
|
createPresetWorkspaceKeyManager,
|
|
174
185
|
createPresetWorkspaceKeyStore,
|
|
175
186
|
createReviewerDecider,
|
|
187
|
+
createTangleRouterModelConfig,
|
|
176
188
|
createTcloudKeyProvisioner,
|
|
177
189
|
createTokenRecallChecker,
|
|
178
190
|
createWorkspaceKeyManager,
|
|
@@ -198,6 +210,8 @@ export {
|
|
|
198
210
|
invokeIntegrationHub,
|
|
199
211
|
isAppToolName,
|
|
200
212
|
isHarness,
|
|
213
|
+
isTangleBillingEnforcementDisabled,
|
|
214
|
+
isTangleExecutionKeyError,
|
|
201
215
|
maskSpans,
|
|
202
216
|
mergePersistedPart,
|
|
203
217
|
messageHasTurnId,
|
|
@@ -214,13 +228,17 @@ export {
|
|
|
214
228
|
resolveChatTurn,
|
|
215
229
|
resolveIntegrationAction,
|
|
216
230
|
resolveSessionHarness,
|
|
231
|
+
resolveTangleExecutionEnvironment,
|
|
217
232
|
resolveTangleModelConfig,
|
|
218
233
|
resolveToolId,
|
|
219
234
|
resolveToolName,
|
|
235
|
+
resolveUserTangleExecutionKey,
|
|
236
|
+
resolveUserTangleExecutionKeyForUser,
|
|
220
237
|
revealSpan,
|
|
221
238
|
reviewCandidate,
|
|
222
239
|
runAppToolLoop,
|
|
223
240
|
streamAppToolLoop,
|
|
241
|
+
tangleExecutionKeyHttpError,
|
|
224
242
|
toLoopEvents,
|
|
225
243
|
verifyCapabilityToken,
|
|
226
244
|
verifyCompletion,
|
|
@@ -2,7 +2,7 @@ import { KnowledgeResearchLoopContext, AddSourceTextInput, SourceAdapter, RunKno
|
|
|
2
2
|
import { KnowledgeSourceSpec, AgentKnowledgeConfig } from '../config/index.js';
|
|
3
3
|
import '../knowledge/index.js';
|
|
4
4
|
import '@tangle-network/agent-eval';
|
|
5
|
-
import '../model-
|
|
5
|
+
import '../model-CKzniMMr.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* `@tangle-network/agent-app/knowledge-loop` — wire the declarative
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the model config a Tangle agent's sandbox/runtime runs on.
|
|
3
|
+
*
|
|
4
|
+
* Every Tangle agent product resolves the SAME thing from env: the Tangle Router
|
|
5
|
+
* (OpenAI-compatible, metered at the platform markup against a single
|
|
6
|
+
* `TANGLE_API_KEY`) by default, with a direct-Anthropic BYOK escape hatch. The
|
|
7
|
+
* shape feeds the sandbox SDK's `backend.model`. Lifted here so no product
|
|
8
|
+
* hand-rolls the env parsing + the router default.
|
|
9
|
+
*/
|
|
10
|
+
interface TangleModelConfig {
|
|
11
|
+
/** The Tangle Router is OpenAI-compatible → driven via `openai-compat`.
|
|
12
|
+
* `anthropic` is the BYOK escape hatch. */
|
|
13
|
+
provider: 'openai-compat' | 'anthropic';
|
|
14
|
+
model: string;
|
|
15
|
+
apiKey: string;
|
|
16
|
+
baseUrl: string;
|
|
17
|
+
}
|
|
18
|
+
type TangleExecutionEnvironment = 'development' | 'staging' | 'production' | 'test';
|
|
19
|
+
type TangleExecutionKeySource = 'local-env' | 'user';
|
|
20
|
+
type TangleExecutionKeyErrorCode = 'local_tangle_api_key_required' | 'tangle_account_not_connected';
|
|
21
|
+
interface ResolveModelOptions {
|
|
22
|
+
/** Env to read (defaults to process.env). */
|
|
23
|
+
env?: Record<string, string | undefined>;
|
|
24
|
+
/** Router base URL default when `TANGLE_ROUTER_BASE_URL` is unset. */
|
|
25
|
+
defaultRouterBaseUrl?: string;
|
|
26
|
+
}
|
|
27
|
+
interface ResolveUserTangleExecutionKeyOptions {
|
|
28
|
+
/** Deployment context. Only local development may fall back to env keys. */
|
|
29
|
+
environment?: TangleExecutionEnvironment;
|
|
30
|
+
/** Env to read for the local-development fallback. */
|
|
31
|
+
env?: Record<string, string | undefined>;
|
|
32
|
+
/** App-owned lookup for the caller's linked platform API key. */
|
|
33
|
+
getUserApiKey: () => string | null | undefined | Promise<string | null | undefined>;
|
|
34
|
+
}
|
|
35
|
+
interface ResolveUserTangleExecutionKeyForUserOptions<UserId = string> {
|
|
36
|
+
userId: UserId;
|
|
37
|
+
environment?: TangleExecutionEnvironment;
|
|
38
|
+
env?: Record<string, string | undefined>;
|
|
39
|
+
getUserApiKey: (userId: UserId) => string | null | undefined | Promise<string | null | undefined>;
|
|
40
|
+
}
|
|
41
|
+
interface ResolvedTangleExecutionKey {
|
|
42
|
+
apiKey: string;
|
|
43
|
+
source: TangleExecutionKeySource;
|
|
44
|
+
}
|
|
45
|
+
interface TangleExecutionKeyHttpError {
|
|
46
|
+
status: number;
|
|
47
|
+
body: {
|
|
48
|
+
error: string;
|
|
49
|
+
code: TangleExecutionKeyErrorCode;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
interface CreateTangleRouterModelConfigOptions {
|
|
53
|
+
apiKey: string;
|
|
54
|
+
model: string;
|
|
55
|
+
baseUrl?: string;
|
|
56
|
+
}
|
|
57
|
+
interface TangleBillingEnforcementOptions {
|
|
58
|
+
/** Env to read (defaults to process.env). */
|
|
59
|
+
env?: Record<string, string | undefined>;
|
|
60
|
+
/**
|
|
61
|
+
* Optional app-specific override flag, e.g. `GTM_BILLING_ENFORCEMENT`.
|
|
62
|
+
* Defaults to the shared `TANGLE_BILLING_ENFORCEMENT`.
|
|
63
|
+
*/
|
|
64
|
+
enforcementEnvVar?: string;
|
|
65
|
+
}
|
|
66
|
+
declare const DEFAULT_TANGLE_ROUTER_BASE_URL = "https://router.tangle.tools/v1";
|
|
67
|
+
declare const DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR = "TANGLE_BILLING_ENFORCEMENT";
|
|
68
|
+
declare class TangleExecutionKeyError extends Error {
|
|
69
|
+
readonly code: TangleExecutionKeyErrorCode;
|
|
70
|
+
readonly status: number;
|
|
71
|
+
constructor(code: TangleExecutionKeyErrorCode, message: string, status: number);
|
|
72
|
+
}
|
|
73
|
+
declare function isTangleExecutionKeyError(error: unknown): error is TangleExecutionKeyError;
|
|
74
|
+
declare function resolveTangleExecutionEnvironment(env?: Record<string, string | undefined>): TangleExecutionEnvironment;
|
|
75
|
+
/**
|
|
76
|
+
* Shared policy for agent products that bill through the Tangle Platform.
|
|
77
|
+
*
|
|
78
|
+
* Local development defaults billing enforcement off so apps can use a local
|
|
79
|
+
* `TANGLE_API_KEY` without requiring a browser-linked platform account. Any
|
|
80
|
+
* non-development environment defaults enforcement on. Apps may pass their own
|
|
81
|
+
* override flag (`FOO_BILLING_ENFORCEMENT`) while new apps can use the shared
|
|
82
|
+
* `TANGLE_BILLING_ENFORCEMENT`.
|
|
83
|
+
*/
|
|
84
|
+
declare function isTangleBillingEnforcementDisabled(opts?: TangleBillingEnforcementOptions): boolean;
|
|
85
|
+
declare function tangleExecutionKeyHttpError(error: unknown): TangleExecutionKeyHttpError | null;
|
|
86
|
+
/**
|
|
87
|
+
* Resolve the user-facing Tangle API key for model execution.
|
|
88
|
+
*
|
|
89
|
+
* Local development may use a server env key so apps remain easy to run.
|
|
90
|
+
* Deployed contexts must use the caller's linked platform key; this keeps
|
|
91
|
+
* model execution, billing, and account ownership aligned across products.
|
|
92
|
+
*/
|
|
93
|
+
declare function resolveUserTangleExecutionKey(opts: ResolveUserTangleExecutionKeyOptions): Promise<ResolvedTangleExecutionKey>;
|
|
94
|
+
declare function resolveUserTangleExecutionKeyForUser<UserId = string>(opts: ResolveUserTangleExecutionKeyForUserOptions<UserId>): Promise<ResolvedTangleExecutionKey>;
|
|
95
|
+
/**
|
|
96
|
+
* Build an OpenAI-compatible Tangle Router model config from an already
|
|
97
|
+
* resolved execution key. This intentionally does not read TANGLE_API_KEY.
|
|
98
|
+
*/
|
|
99
|
+
declare function createTangleRouterModelConfig(opts: CreateTangleRouterModelConfigOptions): TangleModelConfig;
|
|
100
|
+
/**
|
|
101
|
+
* Resolve the model config from env. DEFAULT path (`MODEL_PROVIDER` unset or
|
|
102
|
+
* `openai-compat`/`tangle-router`/`tcloud`): the Tangle Router, authenticated
|
|
103
|
+
* with `TANGLE_API_KEY`, model from `MODEL_NAME`. BYOK path
|
|
104
|
+
* (`MODEL_PROVIDER=anthropic`): direct Anthropic with `ANTHROPIC_API_KEY` +
|
|
105
|
+
* `ANTHROPIC_BASE_URL`. Throws (fail-loud) on a missing required var so a
|
|
106
|
+
* misconfigured deploy fails at boot, not mid-turn.
|
|
107
|
+
*/
|
|
108
|
+
declare function resolveTangleModelConfig(opts?: ResolveModelOptions): TangleModelConfig;
|
|
109
|
+
|
|
110
|
+
export { type CreateTangleRouterModelConfigOptions as C, DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR as D, type ResolveModelOptions as R, type TangleBillingEnforcementOptions as T, DEFAULT_TANGLE_ROUTER_BASE_URL as a, type ResolveUserTangleExecutionKeyForUserOptions as b, type ResolveUserTangleExecutionKeyOptions as c, type ResolvedTangleExecutionKey as d, type TangleExecutionEnvironment as e, TangleExecutionKeyError as f, type TangleExecutionKeyErrorCode as g, type TangleExecutionKeyHttpError as h, type TangleExecutionKeySource as i, type TangleModelConfig as j, createTangleRouterModelConfig as k, isTangleBillingEnforcementDisabled as l, isTangleExecutionKeyError as m, resolveTangleModelConfig as n, resolveUserTangleExecutionKey as o, resolveUserTangleExecutionKeyForUser as p, resolveTangleExecutionEnvironment as r, tangleExecutionKeyHttpError as t };
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { D as DEFAULT_TANGLE_ROUTER_BASE_URL, R as ResolveModelOptions, T as TangleModelConfig, r as resolveTangleModelConfig } from '../model-
|
|
1
|
+
export { C as CreateTangleRouterModelConfigOptions, D as DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR, a as DEFAULT_TANGLE_ROUTER_BASE_URL, R as ResolveModelOptions, b as ResolveUserTangleExecutionKeyForUserOptions, c as ResolveUserTangleExecutionKeyOptions, d as ResolvedTangleExecutionKey, T as TangleBillingEnforcementOptions, e as TangleExecutionEnvironment, f as TangleExecutionKeyError, g as TangleExecutionKeyErrorCode, h as TangleExecutionKeyHttpError, i as TangleExecutionKeySource, j as TangleModelConfig, k as createTangleRouterModelConfig, l as isTangleBillingEnforcementDisabled, m as isTangleExecutionKeyError, r as resolveTangleExecutionEnvironment, n as resolveTangleModelConfig, o as resolveUserTangleExecutionKey, p as resolveUserTangleExecutionKeyForUser, t as tangleExecutionKeyHttpError } from '../model-CKzniMMr.js';
|
|
2
2
|
import { b as AppToolContext, e as AppToolProducedEvent, f as AppToolTaxonomy, c as AppToolHandlers, d as AppToolOutcome } from '../types-CeWor4bQ.js';
|
|
3
3
|
|
|
4
4
|
/**
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,20 +1,38 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR,
|
|
2
3
|
DEFAULT_TANGLE_ROUTER_BASE_URL,
|
|
4
|
+
TangleExecutionKeyError,
|
|
3
5
|
createAgentRuntime,
|
|
4
6
|
createOpenAICompatStreamTurn,
|
|
7
|
+
createTangleRouterModelConfig,
|
|
8
|
+
isTangleBillingEnforcementDisabled,
|
|
9
|
+
isTangleExecutionKeyError,
|
|
10
|
+
resolveTangleExecutionEnvironment,
|
|
5
11
|
resolveTangleModelConfig,
|
|
12
|
+
resolveUserTangleExecutionKey,
|
|
13
|
+
resolveUserTangleExecutionKeyForUser,
|
|
6
14
|
runAppToolLoop,
|
|
7
15
|
streamAppToolLoop,
|
|
16
|
+
tangleExecutionKeyHttpError,
|
|
8
17
|
toLoopEvents
|
|
9
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-SVCJYRVM.js";
|
|
10
19
|
import "../chunk-LT2YIMEB.js";
|
|
11
20
|
export {
|
|
21
|
+
DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR,
|
|
12
22
|
DEFAULT_TANGLE_ROUTER_BASE_URL,
|
|
23
|
+
TangleExecutionKeyError,
|
|
13
24
|
createAgentRuntime,
|
|
14
25
|
createOpenAICompatStreamTurn,
|
|
26
|
+
createTangleRouterModelConfig,
|
|
27
|
+
isTangleBillingEnforcementDisabled,
|
|
28
|
+
isTangleExecutionKeyError,
|
|
29
|
+
resolveTangleExecutionEnvironment,
|
|
15
30
|
resolveTangleModelConfig,
|
|
31
|
+
resolveUserTangleExecutionKey,
|
|
32
|
+
resolveUserTangleExecutionKeyForUser,
|
|
16
33
|
runAppToolLoop,
|
|
17
34
|
streamAppToolLoop,
|
|
35
|
+
tangleExecutionKeyHttpError,
|
|
18
36
|
toLoopEvents
|
|
19
37
|
};
|
|
20
38
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"packageManager": "pnpm@10.33.4",
|
|
5
|
-
"description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent
|
|
5
|
+
"description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent\u2192app tool side channel, integration-hub client, per-workspace billing, and crypto \u2014 composed over the Tangle agent substrate through typed seams.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tangle",
|
|
8
8
|
"ai-agent",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"main": "./dist/index.js",
|
|
29
29
|
"types": "./dist/index.d.ts",
|
|
30
30
|
"files": [
|
|
31
|
-
"dist"
|
|
31
|
+
"dist",
|
|
32
|
+
".claude/skills"
|
|
32
33
|
],
|
|
33
34
|
"exports": {
|
|
34
35
|
".": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runtime/model.ts","../src/runtime/openai-stream.ts","../src/runtime/agent.ts","../src/runtime/index.ts"],"sourcesContent":["/**\n * Resolve the model config a Tangle agent's sandbox/runtime runs on.\n *\n * Every Tangle agent product resolves the SAME thing from env: the Tangle Router\n * (OpenAI-compatible, metered at the platform markup against a single\n * `TANGLE_API_KEY`) by default, with a direct-Anthropic BYOK escape hatch. The\n * shape feeds the sandbox SDK's `backend.model`. Lifted here so no product\n * hand-rolls the env parsing + the router default.\n */\n\nexport interface TangleModelConfig {\n /** The Tangle Router is OpenAI-compatible → driven via `openai-compat`.\n * `anthropic` is the BYOK escape hatch. */\n provider: 'openai-compat' | 'anthropic'\n model: string\n apiKey: string\n baseUrl: string\n}\n\nexport interface ResolveModelOptions {\n /** Env to read (defaults to process.env). */\n env?: Record<string, string | undefined>\n /** Router base URL default when `TANGLE_ROUTER_BASE_URL` is unset. */\n defaultRouterBaseUrl?: string\n}\n\nexport const DEFAULT_TANGLE_ROUTER_BASE_URL = 'https://router.tangle.tools/v1'\n\nfunction requireEnv(env: Record<string, string | undefined>, name: string): string {\n const value = env[name]?.trim()\n if (!value) throw new Error(`${name} is required`)\n return value\n}\n\n/**\n * Resolve the model config from env. DEFAULT path (`MODEL_PROVIDER` unset or\n * `openai-compat`/`tangle-router`/`tcloud`): the Tangle Router, authenticated\n * with `TANGLE_API_KEY`, model from `MODEL_NAME`. BYOK path\n * (`MODEL_PROVIDER=anthropic`): direct Anthropic with `ANTHROPIC_API_KEY` +\n * `ANTHROPIC_BASE_URL`. Throws (fail-loud) on a missing required var so a\n * misconfigured deploy fails at boot, not mid-turn.\n */\nexport function resolveTangleModelConfig(opts: ResolveModelOptions = {}): TangleModelConfig {\n const env = opts.env ?? (process.env as Record<string, string | undefined>)\n const provider = env.MODEL_PROVIDER?.trim() || 'openai-compat'\n const model = requireEnv(env, 'MODEL_NAME')\n\n if (provider === 'openai-compat' || provider === 'tangle-router' || provider === 'tcloud') {\n return {\n provider: 'openai-compat',\n model,\n apiKey: requireEnv(env, 'TANGLE_API_KEY'),\n baseUrl: (env.TANGLE_ROUTER_BASE_URL?.trim() || opts.defaultRouterBaseUrl || DEFAULT_TANGLE_ROUTER_BASE_URL).replace(/\\/+$/, ''),\n }\n }\n\n if (provider === 'anthropic') {\n return {\n provider,\n model,\n apiKey: requireEnv(env, 'ANTHROPIC_API_KEY'),\n baseUrl: requireEnv(env, 'ANTHROPIC_BASE_URL'),\n }\n }\n\n throw new Error(`Unsupported MODEL_PROVIDER: ${provider} (use openai-compat for the Tangle Router, or anthropic for BYOK)`)\n}\n","/**\n * OpenAI-compatible stream → `LoopEvent` adapter, for NON-sandbox copilots.\n *\n * `streamAppToolLoop` takes a `streamTurn` seam that yields `LoopEvent`s. A\n * sandboxed agent produces those from its container; a browser/edge copilot\n * instead calls a model directly. The Tangle Router, the tcloud SDK, and most\n * providers all speak the OpenAI Chat Completions streaming shape — so the ONE\n * reusable piece is assembling that stream (content deltas + FRAGMENTED\n * tool-call deltas) into `LoopEvent`s. That assembly is the boilerplate every\n * copilot would re-write (and get wrong — OpenAI streams tool-call arguments in\n * pieces across chunks).\n *\n * This does NOT implement an HTTP client beyond a minimal `fetch` + SSE reader\n * (browser/edge/Node-safe, zero deps). For richer transport use the tcloud SDK\n * or the Vercel AI SDK and pipe their stream through {@link toLoopEvents}.\n */\nimport type { LoopEvent, LoopToolCall } from './index'\n\n/** Minimal OpenAI Chat Completions streaming chunk (structural — no `openai` dep). */\nexport interface OpenAIStreamChunk {\n choices?: Array<{\n delta?: {\n content?: string | null\n tool_calls?: Array<{\n index: number\n id?: string\n function?: { name?: string; arguments?: string }\n }>\n }\n finish_reason?: string | null\n }>\n}\n\ninterface PartialToolCall {\n id?: string\n name: string\n args: string\n}\n\n/**\n * Map an OpenAI-compat streaming chunk iterator to `LoopEvent`s: each content\n * delta → a `text` event; tool-call deltas are accumulated by index across\n * chunks and emitted as one complete `tool_call` event when the stream finishes\n * (arguments JSON-parsed; an empty/garbled args string yields `{}` rather than\n * throwing). Works for the Tangle Router, tcloud, or any OpenAI-compat source.\n */\nexport async function* toLoopEvents(chunks: AsyncIterable<OpenAIStreamChunk>): AsyncIterable<LoopEvent> {\n const calls = new Map<number, PartialToolCall>()\n for await (const chunk of chunks) {\n const choice = chunk.choices?.[0]\n if (!choice) continue\n const content = choice.delta?.content\n if (content) yield { type: 'text', text: content }\n for (const tc of choice.delta?.tool_calls ?? []) {\n const cur = calls.get(tc.index) ?? { name: '', args: '' }\n if (tc.id) cur.id = tc.id\n if (tc.function?.name) cur.name += tc.function.name\n if (tc.function?.arguments) cur.args += tc.function.arguments\n calls.set(tc.index, cur)\n }\n }\n for (const [, c] of [...calls.entries()].sort((a, b) => a[0] - b[0])) {\n if (!c.name) continue\n yield { type: 'tool_call', call: { toolCallId: c.id, toolName: c.name, args: safeParse(c.args) } satisfies LoopToolCall }\n }\n}\n\nfunction safeParse(s: string): Record<string, unknown> {\n if (!s.trim()) return {}\n try {\n const v = JSON.parse(s)\n return v && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, unknown>) : {}\n } catch {\n return {}\n }\n}\n\nexport interface OpenAICompatStreamTurnOptions {\n /** OpenAI-compat base URL (e.g. the Tangle Router `https://router.tangle.tools/v1`). */\n baseUrl: string\n apiKey: string\n model: string\n /** OpenAI tool definitions — pass `buildAppToolOpenAITools(taxonomy)` so the\n * model can call the app tools. Omit for a tool-free copilot. */\n tools?: unknown[]\n temperature?: number\n fetchImpl?: typeof fetch\n /** Extra body fields (e.g. `max_tokens`). */\n extraBody?: Record<string, unknown>\n}\n\n/**\n * Build a `streamTurn` that calls an OpenAI-compatible `/chat/completions`\n * endpoint (Tangle Router / tcloud / any compat provider) with `stream: true`\n * and yields `LoopEvent`s via {@link toLoopEvents}. Browser/edge/Node-safe —\n * just `fetch` + an SSE reader. Drop straight into `streamAppToolLoop`:\n *\n * const cfg = resolveTangleModelConfig() // or { baseUrl, apiKey, model }\n * streamAppToolLoop({ streamTurn: createOpenAICompatStreamTurn({ ...cfg, tools }), executeToolCall, ... })\n */\nexport function createOpenAICompatStreamTurn(\n opts: OpenAICompatStreamTurnOptions,\n): (messages: Array<{ role: string; content: string }>) => AsyncIterable<LoopEvent> {\n const base = opts.baseUrl.replace(/\\/+$/, '')\n const doFetch = opts.fetchImpl ?? fetch\n return (messages) =>\n toLoopEvents(\n streamChatCompletions(doFetch, `${base}/chat/completions`, opts.apiKey, {\n model: opts.model,\n messages,\n stream: true,\n ...(opts.tools && opts.tools.length > 0 ? { tools: opts.tools } : {}),\n ...(opts.temperature != null ? { temperature: opts.temperature } : {}),\n ...opts.extraBody,\n }),\n )\n}\n\n/** Stream + parse an OpenAI-compat SSE response into chunks. Tolerates `data:`\n * framing, multi-line buffers, and the terminal `[DONE]`. */\nasync function* streamChatCompletions(\n doFetch: typeof fetch,\n url: string,\n apiKey: string,\n body: Record<string, unknown>,\n): AsyncIterable<OpenAIStreamChunk> {\n const res = await doFetch(url, {\n method: 'POST',\n headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', Accept: 'text/event-stream' },\n body: JSON.stringify(body),\n })\n if (!res.ok || !res.body) {\n const text = res.body ? await res.text().catch(() => '') : ''\n throw new Error(`OpenAI-compat stream failed (HTTP ${res.status})${text ? `: ${text.slice(0, 200)}` : ''}`)\n }\n const reader = res.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n for (const line of lines) {\n const trimmed = line.trim()\n if (!trimmed.startsWith('data:')) continue\n const data = trimmed.slice(5).trim()\n if (data === '[DONE]') return\n try {\n yield JSON.parse(data) as OpenAIStreamChunk\n } catch {\n /* skip a partial/garbled SSE frame */\n }\n }\n }\n}\n","/**\n * `createAgentRuntime` — the in-process agent core, assembled.\n *\n * The bricks to run an agent turn WITHOUT a sandbox already exist in this\n * package, but a consumer must hand-wire five of them every time: resolve the\n * model config, build the OpenAI tool schemas from the taxonomy, build a\n * `streamTurn` over the model endpoint, build an `executeToolCall` over the\n * product's handlers, and drive `runAppToolLoop` / `streamAppToolLoop` with an\n * `isExecutableTool` predicate. That boilerplate is identical across every\n * sandbox-free surface (an edge/browser copilot, an eval harness, a Node CLI),\n * and getting it subtly wrong — e.g. NOT advertising the tools, so the model\n * never emits a `tool_call` and no side effect ever fires — is exactly the\n * failure that makes a tool-driven agent score zero off-sandbox.\n *\n * This factory bundles those five into one object configured for ONE agent:\n *\n * const runtime = createAgentRuntime({ model, taxonomy, handlers, systemPrompt })\n * const result = await runtime.run(userMessage, { ctx }) // awaitable\n * for await (const y of runtime.stream(userMessage, { ctx })) {…} // streaming\n *\n * The model is advertised the app tools (so it CAN call them); each call is\n * dispatched against the product's `handlers` (so the side effect is real); the\n * `onProduced` hook fires at the real side-effect site (so an eval/UI credits a\n * persisted proposal or artifact). Substrate-free: no `@tangle-network/sandbox`,\n * no Durable Object, no `@tangle-network/agent-runtime` import. The SAME core\n * the Cloudflare Worker runs, runnable anywhere a `fetch` to an OpenAI-compatible\n * endpoint works.\n *\n * Domain stays out: the proposal taxonomy, the handlers, and the system prompt\n * are all injected — the factory knows nothing about insurance, law, tax, etc.\n */\nimport {\n type AppToolHandlers,\n type AppToolContext,\n type AppToolOutcome,\n type AppToolProducedEvent,\n type AppToolTaxonomy,\n} from '../tools/types'\nimport { buildAppToolOpenAITools, isAppToolName } from '../tools/openai'\nimport { createAppToolRuntimeExecutor } from '../tools/runtime'\nimport {\n runAppToolLoop,\n streamAppToolLoop,\n type LoopEvent,\n type LoopToolCall,\n type StreamLoopYield,\n type ToolLoopResult,\n} from './index'\nimport { createOpenAICompatStreamTurn } from './openai-stream'\n\n/** OpenAI-compatible model endpoint (Tangle Router / tcloud / any compat\n * provider). Build from {@link resolveTangleModelConfig} or pass literals. */\nexport interface AgentRuntimeModelConfig {\n baseUrl: string\n apiKey: string\n model: string\n temperature?: number\n fetchImpl?: typeof fetch\n /** Extra request-body fields (e.g. `max_tokens`, a `reasoning` block). */\n extraBody?: Record<string, unknown>\n}\n\nexport interface CreateAgentRuntimeOptions {\n /** The model endpoint the turns stream from. */\n model: AgentRuntimeModelConfig\n /** The product's proposal taxonomy — advertises `submit_proposal`'s `type`\n * enum to the model and labels the regulated subset on the result. */\n taxonomy: AppToolTaxonomy\n /** Domain handlers persisting each tool to the product's store/vault. */\n handlers: AppToolHandlers\n /** Default agent identity / system prompt. A turn may override it. */\n systemPrompt: string\n /** Max tool-driven re-runs per turn. Default 8. */\n maxToolTurns?: number\n /** Extra OpenAI tool definitions advertised ALONGSIDE the four app tools\n * (e.g. `integration_invoke`). Pair with {@link executeOtherTool}. */\n extraTools?: unknown[]\n /** Execute a tool that is NOT one of the four app tools (e.g. an integration\n * action). Only consulted for names {@link isOtherExecutableTool} accepts. */\n executeOtherTool?: (call: LoopToolCall, ctx: AppToolContext) => Promise<AppToolOutcome>\n /** Which non-app tool names are executable here. Required if {@link executeOtherTool} is set. */\n isOtherExecutableTool?: (toolName: string) => boolean\n}\n\nexport interface AgentTurnOptions {\n /** The trusted per-turn context (who/where the turn runs as). */\n ctx: AppToolContext\n /** Prior conversation turns, in order. */\n priorMessages?: Array<{ role: string; content: string }>\n /** Override the factory's default system prompt for this turn. */\n systemPrompt?: string\n /** Fires at the real side-effect site for each produced proposal/artifact. */\n onProduced?: (event: AppToolProducedEvent) => void\n}\n\nexport interface AgentRuntime {\n /** Run the bounded tool loop to completion; resolve with final text + every\n * executed tool outcome. */\n run(userMessage: string, turn: AgentTurnOptions): Promise<ToolLoopResult>\n /** Stream the bounded tool loop: yields each raw model event and each executed\n * tool result as it happens (for SSE re-emission + telemetry). */\n stream(userMessage: string, turn: AgentTurnOptions): AsyncGenerator<StreamLoopYield<LoopEvent>, void, unknown>\n}\n\n/**\n * Create an in-process agent runtime for one agent. See the module doc for the\n * full rationale; the short version: it advertises the app tools to the model,\n * dispatches each emitted call against `handlers`, and drives the bounded loop —\n * the whole agent core, sandbox-free.\n */\nexport function createAgentRuntime(opts: CreateAgentRuntimeOptions): AgentRuntime {\n if (opts.executeOtherTool && !opts.isOtherExecutableTool) {\n throw new Error('createAgentRuntime: isOtherExecutableTool is required when executeOtherTool is set')\n }\n\n // Tool schemas + the streamTurn are stable across turns — build once. The\n // model MUST be advertised the tools or it never emits a tool_call (the exact\n // failure that scores a tool-driven agent zero off-sandbox).\n const tools = [...buildAppToolOpenAITools(opts.taxonomy), ...(opts.extraTools ?? [])]\n const m = opts.model\n const streamTurn = createOpenAICompatStreamTurn({\n baseUrl: m.baseUrl,\n apiKey: m.apiKey,\n model: m.model,\n tools,\n temperature: m.temperature,\n fetchImpl: m.fetchImpl,\n extraBody: m.extraBody,\n })\n\n const isExecutableTool = (name: string): boolean =>\n isAppToolName(name) || (opts.isOtherExecutableTool?.(name) ?? false)\n\n const buildExecutor = (turn: AgentTurnOptions) => {\n const appExecutor = createAppToolRuntimeExecutor({\n handlers: opts.handlers,\n taxonomy: opts.taxonomy,\n ctx: turn.ctx,\n onProduced: turn.onProduced,\n })\n return async (call: LoopToolCall): Promise<AppToolOutcome> => {\n if (isAppToolName(call.toolName)) return appExecutor({ toolName: call.toolName, args: call.args })\n if (opts.executeOtherTool && opts.isOtherExecutableTool?.(call.toolName)) {\n return opts.executeOtherTool(call, turn.ctx)\n }\n return { ok: false, code: 'unknown_tool', message: `No executor for tool: ${call.toolName}` }\n }\n }\n\n return {\n run(userMessage, turn) {\n return runAppToolLoop({\n systemPrompt: turn.systemPrompt ?? opts.systemPrompt,\n userMessage,\n priorMessages: turn.priorMessages,\n streamTurn,\n executeToolCall: buildExecutor(turn),\n isExecutableTool,\n maxToolTurns: opts.maxToolTurns,\n })\n },\n stream(userMessage, turn) {\n return streamAppToolLoop<LoopEvent>({\n systemPrompt: turn.systemPrompt ?? opts.systemPrompt,\n userMessage,\n priorMessages: turn.priorMessages,\n streamTurn,\n extractText: (ev) => (ev.type === 'text' ? ev.text : ''),\n extractToolCall: (ev) => (ev.type === 'tool_call' ? ev.call : null),\n isExecutableTool,\n executeToolCall: buildExecutor(turn),\n maxToolTurns: opts.maxToolTurns,\n })\n },\n }\n}\n","export * from './model'\nexport * from './openai-stream'\nexport * from './agent'\n/**\n * The bounded agent tool-loop — the mechanism every app's chat runtime\n * hand-rolls on top of `@tangle-network/agent-runtime`.\n *\n * A model turn may emit tool calls (integration-hub actions, the app tools from\n * `../tools`, delegation). The loop: stream a turn, collect the executable tool\n * calls, stop if there are none / no executor / the turn cap is hit, otherwise\n * execute each, fold the results back as a message, and re-run so the model\n * reads them. Bounded by `maxToolTurns` so a model looping on a failing action\n * can't run forever.\n *\n * Substrate-free by design: the app supplies `streamTurn` (wrapping whatever\n * backend / `runAgentTaskStream` it uses) and `executeToolCall` (routing to its\n * integration + app-tool executors). This package owns the LOOP; the app owns\n * the model and the executors.\n *\n * LAYERING NOTE: this turn-level tool-dispatch loop is a generic RUNTIME\n * capability. It has been CONTRIBUTED DOWN and MERGED into\n * `@tangle-network/agent-runtime` as `runToolLoop` / `streamToolLoop` (PR #137),\n * but is not yet PUBLISHED (agent-runtime main is ahead of its last npm release;\n * cutting that release is the agent-runtime maintainer's call). TERMINAL STATE:\n * the moment agent-runtime publishes a version carrying #137, bump the\n * `@tangle-network/agent-runtime` peer-dep here and replace the bodies below with\n * a thin re-export — `streamAppToolLoop = streamToolLoop`, `runAppToolLoop =\n * runToolLoop` (types alias 1:1; `AppToolOutcome` ≡ `ToolCallOutcome`). Kept\n * substrate-free + shipping until then so consumers aren't blocked on the release.\n */\nimport type { AppToolOutcome } from '../tools/types'\n\nexport interface LoopToolCall {\n toolCallId?: string\n toolName: string\n args: Record<string, unknown>\n}\n\n/** Events a turn stream yields. `text` accumulates into the final answer;\n * `tool_call` is collected for dispatch. Extra event types pass through\n * untouched (the caller re-emits them to its own UI stream). */\nexport type LoopEvent =\n | { type: 'text'; text: string }\n | { type: 'tool_call'; call: LoopToolCall }\n | { type: 'other'; event: unknown }\n\nexport interface ToolLoopResult {\n /** The model's final text across the loop. */\n finalText: string\n /** Every tool call executed, with its outcome, in order. */\n toolResults: Array<{ call: LoopToolCall; label: string; outcome: AppToolOutcome }>\n /** Number of model turns run (1 + tool-driven re-runs). */\n turns: number\n /** True when the loop stopped because it hit `maxToolTurns` with calls still pending. */\n cappedOut: boolean\n}\n\nexport interface AppToolLoopOptions {\n systemPrompt: string\n userMessage: string\n priorMessages?: Array<{ role: string; content: string }>\n /** Stream one model turn over the running message list. The app wraps its\n * backend here. */\n streamTurn: (messages: Array<{ role: string; content: string }>) => AsyncIterable<LoopEvent>\n /** Execute one tool call. The app routes to its integration executor / app-tool\n * executor and returns the outcome. */\n executeToolCall: (call: LoopToolCall) => Promise<AppToolOutcome>\n /** Which emitted tool names are executable (others are ignored — e.g. a UI-only\n * tool the app renders but doesn't run here). */\n isExecutableTool: (toolName: string) => boolean\n /** Max tool-driven re-runs. Default 8. */\n maxToolTurns?: number\n /** Render one tool outcome as a line the next turn's message carries. Default\n * is a compact `- <label> → ok/failed: …`. */\n renderResult?: (label: string, outcome: AppToolOutcome) => string\n /** Map a tool call to the label its result is keyed under (default: toolName). */\n labelFor?: (call: LoopToolCall) => string\n}\n\nconst DEFAULT_MAX_TOOL_TURNS = 8\n\nfunction defaultRender(label: string, outcome: AppToolOutcome): string {\n if (outcome.ok) return `- ${label} → ok: ${JSON.stringify(outcome.result)}`\n return `- ${label} → failed (${outcome.code}): ${outcome.message}`\n}\n\n/**\n * Run the bounded tool loop and return the final text + every executed tool\n * outcome. Yields nothing — it's an awaitable driver; callers that need to\n * re-emit events to a UI stream should do so inside `streamTurn`. (A streaming\n * variant can wrap this later; keeping the core awaitable makes it trivially\n * testable.)\n */\nexport async function runAppToolLoop(opts: AppToolLoopOptions): Promise<ToolLoopResult> {\n const maxTurns = opts.maxToolTurns ?? DEFAULT_MAX_TOOL_TURNS\n const render = opts.renderResult ?? defaultRender\n const labelFor = opts.labelFor ?? ((c: LoopToolCall) => c.toolName)\n\n const messages: Array<{ role: string; content: string }> = [\n { role: 'system', content: opts.systemPrompt },\n ...(opts.priorMessages ?? []),\n { role: 'user', content: opts.userMessage },\n ]\n\n const toolResults: ToolLoopResult['toolResults'] = []\n let finalText = ''\n let turns = 0\n\n for (let toolTurn = 0; ; toolTurn++) {\n turns++\n let turnText = ''\n const pending: LoopToolCall[] = []\n\n for await (const ev of opts.streamTurn([...messages])) {\n if (ev.type === 'text') {\n turnText += ev.text\n finalText += ev.text\n } else if (ev.type === 'tool_call' && opts.isExecutableTool(ev.call.toolName)) {\n pending.push(ev.call)\n }\n }\n\n if (pending.length === 0) break\n if (toolTurn >= maxTurns) {\n return { finalText, toolResults, turns, cappedOut: true }\n }\n\n // Record the assistant's tool-calling turn so the next turn has its context.\n if (turnText.trim()) messages.push({ role: 'assistant', content: turnText })\n\n const lines: string[] = []\n for (const call of pending) {\n let outcome: AppToolOutcome\n try {\n outcome = await opts.executeToolCall(call)\n } catch (err) {\n outcome = { ok: false, code: 'executor_error', message: err instanceof Error ? err.message : String(err) }\n }\n const label = labelFor(call)\n toolResults.push({ call, label, outcome })\n lines.push(render(label, outcome))\n }\n // Fold every outcome back as one user-role message so the model reads them.\n messages.push({ role: 'user', content: `Tool results:\\n${lines.join('\\n')}` })\n }\n\n return { finalText, toolResults, turns, cappedOut: false }\n}\n\n// ── Streaming variant ──────────────────────────────────────────────────────\n//\n// `runAppToolLoop` is awaitable — perfect for tests and drain-only callers. A\n// real chat runtime instead needs to STREAM each model event to the client (SSE)\n// AND record telemetry per event as it happens. `streamAppToolLoop` is the same\n// bounded loop as an async generator: it yields every raw turn event (the app\n// maps + telemetries + re-emits it) and every executed tool result (same), while\n// owning the loop control flow (collect → stop/dispatch → fold → re-run, capped).\n// `Raw` is the app's own runtime-event type — this package stays substrate-free.\n\nexport type StreamLoopYield<Raw> =\n | { kind: 'event'; event: Raw }\n | { kind: 'tool_result'; toolName: string; toolCallId?: string; label: string; outcome: AppToolOutcome }\n | { kind: 'capped'; pending: number }\n\nexport interface StreamAppToolLoopOptions<Raw> {\n systemPrompt: string\n userMessage: string\n priorMessages?: Array<{ role: string; content: string }>\n /** Stream one model turn (the app wraps its backend / runAgentTaskStream). */\n streamTurn: (messages: Array<{ role: string; content: string }>) => AsyncIterable<Raw>\n /** Text contribution of a raw event, '' if none — used to record the\n * assistant's turn so the next turn has its context. */\n extractText: (event: Raw) => string\n /** The tool call a raw event represents, or null. */\n extractToolCall: (event: Raw) => LoopToolCall | null\n /** Which tool names are executable here (others pass through, unexecuted). */\n isExecutableTool: (toolName: string) => boolean\n /** Execute one call — the app routes to its integration / app-tool executor. */\n executeToolCall: (call: LoopToolCall) => Promise<AppToolOutcome>\n maxToolTurns?: number\n renderResult?: (label: string, outcome: AppToolOutcome) => string\n labelFor?: (call: LoopToolCall) => string\n}\n\n/**\n * The streaming bounded tool loop. Yields `event` for each raw turn event and\n * `tool_result` for each executed tool; emits a single `capped` when it stops at\n * the turn limit with calls still pending. The app drives telemetry + UI\n * emission off the yielded items.\n */\nexport async function* streamAppToolLoop<Raw>(opts: StreamAppToolLoopOptions<Raw>): AsyncGenerator<StreamLoopYield<Raw>, void, unknown> {\n const maxTurns = opts.maxToolTurns ?? DEFAULT_MAX_TOOL_TURNS\n const render = opts.renderResult ?? defaultRender\n const labelFor = opts.labelFor ?? ((c: LoopToolCall) => c.toolName)\n\n const messages: Array<{ role: string; content: string }> = [\n { role: 'system', content: opts.systemPrompt },\n ...(opts.priorMessages ?? []),\n { role: 'user', content: opts.userMessage },\n ]\n\n for (let toolTurn = 0; ; toolTurn++) {\n let turnText = ''\n const pending: LoopToolCall[] = []\n\n for await (const event of opts.streamTurn([...messages])) {\n yield { kind: 'event', event }\n turnText += opts.extractText(event)\n const call = opts.extractToolCall(event)\n if (call && opts.isExecutableTool(call.toolName)) pending.push(call)\n }\n\n if (pending.length === 0) return\n if (toolTurn >= maxTurns) {\n yield { kind: 'capped', pending: pending.length }\n return\n }\n\n if (turnText.trim()) messages.push({ role: 'assistant', content: turnText })\n\n const lines: string[] = []\n for (const call of pending) {\n let outcome: AppToolOutcome\n try {\n outcome = await opts.executeToolCall(call)\n } catch (err) {\n outcome = { ok: false, code: 'executor_error', message: err instanceof Error ? err.message : String(err) }\n }\n const label = labelFor(call)\n yield { kind: 'tool_result', toolName: call.toolName, toolCallId: call.toolCallId, label, outcome }\n lines.push(render(label, outcome))\n }\n messages.push({ role: 'user', content: `Tool results:\\n${lines.join('\\n')}` })\n }\n}\n"],"mappings":"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","names":[]}
|