@graphorin/agent 0.5.0 → 0.6.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/CHANGELOG.md +99 -0
- package/README.md +12 -12
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/errors/index.js.map +1 -1
- package/dist/evaluator-optimizer/index.d.ts +1 -1
- package/dist/evaluator-optimizer/index.js.map +1 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +517 -108
- package/dist/factory.js.map +1 -1
- package/dist/fallback/index.d.ts +1 -1
- package/dist/fallback/index.js.map +1 -1
- package/dist/fanout/index.d.ts +8 -8
- package/dist/fanout/index.js +3 -3
- package/dist/fanout/index.js.map +1 -1
- package/dist/filters/index.d.ts +1 -1
- package/dist/filters/index.js +2 -2
- package/dist/filters/index.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/internal/ids.js +1 -1
- package/dist/internal/ids.js.map +1 -1
- package/dist/internal/usage-accumulator.js +17 -2
- package/dist/internal/usage-accumulator.js.map +1 -1
- package/dist/lateral-leak/causality-monitor.d.ts +4 -4
- package/dist/lateral-leak/causality-monitor.js +3 -3
- package/dist/lateral-leak/causality-monitor.js.map +1 -1
- package/dist/lateral-leak/merge-guard.d.ts +2 -2
- package/dist/lateral-leak/merge-guard.js +1 -1
- package/dist/lateral-leak/merge-guard.js.map +1 -1
- package/dist/lateral-leak/protocol-guard.d.ts +4 -4
- package/dist/lateral-leak/protocol-guard.js +4 -4
- package/dist/lateral-leak/protocol-guard.js.map +1 -1
- package/dist/preferred-model/index.d.ts +2 -2
- package/dist/preferred-model/index.js +2 -2
- package/dist/preferred-model/index.js.map +1 -1
- package/dist/progress/index.js +2 -2
- package/dist/progress/index.js.map +1 -1
- package/dist/run-state/index.d.ts +7 -5
- package/dist/run-state/index.d.ts.map +1 -1
- package/dist/run-state/index.js +33 -4
- package/dist/run-state/index.js.map +1 -1
- package/dist/testing/replay-provider.d.ts +18 -0
- package/dist/testing/replay-provider.d.ts.map +1 -0
- package/dist/testing/replay-provider.js +100 -0
- package/dist/testing/replay-provider.js.map +1 -0
- package/dist/tooling/adapters.js +6 -6
- package/dist/tooling/adapters.js.map +1 -1
- package/dist/tooling/catalogue.js +1 -1
- package/dist/tooling/catalogue.js.map +1 -1
- package/dist/tooling/dataflow.js +19 -3
- package/dist/tooling/dataflow.js.map +1 -1
- package/dist/tooling/plan.js +92 -0
- package/dist/tooling/plan.js.map +1 -0
- package/dist/tooling/policy.js +45 -0
- package/dist/tooling/policy.js.map +1 -0
- package/dist/tooling/registry-build.js +1 -1
- package/dist/tooling/registry-build.js.map +1 -1
- package/dist/types.d.ts +189 -18
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"causality-monitor.js","names":["DEFAULT_DENIAL_PATTERNS: ReadonlyArray<RegExp>","#chain","matchedPattern: string | undefined"],"sources":["../../src/lateral-leak/causality-monitor.ts"],"sourcesContent":["/**\n * `CausalityMonitor`
|
|
1
|
+
{"version":3,"file":"causality-monitor.js","names":["DEFAULT_DENIAL_PATTERNS: ReadonlyArray<RegExp>","#chain","matchedPattern: string | undefined"],"sources":["../../src/lateral-leak/causality-monitor.ts"],"sourcesContent":["/**\n * `CausalityMonitor` - lateral-leak defense primitive that maintains\n * a per-`RunContext` `causalityChain` of bounded depth and refuses\n * to let an assistant message reference information about a denied\n * earlier action.\n *\n * The monitor is opt-in: agents declare `causalityMonitor:\n * { strictness, denialPatterns?, ... }` on `createAgent({...})`.\n * When the field is absent, the runtime instantiates a no-op monitor\n * that records nothing and never flags.\n *\n * @packageDocumentation\n */\n\nimport type { LateralLeakVector } from '@graphorin/core';\n\n/**\n * Operator-tunable strictness level. Default `'detect-and-flag'`\n * for cloud-tier providers; `'detect'` for loopback providers;\n * `'off'` for v0.1-alpha backward compatibility.\n *\n * @stable\n */\nexport type CausalityMonitorStrictness = 'off' | 'detect' | 'detect-and-flag' | 'detect-and-block';\n\n/**\n * Per-agent configuration accepted by `createAgent({ causalityMonitor })`.\n *\n * @stable\n */\nexport interface CausalityMonitorConfig {\n readonly strictness: CausalityMonitorStrictness;\n /** Maximum depth of the chain. Default `32`. */\n readonly maxChainDepth?: number;\n /** Operator-extensible denial patterns. */\n readonly denialPatterns?: ReadonlyArray<RegExp>;\n /**\n * When `true`, emit the chain on every `checkMessage(...)` call\n * (high-cardinality; opt-in for compliance audits). Default\n * `false` - only emit on detected leaks.\n */\n readonly auditAllChains?: boolean;\n}\n\n/**\n * Default denial-pattern catalogue. The agent runtime extends this\n * list when the operator supplies their own patterns.\n *\n * @stable\n */\nexport const DEFAULT_DENIAL_PATTERNS: ReadonlyArray<RegExp> = [\n /SecretAccessDenied/i,\n /ToolApprovalDenied/i,\n /HandoffPermissionDenied/i,\n /SandboxViolation/i,\n];\n\n/** Default chain depth when not specified. */\nexport const DEFAULT_MAX_CHAIN_DEPTH = 32;\n\n/**\n * Result returned by {@link CausalityMonitor.checkMessage}.\n *\n * @stable\n */\nexport interface CausalityMonitorCheck {\n readonly leakDetected: boolean;\n readonly severity: 'info' | 'warn' | 'block';\n readonly causalityChain: ReadonlyArray<string>;\n readonly matchedPattern?: string;\n readonly decision: 'detect' | 'flag' | 'strip' | 'block';\n readonly vector: LateralLeakVector;\n}\n\n/**\n * In-memory primitive instantiated per `RunContext`. Bounded-depth\n * append discipline keeps the memory footprint trivial even on long\n * runs.\n *\n * @stable\n */\nexport class CausalityMonitor {\n readonly strictness: CausalityMonitorStrictness;\n readonly maxChainDepth: number;\n readonly denialPatterns: ReadonlyArray<RegExp>;\n readonly auditAllChains: boolean;\n #chain: string[] = [];\n\n constructor(cfg: CausalityMonitorConfig) {\n this.strictness = cfg.strictness;\n this.maxChainDepth = cfg.maxChainDepth ?? DEFAULT_MAX_CHAIN_DEPTH;\n this.denialPatterns = cfg.denialPatterns\n ? [...DEFAULT_DENIAL_PATTERNS, ...cfg.denialPatterns]\n : DEFAULT_DENIAL_PATTERNS;\n this.auditAllChains = cfg.auditAllChains ?? false;\n }\n\n /** Snapshot the current causality chain. */\n get chain(): ReadonlyArray<string> {\n return this.#chain.slice();\n }\n\n /**\n * Append an entry to the causality chain, dropping the oldest\n * when the chain exceeds `maxChainDepth`. Bounded-length, no PII,\n * no secret values - entries are short opaque strings like\n * `tool:slack-notify`, `tool.error:SecretAccessDenied`,\n * `subagent:research-east`, `compaction:auto-trigger`.\n */\n recordCall(entry: string): void {\n if (this.strictness === 'off') return;\n if (entry.length === 0) return;\n this.#chain.push(entry);\n if (this.#chain.length > this.maxChainDepth) {\n this.#chain.shift();\n }\n }\n\n /** Reset the chain - e.g. on `agent.run` boundary. */\n reset(): void {\n this.#chain = [];\n }\n\n /**\n * Inspect a candidate assistant-visible string and return whether\n * the lateral-leak defense should fire. Pure decision based on\n * the current chain + the operator-extensible denial patterns.\n */\n checkMessage(content: string): CausalityMonitorCheck {\n if (this.strictness === 'off') {\n return {\n leakDetected: false,\n severity: 'info',\n causalityChain: [],\n decision: 'detect',\n vector: 'causality-laundering',\n };\n }\n const chainHasDenial = this.#chain.some((entry) =>\n this.denialPatterns.some((pat) => pat.test(entry)),\n );\n if (!chainHasDenial) {\n return {\n leakDetected: false,\n severity: 'info',\n causalityChain: this.chain,\n decision: 'detect',\n vector: 'causality-laundering',\n };\n }\n let matchedPattern: string | undefined;\n for (const pat of this.denialPatterns) {\n if (pat.test(content)) {\n matchedPattern = pat.source;\n break;\n }\n }\n const leakDetected = matchedPattern !== undefined;\n if (!leakDetected) {\n return {\n leakDetected: false,\n severity: 'info',\n causalityChain: this.chain,\n decision: 'detect',\n vector: 'causality-laundering',\n };\n }\n switch (this.strictness) {\n case 'detect':\n return {\n leakDetected: true,\n severity: 'info',\n causalityChain: this.chain,\n ...(matchedPattern !== undefined ? { matchedPattern } : {}),\n decision: 'detect',\n vector: 'causality-laundering',\n };\n case 'detect-and-flag':\n return {\n leakDetected: true,\n severity: 'warn',\n causalityChain: this.chain,\n ...(matchedPattern !== undefined ? { matchedPattern } : {}),\n decision: 'flag',\n vector: 'causality-laundering',\n };\n case 'detect-and-block':\n return {\n leakDetected: true,\n severity: 'block',\n causalityChain: this.chain,\n ...(matchedPattern !== undefined ? { matchedPattern } : {}),\n decision: 'block',\n vector: 'causality-laundering',\n };\n default: {\n const _exhaustive: never = this.strictness;\n void _exhaustive;\n return {\n leakDetected: false,\n severity: 'info',\n causalityChain: this.chain,\n decision: 'detect',\n vector: 'causality-laundering',\n };\n }\n }\n }\n\n /**\n * Drain the chain to the audit log on `agent.run` completion or\n * `agent.abort`. The runtime supplies the audit emitter - the\n * primitive itself is storage-agnostic.\n */\n flush(reason: 'agent.run.complete' | 'agent.abort'): {\n readonly chain: ReadonlyArray<string>;\n readonly reason: 'agent.run.complete' | 'agent.abort';\n } {\n return { chain: this.chain, reason };\n }\n}\n"],"mappings":";;;;;;;AAkDA,MAAaA,0BAAiD;CAC5D;CACA;CACA;CACA;CACD;;AAGD,MAAa,0BAA0B;;;;;;;;AAuBvC,IAAa,mBAAb,MAA8B;CAC5B,AAAS;CACT,AAAS;CACT,AAAS;CACT,AAAS;CACT,SAAmB,EAAE;CAErB,YAAY,KAA6B;AACvC,OAAK,aAAa,IAAI;AACtB,OAAK,gBAAgB,IAAI,iBAAiB;AAC1C,OAAK,iBAAiB,IAAI,iBACtB,CAAC,GAAG,yBAAyB,GAAG,IAAI,eAAe,GACnD;AACJ,OAAK,iBAAiB,IAAI,kBAAkB;;;CAI9C,IAAI,QAA+B;AACjC,SAAO,MAAKC,MAAO,OAAO;;;;;;;;;CAU5B,WAAW,OAAqB;AAC9B,MAAI,KAAK,eAAe,MAAO;AAC/B,MAAI,MAAM,WAAW,EAAG;AACxB,QAAKA,MAAO,KAAK,MAAM;AACvB,MAAI,MAAKA,MAAO,SAAS,KAAK,cAC5B,OAAKA,MAAO,OAAO;;;CAKvB,QAAc;AACZ,QAAKA,QAAS,EAAE;;;;;;;CAQlB,aAAa,SAAwC;AACnD,MAAI,KAAK,eAAe,MACtB,QAAO;GACL,cAAc;GACd,UAAU;GACV,gBAAgB,EAAE;GAClB,UAAU;GACV,QAAQ;GACT;AAKH,MAAI,CAHmB,MAAKA,MAAO,MAAM,UACvC,KAAK,eAAe,MAAM,QAAQ,IAAI,KAAK,MAAM,CAAC,CACnD,CAEC,QAAO;GACL,cAAc;GACd,UAAU;GACV,gBAAgB,KAAK;GACrB,UAAU;GACV,QAAQ;GACT;EAEH,IAAIC;AACJ,OAAK,MAAM,OAAO,KAAK,eACrB,KAAI,IAAI,KAAK,QAAQ,EAAE;AACrB,oBAAiB,IAAI;AACrB;;AAIJ,MAAI,EADiB,mBAAmB,QAEtC,QAAO;GACL,cAAc;GACd,UAAU;GACV,gBAAgB,KAAK;GACrB,UAAU;GACV,QAAQ;GACT;AAEH,UAAQ,KAAK,YAAb;GACE,KAAK,SACH,QAAO;IACL,cAAc;IACd,UAAU;IACV,gBAAgB,KAAK;IACrB,GAAI,mBAAmB,SAAY,EAAE,gBAAgB,GAAG,EAAE;IAC1D,UAAU;IACV,QAAQ;IACT;GACH,KAAK,kBACH,QAAO;IACL,cAAc;IACd,UAAU;IACV,gBAAgB,KAAK;IACrB,GAAI,mBAAmB,SAAY,EAAE,gBAAgB,GAAG,EAAE;IAC1D,UAAU;IACV,QAAQ;IACT;GACH,KAAK,mBACH,QAAO;IACL,cAAc;IACd,UAAU;IACV,gBAAgB,KAAK;IACrB,GAAI,mBAAmB,SAAY,EAAE,gBAAgB,GAAG,EAAE;IAC1D,UAAU;IACV,QAAQ;IACT;GACH;AAC6B,SAAK;AAEhC,WAAO;KACL,cAAc;KACd,UAAU;KACV,gBAAgB,KAAK;KACrB,UAAU;KACV,QAAQ;KACT;;;;;;;;CAUP,MAAM,QAGJ;AACA,SAAO;GAAE,OAAO,KAAK;GAAO;GAAQ"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/lateral-leak/merge-guard.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* `MergeAgentSidewaysInjectionGuard`
|
|
3
|
+
* `MergeAgentSidewaysInjectionGuard` - layered on top of the
|
|
4
4
|
* `Agent.fanOut(...)` `'judge-merge'` strategy. Computes a
|
|
5
5
|
* per-child `sourceTrust` score in `[0.0, 1.0]` and flags merges
|
|
6
6
|
* where a low-trust child's contribution-weight exceeds the
|
|
@@ -69,7 +69,7 @@ interface MergeBiasDecision {
|
|
|
69
69
|
readonly decision: 'pass-through' | 'flag' | 'block';
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
72
|
-
* Evaluate whether the merge is biased
|
|
72
|
+
* Evaluate whether the merge is biased - a child with
|
|
73
73
|
* `sourceTrust < lowTrustThreshold` contributing more than
|
|
74
74
|
* `maxLowTrustWeight` of the merged output.
|
|
75
75
|
*
|
|
@@ -27,7 +27,7 @@ function computeSourceTrust(input, overrides = {}) {
|
|
|
27
27
|
return Math.max(0, Math.min(1, baseline * provenance * history));
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
|
-
* Evaluate whether the merge is biased
|
|
30
|
+
* Evaluate whether the merge is biased - a child with
|
|
31
31
|
* `sourceTrust < lowTrustThreshold` contributing more than
|
|
32
32
|
* `maxLowTrustWeight` of the merged output.
|
|
33
33
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge-guard.js","names":["TRUST_CLASS_BASELINE: Record<TrustClass, number>","ORIGIN_MULTIPLIER: Record<ContentOriginKind, number>","decision: MergeBiasDecision['decision']"],"sources":["../../src/lateral-leak/merge-guard.ts"],"sourcesContent":["/**\n * `MergeAgentSidewaysInjectionGuard`
|
|
1
|
+
{"version":3,"file":"merge-guard.js","names":["TRUST_CLASS_BASELINE: Record<TrustClass, number>","ORIGIN_MULTIPLIER: Record<ContentOriginKind, number>","decision: MergeBiasDecision['decision']"],"sources":["../../src/lateral-leak/merge-guard.ts"],"sourcesContent":["/**\n * `MergeAgentSidewaysInjectionGuard` - layered on top of the\n * `Agent.fanOut(...)` `'judge-merge'` strategy. Computes a\n * per-child `sourceTrust` score in `[0.0, 1.0]` and flags merges\n * where a low-trust child's contribution-weight exceeds the\n * configured `maxLowTrustWeight` threshold.\n *\n * @packageDocumentation\n */\n\n/**\n * Trust-class baseline used by the guard's `sourceTrust`\n * computation. Mirrors the DEC-149 trust-class taxonomy from\n * `@graphorin/provider`.\n *\n * @stable\n */\nexport type TrustClass = 'loopback' | 'public-tls' | 'public-mtls' | 'untrusted-skill';\n\nconst TRUST_CLASS_BASELINE: Record<TrustClass, number> = {\n loopback: 0.9,\n 'public-tls': 0.7,\n 'public-mtls': 0.8,\n 'untrusted-skill': 0.3,\n};\n\n/**\n * Tool-source provenance multiplier. Mirrors the `ContentOrigin`\n * annotation from `@graphorin/memory.context-engine`.\n *\n * @stable\n */\nexport type ContentOriginKind =\n | 'built-in'\n | 'user-defined'\n | 'trusted-skill'\n | 'untrusted-skill'\n | 'mcp'\n | 'web-search';\n\nconst ORIGIN_MULTIPLIER: Record<ContentOriginKind, number> = {\n 'built-in': 1.0,\n 'user-defined': 0.9,\n 'trusted-skill': 0.85,\n 'untrusted-skill': 0.4,\n mcp: 0.7,\n 'web-search': 0.5,\n};\n\n/**\n * Per-agent guard configuration accepted by\n * `createAgent({ mergeGuard })`.\n *\n * @stable\n */\nexport interface MergeGuardConfig {\n readonly strictness: 'off' | 'detect' | 'detect-and-flag' | 'detect-and-block';\n /** Default `0.3`. */\n readonly maxLowTrustWeight?: number;\n /** Default `0.5`. */\n readonly lowTrustThreshold?: number;\n /** Operator overrides for known agent ids. */\n readonly sourceTrustOverrides?: Readonly<Record<string, number>>;\n}\n\n/**\n * Per-child input descriptor for {@link computeSourceTrust}.\n *\n * @stable\n */\nexport interface ChildTrustInput {\n readonly agentId: string;\n readonly trustClass: TrustClass;\n readonly origin: ContentOriginKind;\n /** Rolling trust score in `[0.0, 1.0]`. Defaults to `1.0`. */\n readonly historyAdjustment?: number;\n}\n\n/**\n * Compose `baseline * provenance * historyAdjustment` and clamp.\n *\n * @stable\n */\nexport function computeSourceTrust(\n input: ChildTrustInput,\n overrides: Readonly<Record<string, number>> = {},\n): number {\n const override = overrides[input.agentId];\n if (override !== undefined) return Math.max(0, Math.min(1, override));\n const baseline = TRUST_CLASS_BASELINE[input.trustClass];\n const provenance = ORIGIN_MULTIPLIER[input.origin];\n const history = input.historyAdjustment ?? 1.0;\n return Math.max(0, Math.min(1, baseline * provenance * history));\n}\n\n/**\n * Pure decision returned by {@link evaluateMerge}.\n *\n * @stable\n */\nexport interface MergeBiasDecision {\n readonly biased: boolean;\n readonly offendingChild?: string;\n readonly contributionWeight?: number;\n readonly sourceTrust?: number;\n readonly decision: 'pass-through' | 'flag' | 'block';\n}\n\n/**\n * Evaluate whether the merge is biased - a child with\n * `sourceTrust < lowTrustThreshold` contributing more than\n * `maxLowTrustWeight` of the merged output.\n *\n * Inputs are pre-computed per-child trust scores together with the\n * estimated contribution weights (token-count overlap between each\n * child's output and the merged output, normalized to sum to ~1.0).\n *\n * @stable\n */\nexport function evaluateMerge(\n perChild: ReadonlyArray<{\n readonly agentId: string;\n readonly sourceTrust: number;\n readonly contributionWeight: number;\n }>,\n cfg: MergeGuardConfig,\n): MergeBiasDecision {\n if (cfg.strictness === 'off') {\n return { biased: false, decision: 'pass-through' };\n }\n const lowTrustThreshold = cfg.lowTrustThreshold ?? 0.5;\n const maxLowTrustWeight = cfg.maxLowTrustWeight ?? 0.3;\n for (const child of perChild) {\n if (child.sourceTrust < lowTrustThreshold && child.contributionWeight > maxLowTrustWeight) {\n const decision: MergeBiasDecision['decision'] =\n cfg.strictness === 'detect-and-block'\n ? 'block'\n : cfg.strictness === 'detect-and-flag'\n ? 'flag'\n : 'pass-through';\n return {\n biased: true,\n offendingChild: child.agentId,\n contributionWeight: child.contributionWeight,\n sourceTrust: child.sourceTrust,\n decision,\n };\n }\n }\n return { biased: false, decision: 'pass-through' };\n}\n"],"mappings":";AAmBA,MAAMA,uBAAmD;CACvD,UAAU;CACV,cAAc;CACd,eAAe;CACf,mBAAmB;CACpB;AAgBD,MAAMC,oBAAuD;CAC3D,YAAY;CACZ,gBAAgB;CAChB,iBAAiB;CACjB,mBAAmB;CACnB,KAAK;CACL,cAAc;CACf;;;;;;AAoCD,SAAgB,mBACd,OACA,YAA8C,EAAE,EACxC;CACR,MAAM,WAAW,UAAU,MAAM;AACjC,KAAI,aAAa,OAAW,QAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,SAAS,CAAC;CACrE,MAAM,WAAW,qBAAqB,MAAM;CAC5C,MAAM,aAAa,kBAAkB,MAAM;CAC3C,MAAM,UAAU,MAAM,qBAAqB;AAC3C,QAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,WAAW,aAAa,QAAQ,CAAC;;;;;;;;;;;;;AA2BlE,SAAgB,cACd,UAKA,KACmB;AACnB,KAAI,IAAI,eAAe,MACrB,QAAO;EAAE,QAAQ;EAAO,UAAU;EAAgB;CAEpD,MAAM,oBAAoB,IAAI,qBAAqB;CACnD,MAAM,oBAAoB,IAAI,qBAAqB;AACnD,MAAK,MAAM,SAAS,SAClB,KAAI,MAAM,cAAc,qBAAqB,MAAM,qBAAqB,mBAAmB;EACzF,MAAMC,WACJ,IAAI,eAAe,qBACf,UACA,IAAI,eAAe,oBACjB,SACA;AACR,SAAO;GACL,QAAQ;GACR,gBAAgB,MAAM;GACtB,oBAAoB,MAAM;GAC1B,aAAa,MAAM;GACnB;GACD;;AAGL,QAAO;EAAE,QAAQ;EAAO,UAAU;EAAgB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/lateral-leak/protocol-guard.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* Protocol/header injection guard
|
|
3
|
+
* Protocol/header injection guard - escapes control characters in
|
|
4
4
|
* tool result bodies before they cross internal-service delivery
|
|
5
5
|
* boundaries (SSE, WebSocket, REST body, HTTP header, audit row).
|
|
6
6
|
*
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* - `'ws' | 'rest-body'` → `'replace'` (replace with the Unicode
|
|
14
14
|
* replacement character `\uFFFD`).
|
|
15
15
|
* - `'audit'` → `'strict'` (regulated deployments).
|
|
16
|
-
* - `'reject'` is operator opt-in
|
|
16
|
+
* - `'reject'` is operator opt-in - the guard throws
|
|
17
17
|
* {@link ProtocolInjectionRejectError} when control characters are
|
|
18
18
|
* detected.
|
|
19
19
|
*
|
|
@@ -46,7 +46,7 @@ interface ProtocolGuardConfig {
|
|
|
46
46
|
readonly audit?: ProtocolEscapePolicy;
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
-
* Resolved policy lookup. Pure function
|
|
49
|
+
* Resolved policy lookup. Pure function - no side effects.
|
|
50
50
|
*
|
|
51
51
|
* @stable
|
|
52
52
|
*/
|
|
@@ -66,7 +66,7 @@ interface GuardOutcome {
|
|
|
66
66
|
}
|
|
67
67
|
/**
|
|
68
68
|
* Apply the configured escape policy to a single string body. Pure
|
|
69
|
-
*
|
|
69
|
+
* - never mutates inputs.
|
|
70
70
|
*
|
|
71
71
|
* @stable
|
|
72
72
|
*/
|
|
@@ -2,7 +2,7 @@ import { ProtocolInjectionRejectError } from "../errors/index.js";
|
|
|
2
2
|
|
|
3
3
|
//#region src/lateral-leak/protocol-guard.ts
|
|
4
4
|
/**
|
|
5
|
-
* Protocol/header injection guard
|
|
5
|
+
* Protocol/header injection guard - escapes control characters in
|
|
6
6
|
* tool result bodies before they cross internal-service delivery
|
|
7
7
|
* boundaries (SSE, WebSocket, REST body, HTTP header, audit row).
|
|
8
8
|
*
|
|
@@ -15,14 +15,14 @@ import { ProtocolInjectionRejectError } from "../errors/index.js";
|
|
|
15
15
|
* - `'ws' | 'rest-body'` → `'replace'` (replace with the Unicode
|
|
16
16
|
* replacement character `\uFFFD`).
|
|
17
17
|
* - `'audit'` → `'strict'` (regulated deployments).
|
|
18
|
-
* - `'reject'` is operator opt-in
|
|
18
|
+
* - `'reject'` is operator opt-in - the guard throws
|
|
19
19
|
* {@link ProtocolInjectionRejectError} when control characters are
|
|
20
20
|
* detected.
|
|
21
21
|
*
|
|
22
22
|
* @packageDocumentation
|
|
23
23
|
*/
|
|
24
24
|
/**
|
|
25
|
-
* Resolved policy lookup. Pure function
|
|
25
|
+
* Resolved policy lookup. Pure function - no side effects.
|
|
26
26
|
*
|
|
27
27
|
* @stable
|
|
28
28
|
*/
|
|
@@ -94,7 +94,7 @@ function findMatchedPattern(input) {
|
|
|
94
94
|
}
|
|
95
95
|
/**
|
|
96
96
|
* Apply the configured escape policy to a single string body. Pure
|
|
97
|
-
*
|
|
97
|
+
* - never mutates inputs.
|
|
98
98
|
*
|
|
99
99
|
* @stable
|
|
100
100
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol-guard.js","names":["codePoints: number[]"],"sources":["../../src/lateral-leak/protocol-guard.ts"],"sourcesContent":["/**\n * Protocol/header injection guard
|
|
1
|
+
{"version":3,"file":"protocol-guard.js","names":["codePoints: number[]"],"sources":["../../src/lateral-leak/protocol-guard.ts"],"sourcesContent":["/**\n * Protocol/header injection guard - escapes control characters in\n * tool result bodies before they cross internal-service delivery\n * boundaries (SSE, WebSocket, REST body, HTTP header, audit row).\n *\n * The default policy mirrors the deployment-posture matrix from the\n * lateral-leak design (DEC-171 / suggested ADR-059):\n *\n * - `'sse' | 'http-header'` → `'strict'` (escape control chars to\n * `\\xNN` hex literals; the safest default for cleartext frame\n * protocols).\n * - `'ws' | 'rest-body'` → `'replace'` (replace with the Unicode\n * replacement character `\\uFFFD`).\n * - `'audit'` → `'strict'` (regulated deployments).\n * - `'reject'` is operator opt-in - the guard throws\n * {@link ProtocolInjectionRejectError} when control characters are\n * detected.\n *\n * @packageDocumentation\n */\n\nimport { ProtocolInjectionRejectError } from '../errors/index.js';\n\n/**\n * Per-boundary identifier used by the runtime when calling the\n * guard.\n *\n * @stable\n */\nexport type ProtocolBoundary = 'sse' | 'http-header' | 'ws' | 'rest-body' | 'audit';\n\n/**\n * Per-boundary escape policy.\n *\n * @stable\n */\nexport type ProtocolEscapePolicy = 'strict' | 'replace' | 'reject';\n\n/**\n * Configurable per-boundary policy table. Operators may override\n * specific boundaries via `Agent.protocolGuard?: { ... }`.\n *\n * @stable\n */\nexport interface ProtocolGuardConfig {\n readonly sse?: ProtocolEscapePolicy;\n readonly httpHeader?: ProtocolEscapePolicy;\n readonly ws?: ProtocolEscapePolicy;\n readonly restBody?: ProtocolEscapePolicy;\n readonly audit?: ProtocolEscapePolicy;\n}\n\n/**\n * Resolved policy lookup. Pure function - no side effects.\n *\n * @stable\n */\nexport function resolvePolicy(\n boundary: ProtocolBoundary,\n cfg: ProtocolGuardConfig = {},\n): ProtocolEscapePolicy {\n switch (boundary) {\n case 'sse':\n return cfg.sse ?? 'strict';\n case 'http-header':\n return cfg.httpHeader ?? 'strict';\n case 'ws':\n return cfg.ws ?? 'replace';\n case 'rest-body':\n return cfg.restBody ?? 'replace';\n case 'audit':\n return cfg.audit ?? 'strict';\n default: {\n const _exhaustive: never = boundary;\n void _exhaustive;\n return 'strict';\n }\n }\n}\n\n// Control-character catalogue used to detect injection attempts at\n// the framework's outbound delivery boundaries. Building the regex\n// programmatically avoids embedding control bytes in the source -\n// the bytes are exactly the ones a malicious tool result might\n// inject to escape framing on cleartext protocols.\nfunction buildControlCharsRegex(): RegExp {\n const codePoints: number[] = [];\n for (let n = 0; n < 0x20; n++) {\n if (n === 0x09 || n === 0x0a || n === 0x0d) continue;\n codePoints.push(n);\n }\n codePoints.push(0x7f);\n const cls = codePoints.map((c) => `\\\\x${c.toString(16).padStart(2, '0')}`).join('');\n return new RegExp(`[${cls}]`, 'g');\n}\nconst CONTROL_CHARS_RE = buildControlCharsRegex();\nconst SSE_FRAME_RE = /\\r?\\n\\r?\\n/g;\nconst HEADER_CRLF_RE = /\\r\\n/g;\n\n/**\n * Outcome of {@link guardOutboundContent}.\n *\n * @stable\n */\nexport interface GuardOutcome {\n readonly content: string;\n readonly escapedCharCount: number;\n readonly matchedPattern?: string;\n readonly decision: 'pass-through' | 'escaped' | 'replaced' | 'rejected';\n readonly boundary: ProtocolBoundary;\n readonly policy: ProtocolEscapePolicy;\n}\n\nfunction escapeStrict(\n input: string,\n boundary: ProtocolBoundary,\n): { content: string; count: number } {\n let count = 0;\n let out = input.replace(CONTROL_CHARS_RE, (ch) => {\n count += 1;\n return `\\\\x${ch.charCodeAt(0).toString(16).padStart(2, '0')}`;\n });\n if (boundary === 'sse') {\n out = out.replace(SSE_FRAME_RE, (m) => {\n count += 1;\n return m.replace(/\\n/g, '\\\\n').replace(/\\r/g, '\\\\r');\n });\n }\n if (boundary === 'http-header' || boundary === 'audit') {\n out = out.replace(HEADER_CRLF_RE, () => {\n count += 1;\n return '\\\\r\\\\n';\n });\n }\n return { content: out, count };\n}\n\nfunction replaceUFFFD(input: string): { content: string; count: number } {\n let count = 0;\n const out = input.replace(CONTROL_CHARS_RE, () => {\n count += 1;\n return '\\uFFFD';\n });\n return { content: out, count };\n}\n\nfunction findMatchedPattern(input: string): string | undefined {\n if (CONTROL_CHARS_RE.test(input)) {\n CONTROL_CHARS_RE.lastIndex = 0;\n return 'control-char';\n }\n if (SSE_FRAME_RE.test(input)) {\n SSE_FRAME_RE.lastIndex = 0;\n return 'sse-frame';\n }\n if (HEADER_CRLF_RE.test(input)) {\n HEADER_CRLF_RE.lastIndex = 0;\n return 'header-crlf';\n }\n return undefined;\n}\n\n/**\n * Apply the configured escape policy to a single string body. Pure\n * - never mutates inputs.\n *\n * @stable\n */\nexport function guardOutboundContent(\n input: string,\n boundary: ProtocolBoundary,\n cfg: ProtocolGuardConfig = {},\n): GuardOutcome {\n const policy = resolvePolicy(boundary, cfg);\n const matchedPattern = findMatchedPattern(input);\n if (matchedPattern === undefined) {\n return {\n content: input,\n escapedCharCount: 0,\n decision: 'pass-through',\n boundary,\n policy,\n };\n }\n switch (policy) {\n case 'strict': {\n const r = escapeStrict(input, boundary);\n return {\n content: r.content,\n escapedCharCount: r.count,\n matchedPattern,\n decision: 'escaped',\n boundary,\n policy,\n };\n }\n case 'replace': {\n const r = replaceUFFFD(input);\n return {\n content: r.content,\n escapedCharCount: r.count,\n matchedPattern,\n decision: 'replaced',\n boundary,\n policy,\n };\n }\n case 'reject':\n throw new ProtocolInjectionRejectError(boundary, matchedPattern);\n default: {\n const _exhaustive: never = policy;\n void _exhaustive;\n return {\n content: input,\n escapedCharCount: 0,\n decision: 'pass-through',\n boundary,\n policy,\n };\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,SAAgB,cACd,UACA,MAA2B,EAAE,EACP;AACtB,SAAQ,UAAR;EACE,KAAK,MACH,QAAO,IAAI,OAAO;EACpB,KAAK,cACH,QAAO,IAAI,cAAc;EAC3B,KAAK,KACH,QAAO,IAAI,MAAM;EACnB,KAAK,YACH,QAAO,IAAI,YAAY;EACzB,KAAK,QACH,QAAO,IAAI,SAAS;EACtB,QAGE,QAAO;;;AAUb,SAAS,yBAAiC;CACxC,MAAMA,aAAuB,EAAE;AAC/B,MAAK,IAAI,IAAI,GAAG,IAAI,IAAM,KAAK;AAC7B,MAAI,MAAM,KAAQ,MAAM,MAAQ,MAAM,GAAM;AAC5C,aAAW,KAAK,EAAE;;AAEpB,YAAW,KAAK,IAAK;CACrB,MAAM,MAAM,WAAW,KAAK,MAAM,MAAM,EAAE,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,GAAG,CAAC,KAAK,GAAG;AACnF,QAAO,IAAI,OAAO,IAAI,IAAI,IAAI,IAAI;;AAEpC,MAAM,mBAAmB,wBAAwB;AACjD,MAAM,eAAe;AACrB,MAAM,iBAAiB;AAgBvB,SAAS,aACP,OACA,UACoC;CACpC,IAAI,QAAQ;CACZ,IAAI,MAAM,MAAM,QAAQ,mBAAmB,OAAO;AAChD,WAAS;AACT,SAAO,MAAM,GAAG,WAAW,EAAE,CAAC,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;GAC3D;AACF,KAAI,aAAa,MACf,OAAM,IAAI,QAAQ,eAAe,MAAM;AACrC,WAAS;AACT,SAAO,EAAE,QAAQ,OAAO,MAAM,CAAC,QAAQ,OAAO,MAAM;GACpD;AAEJ,KAAI,aAAa,iBAAiB,aAAa,QAC7C,OAAM,IAAI,QAAQ,sBAAsB;AACtC,WAAS;AACT,SAAO;GACP;AAEJ,QAAO;EAAE,SAAS;EAAK;EAAO;;AAGhC,SAAS,aAAa,OAAmD;CACvE,IAAI,QAAQ;AAKZ,QAAO;EAAE,SAJG,MAAM,QAAQ,wBAAwB;AAChD,YAAS;AACT,UAAO;IACP;EACqB;EAAO;;AAGhC,SAAS,mBAAmB,OAAmC;AAC7D,KAAI,iBAAiB,KAAK,MAAM,EAAE;AAChC,mBAAiB,YAAY;AAC7B,SAAO;;AAET,KAAI,aAAa,KAAK,MAAM,EAAE;AAC5B,eAAa,YAAY;AACzB,SAAO;;AAET,KAAI,eAAe,KAAK,MAAM,EAAE;AAC9B,iBAAe,YAAY;AAC3B,SAAO;;;;;;;;;AAWX,SAAgB,qBACd,OACA,UACA,MAA2B,EAAE,EACf;CACd,MAAM,SAAS,cAAc,UAAU,IAAI;CAC3C,MAAM,iBAAiB,mBAAmB,MAAM;AAChD,KAAI,mBAAmB,OACrB,QAAO;EACL,SAAS;EACT,kBAAkB;EAClB,UAAU;EACV;EACA;EACD;AAEH,SAAQ,QAAR;EACE,KAAK,UAAU;GACb,MAAM,IAAI,aAAa,OAAO,SAAS;AACvC,UAAO;IACL,SAAS,EAAE;IACX,kBAAkB,EAAE;IACpB;IACA,UAAU;IACV;IACA;IACD;;EAEH,KAAK,WAAW;GACd,MAAM,IAAI,aAAa,MAAM;AAC7B,UAAO;IACL,SAAS,EAAE;IACX,kBAAkB,EAAE;IACpB;IACA,UAAU;IACV;IACA;IACD;;EAEH,KAAK,SACH,OAAM,IAAI,6BAA6B,UAAU,eAAe;EAClE,QAGE,QAAO;GACL,SAAS;GACT,kBAAkB;GAClB,UAAU;GACV;GACA;GACD"}
|
|
@@ -29,7 +29,7 @@ interface ResolvePreferredModelInput {
|
|
|
29
29
|
/**
|
|
30
30
|
* Pick the highest-cost tier across the supplied per-tool hints.
|
|
31
31
|
* Explicit `ModelSpec` entries are treated as the highest tier
|
|
32
|
-
* (`'smart'`) for tie-breaking
|
|
32
|
+
* (`'smart'`) for tie-breaking - the conservative-correctness rule
|
|
33
33
|
* documented in DEC-169 / suggested ADR-057.
|
|
34
34
|
*
|
|
35
35
|
* Returns the picked hint together with the original `ModelSpec`
|
|
@@ -43,7 +43,7 @@ declare function pickTopTierAcrossTools(hints: ReadonlyArray<ModelHint | ModelSp
|
|
|
43
43
|
} | undefined;
|
|
44
44
|
/**
|
|
45
45
|
* Walk the precedence ladder and return the resolved provider for a
|
|
46
|
-
* single agent step. Pure function
|
|
46
|
+
* single agent step. Pure function - no side effects.
|
|
47
47
|
*
|
|
48
48
|
* @stable
|
|
49
49
|
*/
|
|
@@ -18,7 +18,7 @@ function modelIdFromSpec(spec) {
|
|
|
18
18
|
/**
|
|
19
19
|
* Pick the highest-cost tier across the supplied per-tool hints.
|
|
20
20
|
* Explicit `ModelSpec` entries are treated as the highest tier
|
|
21
|
-
* (`'smart'`) for tie-breaking
|
|
21
|
+
* (`'smart'`) for tie-breaking - the conservative-correctness rule
|
|
22
22
|
* documented in DEC-169 / suggested ADR-057.
|
|
23
23
|
*
|
|
24
24
|
* Returns the picked hint together with the original `ModelSpec`
|
|
@@ -57,7 +57,7 @@ function pickTopTierAcrossTools(hints) {
|
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
59
|
* Walk the precedence ladder and return the resolved provider for a
|
|
60
|
-
* single agent step. Pure function
|
|
60
|
+
* single agent step. Pure function - no side effects.
|
|
61
61
|
*
|
|
62
62
|
* @stable
|
|
63
63
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["TIER_ORDER: Record<ModelHint, number>","bestSpec: ModelSpec | undefined","bestHint: ModelHint | undefined","agent"],"sources":["../../src/preferred-model/index.ts"],"sourcesContent":["/**\n * Per-tool / per-agent preferred-model resolution. Pure functions\n * consulted by the agent loop AFTER the model has decided which\n * tool(s) to call but BEFORE `provider.stream(...)` is invoked.\n *\n * The four-step precedence ladder (highest wins):\n *\n * 1. `prepareStep({ provider })`
|
|
1
|
+
{"version":3,"file":"index.js","names":["TIER_ORDER: Record<ModelHint, number>","bestSpec: ModelSpec | undefined","bestHint: ModelHint | undefined","agent"],"sources":["../../src/preferred-model/index.ts"],"sourcesContent":["/**\n * Per-tool / per-agent preferred-model resolution. Pure functions\n * consulted by the agent loop AFTER the model has decided which\n * tool(s) to call but BEFORE `provider.stream(...)` is invoked.\n *\n * The four-step precedence ladder (highest wins):\n *\n * 1. `prepareStep({ provider })` - the operator's explicit per-step\n * override always wins.\n * 2. `Tool.preferredModel` - the tool author's per-tool hint.\n * Only the tools the model actually CALLED on the previous step\n * are consulted (AG-15) - an advertised-but-uncalled hint never\n * escalates the run. Multi-tool ties resolve to the highest cost\n * tier (`'smart' > 'balanced' > 'fast'`; explicit `ModelSpec` is\n * treated as the highest tier).\n * 3. `Agent.preferredModel?` - the per-agent default.\n * 4. `Agent` default `provider` - the v0.1-alpha behaviour.\n *\n * Cost-tier resolution against `Agent.modelTierMap` is documented\n * as a hint: when the requested tier is unmapped, the resolver\n * falls through to the next precedence step rather than throwing.\n *\n * @packageDocumentation\n */\n\nimport type { ModelHint, ModelSpec, Provider } from '@graphorin/core';\n\nconst TIER_ORDER: Record<ModelHint, number> = { fast: 0, balanced: 1, smart: 2 };\n\n/**\n * Result returned by {@link resolvePreferredModel}.\n *\n * @stable\n */\nexport interface PreferredModelResolution {\n readonly resolvedProvider: Provider;\n readonly resolvedModelId: string;\n readonly source: 'prepare-step' | 'tier-map' | 'spec' | 'agent-preferred' | 'fallthrough-default';\n readonly hintApplied?: ModelHint;\n readonly fallthroughReason?:\n | 'tier-not-mapped'\n | 'provider-unavailable'\n | 'override-takes-precedence';\n}\n\n/**\n * Pure inputs to {@link resolvePreferredModel}.\n *\n * @stable\n */\nexport interface ResolvePreferredModelInput {\n readonly prepareStepProvider?: Provider;\n readonly toolPreferredModels: ReadonlyArray<ModelHint | ModelSpec | undefined>;\n readonly agentPreferredModel?: ModelHint | ModelSpec;\n readonly agentDefaultProvider: Provider;\n readonly modelTierMap?: Partial<Record<ModelHint, ModelSpec>>;\n}\n\nfunction isModelHint(value: ModelHint | ModelSpec | undefined): value is ModelHint {\n return value === 'fast' || value === 'balanced' || value === 'smart';\n}\n\nfunction specToProvider(spec: ModelSpec): Provider {\n if ('provider' in spec) return spec.provider as Provider;\n return spec as Provider;\n}\n\nfunction modelIdFromSpec(spec: ModelSpec): string {\n if ('provider' in spec) return spec.model;\n return (spec as Provider).modelId;\n}\n\n/**\n * Pick the highest-cost tier across the supplied per-tool hints.\n * Explicit `ModelSpec` entries are treated as the highest tier\n * (`'smart'`) for tie-breaking - the conservative-correctness rule\n * documented in DEC-169 / suggested ADR-057.\n *\n * Returns the picked hint together with the original `ModelSpec`\n * (when an explicit spec won the tie-break).\n *\n * @stable\n */\nexport function pickTopTierAcrossTools(\n hints: ReadonlyArray<ModelHint | ModelSpec | undefined>,\n): { readonly hint: ModelHint; readonly spec?: ModelSpec } | undefined {\n let bestRank = -1;\n let bestSpec: ModelSpec | undefined;\n let bestHint: ModelHint | undefined;\n for (const h of hints) {\n if (h === undefined) continue;\n if (isModelHint(h)) {\n const rank = TIER_ORDER[h];\n if (rank > bestRank) {\n bestRank = rank;\n bestSpec = undefined;\n bestHint = h;\n }\n } else {\n // Explicit `ModelSpec` -> treat as the highest tier.\n const rank = TIER_ORDER.smart;\n if (rank >= bestRank) {\n bestRank = rank;\n bestSpec = h;\n bestHint = 'smart';\n }\n }\n }\n if (bestHint === undefined) return undefined;\n if (bestSpec !== undefined) return { hint: bestHint, spec: bestSpec };\n return { hint: bestHint };\n}\n\n/**\n * Walk the precedence ladder and return the resolved provider for a\n * single agent step. Pure function - no side effects.\n *\n * @stable\n */\nexport function resolvePreferredModel(input: ResolvePreferredModelInput): PreferredModelResolution {\n if (input.prepareStepProvider !== undefined) {\n return {\n resolvedProvider: input.prepareStepProvider,\n resolvedModelId: input.prepareStepProvider.modelId,\n source: 'prepare-step',\n };\n }\n const top = pickTopTierAcrossTools(input.toolPreferredModels);\n if (top !== undefined) {\n if (top.spec !== undefined) {\n return {\n resolvedProvider: specToProvider(top.spec),\n resolvedModelId: modelIdFromSpec(top.spec),\n source: 'spec',\n };\n }\n const mapped = input.modelTierMap?.[top.hint];\n if (mapped !== undefined) {\n return {\n resolvedProvider: specToProvider(mapped),\n resolvedModelId: modelIdFromSpec(mapped),\n source: 'tier-map',\n hintApplied: top.hint,\n };\n }\n // Fall through to agent-preferred / default.\n const agent = input.agentPreferredModel;\n if (agent !== undefined) {\n if (isModelHint(agent)) {\n const agentMapped = input.modelTierMap?.[agent];\n if (agentMapped !== undefined) {\n return {\n resolvedProvider: specToProvider(agentMapped),\n resolvedModelId: modelIdFromSpec(agentMapped),\n source: 'agent-preferred',\n hintApplied: agent,\n fallthroughReason: 'tier-not-mapped',\n };\n }\n } else {\n return {\n resolvedProvider: specToProvider(agent),\n resolvedModelId: modelIdFromSpec(agent),\n source: 'agent-preferred',\n fallthroughReason: 'tier-not-mapped',\n };\n }\n }\n return {\n resolvedProvider: input.agentDefaultProvider,\n resolvedModelId: input.agentDefaultProvider.modelId,\n source: 'fallthrough-default',\n hintApplied: top.hint,\n fallthroughReason: 'tier-not-mapped',\n };\n }\n // No per-tool hints. Honour `Agent.preferredModel` next.\n const agent = input.agentPreferredModel;\n if (agent !== undefined) {\n if (isModelHint(agent)) {\n const mapped = input.modelTierMap?.[agent];\n if (mapped !== undefined) {\n return {\n resolvedProvider: specToProvider(mapped),\n resolvedModelId: modelIdFromSpec(mapped),\n source: 'agent-preferred',\n hintApplied: agent,\n };\n }\n // Agent-default tier not mapped: fall through.\n return {\n resolvedProvider: input.agentDefaultProvider,\n resolvedModelId: input.agentDefaultProvider.modelId,\n source: 'fallthrough-default',\n hintApplied: agent,\n fallthroughReason: 'tier-not-mapped',\n };\n }\n return {\n resolvedProvider: specToProvider(agent),\n resolvedModelId: modelIdFromSpec(agent),\n source: 'agent-preferred',\n };\n }\n return {\n resolvedProvider: input.agentDefaultProvider,\n resolvedModelId: input.agentDefaultProvider.modelId,\n source: 'fallthrough-default',\n };\n}\n"],"mappings":";AA2BA,MAAMA,aAAwC;CAAE,MAAM;CAAG,UAAU;CAAG,OAAO;CAAG;AA+BhF,SAAS,YAAY,OAA8D;AACjF,QAAO,UAAU,UAAU,UAAU,cAAc,UAAU;;AAG/D,SAAS,eAAe,MAA2B;AACjD,KAAI,cAAc,KAAM,QAAO,KAAK;AACpC,QAAO;;AAGT,SAAS,gBAAgB,MAAyB;AAChD,KAAI,cAAc,KAAM,QAAO,KAAK;AACpC,QAAQ,KAAkB;;;;;;;;;;;;;AAc5B,SAAgB,uBACd,OACqE;CACrE,IAAI,WAAW;CACf,IAAIC;CACJ,IAAIC;AACJ,MAAK,MAAM,KAAK,OAAO;AACrB,MAAI,MAAM,OAAW;AACrB,MAAI,YAAY,EAAE,EAAE;GAClB,MAAM,OAAO,WAAW;AACxB,OAAI,OAAO,UAAU;AACnB,eAAW;AACX,eAAW;AACX,eAAW;;SAER;GAEL,MAAM,OAAO,WAAW;AACxB,OAAI,QAAQ,UAAU;AACpB,eAAW;AACX,eAAW;AACX,eAAW;;;;AAIjB,KAAI,aAAa,OAAW,QAAO;AACnC,KAAI,aAAa,OAAW,QAAO;EAAE,MAAM;EAAU,MAAM;EAAU;AACrE,QAAO,EAAE,MAAM,UAAU;;;;;;;;AAS3B,SAAgB,sBAAsB,OAA6D;AACjG,KAAI,MAAM,wBAAwB,OAChC,QAAO;EACL,kBAAkB,MAAM;EACxB,iBAAiB,MAAM,oBAAoB;EAC3C,QAAQ;EACT;CAEH,MAAM,MAAM,uBAAuB,MAAM,oBAAoB;AAC7D,KAAI,QAAQ,QAAW;AACrB,MAAI,IAAI,SAAS,OACf,QAAO;GACL,kBAAkB,eAAe,IAAI,KAAK;GAC1C,iBAAiB,gBAAgB,IAAI,KAAK;GAC1C,QAAQ;GACT;EAEH,MAAM,SAAS,MAAM,eAAe,IAAI;AACxC,MAAI,WAAW,OACb,QAAO;GACL,kBAAkB,eAAe,OAAO;GACxC,iBAAiB,gBAAgB,OAAO;GACxC,QAAQ;GACR,aAAa,IAAI;GAClB;EAGH,MAAMC,UAAQ,MAAM;AACpB,MAAIA,YAAU,OACZ,KAAI,YAAYA,QAAM,EAAE;GACtB,MAAM,cAAc,MAAM,eAAeA;AACzC,OAAI,gBAAgB,OAClB,QAAO;IACL,kBAAkB,eAAe,YAAY;IAC7C,iBAAiB,gBAAgB,YAAY;IAC7C,QAAQ;IACR,aAAaA;IACb,mBAAmB;IACpB;QAGH,QAAO;GACL,kBAAkB,eAAeA,QAAM;GACvC,iBAAiB,gBAAgBA,QAAM;GACvC,QAAQ;GACR,mBAAmB;GACpB;AAGL,SAAO;GACL,kBAAkB,MAAM;GACxB,iBAAiB,MAAM,qBAAqB;GAC5C,QAAQ;GACR,aAAa,IAAI;GACjB,mBAAmB;GACpB;;CAGH,MAAM,QAAQ,MAAM;AACpB,KAAI,UAAU,QAAW;AACvB,MAAI,YAAY,MAAM,EAAE;GACtB,MAAM,SAAS,MAAM,eAAe;AACpC,OAAI,WAAW,OACb,QAAO;IACL,kBAAkB,eAAe,OAAO;IACxC,iBAAiB,gBAAgB,OAAO;IACxC,QAAQ;IACR,aAAa;IACd;AAGH,UAAO;IACL,kBAAkB,MAAM;IACxB,iBAAiB,MAAM,qBAAqB;IAC5C,QAAQ;IACR,aAAa;IACb,mBAAmB;IACpB;;AAEH,SAAO;GACL,kBAAkB,eAAe,MAAM;GACvC,iBAAiB,gBAAgB,MAAM;GACvC,QAAQ;GACT;;AAEH,QAAO;EACL,kBAAkB,MAAM;EACxB,iBAAiB,MAAM,qBAAqB;EAC5C,QAAQ;EACT"}
|
package/dist/progress/index.js
CHANGED
|
@@ -12,12 +12,12 @@ import { join } from "node:path";
|
|
|
12
12
|
*
|
|
13
13
|
* Cross-session continuity flow:
|
|
14
14
|
*
|
|
15
|
-
* 1. The current agent calls `agent.progress.write(content)`
|
|
15
|
+
* 1. The current agent calls `agent.progress.write(content)` -
|
|
16
16
|
* runtime persists the file and queues the
|
|
17
17
|
* `agent.progress.written` event (drained into the active or
|
|
18
18
|
* next consumed stream).
|
|
19
19
|
* 2. A sibling / future agent calls
|
|
20
|
-
* `agent.progress.read({ runId: priorRunId })`
|
|
20
|
+
* `agent.progress.read({ runId: priorRunId })` - runtime
|
|
21
21
|
* discovers existing files (no implicit auto-discovery; the
|
|
22
22
|
* operator must supply the `runId` cursor).
|
|
23
23
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["defaultSensitivity: Sensitivity","redact: (s: string) => string","entries: string[]","out: ProgressArtifactRef[]"],"sources":["../../src/progress/index.ts"],"sourcesContent":["/**\n * Structured progress-artifact IO. Persists UTF-8 text artifacts\n * under `<artifactRoot>/<runId>/progress/<role>.<seqPadded>.txt`\n * via atomic-write `.tmp + rename` discipline.\n *\n * Cross-session continuity flow:\n *\n * 1. The current agent calls `agent.progress.write(content)` —\n * runtime persists the file and queues the\n * `agent.progress.written` event (drained into the active or\n * next consumed stream).\n * 2. A sibling / future agent calls\n * `agent.progress.read({ runId: priorRunId })` — runtime\n * discovers existing files (no implicit auto-discovery; the\n * operator must supply the `runId` cursor).\n *\n * Auto-discovery across runs is deferred to v0.2.\n *\n * @packageDocumentation\n */\n\nimport { createHash } from 'node:crypto';\nimport { mkdir, readdir, readFile, rename, stat, unlink, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport type { ProgressArtifactRef, Sensitivity } from '@graphorin/core';\nimport { ProgressWriteError } from '../errors/index.js';\n\nconst DEFAULT_ARTIFACT_DIR = 'graphorin-progress';\nconst SEQ_PAD_WIDTH = 3;\n\n/**\n * Optional configuration accepted by {@link createProgressIO}.\n *\n * @stable\n */\nexport interface ProgressIOConfig {\n /** Filesystem root under which `<runId>/progress/<role>.<seq>.txt` files live. */\n readonly artifactRoot?: string;\n /** Default `Sensitivity` applied when the caller does not override. */\n readonly defaultSensitivity?: Sensitivity;\n /** Optional redaction transform applied to content before write. */\n readonly redact?: (content: string) => string;\n}\n\n/**\n * Per-call options for {@link ProgressIO.write}.\n *\n * @stable\n */\nexport interface ProgressWriteOptions {\n readonly role?: string;\n /** Explicit sequence number; default auto-increments per `(runId, role)`. */\n readonly seq?: number;\n readonly sensitivity?: Sensitivity;\n readonly tags?: ReadonlyArray<string>;\n}\n\n/**\n * Per-call options for {@link ProgressIO.read}.\n *\n * @stable\n */\nexport interface ProgressReadOptions {\n readonly runId?: string;\n readonly role?: string;\n /** Skip artifacts whose `seq <= sinceSeq`. */\n readonly sinceSeq?: number;\n /** Default `100`. */\n readonly maxArtifacts?: number;\n}\n\n/**\n * Public surface returned by {@link createProgressIO}. Used by the\n * agent runtime to back `agent.progress.write / read`.\n *\n * @stable\n */\nexport interface ProgressIO {\n write(\n runId: string,\n content: string,\n options?: ProgressWriteOptions,\n ): Promise<ProgressArtifactRef>;\n read(\n currentRunId: string,\n options?: ProgressReadOptions,\n ): Promise<ReadonlyArray<ProgressArtifactRef>>;\n rootFor(runId: string): string;\n}\n\ninterface InternalSeqState {\n next: number;\n}\n\nconst seqWidth = (n: number): string => String(n).padStart(SEQ_PAD_WIDTH, '0');\n\nconst sha256Hex = (input: string): string =>\n createHash('sha256').update(input, 'utf8').digest('hex');\n\n/**\n * Build a {@link ProgressIO} bound to a particular artifact root.\n *\n * @stable\n */\nexport function createProgressIO(config: ProgressIOConfig = {}): ProgressIO {\n const root = config.artifactRoot ?? join(tmpdir(), DEFAULT_ARTIFACT_DIR);\n const defaultSensitivity: Sensitivity = config.defaultSensitivity ?? 'internal';\n const redact: (s: string) => string = config.redact ?? ((s) => s);\n const seqState = new Map<string, InternalSeqState>();\n\n const directoryFor = (runId: string): string => join(root, runId, 'progress');\n\n const seqKey = (runId: string, role: string): string => `${runId}::${role}`;\n\n const ensureSeqState = async (runId: string, role: string): Promise<InternalSeqState> => {\n const key = seqKey(runId, role);\n let state = seqState.get(key);\n if (state !== undefined) return state;\n state = { next: 1 };\n try {\n const dir = directoryFor(runId);\n const entries = await readdir(dir);\n let max = 0;\n const prefix = `${role}.`;\n for (const entry of entries) {\n if (!entry.startsWith(prefix) || !entry.endsWith('.txt')) continue;\n const seqStr = entry.slice(prefix.length, entry.length - 4);\n const n = Number(seqStr);\n if (Number.isFinite(n) && n > max) max = n;\n }\n state.next = max + 1;\n } catch {\n // Directory does not exist yet — first write will create it.\n }\n seqState.set(key, state);\n return state;\n };\n\n const write = async (\n runId: string,\n rawContent: string,\n options: ProgressWriteOptions = {},\n ): Promise<ProgressArtifactRef> => {\n const role = options.role ?? 'agent';\n const sensitivity = options.sensitivity ?? defaultSensitivity;\n const tags = options.tags ?? [];\n const seqSource = await ensureSeqState(runId, role);\n const seq = options.seq ?? seqSource.next;\n seqSource.next = Math.max(seqSource.next, seq + 1);\n\n const content = redact(rawContent);\n const dir = directoryFor(runId);\n const filename = `${role}.${seqWidth(seq)}.txt`;\n const finalPath = join(dir, filename);\n const tmpPath = `${finalPath}.tmp`;\n\n try {\n await mkdir(dir, { recursive: true });\n await writeFile(tmpPath, content, { encoding: 'utf8' });\n await rename(tmpPath, finalPath);\n } catch (cause) {\n try {\n await unlink(tmpPath);\n } catch {\n // Best-effort cleanup; the write error is the operator-facing signal.\n }\n throw new ProgressWriteError(finalPath, cause);\n }\n\n const stats = await stat(finalPath);\n const sha256 = sha256Hex(content);\n return {\n path: finalPath,\n role,\n seq,\n sizeBytes: stats.size,\n sensitivity,\n ...(tags.length > 0 ? { tags } : {}),\n writtenAtIso: new Date().toISOString(),\n sha256,\n };\n };\n\n const read = async (\n currentRunId: string,\n options: ProgressReadOptions = {},\n ): Promise<ReadonlyArray<ProgressArtifactRef>> => {\n const runId = options.runId ?? currentRunId;\n const max = options.maxArtifacts ?? 100;\n const sinceSeq = options.sinceSeq ?? 0;\n const dir = directoryFor(runId);\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n return [];\n }\n const out: ProgressArtifactRef[] = [];\n const role = options.role;\n for (const entry of entries) {\n if (!entry.endsWith('.txt')) continue;\n const dot = entry.indexOf('.');\n if (dot < 0) continue;\n const entryRole = entry.slice(0, dot);\n const seqStr = entry.slice(dot + 1, entry.length - 4);\n const seq = Number(seqStr);\n if (!Number.isFinite(seq)) continue;\n if (role !== undefined && entryRole !== role) continue;\n if (seq <= sinceSeq) continue;\n const fullPath = join(dir, entry);\n const stats = await stat(fullPath);\n const content = await readFile(fullPath, { encoding: 'utf8' });\n const sha256 = sha256Hex(content);\n out.push({\n path: fullPath,\n role: entryRole,\n seq,\n sizeBytes: stats.size,\n sensitivity: 'internal',\n writtenAtIso: stats.mtime.toISOString(),\n sha256,\n });\n if (out.length >= max) break;\n }\n out.sort((a, b) => {\n if (a.role !== b.role) return a.role.localeCompare(b.role);\n return a.seq - b.seq;\n });\n return out;\n };\n\n return {\n write,\n read,\n rootFor: (runId) => directoryFor(runId),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["defaultSensitivity: Sensitivity","redact: (s: string) => string","entries: string[]","out: ProgressArtifactRef[]"],"sources":["../../src/progress/index.ts"],"sourcesContent":["/**\n * Structured progress-artifact IO. Persists UTF-8 text artifacts\n * under `<artifactRoot>/<runId>/progress/<role>.<seqPadded>.txt`\n * via atomic-write `.tmp + rename` discipline.\n *\n * Cross-session continuity flow:\n *\n * 1. The current agent calls `agent.progress.write(content)` -\n * runtime persists the file and queues the\n * `agent.progress.written` event (drained into the active or\n * next consumed stream).\n * 2. A sibling / future agent calls\n * `agent.progress.read({ runId: priorRunId })` - runtime\n * discovers existing files (no implicit auto-discovery; the\n * operator must supply the `runId` cursor).\n *\n * Auto-discovery across runs is deferred to v0.2.\n *\n * @packageDocumentation\n */\n\nimport { createHash } from 'node:crypto';\nimport { mkdir, readdir, readFile, rename, stat, unlink, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport type { ProgressArtifactRef, Sensitivity } from '@graphorin/core';\nimport { ProgressWriteError } from '../errors/index.js';\n\nconst DEFAULT_ARTIFACT_DIR = 'graphorin-progress';\nconst SEQ_PAD_WIDTH = 3;\n\n/**\n * Optional configuration accepted by {@link createProgressIO}.\n *\n * @stable\n */\nexport interface ProgressIOConfig {\n /** Filesystem root under which `<runId>/progress/<role>.<seq>.txt` files live. */\n readonly artifactRoot?: string;\n /** Default `Sensitivity` applied when the caller does not override. */\n readonly defaultSensitivity?: Sensitivity;\n /** Optional redaction transform applied to content before write. */\n readonly redact?: (content: string) => string;\n}\n\n/**\n * Per-call options for {@link ProgressIO.write}.\n *\n * @stable\n */\nexport interface ProgressWriteOptions {\n readonly role?: string;\n /** Explicit sequence number; default auto-increments per `(runId, role)`. */\n readonly seq?: number;\n readonly sensitivity?: Sensitivity;\n readonly tags?: ReadonlyArray<string>;\n}\n\n/**\n * Per-call options for {@link ProgressIO.read}.\n *\n * @stable\n */\nexport interface ProgressReadOptions {\n readonly runId?: string;\n readonly role?: string;\n /** Skip artifacts whose `seq <= sinceSeq`. */\n readonly sinceSeq?: number;\n /** Default `100`. */\n readonly maxArtifacts?: number;\n}\n\n/**\n * Public surface returned by {@link createProgressIO}. Used by the\n * agent runtime to back `agent.progress.write / read`.\n *\n * @stable\n */\nexport interface ProgressIO {\n write(\n runId: string,\n content: string,\n options?: ProgressWriteOptions,\n ): Promise<ProgressArtifactRef>;\n read(\n currentRunId: string,\n options?: ProgressReadOptions,\n ): Promise<ReadonlyArray<ProgressArtifactRef>>;\n rootFor(runId: string): string;\n}\n\ninterface InternalSeqState {\n next: number;\n}\n\nconst seqWidth = (n: number): string => String(n).padStart(SEQ_PAD_WIDTH, '0');\n\nconst sha256Hex = (input: string): string =>\n createHash('sha256').update(input, 'utf8').digest('hex');\n\n/**\n * Build a {@link ProgressIO} bound to a particular artifact root.\n *\n * @stable\n */\nexport function createProgressIO(config: ProgressIOConfig = {}): ProgressIO {\n const root = config.artifactRoot ?? join(tmpdir(), DEFAULT_ARTIFACT_DIR);\n const defaultSensitivity: Sensitivity = config.defaultSensitivity ?? 'internal';\n const redact: (s: string) => string = config.redact ?? ((s) => s);\n const seqState = new Map<string, InternalSeqState>();\n\n const directoryFor = (runId: string): string => join(root, runId, 'progress');\n\n const seqKey = (runId: string, role: string): string => `${runId}::${role}`;\n\n const ensureSeqState = async (runId: string, role: string): Promise<InternalSeqState> => {\n const key = seqKey(runId, role);\n let state = seqState.get(key);\n if (state !== undefined) return state;\n state = { next: 1 };\n try {\n const dir = directoryFor(runId);\n const entries = await readdir(dir);\n let max = 0;\n const prefix = `${role}.`;\n for (const entry of entries) {\n if (!entry.startsWith(prefix) || !entry.endsWith('.txt')) continue;\n const seqStr = entry.slice(prefix.length, entry.length - 4);\n const n = Number(seqStr);\n if (Number.isFinite(n) && n > max) max = n;\n }\n state.next = max + 1;\n } catch {\n // Directory does not exist yet - first write will create it.\n }\n seqState.set(key, state);\n return state;\n };\n\n const write = async (\n runId: string,\n rawContent: string,\n options: ProgressWriteOptions = {},\n ): Promise<ProgressArtifactRef> => {\n const role = options.role ?? 'agent';\n const sensitivity = options.sensitivity ?? defaultSensitivity;\n const tags = options.tags ?? [];\n const seqSource = await ensureSeqState(runId, role);\n const seq = options.seq ?? seqSource.next;\n seqSource.next = Math.max(seqSource.next, seq + 1);\n\n const content = redact(rawContent);\n const dir = directoryFor(runId);\n const filename = `${role}.${seqWidth(seq)}.txt`;\n const finalPath = join(dir, filename);\n const tmpPath = `${finalPath}.tmp`;\n\n try {\n await mkdir(dir, { recursive: true });\n await writeFile(tmpPath, content, { encoding: 'utf8' });\n await rename(tmpPath, finalPath);\n } catch (cause) {\n try {\n await unlink(tmpPath);\n } catch {\n // Best-effort cleanup; the write error is the operator-facing signal.\n }\n throw new ProgressWriteError(finalPath, cause);\n }\n\n const stats = await stat(finalPath);\n const sha256 = sha256Hex(content);\n return {\n path: finalPath,\n role,\n seq,\n sizeBytes: stats.size,\n sensitivity,\n ...(tags.length > 0 ? { tags } : {}),\n writtenAtIso: new Date().toISOString(),\n sha256,\n };\n };\n\n const read = async (\n currentRunId: string,\n options: ProgressReadOptions = {},\n ): Promise<ReadonlyArray<ProgressArtifactRef>> => {\n const runId = options.runId ?? currentRunId;\n const max = options.maxArtifacts ?? 100;\n const sinceSeq = options.sinceSeq ?? 0;\n const dir = directoryFor(runId);\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n return [];\n }\n const out: ProgressArtifactRef[] = [];\n const role = options.role;\n for (const entry of entries) {\n if (!entry.endsWith('.txt')) continue;\n const dot = entry.indexOf('.');\n if (dot < 0) continue;\n const entryRole = entry.slice(0, dot);\n const seqStr = entry.slice(dot + 1, entry.length - 4);\n const seq = Number(seqStr);\n if (!Number.isFinite(seq)) continue;\n if (role !== undefined && entryRole !== role) continue;\n if (seq <= sinceSeq) continue;\n const fullPath = join(dir, entry);\n const stats = await stat(fullPath);\n const content = await readFile(fullPath, { encoding: 'utf8' });\n const sha256 = sha256Hex(content);\n out.push({\n path: fullPath,\n role: entryRole,\n seq,\n sizeBytes: stats.size,\n sensitivity: 'internal',\n writtenAtIso: stats.mtime.toISOString(),\n sha256,\n });\n if (out.length >= max) break;\n }\n out.sort((a, b) => {\n if (a.role !== b.role) return a.role.localeCompare(b.role);\n return a.seq - b.seq;\n });\n return out;\n };\n\n return {\n write,\n read,\n rootFor: (runId) => directoryFor(runId),\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CompletedToolCall, HandoffRecord, Message, RunState, RunStateUsageByModel, RunStatus, RunStep, RunTaintSummary, ToolApproval, Usage } from "@graphorin/core";
|
|
1
|
+
import { CompletedToolCall, HandoffRecord, Message, RunState, RunStateUsageByModel, RunStatus, RunStep, RunTaintSummary, TodoItem, ToolApproval, Usage } from "@graphorin/core";
|
|
2
2
|
|
|
3
3
|
//#region src/run-state/index.d.ts
|
|
4
4
|
|
|
@@ -38,6 +38,8 @@ interface SerializedRunState {
|
|
|
38
38
|
readonly taintSummary?: RunTaintSummary;
|
|
39
39
|
/** AG-19: deferred tools promoted by `tool_search` this run. */
|
|
40
40
|
readonly promotedTools?: ReadonlyArray<string>;
|
|
41
|
+
/** D6: journaled structured plan/todo list. */
|
|
42
|
+
readonly todos?: ReadonlyArray<TodoItem>;
|
|
41
43
|
readonly startedAt: string;
|
|
42
44
|
readonly finishedAt?: string;
|
|
43
45
|
readonly error?: {
|
|
@@ -55,8 +57,8 @@ interface SerializeRunStateOptions {
|
|
|
55
57
|
/**
|
|
56
58
|
* Deep-redact secret-named keys (`apiKey`, `authorization`,
|
|
57
59
|
* `bearerToken` / `accessToken` / `refreshToken`, `password`,
|
|
58
|
-
* `secret`, …) anywhere in the snapshot
|
|
59
|
-
* included
|
|
60
|
+
* `secret`, …) anywhere in the snapshot - tool results and messages
|
|
61
|
+
* included - replacing their values with `'[redacted]'`. Defaults to
|
|
60
62
|
* `false` for the round-trip canonical helper; the agent runtime
|
|
61
63
|
* passes `true` when persisting through the checkpoint store
|
|
62
64
|
* (AG-23). Redaction is best-effort by key name: secrets stored
|
|
@@ -73,7 +75,7 @@ interface SerializeRunStateOptions {
|
|
|
73
75
|
declare function serializeRunState(state: RunState, options?: SerializeRunStateOptions): SerializedRunState;
|
|
74
76
|
/**
|
|
75
77
|
* Render the canonical JSON string representation of the supplied
|
|
76
|
-
* {@link RunState}. `JSON.stringify(serializeRunState(state))`
|
|
78
|
+
* {@link RunState}. `JSON.stringify(serializeRunState(state))` -
|
|
77
79
|
* provided as a convenience.
|
|
78
80
|
*
|
|
79
81
|
* @stable
|
|
@@ -124,7 +126,7 @@ declare function createInitialRunState(args: {
|
|
|
124
126
|
}): RunState;
|
|
125
127
|
/**
|
|
126
128
|
* Append a per-model usage entry to {@link RunState.usageByModel}.
|
|
127
|
-
* Mutates the supplied state in place
|
|
129
|
+
* Mutates the supplied state in place - used by the agent runtime's
|
|
128
130
|
* per-step retry loop. Pure callers that need an immutable update
|
|
129
131
|
* should clone the state first.
|
|
130
132
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/run-state/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/run-state/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AA6D0B,cA5Bb,wBA4Ba,EAAA,yBAAA;;;;;;AAiBT,cAtCJ,gCAAA,GAsC4B,CAAA;AAoBzC;;;;;AAqFA;AAIU,UA3IO,kBAAA,CA2IW;EA+BZ,SAAA,OAAA,EAAA,OAzKW,wBAyKoC;EAgL/C,SAAA,EAAA,EAAA,MAAgB;EAkBhB,SAAA,OAAA,EAAA,MAAqB;EA+BrB,SAAA,cAAa,EAAA,MAAQ;EAwCrB,SAAA,SAAA,EAAA,MAAA;EA6CA,SAAA,MAAA,CAAA,EAAA,MAAA;EAAmC,SAAA,MAAA,EAzdhC,SAydgC;EAAyB,SAAA,KAAA,EAxd1D,aAwd0D,CAxd5C,OAwd4C,CAAA;EAAd,SAAA,QAAA,EAvdzC,aAudyC,CAvd3B,OAud2B,CAAA;EAAa,SAAA,gBAAA,EAtd9C,aAsd8C,CAtdhC,YAsdgC,CAAA;qBArdtD,cAAc;kBACjB;0BACQ;;0BAEA;;2BAEC;;mBAER,cAAc;;;;;;;;;;;;;;UAWhB,wBAAA;;;;;;;;;;;;;;;;;;;iBAoBD,iBAAA,QACP,oBACE,2BACR;;;;;;;;iBAkFa,cAAA,QAAsB,oBAAoB;UAIhD,kBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+BM,mBAAA,6BAA+C,qBAA0B;;iBAgLzE,gBAAA,+BAA+C,qBAAqB;;;;;;;;iBAkBpE,qBAAA;;;;;;IAMZ;;;;;;;;;iBAyBY,aAAA,QAAqB,kCAAkC;;;;;;;;iBAwCvD,yBAAA,UAAmC,mCAAmC;;;;;;;;iBA6CtE,2BAAA,QAAmC,WAAW,cAAc"}
|
package/dist/run-state/index.js
CHANGED
|
@@ -37,6 +37,7 @@ function serializeRunState(state, options = {}) {
|
|
|
37
37
|
...state.usageByModel !== void 0 ? { usageByModel: state.usageByModel } : {},
|
|
38
38
|
...state.taintSummary !== void 0 ? { taintSummary: state.taintSummary } : {},
|
|
39
39
|
...state.promotedTools !== void 0 ? { promotedTools: state.promotedTools } : {},
|
|
40
|
+
...state.todos !== void 0 ? { todos: state.todos } : {},
|
|
40
41
|
startedAt: state.startedAt,
|
|
41
42
|
...state.finishedAt !== void 0 ? { finishedAt: state.finishedAt } : {},
|
|
42
43
|
...state.error !== void 0 ? { error: state.error } : {}
|
|
@@ -79,7 +80,7 @@ function redactJsonString(content) {
|
|
|
79
80
|
}
|
|
80
81
|
/**
|
|
81
82
|
* Render the canonical JSON string representation of the supplied
|
|
82
|
-
* {@link RunState}. `JSON.stringify(serializeRunState(state))`
|
|
83
|
+
* {@link RunState}. `JSON.stringify(serializeRunState(state))` -
|
|
83
84
|
* provided as a convenience.
|
|
84
85
|
*
|
|
85
86
|
* @stable
|
|
@@ -136,6 +137,8 @@ function deserializeRunState(payload, options = {}) {
|
|
|
136
137
|
completionTokens: Number(usageRaw.completionTokens ?? 0),
|
|
137
138
|
totalTokens: Number(usageRaw.totalTokens ?? 0),
|
|
138
139
|
...usageRaw.reasoningTokens !== void 0 ? { reasoningTokens: Number(usageRaw.reasoningTokens) } : {},
|
|
140
|
+
...usageRaw.cachedReadTokens !== void 0 ? { cachedReadTokens: Number(usageRaw.cachedReadTokens) } : {},
|
|
141
|
+
...usageRaw.cacheWriteTokens !== void 0 ? { cacheWriteTokens: Number(usageRaw.cacheWriteTokens) } : {},
|
|
139
142
|
...isRecord(usageRaw.cost) ? { cost: {
|
|
140
143
|
amount: Number(usageRaw.cost.amount ?? 0),
|
|
141
144
|
currency: String(usageRaw.cost.currency ?? "USD")
|
|
@@ -162,6 +165,8 @@ function deserializeRunState(payload, options = {}) {
|
|
|
162
165
|
totalTokens: Number(entryRaw.totalTokens ?? 0),
|
|
163
166
|
attemptCount: Number(entryRaw.attemptCount ?? 1),
|
|
164
167
|
...entryRaw.reasoningTokens !== void 0 ? { reasoningTokens: Number(entryRaw.reasoningTokens) } : {},
|
|
168
|
+
...entryRaw.cachedReadTokens !== void 0 ? { cachedReadTokens: Number(entryRaw.cachedReadTokens) } : {},
|
|
169
|
+
...entryRaw.cacheWriteTokens !== void 0 ? { cacheWriteTokens: Number(entryRaw.cacheWriteTokens) } : {},
|
|
165
170
|
...isRecord(entryRaw.cost) ? { cost: {
|
|
166
171
|
amount: Number(entryRaw.cost.amount ?? 0),
|
|
167
172
|
currency: String(entryRaw.cost.currency ?? "USD")
|
|
@@ -179,13 +184,20 @@ function deserializeRunState(payload, options = {}) {
|
|
|
179
184
|
let taintSummary;
|
|
180
185
|
if (isRecord(payload.taintSummary)) {
|
|
181
186
|
const ts = payload.taintSummary;
|
|
187
|
+
const tileHashes = Array.isArray(ts.spanTileHashes) ? ts.spanTileHashes.filter((h) => typeof h === "string") : void 0;
|
|
182
188
|
taintSummary = {
|
|
183
189
|
untrustedSeen: ts.untrustedSeen === true,
|
|
184
190
|
sensitiveSeen: ts.sensitiveSeen === true,
|
|
185
|
-
untrustedSourceKinds: Array.isArray(ts.untrustedSourceKinds) ? ts.untrustedSourceKinds.filter((k) => typeof k === "string") : []
|
|
191
|
+
untrustedSourceKinds: Array.isArray(ts.untrustedSourceKinds) ? ts.untrustedSourceKinds.filter((k) => typeof k === "string") : [],
|
|
192
|
+
...tileHashes !== void 0 && tileHashes.length > 0 ? { spanTileHashes: tileHashes } : {}
|
|
186
193
|
};
|
|
187
194
|
}
|
|
188
195
|
const promotedTools = Array.isArray(payload.promotedTools) ? payload.promotedTools.filter((t) => typeof t === "string") : void 0;
|
|
196
|
+
const todos = Array.isArray(payload.todos) ? payload.todos.filter(isRecord).filter((t) => typeof t.id === "string" && typeof t.content === "string" && (t.status === "pending" || t.status === "in_progress" || t.status === "completed")).map((t) => ({
|
|
197
|
+
id: t.id,
|
|
198
|
+
content: t.content,
|
|
199
|
+
status: t.status
|
|
200
|
+
})) : void 0;
|
|
189
201
|
return {
|
|
190
202
|
id,
|
|
191
203
|
agentId,
|
|
@@ -201,6 +213,7 @@ function deserializeRunState(payload, options = {}) {
|
|
|
201
213
|
...usageByModel !== void 0 ? { usageByModel } : {},
|
|
202
214
|
...taintSummary !== void 0 ? { taintSummary } : {},
|
|
203
215
|
...promotedTools !== void 0 ? { promotedTools } : {},
|
|
216
|
+
...todos !== void 0 ? { todos } : {},
|
|
204
217
|
startedAt,
|
|
205
218
|
...typeof finishedAtRaw === "string" ? { finishedAt: finishedAtRaw } : {},
|
|
206
219
|
...error !== void 0 ? { error } : {}
|
|
@@ -241,7 +254,7 @@ function createInitialRunState(args) {
|
|
|
241
254
|
}
|
|
242
255
|
/**
|
|
243
256
|
* Append a per-model usage entry to {@link RunState.usageByModel}.
|
|
244
|
-
* Mutates the supplied state in place
|
|
257
|
+
* Mutates the supplied state in place - used by the agent runtime's
|
|
245
258
|
* per-step retry loop. Pure callers that need an immutable update
|
|
246
259
|
* should clone the state first.
|
|
247
260
|
*
|
|
@@ -255,6 +268,8 @@ function addModelUsage(state, modelId, delta) {
|
|
|
255
268
|
completionTokens: prev.completionTokens + delta.completionTokens,
|
|
256
269
|
totalTokens: prev.totalTokens + delta.totalTokens,
|
|
257
270
|
...delta.reasoningTokens !== void 0 || prev.reasoningTokens !== void 0 ? { reasoningTokens: (prev.reasoningTokens ?? 0) + (delta.reasoningTokens ?? 0) } : {},
|
|
271
|
+
...delta.cachedReadTokens !== void 0 || prev.cachedReadTokens !== void 0 ? { cachedReadTokens: (prev.cachedReadTokens ?? 0) + (delta.cachedReadTokens ?? 0) } : {},
|
|
272
|
+
...delta.cacheWriteTokens !== void 0 || prev.cacheWriteTokens !== void 0 ? { cacheWriteTokens: (prev.cacheWriteTokens ?? 0) + (delta.cacheWriteTokens ?? 0) } : {},
|
|
258
273
|
attemptCount: prev.attemptCount + 1,
|
|
259
274
|
...prev.cost !== void 0 ? { cost: prev.cost } : {}
|
|
260
275
|
} : {
|
|
@@ -276,7 +291,11 @@ function aggregateUsageFromByModel(byModel) {
|
|
|
276
291
|
let ct = 0;
|
|
277
292
|
let tt = 0;
|
|
278
293
|
let rt = 0;
|
|
294
|
+
let cr = 0;
|
|
295
|
+
let cw = 0;
|
|
279
296
|
let hasReasoning = false;
|
|
297
|
+
let hasCachedRead = false;
|
|
298
|
+
let hasCacheWrite = false;
|
|
280
299
|
for (const entry of Object.values(byModel)) {
|
|
281
300
|
pt += entry.promptTokens;
|
|
282
301
|
ct += entry.completionTokens;
|
|
@@ -285,12 +304,22 @@ function aggregateUsageFromByModel(byModel) {
|
|
|
285
304
|
rt += entry.reasoningTokens;
|
|
286
305
|
hasReasoning = true;
|
|
287
306
|
}
|
|
307
|
+
if (entry.cachedReadTokens !== void 0) {
|
|
308
|
+
cr += entry.cachedReadTokens;
|
|
309
|
+
hasCachedRead = true;
|
|
310
|
+
}
|
|
311
|
+
if (entry.cacheWriteTokens !== void 0) {
|
|
312
|
+
cw += entry.cacheWriteTokens;
|
|
313
|
+
hasCacheWrite = true;
|
|
314
|
+
}
|
|
288
315
|
}
|
|
289
316
|
return {
|
|
290
317
|
promptTokens: pt,
|
|
291
318
|
completionTokens: ct,
|
|
292
319
|
totalTokens: tt,
|
|
293
|
-
...hasReasoning ? { reasoningTokens: rt } : {}
|
|
320
|
+
...hasReasoning ? { reasoningTokens: rt } : {},
|
|
321
|
+
...hasCachedRead ? { cachedReadTokens: cr } : {},
|
|
322
|
+
...hasCacheWrite ? { cacheWriteTokens: cw } : {}
|
|
294
323
|
};
|
|
295
324
|
}
|
|
296
325
|
/**
|