@graphorin/memory 0.5.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 +15 -0
- package/LICENSE +21 -0
- package/README.md +401 -0
- package/dist/conflict/index.d.ts +3 -0
- package/dist/conflict/index.js +7 -0
- package/dist/conflict/locale-packs/en.js +262 -0
- package/dist/conflict/locale-packs/en.js.map +1 -0
- package/dist/conflict/locale-packs/index.js +4 -0
- package/dist/conflict/locale-packs/types.d.ts +103 -0
- package/dist/conflict/locale-packs/types.d.ts.map +1 -0
- package/dist/conflict/locale-packs/types.js +55 -0
- package/dist/conflict/locale-packs/types.js.map +1 -0
- package/dist/conflict/pipeline.d.ts +35 -0
- package/dist/conflict/pipeline.d.ts.map +1 -0
- package/dist/conflict/pipeline.js +257 -0
- package/dist/conflict/pipeline.js.map +1 -0
- package/dist/conflict/stages/helpers.js +66 -0
- package/dist/conflict/stages/helpers.js.map +1 -0
- package/dist/conflict/stages/stage1-exact-dedup.js +21 -0
- package/dist/conflict/stages/stage1-exact-dedup.js.map +1 -0
- package/dist/conflict/stages/stage2-embedding-three-zone.js +37 -0
- package/dist/conflict/stages/stage2-embedding-three-zone.js.map +1 -0
- package/dist/conflict/stages/stage3-heuristic-regex.js +45 -0
- package/dist/conflict/stages/stage3-heuristic-regex.js.map +1 -0
- package/dist/conflict/stages/stage4-subject-predicate.js +31 -0
- package/dist/conflict/stages/stage4-subject-predicate.js.map +1 -0
- package/dist/conflict/stages/stage5-defer-to-deep.js +22 -0
- package/dist/conflict/stages/stage5-defer-to-deep.js.map +1 -0
- package/dist/conflict/types.d.ts +222 -0
- package/dist/conflict/types.d.ts.map +1 -0
- package/dist/conflict/types.js +50 -0
- package/dist/conflict/types.js.map +1 -0
- package/dist/consolidator/budget.d.ts +1 -0
- package/dist/consolidator/budget.js +214 -0
- package/dist/consolidator/budget.js.map +1 -0
- package/dist/consolidator/decay.d.ts +35 -0
- package/dist/consolidator/decay.d.ts.map +1 -0
- package/dist/consolidator/decay.js +91 -0
- package/dist/consolidator/decay.js.map +1 -0
- package/dist/consolidator/dlq.js +58 -0
- package/dist/consolidator/dlq.js.map +1 -0
- package/dist/consolidator/errors.d.ts +54 -0
- package/dist/consolidator/errors.d.ts.map +1 -0
- package/dist/consolidator/errors.js +67 -0
- package/dist/consolidator/errors.js.map +1 -0
- package/dist/consolidator/idempotency.d.ts +1 -0
- package/dist/consolidator/idempotency.js +22 -0
- package/dist/consolidator/idempotency.js.map +1 -0
- package/dist/consolidator/index.d.ts +34 -0
- package/dist/consolidator/index.d.ts.map +1 -0
- package/dist/consolidator/index.js +163 -0
- package/dist/consolidator/index.js.map +1 -0
- package/dist/consolidator/lock.d.ts +2 -0
- package/dist/consolidator/lock.js +89 -0
- package/dist/consolidator/lock.js.map +1 -0
- package/dist/consolidator/noise-filter.d.ts +2 -0
- package/dist/consolidator/noise-filter.js +173 -0
- package/dist/consolidator/noise-filter.js.map +1 -0
- package/dist/consolidator/phases/deep.d.ts +4 -0
- package/dist/consolidator/phases/deep.js +198 -0
- package/dist/consolidator/phases/deep.js.map +1 -0
- package/dist/consolidator/phases/induce.d.ts +154 -0
- package/dist/consolidator/phases/induce.d.ts.map +1 -0
- package/dist/consolidator/phases/induce.js +253 -0
- package/dist/consolidator/phases/induce.js.map +1 -0
- package/dist/consolidator/phases/light.d.ts +4 -0
- package/dist/consolidator/phases/light.js +99 -0
- package/dist/consolidator/phases/light.js.map +1 -0
- package/dist/consolidator/phases/reflect.js +287 -0
- package/dist/consolidator/phases/reflect.js.map +1 -0
- package/dist/consolidator/phases/standard.d.ts +8 -0
- package/dist/consolidator/phases/standard.js +499 -0
- package/dist/consolidator/phases/standard.js.map +1 -0
- package/dist/consolidator/reconcile.js +192 -0
- package/dist/consolidator/reconcile.js.map +1 -0
- package/dist/consolidator/runtime.d.ts +62 -0
- package/dist/consolidator/runtime.d.ts.map +1 -0
- package/dist/consolidator/runtime.js +580 -0
- package/dist/consolidator/runtime.js.map +1 -0
- package/dist/consolidator/scheduler.d.ts +111 -0
- package/dist/consolidator/scheduler.d.ts.map +1 -0
- package/dist/consolidator/scheduler.js +71 -0
- package/dist/consolidator/scheduler.js.map +1 -0
- package/dist/consolidator/triggers.d.ts +40 -0
- package/dist/consolidator/triggers.d.ts.map +1 -0
- package/dist/consolidator/triggers.js +94 -0
- package/dist/consolidator/triggers.js.map +1 -0
- package/dist/consolidator/types.d.ts +397 -0
- package/dist/consolidator/types.d.ts.map +1 -0
- package/dist/consolidator/types.js +117 -0
- package/dist/consolidator/types.js.map +1 -0
- package/dist/context-engine/annotations.d.ts +118 -0
- package/dist/context-engine/annotations.d.ts.map +1 -0
- package/dist/context-engine/annotations.js +102 -0
- package/dist/context-engine/annotations.js.map +1 -0
- package/dist/context-engine/auto-recall.d.ts +52 -0
- package/dist/context-engine/auto-recall.d.ts.map +1 -0
- package/dist/context-engine/auto-recall.js +41 -0
- package/dist/context-engine/auto-recall.js.map +1 -0
- package/dist/context-engine/compaction/clear-tool-results.d.ts +55 -0
- package/dist/context-engine/compaction/clear-tool-results.d.ts.map +1 -0
- package/dist/context-engine/compaction/clear-tool-results.js +97 -0
- package/dist/context-engine/compaction/clear-tool-results.js.map +1 -0
- package/dist/context-engine/compaction/compactor.d.ts +52 -0
- package/dist/context-engine/compaction/compactor.d.ts.map +1 -0
- package/dist/context-engine/compaction/compactor.js +196 -0
- package/dist/context-engine/compaction/compactor.js.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.d.ts +15 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.d.ts.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.js +27 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.js.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.d.ts +18 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.d.ts.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.js +47 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.js.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.d.ts +15 -0
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.d.ts.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.js +32 -0
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.js.map +1 -0
- package/dist/context-engine/compaction/hooks/types.d.ts +42 -0
- package/dist/context-engine/compaction/hooks/types.d.ts.map +1 -0
- package/dist/context-engine/compaction/index.d.ts +9 -0
- package/dist/context-engine/compaction/index.js +9 -0
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts +84 -0
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts.map +1 -0
- package/dist/context-engine/compaction/templates/summary-9-section.js +84 -0
- package/dist/context-engine/compaction/templates/summary-9-section.js.map +1 -0
- package/dist/context-engine/compaction/thresholds.d.ts +68 -0
- package/dist/context-engine/compaction/thresholds.d.ts.map +1 -0
- package/dist/context-engine/compaction/thresholds.js +63 -0
- package/dist/context-engine/compaction/thresholds.js.map +1 -0
- package/dist/context-engine/compaction/types.d.ts +202 -0
- package/dist/context-engine/compaction/types.d.ts.map +1 -0
- package/dist/context-engine/engine.d.ts +258 -0
- package/dist/context-engine/engine.d.ts.map +1 -0
- package/dist/context-engine/engine.js +424 -0
- package/dist/context-engine/engine.js.map +1 -0
- package/dist/context-engine/index.d.ts +68 -0
- package/dist/context-engine/index.d.ts.map +1 -0
- package/dist/context-engine/index.js +24 -0
- package/dist/context-engine/locale-packs/en.d.ts +14 -0
- package/dist/context-engine/locale-packs/en.d.ts.map +1 -0
- package/dist/context-engine/locale-packs/en.js +87 -0
- package/dist/context-engine/locale-packs/en.js.map +1 -0
- package/dist/context-engine/locale-packs/index.js +4 -0
- package/dist/context-engine/locale-packs/resolver.d.ts +41 -0
- package/dist/context-engine/locale-packs/resolver.d.ts.map +1 -0
- package/dist/context-engine/locale-packs/resolver.js +119 -0
- package/dist/context-engine/locale-packs/resolver.js.map +1 -0
- package/dist/context-engine/locale-packs/types.d.ts +119 -0
- package/dist/context-engine/locale-packs/types.d.ts.map +1 -0
- package/dist/context-engine/locale-packs/types.js +24 -0
- package/dist/context-engine/locale-packs/types.js.map +1 -0
- package/dist/context-engine/metadata.d.ts +40 -0
- package/dist/context-engine/metadata.d.ts.map +1 -0
- package/dist/context-engine/metadata.js +66 -0
- package/dist/context-engine/metadata.js.map +1 -0
- package/dist/context-engine/preambles/inbound-en.d.ts +20 -0
- package/dist/context-engine/preambles/inbound-en.d.ts.map +1 -0
- package/dist/context-engine/preambles/inbound-en.js +23 -0
- package/dist/context-engine/preambles/inbound-en.js.map +1 -0
- package/dist/context-engine/privacy-filter.d.ts +108 -0
- package/dist/context-engine/privacy-filter.d.ts.map +1 -0
- package/dist/context-engine/privacy-filter.js +113 -0
- package/dist/context-engine/privacy-filter.js.map +1 -0
- package/dist/context-engine/templates/base-en.d.ts +19 -0
- package/dist/context-engine/templates/base-en.d.ts.map +1 -0
- package/dist/context-engine/templates/base-en.js +22 -0
- package/dist/context-engine/templates/base-en.js.map +1 -0
- package/dist/context-engine/templates/composer.d.ts +55 -0
- package/dist/context-engine/templates/composer.d.ts.map +1 -0
- package/dist/context-engine/templates/composer.js +51 -0
- package/dist/context-engine/templates/composer.js.map +1 -0
- package/dist/context-engine/token-budget.d.ts +99 -0
- package/dist/context-engine/token-budget.d.ts.map +1 -0
- package/dist/context-engine/token-budget.js +211 -0
- package/dist/context-engine/token-budget.js.map +1 -0
- package/dist/context-engine/token-counter.d.ts +54 -0
- package/dist/context-engine/token-counter.d.ts.map +1 -0
- package/dist/context-engine/token-counter.js +81 -0
- package/dist/context-engine/token-counter.js.map +1 -0
- package/dist/errors/index.d.ts +171 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +206 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/facade.d.ts +278 -0
- package/dist/facade.d.ts.map +1 -0
- package/dist/facade.js +265 -0
- package/dist/facade.js.map +1 -0
- package/dist/graph/entity-resolver.d.ts +135 -0
- package/dist/graph/entity-resolver.d.ts.map +1 -0
- package/dist/graph/entity-resolver.js +202 -0
- package/dist/graph/entity-resolver.js.map +1 -0
- package/dist/graph/index.d.ts +1 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +95 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/contextualize.d.ts +14 -0
- package/dist/internal/contextualize.d.ts.map +1 -0
- package/dist/internal/contextualize.js +110 -0
- package/dist/internal/contextualize.js.map +1 -0
- package/dist/internal/embedder-binding.js +50 -0
- package/dist/internal/embedder-binding.js.map +1 -0
- package/dist/internal/id.js +24 -0
- package/dist/internal/id.js.map +1 -0
- package/dist/internal/injection-heuristics.js +64 -0
- package/dist/internal/injection-heuristics.js.map +1 -0
- package/dist/internal/spans.js +35 -0
- package/dist/internal/spans.js.map +1 -0
- package/dist/internal/storage-adapter.d.ts +742 -0
- package/dist/internal/storage-adapter.d.ts.map +1 -0
- package/dist/migration/embedder-migration.d.ts +124 -0
- package/dist/migration/embedder-migration.d.ts.map +1 -0
- package/dist/migration/embedder-migration.js +109 -0
- package/dist/migration/embedder-migration.js.map +1 -0
- package/dist/migration/index.d.ts +2 -0
- package/dist/migration/index.js +3 -0
- package/dist/search/explain.d.ts +74 -0
- package/dist/search/explain.d.ts.map +1 -0
- package/dist/search/explain.js +50 -0
- package/dist/search/explain.js.map +1 -0
- package/dist/search/index.d.ts +6 -0
- package/dist/search/index.js +6 -0
- package/dist/search/iterative.d.ts +223 -0
- package/dist/search/iterative.d.ts.map +1 -0
- package/dist/search/iterative.js +330 -0
- package/dist/search/iterative.js.map +1 -0
- package/dist/search/query-transform.d.ts +112 -0
- package/dist/search/query-transform.d.ts.map +1 -0
- package/dist/search/query-transform.js +184 -0
- package/dist/search/query-transform.js.map +1 -0
- package/dist/search/rrf.d.ts +95 -0
- package/dist/search/rrf.d.ts.map +1 -0
- package/dist/search/rrf.js +166 -0
- package/dist/search/rrf.js.map +1 -0
- package/dist/search/types.d.ts +50 -0
- package/dist/search/types.d.ts.map +1 -0
- package/dist/tiers/episodic-memory.d.ts +142 -0
- package/dist/tiers/episodic-memory.d.ts.map +1 -0
- package/dist/tiers/episodic-memory.js +254 -0
- package/dist/tiers/episodic-memory.js.map +1 -0
- package/dist/tiers/index.d.ts +8 -0
- package/dist/tiers/index.js +9 -0
- package/dist/tiers/insight-memory.d.ts +71 -0
- package/dist/tiers/insight-memory.d.ts.map +1 -0
- package/dist/tiers/insight-memory.js +133 -0
- package/dist/tiers/insight-memory.js.map +1 -0
- package/dist/tiers/procedural-memory.d.ts +147 -0
- package/dist/tiers/procedural-memory.d.ts.map +1 -0
- package/dist/tiers/procedural-memory.js +267 -0
- package/dist/tiers/procedural-memory.js.map +1 -0
- package/dist/tiers/semantic-memory.d.ts +461 -0
- package/dist/tiers/semantic-memory.d.ts.map +1 -0
- package/dist/tiers/semantic-memory.js +631 -0
- package/dist/tiers/semantic-memory.js.map +1 -0
- package/dist/tiers/session-memory.d.ts +117 -0
- package/dist/tiers/session-memory.d.ts.map +1 -0
- package/dist/tiers/session-memory.js +163 -0
- package/dist/tiers/session-memory.js.map +1 -0
- package/dist/tiers/shared-memory.d.ts +29 -0
- package/dist/tiers/shared-memory.d.ts.map +1 -0
- package/dist/tiers/shared-memory.js +58 -0
- package/dist/tiers/shared-memory.js.map +1 -0
- package/dist/tiers/working-memory.d.ts +129 -0
- package/dist/tiers/working-memory.d.ts.map +1 -0
- package/dist/tiers/working-memory.js +229 -0
- package/dist/tiers/working-memory.js.map +1 -0
- package/dist/tools/block-tools.d.ts +98 -0
- package/dist/tools/block-tools.d.ts.map +1 -0
- package/dist/tools/block-tools.js +114 -0
- package/dist/tools/block-tools.js.map +1 -0
- package/dist/tools/fact-tools.d.ts +294 -0
- package/dist/tools/fact-tools.d.ts.map +1 -0
- package/dist/tools/fact-tools.js +293 -0
- package/dist/tools/fact-tools.js.map +1 -0
- package/dist/tools/index.d.ts +35 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +36 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/recall-tools.d.ts +209 -0
- package/dist/tools/recall-tools.d.ts.map +1 -0
- package/dist/tools/recall-tools.js +170 -0
- package/dist/tools/recall-tools.js.map +1 -0
- package/dist/tools/types.d.ts +40 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/package.json +119 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ContextLocalePack } from "../locale-packs/types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/context-engine/templates/composer.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Layer 1 mode discriminator. `'full'` ships the full educational
|
|
7
|
+
* template (~250-350 tok); `'minimal'` ships the shortened version
|
|
8
|
+
* (~80-120 tok) for top-tier models.
|
|
9
|
+
*
|
|
10
|
+
* @stable
|
|
11
|
+
*/
|
|
12
|
+
type MemoryBaseMode = 'full' | 'minimal';
|
|
13
|
+
/**
|
|
14
|
+
* Compose Layer 1 from the resolved locale pack.
|
|
15
|
+
*
|
|
16
|
+
* @stable
|
|
17
|
+
*/
|
|
18
|
+
declare function composeLayer1(pack: ContextLocalePack, mode: MemoryBaseMode): string;
|
|
19
|
+
/**
|
|
20
|
+
* Render the optional Layer 2 (`agent_instructions`) wrapper.
|
|
21
|
+
* Returns the empty string when `instructions` is empty so the
|
|
22
|
+
* layer is dropped from the assembled prompt.
|
|
23
|
+
*
|
|
24
|
+
* @stable
|
|
25
|
+
*/
|
|
26
|
+
declare function composeLayer2(instructions: string | undefined): string;
|
|
27
|
+
/**
|
|
28
|
+
* Render Layer 4 skills metadata cards. Each entry is a
|
|
29
|
+
* `<skill ... />` self-closing tag so the model sees only the
|
|
30
|
+
* progressive-disclosure metadata — the full SKILL.md body is
|
|
31
|
+
* loaded on-demand via `activate_skill(name)` per RB-04.
|
|
32
|
+
*
|
|
33
|
+
* @stable
|
|
34
|
+
*/
|
|
35
|
+
interface SkillMetadataCard {
|
|
36
|
+
readonly name: string;
|
|
37
|
+
readonly description: string;
|
|
38
|
+
readonly location?: string;
|
|
39
|
+
/** When `true`, the card is excluded from the assembled prompt. */
|
|
40
|
+
readonly disableModelInvocation?: boolean;
|
|
41
|
+
}
|
|
42
|
+
declare function composeLayer4Skills(cards: ReadonlyArray<SkillMetadataCard>): string;
|
|
43
|
+
/**
|
|
44
|
+
* Render the inbound-sanitization preamble fragment. Emitted
|
|
45
|
+
* AFTER the cache breakpoint so the Layer 1-4 cache prefix is
|
|
46
|
+
* unaffected. Caller threads the fragment into the system content
|
|
47
|
+
* post-Layer 5; the fragment is returned as a string so the
|
|
48
|
+
* caller does not need to know the cache-breakpoint policy.
|
|
49
|
+
*
|
|
50
|
+
* @stable
|
|
51
|
+
*/
|
|
52
|
+
declare function composeInboundPreamble(pack: ContextLocalePack): string;
|
|
53
|
+
//#endregion
|
|
54
|
+
export { MemoryBaseMode, SkillMetadataCard, composeInboundPreamble, composeLayer1, composeLayer2, composeLayer4Skills };
|
|
55
|
+
//# sourceMappingURL=composer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composer.d.ts","names":[],"sources":["../../../src/context-engine/templates/composer.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;KAkBY,cAAA;;;;;;iBAOI,aAAA,OAAoB,yBAAyB;;;;;;;;iBAW7C,aAAA;;;;;;;;;UAaC,iBAAA;;;;;;;iBAQD,mBAAA,QAA2B,cAAc;;;;;;;;;;iBAuBzC,sBAAA,OAA6B"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//#region src/context-engine/templates/composer.ts
|
|
2
|
+
/**
|
|
3
|
+
* Compose Layer 1 from the resolved locale pack.
|
|
4
|
+
*
|
|
5
|
+
* @stable
|
|
6
|
+
*/
|
|
7
|
+
function composeLayer1(pack, mode) {
|
|
8
|
+
return mode === "minimal" ? pack.baseTemplate.minimal : pack.baseTemplate.full;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Render the optional Layer 2 (`agent_instructions`) wrapper.
|
|
12
|
+
* Returns the empty string when `instructions` is empty so the
|
|
13
|
+
* layer is dropped from the assembled prompt.
|
|
14
|
+
*
|
|
15
|
+
* @stable
|
|
16
|
+
*/
|
|
17
|
+
function composeLayer2(instructions) {
|
|
18
|
+
if (instructions === void 0 || instructions.trim().length === 0) return "";
|
|
19
|
+
return `<agent_instructions>\n${instructions}\n</agent_instructions>`;
|
|
20
|
+
}
|
|
21
|
+
function composeLayer4Skills(cards) {
|
|
22
|
+
const visible = cards.filter((c) => c.disableModelInvocation !== true);
|
|
23
|
+
if (visible.length === 0) return "";
|
|
24
|
+
const lines = ["<skills_available>"];
|
|
25
|
+
for (const card of visible) {
|
|
26
|
+
const description = ` description="${escapeXmlAttr(card.description)}"`;
|
|
27
|
+
const location = card.location !== void 0 ? ` location="${escapeXmlAttr(card.location)}"` : "";
|
|
28
|
+
lines.push(` <skill name="${escapeXmlAttr(card.name)}"${description}${location} />`);
|
|
29
|
+
}
|
|
30
|
+
lines.push("</skills_available>");
|
|
31
|
+
return lines.join("\n");
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Render the inbound-sanitization preamble fragment. Emitted
|
|
35
|
+
* AFTER the cache breakpoint so the Layer 1-4 cache prefix is
|
|
36
|
+
* unaffected. Caller threads the fragment into the system content
|
|
37
|
+
* post-Layer 5; the fragment is returned as a string so the
|
|
38
|
+
* caller does not need to know the cache-breakpoint policy.
|
|
39
|
+
*
|
|
40
|
+
* @stable
|
|
41
|
+
*/
|
|
42
|
+
function composeInboundPreamble(pack) {
|
|
43
|
+
return pack.inboundSanitizationPreamble.text;
|
|
44
|
+
}
|
|
45
|
+
function escapeXmlAttr(value) {
|
|
46
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
export { composeInboundPreamble, composeLayer1, composeLayer2, composeLayer4Skills };
|
|
51
|
+
//# sourceMappingURL=composer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composer.js","names":[],"sources":["../../../src/context-engine/templates/composer.ts"],"sourcesContent":["/**\n * Layer 1 base-template composer. Resolves the locale pack, picks\n * the right base fragment based on `memoryBaseMode`, and exposes\n * the per-step inbound preamble as an additive Layer 5/6 fragment\n * (so the Layer 1-4 cache prefix stays bytes-equal across the run).\n *\n * @packageDocumentation\n */\n\nimport type { ContextLocalePack } from '../locale-packs/index.js';\n\n/**\n * Layer 1 mode discriminator. `'full'` ships the full educational\n * template (~250-350 tok); `'minimal'` ships the shortened version\n * (~80-120 tok) for top-tier models.\n *\n * @stable\n */\nexport type MemoryBaseMode = 'full' | 'minimal';\n\n/**\n * Compose Layer 1 from the resolved locale pack.\n *\n * @stable\n */\nexport function composeLayer1(pack: ContextLocalePack, mode: MemoryBaseMode): string {\n return mode === 'minimal' ? pack.baseTemplate.minimal : pack.baseTemplate.full;\n}\n\n/**\n * Render the optional Layer 2 (`agent_instructions`) wrapper.\n * Returns the empty string when `instructions` is empty so the\n * layer is dropped from the assembled prompt.\n *\n * @stable\n */\nexport function composeLayer2(instructions: string | undefined): string {\n if (instructions === undefined || instructions.trim().length === 0) return '';\n return `<agent_instructions>\\n${instructions}\\n</agent_instructions>`;\n}\n\n/**\n * Render Layer 4 skills metadata cards. Each entry is a\n * `<skill ... />` self-closing tag so the model sees only the\n * progressive-disclosure metadata — the full SKILL.md body is\n * loaded on-demand via `activate_skill(name)` per RB-04.\n *\n * @stable\n */\nexport interface SkillMetadataCard {\n readonly name: string;\n readonly description: string;\n readonly location?: string;\n /** When `true`, the card is excluded from the assembled prompt. */\n readonly disableModelInvocation?: boolean;\n}\n\nexport function composeLayer4Skills(cards: ReadonlyArray<SkillMetadataCard>): string {\n const visible = cards.filter((c) => c.disableModelInvocation !== true);\n if (visible.length === 0) return '';\n const lines = ['<skills_available>'];\n for (const card of visible) {\n const description = ` description=\"${escapeXmlAttr(card.description)}\"`;\n const location =\n card.location !== undefined ? ` location=\"${escapeXmlAttr(card.location)}\"` : '';\n lines.push(` <skill name=\"${escapeXmlAttr(card.name)}\"${description}${location} />`);\n }\n lines.push('</skills_available>');\n return lines.join('\\n');\n}\n\n/**\n * Render the inbound-sanitization preamble fragment. Emitted\n * AFTER the cache breakpoint so the Layer 1-4 cache prefix is\n * unaffected. Caller threads the fragment into the system content\n * post-Layer 5; the fragment is returned as a string so the\n * caller does not need to know the cache-breakpoint policy.\n *\n * @stable\n */\nexport function composeInboundPreamble(pack: ContextLocalePack): string {\n return pack.inboundSanitizationPreamble.text;\n}\n\nfunction escapeXmlAttr(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n"],"mappings":";;;;;;AAyBA,SAAgB,cAAc,MAAyB,MAA8B;AACnF,QAAO,SAAS,YAAY,KAAK,aAAa,UAAU,KAAK,aAAa;;;;;;;;;AAU5E,SAAgB,cAAc,cAA0C;AACtE,KAAI,iBAAiB,UAAa,aAAa,MAAM,CAAC,WAAW,EAAG,QAAO;AAC3E,QAAO,yBAAyB,aAAa;;AAmB/C,SAAgB,oBAAoB,OAAiD;CACnF,MAAM,UAAU,MAAM,QAAQ,MAAM,EAAE,2BAA2B,KAAK;AACtE,KAAI,QAAQ,WAAW,EAAG,QAAO;CACjC,MAAM,QAAQ,CAAC,qBAAqB;AACpC,MAAK,MAAM,QAAQ,SAAS;EAC1B,MAAM,cAAc,iBAAiB,cAAc,KAAK,YAAY,CAAC;EACrE,MAAM,WACJ,KAAK,aAAa,SAAY,cAAc,cAAc,KAAK,SAAS,CAAC,KAAK;AAChF,QAAM,KAAK,kBAAkB,cAAc,KAAK,KAAK,CAAC,GAAG,cAAc,SAAS,KAAK;;AAEvF,OAAM,KAAK,sBAAsB;AACjC,QAAO,MAAM,KAAK,KAAK;;;;;;;;;;;AAYzB,SAAgB,uBAAuB,MAAiC;AACtE,QAAO,KAAK,4BAA4B;;AAG1C,SAAS,cAAc,OAAuB;AAC5C,QAAO,MACJ,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,SAAS"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { ContextTokenCounter } from "./token-counter.js";
|
|
2
|
+
|
|
3
|
+
//#region src/context-engine/token-budget.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Layer-id discriminator. Mirrors the documented Layer 1-6
|
|
7
|
+
* structure of the layered system prompt.
|
|
8
|
+
*
|
|
9
|
+
* @stable
|
|
10
|
+
*/
|
|
11
|
+
type LayerId = 'identity' | 'memoryMetadata' | 'activeRules' | 'workingBlocks' | 'activeSkills' | 'autoRecall';
|
|
12
|
+
/**
|
|
13
|
+
* Default priority ladder. Higher priority means the layer is
|
|
14
|
+
* preserved longer when the budget is tight.
|
|
15
|
+
*
|
|
16
|
+
* @stable
|
|
17
|
+
*/
|
|
18
|
+
declare const DEFAULT_LAYER_PRIORITY: ReadonlyArray<LayerId>;
|
|
19
|
+
/**
|
|
20
|
+
* Single layer candidate fed to {@link allocate}. The `text` field
|
|
21
|
+
* carries the rendered fragment; `cap` is the optional per-layer
|
|
22
|
+
* upper bound (in tokens); `priority` overrides the default ladder
|
|
23
|
+
* for advanced use cases.
|
|
24
|
+
*
|
|
25
|
+
* @stable
|
|
26
|
+
*/
|
|
27
|
+
interface LayerCandidate {
|
|
28
|
+
readonly id: LayerId;
|
|
29
|
+
readonly text: string;
|
|
30
|
+
readonly cap?: number;
|
|
31
|
+
readonly priority?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Per-layer truncation mode used when the layer overflows its cap
|
|
35
|
+
* or the global budget. `truncate` is the default; `drop` removes
|
|
36
|
+
* the layer entirely when it would otherwise overflow.
|
|
37
|
+
*
|
|
38
|
+
* @stable
|
|
39
|
+
*/
|
|
40
|
+
type OverflowMode = 'truncate' | 'drop';
|
|
41
|
+
/**
|
|
42
|
+
* Output of {@link allocate} — one entry per surviving layer in
|
|
43
|
+
* priority order, plus a `truncated` flag for observability.
|
|
44
|
+
*
|
|
45
|
+
* @stable
|
|
46
|
+
*/
|
|
47
|
+
interface LayerAllocation {
|
|
48
|
+
readonly id: LayerId;
|
|
49
|
+
readonly text: string;
|
|
50
|
+
readonly tokens: number;
|
|
51
|
+
readonly truncated: boolean;
|
|
52
|
+
readonly droppedTokens: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Result of an allocator run.
|
|
56
|
+
*
|
|
57
|
+
* @stable
|
|
58
|
+
*/
|
|
59
|
+
interface AllocationResult {
|
|
60
|
+
readonly layers: ReadonlyArray<LayerAllocation>;
|
|
61
|
+
readonly totalTokens: number;
|
|
62
|
+
readonly budgetTokens: number;
|
|
63
|
+
readonly overflowDropped: ReadonlyArray<LayerId>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Truncate `text` to fit `maxTokens`, preserving the leading
|
|
67
|
+
* portion and replacing the trailing portion with the literal
|
|
68
|
+
* `[...truncated]` marker. The token estimate is computed via the
|
|
69
|
+
* supplied `counter`; truncation falls back to character-based
|
|
70
|
+
* trimming when the estimate is non-monotonic.
|
|
71
|
+
*
|
|
72
|
+
* Structure-aware (CE-16e): the cut never splits a tag, and block
|
|
73
|
+
* tags the cut leaves open are re-closed after the marker, so a
|
|
74
|
+
* capped layer of XML-ish markup (e.g. `<memory_blocks>`) stays
|
|
75
|
+
* well-formed in the assembled prompt. Plain strings are unaffected.
|
|
76
|
+
*
|
|
77
|
+
* @stable
|
|
78
|
+
*/
|
|
79
|
+
declare function truncateToTokens(text: string, maxTokens: number, counter: ContextTokenCounter): Promise<{
|
|
80
|
+
readonly text: string;
|
|
81
|
+
readonly tokens: number;
|
|
82
|
+
readonly truncated: boolean;
|
|
83
|
+
}>;
|
|
84
|
+
/**
|
|
85
|
+
* Run the allocator. Layers are sorted by priority ascending (the
|
|
86
|
+
* first layer is the highest priority); when the running total
|
|
87
|
+
* exceeds the budget, lower-priority layers are first capped at
|
|
88
|
+
* their per-layer `cap` (when set) and finally truncated to
|
|
89
|
+
* whatever fits.
|
|
90
|
+
*
|
|
91
|
+
* @stable
|
|
92
|
+
*/
|
|
93
|
+
declare function allocate(layers: ReadonlyArray<LayerCandidate>, budgetTokens: number, counter: ContextTokenCounter, options?: {
|
|
94
|
+
readonly priority?: ReadonlyArray<LayerId>;
|
|
95
|
+
readonly overflowMode?: OverflowMode;
|
|
96
|
+
}): Promise<AllocationResult>;
|
|
97
|
+
//#endregion
|
|
98
|
+
export { AllocationResult, DEFAULT_LAYER_PRIORITY, LayerAllocation, LayerCandidate, LayerId, OverflowMode, allocate, truncateToTokens };
|
|
99
|
+
//# sourceMappingURL=token-budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-budget.d.ts","names":[],"sources":["../../src/context-engine/token-budget.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;KAiCY,OAAA;;;;;;;cAcC,wBAAwB,cAAc;;;;;;;;;UAiBlC,cAAA;eACF;;;;;;;;;;;;KAaH,YAAA;;;;;;;UAQK,eAAA;eACF;;;;;;;;;;;UAYE,gBAAA;mBACE,cAAc;;;4BAGL,cAAc;;;;;;;;;;;;;;;;iBAmDpB,gBAAA,2CAGX,sBACR;;;;;;;;;;;;;;iBAuDmB,QAAA,SACZ,cAAc,gDAEb;sBAEa,cAAc;0BACV;IAEzB,QAAQ"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
//#region src/context-engine/token-budget.ts
|
|
2
|
+
/**
|
|
3
|
+
* Default priority ladder. Higher priority means the layer is
|
|
4
|
+
* preserved longer when the budget is tight.
|
|
5
|
+
*
|
|
6
|
+
* @stable
|
|
7
|
+
*/
|
|
8
|
+
const DEFAULT_LAYER_PRIORITY = Object.freeze([
|
|
9
|
+
"identity",
|
|
10
|
+
"memoryMetadata",
|
|
11
|
+
"activeRules",
|
|
12
|
+
"workingBlocks",
|
|
13
|
+
"activeSkills",
|
|
14
|
+
"autoRecall"
|
|
15
|
+
]);
|
|
16
|
+
/**
|
|
17
|
+
* Tags opened-but-not-closed in `text`, innermost-first. A heuristic
|
|
18
|
+
* scanner sized for the engine's own XML-ish layer markup
|
|
19
|
+
* (`<memory_blocks>`, `<memory_rules>`, ...) — self-closing tags are
|
|
20
|
+
* skipped and an unmatched closer is ignored.
|
|
21
|
+
*/
|
|
22
|
+
function unclosedTags(text) {
|
|
23
|
+
const stack = [];
|
|
24
|
+
const re = /<(\/?)([A-Za-z_][\w.-]*)(?:\s[^<>]*)?(\/?)>/g;
|
|
25
|
+
let match = re.exec(text);
|
|
26
|
+
while (match !== null) {
|
|
27
|
+
const [, closing, name, selfClosing] = match;
|
|
28
|
+
if (selfClosing !== "/" && name !== void 0) if (closing === "/") {
|
|
29
|
+
const idx = stack.lastIndexOf(name);
|
|
30
|
+
if (idx !== -1) stack.splice(idx, 1);
|
|
31
|
+
} else stack.push(name);
|
|
32
|
+
match = re.exec(text);
|
|
33
|
+
}
|
|
34
|
+
return stack.reverse();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Snap a prefix cut to a structure-safe point (CE-16e): never end
|
|
38
|
+
* inside a partially-emitted tag (`...<memory_blo`).
|
|
39
|
+
*/
|
|
40
|
+
function snapCut(kept) {
|
|
41
|
+
const lastOpen = kept.lastIndexOf("<");
|
|
42
|
+
return lastOpen > kept.lastIndexOf(">") ? kept.slice(0, lastOpen) : kept;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Truncate `text` to fit `maxTokens`, preserving the leading
|
|
46
|
+
* portion and replacing the trailing portion with the literal
|
|
47
|
+
* `[...truncated]` marker. The token estimate is computed via the
|
|
48
|
+
* supplied `counter`; truncation falls back to character-based
|
|
49
|
+
* trimming when the estimate is non-monotonic.
|
|
50
|
+
*
|
|
51
|
+
* Structure-aware (CE-16e): the cut never splits a tag, and block
|
|
52
|
+
* tags the cut leaves open are re-closed after the marker, so a
|
|
53
|
+
* capped layer of XML-ish markup (e.g. `<memory_blocks>`) stays
|
|
54
|
+
* well-formed in the assembled prompt. Plain strings are unaffected.
|
|
55
|
+
*
|
|
56
|
+
* @stable
|
|
57
|
+
*/
|
|
58
|
+
async function truncateToTokens(text, maxTokens, counter) {
|
|
59
|
+
if (maxTokens <= 0) return {
|
|
60
|
+
text: "",
|
|
61
|
+
tokens: 0,
|
|
62
|
+
truncated: text.length > 0
|
|
63
|
+
};
|
|
64
|
+
const initial = await counter.countText(text);
|
|
65
|
+
if (initial <= maxTokens) return {
|
|
66
|
+
text,
|
|
67
|
+
tokens: initial,
|
|
68
|
+
truncated: false
|
|
69
|
+
};
|
|
70
|
+
const marker = "\n[...truncated]";
|
|
71
|
+
const markerTokens = await counter.countText(marker);
|
|
72
|
+
const targetTokens = Math.max(0, maxTokens - markerTokens);
|
|
73
|
+
if (targetTokens <= 0) return {
|
|
74
|
+
text: marker,
|
|
75
|
+
tokens: markerTokens,
|
|
76
|
+
truncated: true
|
|
77
|
+
};
|
|
78
|
+
let lo = 0;
|
|
79
|
+
let hi = text.length;
|
|
80
|
+
let bestCut = 0;
|
|
81
|
+
while (lo <= hi) {
|
|
82
|
+
const mid = lo + hi >>> 1;
|
|
83
|
+
const slice = text.slice(0, mid);
|
|
84
|
+
if (await counter.countText(slice) <= targetTokens) {
|
|
85
|
+
bestCut = mid;
|
|
86
|
+
lo = mid + 1;
|
|
87
|
+
} else hi = mid - 1;
|
|
88
|
+
}
|
|
89
|
+
let kept = snapCut(text.slice(0, bestCut));
|
|
90
|
+
let closers = unclosedTags(kept).map((t) => `</${t}>`).join("");
|
|
91
|
+
let truncated = `${kept}${marker}${closers.length > 0 ? `\n${closers}` : ""}`;
|
|
92
|
+
let tokens = await counter.countText(truncated);
|
|
93
|
+
while (tokens > maxTokens && kept.length > 0) {
|
|
94
|
+
kept = snapCut(kept.slice(0, kept.length - Math.ceil(kept.length * .1)));
|
|
95
|
+
closers = unclosedTags(kept).map((t) => `</${t}>`).join("");
|
|
96
|
+
truncated = `${kept}${marker}${closers.length > 0 ? `\n${closers}` : ""}`;
|
|
97
|
+
tokens = await counter.countText(truncated);
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
text: truncated,
|
|
101
|
+
tokens,
|
|
102
|
+
truncated: true
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Run the allocator. Layers are sorted by priority ascending (the
|
|
107
|
+
* first layer is the highest priority); when the running total
|
|
108
|
+
* exceeds the budget, lower-priority layers are first capped at
|
|
109
|
+
* their per-layer `cap` (when set) and finally truncated to
|
|
110
|
+
* whatever fits.
|
|
111
|
+
*
|
|
112
|
+
* @stable
|
|
113
|
+
*/
|
|
114
|
+
async function allocate(layers, budgetTokens, counter, options = {}) {
|
|
115
|
+
const priorityOrder = options.priority ?? DEFAULT_LAYER_PRIORITY;
|
|
116
|
+
const overflowMode = options.overflowMode ?? "truncate";
|
|
117
|
+
const orderIndex = /* @__PURE__ */ new Map();
|
|
118
|
+
priorityOrder.forEach((id, idx) => {
|
|
119
|
+
orderIndex.set(id, idx);
|
|
120
|
+
});
|
|
121
|
+
const sorted = [...layers].sort((a, b) => {
|
|
122
|
+
return (a.priority ?? orderIndex.get(a.id) ?? 999) - (b.priority ?? orderIndex.get(b.id) ?? 999);
|
|
123
|
+
});
|
|
124
|
+
const capped = [];
|
|
125
|
+
for (const candidate of sorted) {
|
|
126
|
+
if (candidate.text.length === 0) {
|
|
127
|
+
capped.push({
|
|
128
|
+
candidate,
|
|
129
|
+
text: "",
|
|
130
|
+
tokens: 0,
|
|
131
|
+
truncated: false
|
|
132
|
+
});
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (candidate.cap !== void 0 && candidate.cap >= 0) {
|
|
136
|
+
const trimmed = await truncateToTokens(candidate.text, candidate.cap, counter);
|
|
137
|
+
capped.push({
|
|
138
|
+
candidate,
|
|
139
|
+
text: trimmed.text,
|
|
140
|
+
tokens: trimmed.tokens,
|
|
141
|
+
truncated: trimmed.truncated
|
|
142
|
+
});
|
|
143
|
+
} else {
|
|
144
|
+
const tokens = await counter.countText(candidate.text);
|
|
145
|
+
capped.push({
|
|
146
|
+
candidate,
|
|
147
|
+
text: candidate.text,
|
|
148
|
+
tokens,
|
|
149
|
+
truncated: false
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const budget = Math.max(0, budgetTokens);
|
|
154
|
+
const layersOut = [];
|
|
155
|
+
const overflowDropped = [];
|
|
156
|
+
let runningTotal = 0;
|
|
157
|
+
for (const entry of capped) {
|
|
158
|
+
if (entry.text.length === 0) {
|
|
159
|
+
layersOut.push({
|
|
160
|
+
id: entry.candidate.id,
|
|
161
|
+
text: "",
|
|
162
|
+
tokens: 0,
|
|
163
|
+
truncated: false,
|
|
164
|
+
droppedTokens: 0
|
|
165
|
+
});
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (runningTotal + entry.tokens <= budget) {
|
|
169
|
+
runningTotal += entry.tokens;
|
|
170
|
+
layersOut.push({
|
|
171
|
+
id: entry.candidate.id,
|
|
172
|
+
text: entry.text,
|
|
173
|
+
tokens: entry.tokens,
|
|
174
|
+
truncated: entry.truncated,
|
|
175
|
+
droppedTokens: 0
|
|
176
|
+
});
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const remaining = Math.max(0, budget - runningTotal);
|
|
180
|
+
if (overflowMode === "drop" || remaining === 0) {
|
|
181
|
+
overflowDropped.push(entry.candidate.id);
|
|
182
|
+
layersOut.push({
|
|
183
|
+
id: entry.candidate.id,
|
|
184
|
+
text: "",
|
|
185
|
+
tokens: 0,
|
|
186
|
+
truncated: false,
|
|
187
|
+
droppedTokens: entry.tokens
|
|
188
|
+
});
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const trimmed = await truncateToTokens(entry.text, remaining, counter);
|
|
192
|
+
runningTotal += trimmed.tokens;
|
|
193
|
+
layersOut.push({
|
|
194
|
+
id: entry.candidate.id,
|
|
195
|
+
text: trimmed.text,
|
|
196
|
+
tokens: trimmed.tokens,
|
|
197
|
+
truncated: true,
|
|
198
|
+
droppedTokens: entry.tokens - trimmed.tokens
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return Object.freeze({
|
|
202
|
+
layers: Object.freeze(layersOut),
|
|
203
|
+
totalTokens: runningTotal,
|
|
204
|
+
budgetTokens: budget,
|
|
205
|
+
overflowDropped: Object.freeze(overflowDropped)
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
//#endregion
|
|
210
|
+
export { DEFAULT_LAYER_PRIORITY, allocate, truncateToTokens };
|
|
211
|
+
//# sourceMappingURL=token-budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-budget.js","names":["DEFAULT_LAYER_PRIORITY: ReadonlyArray<LayerId>","stack: string[]","capped: Array<{\n readonly candidate: LayerCandidate;\n text: string;\n tokens: number;\n truncated: boolean;\n }>","layersOut: LayerAllocation[]","overflowDropped: LayerId[]"],"sources":["../../src/context-engine/token-budget.ts"],"sourcesContent":["/**\n * Priority-ordered token-budget allocator. The allocator runs over the six\n * layers of the {@link ContextEngine}'s system prompt and trims the\n * lowest-priority layers first when the combined cost exceeds the configured\n * budget.\n *\n * This **truncation priority** is deliberately separate from the **emission\n * order** (CE-9): the engine emits the surviving layers in a stable order\n * (identity → rules → blocks → skills → [KV-cache breakpoint] → metadata →\n * auto-recall) regardless of which were trimmed, so the cacheable prefix stays\n * byte-stable across turns. This ladder governs only *what gets dropped first*.\n *\n * Priority (configurable), highest preserved → first truncated:\n *\n * 1. **identity** — Layer 1 (`graphorin_memory_base` + agent\n * instructions). Always preserved; truncated last.\n * 2. **memoryMetadata** — Layer 5. Small (~100-300 tokens).\n * 3. **activeRules** — Layer 4 procedural rules.\n * 4. **workingBlocks** — Layer 3.\n * 5. **activeSkills** — Layer 4 skills metadata cards.\n * 6. **autoRecall** — Layer 6. Lowest priority; truncated first.\n *\n * @packageDocumentation\n */\n\nimport type { ContextTokenCounter } from './token-counter.js';\n\n/**\n * Layer-id discriminator. Mirrors the documented Layer 1-6\n * structure of the layered system prompt.\n *\n * @stable\n */\nexport type LayerId =\n | 'identity'\n | 'memoryMetadata'\n | 'activeRules'\n | 'workingBlocks'\n | 'activeSkills'\n | 'autoRecall';\n\n/**\n * Default priority ladder. Higher priority means the layer is\n * preserved longer when the budget is tight.\n *\n * @stable\n */\nexport const DEFAULT_LAYER_PRIORITY: ReadonlyArray<LayerId> = Object.freeze([\n 'identity',\n 'memoryMetadata',\n 'activeRules',\n 'workingBlocks',\n 'activeSkills',\n 'autoRecall',\n]);\n\n/**\n * Single layer candidate fed to {@link allocate}. The `text` field\n * carries the rendered fragment; `cap` is the optional per-layer\n * upper bound (in tokens); `priority` overrides the default ladder\n * for advanced use cases.\n *\n * @stable\n */\nexport interface LayerCandidate {\n readonly id: LayerId;\n readonly text: string;\n readonly cap?: number;\n readonly priority?: number;\n}\n\n/**\n * Per-layer truncation mode used when the layer overflows its cap\n * or the global budget. `truncate` is the default; `drop` removes\n * the layer entirely when it would otherwise overflow.\n *\n * @stable\n */\nexport type OverflowMode = 'truncate' | 'drop';\n\n/**\n * Output of {@link allocate} — one entry per surviving layer in\n * priority order, plus a `truncated` flag for observability.\n *\n * @stable\n */\nexport interface LayerAllocation {\n readonly id: LayerId;\n readonly text: string;\n readonly tokens: number;\n readonly truncated: boolean;\n readonly droppedTokens: number;\n}\n\n/**\n * Result of an allocator run.\n *\n * @stable\n */\nexport interface AllocationResult {\n readonly layers: ReadonlyArray<LayerAllocation>;\n readonly totalTokens: number;\n readonly budgetTokens: number;\n readonly overflowDropped: ReadonlyArray<LayerId>;\n}\n\n/**\n * Tags opened-but-not-closed in `text`, innermost-first. A heuristic\n * scanner sized for the engine's own XML-ish layer markup\n * (`<memory_blocks>`, `<memory_rules>`, ...) — self-closing tags are\n * skipped and an unmatched closer is ignored.\n */\nfunction unclosedTags(text: string): string[] {\n const stack: string[] = [];\n const re = /<(\\/?)([A-Za-z_][\\w.-]*)(?:\\s[^<>]*)?(\\/?)>/g;\n let match = re.exec(text);\n while (match !== null) {\n const [, closing, name, selfClosing] = match;\n if (selfClosing !== '/' && name !== undefined) {\n if (closing === '/') {\n const idx = stack.lastIndexOf(name);\n if (idx !== -1) stack.splice(idx, 1);\n } else {\n stack.push(name);\n }\n }\n match = re.exec(text);\n }\n return stack.reverse();\n}\n\n/**\n * Snap a prefix cut to a structure-safe point (CE-16e): never end\n * inside a partially-emitted tag (`...<memory_blo`).\n */\nfunction snapCut(kept: string): string {\n const lastOpen = kept.lastIndexOf('<');\n return lastOpen > kept.lastIndexOf('>') ? kept.slice(0, lastOpen) : kept;\n}\n\n/**\n * Truncate `text` to fit `maxTokens`, preserving the leading\n * portion and replacing the trailing portion with the literal\n * `[...truncated]` marker. The token estimate is computed via the\n * supplied `counter`; truncation falls back to character-based\n * trimming when the estimate is non-monotonic.\n *\n * Structure-aware (CE-16e): the cut never splits a tag, and block\n * tags the cut leaves open are re-closed after the marker, so a\n * capped layer of XML-ish markup (e.g. `<memory_blocks>`) stays\n * well-formed in the assembled prompt. Plain strings are unaffected.\n *\n * @stable\n */\nexport async function truncateToTokens(\n text: string,\n maxTokens: number,\n counter: ContextTokenCounter,\n): Promise<{ readonly text: string; readonly tokens: number; readonly truncated: boolean }> {\n if (maxTokens <= 0) return { text: '', tokens: 0, truncated: text.length > 0 };\n const initial = await counter.countText(text);\n if (initial <= maxTokens) return { text, tokens: initial, truncated: false };\n const marker = '\\n[...truncated]';\n const markerTokens = await counter.countText(marker);\n const targetTokens = Math.max(0, maxTokens - markerTokens);\n if (targetTokens <= 0) return { text: marker, tokens: markerTokens, truncated: true };\n // Approximate the cut point via the inverse of the heuristic\n // chars-per-token ratio. Falls back to repeated halving when the\n // ratio is non-monotonic for the supplied counter.\n let lo = 0;\n let hi = text.length;\n let bestCut = 0;\n while (lo <= hi) {\n const mid = (lo + hi) >>> 1;\n const slice = text.slice(0, mid);\n const tokens = await counter.countText(slice);\n if (tokens <= targetTokens) {\n bestCut = mid;\n lo = mid + 1;\n } else {\n hi = mid - 1;\n }\n }\n let kept = snapCut(text.slice(0, bestCut));\n let closers = unclosedTags(kept)\n .map((t) => `</${t}>`)\n .join('');\n let truncated = `${kept}${marker}${closers.length > 0 ? `\\n${closers}` : ''}`;\n let tokens = await counter.countText(truncated);\n // Re-closing may push past the cap — shave the kept body until the\n // whole thing (body + marker + closers) fits. Terminates: `kept`\n // shrinks by ≥1 char per pass, and the empty body fits by the\n // `targetTokens > 0` guard above.\n while (tokens > maxTokens && kept.length > 0) {\n kept = snapCut(kept.slice(0, kept.length - Math.ceil(kept.length * 0.1)));\n closers = unclosedTags(kept)\n .map((t) => `</${t}>`)\n .join('');\n truncated = `${kept}${marker}${closers.length > 0 ? `\\n${closers}` : ''}`;\n tokens = await counter.countText(truncated);\n }\n return { text: truncated, tokens, truncated: true };\n}\n\n/**\n * Run the allocator. Layers are sorted by priority ascending (the\n * first layer is the highest priority); when the running total\n * exceeds the budget, lower-priority layers are first capped at\n * their per-layer `cap` (when set) and finally truncated to\n * whatever fits.\n *\n * @stable\n */\nexport async function allocate(\n layers: ReadonlyArray<LayerCandidate>,\n budgetTokens: number,\n counter: ContextTokenCounter,\n options: {\n readonly priority?: ReadonlyArray<LayerId>;\n readonly overflowMode?: OverflowMode;\n } = {},\n): Promise<AllocationResult> {\n const priorityOrder = options.priority ?? DEFAULT_LAYER_PRIORITY;\n const overflowMode = options.overflowMode ?? 'truncate';\n const orderIndex = new Map<LayerId, number>();\n priorityOrder.forEach((id, idx) => {\n orderIndex.set(id, idx);\n });\n const sorted = [...layers].sort((a, b) => {\n const aPriority = a.priority ?? orderIndex.get(a.id) ?? 999;\n const bPriority = b.priority ?? orderIndex.get(b.id) ?? 999;\n return aPriority - bPriority;\n });\n\n // First pass: enforce per-layer caps (regardless of total budget).\n const capped: Array<{\n readonly candidate: LayerCandidate;\n text: string;\n tokens: number;\n truncated: boolean;\n }> = [];\n for (const candidate of sorted) {\n if (candidate.text.length === 0) {\n capped.push({ candidate, text: '', tokens: 0, truncated: false });\n continue;\n }\n if (candidate.cap !== undefined && candidate.cap >= 0) {\n const trimmed = await truncateToTokens(candidate.text, candidate.cap, counter);\n capped.push({\n candidate,\n text: trimmed.text,\n tokens: trimmed.tokens,\n truncated: trimmed.truncated,\n });\n } else {\n const tokens = await counter.countText(candidate.text);\n capped.push({ candidate, text: candidate.text, tokens, truncated: false });\n }\n }\n\n // Second pass: enforce the global budget. Walk the layers in\n // priority order, accumulating tokens; when the running total\n // exceeds the budget, the rest are dropped or truncated to fit.\n const budget = Math.max(0, budgetTokens);\n const layersOut: LayerAllocation[] = [];\n const overflowDropped: LayerId[] = [];\n let runningTotal = 0;\n for (const entry of capped) {\n if (entry.text.length === 0) {\n layersOut.push({\n id: entry.candidate.id,\n text: '',\n tokens: 0,\n truncated: false,\n droppedTokens: 0,\n });\n continue;\n }\n if (runningTotal + entry.tokens <= budget) {\n runningTotal += entry.tokens;\n layersOut.push({\n id: entry.candidate.id,\n text: entry.text,\n tokens: entry.tokens,\n truncated: entry.truncated,\n droppedTokens: 0,\n });\n continue;\n }\n const remaining = Math.max(0, budget - runningTotal);\n if (overflowMode === 'drop' || remaining === 0) {\n overflowDropped.push(entry.candidate.id);\n layersOut.push({\n id: entry.candidate.id,\n text: '',\n tokens: 0,\n truncated: false,\n droppedTokens: entry.tokens,\n });\n continue;\n }\n const trimmed = await truncateToTokens(entry.text, remaining, counter);\n runningTotal += trimmed.tokens;\n layersOut.push({\n id: entry.candidate.id,\n text: trimmed.text,\n tokens: trimmed.tokens,\n truncated: true,\n droppedTokens: entry.tokens - trimmed.tokens,\n });\n }\n\n return Object.freeze({\n layers: Object.freeze(layersOut),\n totalTokens: runningTotal,\n budgetTokens: budget,\n overflowDropped: Object.freeze(overflowDropped),\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Message, TokenCounter } from "@graphorin/core";
|
|
2
|
+
|
|
3
|
+
//#region src/context-engine/token-counter.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Pluggable text-token counter used inside the ContextEngine. The
|
|
7
|
+
* surface is narrower than {@link TokenCounter} because the engine
|
|
8
|
+
* never assembles a message list during the budget-allocation
|
|
9
|
+
* phase — it operates on rendered text fragments.
|
|
10
|
+
*
|
|
11
|
+
* @stable
|
|
12
|
+
*/
|
|
13
|
+
interface ContextTokenCounter {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
countText(text: string): Promise<number>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Built-in heuristic counter — chars/4 for Latin-ish text plus one
|
|
19
|
+
* token per dense-script (CJK/kana/hangul) character (CE-13). Stable
|
|
20
|
+
* default when the operator does not pass a real {@link TokenCounter}.
|
|
21
|
+
*
|
|
22
|
+
* @stable
|
|
23
|
+
*/
|
|
24
|
+
declare const HEURISTIC_TOKEN_COUNTER: ContextTokenCounter;
|
|
25
|
+
/**
|
|
26
|
+
* Wrap a real {@link TokenCounter} into the narrower
|
|
27
|
+
* {@link ContextTokenCounter} surface. Calls `countText(text)`
|
|
28
|
+
* directly for max precision; falls back to the synthetic
|
|
29
|
+
* single-message bridge when only `count(messages)` is supported.
|
|
30
|
+
*
|
|
31
|
+
* @stable
|
|
32
|
+
*/
|
|
33
|
+
declare function adaptTokenCounter(counter: TokenCounter): ContextTokenCounter;
|
|
34
|
+
/**
|
|
35
|
+
* Count tokens across a message list using a {@link TokenCounter}
|
|
36
|
+
* (or the heuristic fallback). Used by the trigger-evaluation hot
|
|
37
|
+
* path of the auto-compaction subsystem (RB-46) at the top of every
|
|
38
|
+
* step.
|
|
39
|
+
*
|
|
40
|
+
* @stable
|
|
41
|
+
*/
|
|
42
|
+
declare function countMessageTokens(messages: ReadonlyArray<Message>, counter: TokenCounter | ContextTokenCounter): Promise<number>;
|
|
43
|
+
/**
|
|
44
|
+
* Render a `Message` into a single textual approximation suitable
|
|
45
|
+
* for token counting. Multimodal parts other than `'text'` /
|
|
46
|
+
* `'reasoning'` contribute a constant approximation so the counter
|
|
47
|
+
* does not silently under-count.
|
|
48
|
+
*
|
|
49
|
+
* @stable
|
|
50
|
+
*/
|
|
51
|
+
declare function renderMessageText(message: Message): string;
|
|
52
|
+
//#endregion
|
|
53
|
+
export { ContextTokenCounter, HEURISTIC_TOKEN_COUNTER, adaptTokenCounter, countMessageTokens, renderMessageText };
|
|
54
|
+
//# sourceMappingURL=token-counter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-counter.d.ts","names":[],"sources":["../../src/context-engine/token-counter.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;UA0BiB,mBAAA;;2BAEU;;;;;;;;;cAyBd,yBAAyB;;;;;;;;;iBAkBtB,iBAAA,UAA2B,eAAe;;;;;;;;;iBAkBpC,kBAAA,WACV,cAAc,mBACf,eAAe,sBACvB;;;;;;;;;iBAmBa,iBAAA,UAA2B"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
//#region src/context-engine/token-counter.ts
|
|
2
|
+
/**
|
|
3
|
+
* Script-aware heuristic (CE-13). The flat chars/4 rule undercounts
|
|
4
|
+
* non-Latin scripts ~4x — real tokenizers emit roughly one token per
|
|
5
|
+
* CJK character (and per kana / hangul syllable), so a 200k-window
|
|
6
|
+
* threshold expressed in "heuristic tokens" let CJK conversations blow
|
|
7
|
+
* past the provider limit before compaction ever fired. Characters
|
|
8
|
+
* outside the Latin-ish range count at one token each; Latin text keeps
|
|
9
|
+
* the classic chars/4. Still a heuristic — wire a real `TokenCounter`
|
|
10
|
+
* (e.g. the provider package's JsTiktokenCounter) for production
|
|
11
|
+
* accuracy.
|
|
12
|
+
*/
|
|
13
|
+
const DENSE_SCRIPT_RE = /[ᄀ-ᇿ⺀-〿-ヿ-ㇰ-䶿一-鿿ꥠ-가-豈--]/g;
|
|
14
|
+
/**
|
|
15
|
+
* Built-in heuristic counter — chars/4 for Latin-ish text plus one
|
|
16
|
+
* token per dense-script (CJK/kana/hangul) character (CE-13). Stable
|
|
17
|
+
* default when the operator does not pass a real {@link TokenCounter}.
|
|
18
|
+
*
|
|
19
|
+
* @stable
|
|
20
|
+
*/
|
|
21
|
+
const HEURISTIC_TOKEN_COUNTER = Object.freeze({
|
|
22
|
+
id: "graphorin/heuristic@chars-per-4+dense-script",
|
|
23
|
+
async countText(text) {
|
|
24
|
+
if (typeof text !== "string" || text.length === 0) return 0;
|
|
25
|
+
const dense = text.match(DENSE_SCRIPT_RE)?.length ?? 0;
|
|
26
|
+
const latin = text.length - dense;
|
|
27
|
+
return Math.ceil(latin / 4) + dense;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* Wrap a real {@link TokenCounter} into the narrower
|
|
32
|
+
* {@link ContextTokenCounter} surface. Calls `countText(text)`
|
|
33
|
+
* directly for max precision; falls back to the synthetic
|
|
34
|
+
* single-message bridge when only `count(messages)` is supported.
|
|
35
|
+
*
|
|
36
|
+
* @stable
|
|
37
|
+
*/
|
|
38
|
+
function adaptTokenCounter(counter) {
|
|
39
|
+
return Object.freeze({
|
|
40
|
+
id: counter.id,
|
|
41
|
+
async countText(text) {
|
|
42
|
+
if (typeof text !== "string" || text.length === 0) return 0;
|
|
43
|
+
return counter.countText(text);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Count tokens across a message list using a {@link TokenCounter}
|
|
49
|
+
* (or the heuristic fallback). Used by the trigger-evaluation hot
|
|
50
|
+
* path of the auto-compaction subsystem (RB-46) at the top of every
|
|
51
|
+
* step.
|
|
52
|
+
*
|
|
53
|
+
* @stable
|
|
54
|
+
*/
|
|
55
|
+
async function countMessageTokens(messages, counter) {
|
|
56
|
+
if ("count" in counter && typeof counter.count === "function") return counter.count(messages);
|
|
57
|
+
let total = 0;
|
|
58
|
+
for (const message of messages) total += await counter.countText(renderMessageText(message));
|
|
59
|
+
return total;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Render a `Message` into a single textual approximation suitable
|
|
63
|
+
* for token counting. Multimodal parts other than `'text'` /
|
|
64
|
+
* `'reasoning'` contribute a constant approximation so the counter
|
|
65
|
+
* does not silently under-count.
|
|
66
|
+
*
|
|
67
|
+
* @stable
|
|
68
|
+
*/
|
|
69
|
+
function renderMessageText(message) {
|
|
70
|
+
if (message.role === "system") return message.content;
|
|
71
|
+
const content = message.content;
|
|
72
|
+
if (typeof content === "string") return content;
|
|
73
|
+
let out = "";
|
|
74
|
+
for (const part of content) if (part.type === "text" || part.type === "reasoning") out += `${part.text}\n`;
|
|
75
|
+
else out += "[non-text-part]\n";
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
//#endregion
|
|
80
|
+
export { HEURISTIC_TOKEN_COUNTER, adaptTokenCounter, countMessageTokens, renderMessageText };
|
|
81
|
+
//# sourceMappingURL=token-counter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-counter.js","names":["HEURISTIC_TOKEN_COUNTER: ContextTokenCounter"],"sources":["../../src/context-engine/token-counter.ts"],"sourcesContent":["/**\n * Token-counting helpers used by the {@link ContextEngine}. The\n * production path accepts a {@link TokenCounter} from\n * `@graphorin/core` (the default `JsTiktokenCounter` lives in\n * `@graphorin/provider`); the fallback path is a deterministic\n * heuristic that keeps unit tests dependency-free.\n *\n * The fallback counts ~1 token per 4 characters — close enough for\n * budgeting decisions, intentionally less precise than a real BPE\n * tokenizer. Production deployments wire a real\n * {@link TokenCounter} through `createMemory({ contextEngine: {\n * tokenCounter: ... } })`.\n *\n * @packageDocumentation\n */\n\nimport type { Message, TokenCounter } from '@graphorin/core';\n\n/**\n * Pluggable text-token counter used inside the ContextEngine. The\n * surface is narrower than {@link TokenCounter} because the engine\n * never assembles a message list during the budget-allocation\n * phase — it operates on rendered text fragments.\n *\n * @stable\n */\nexport interface ContextTokenCounter {\n readonly id: string;\n countText(text: string): Promise<number>;\n}\n\n/**\n * Script-aware heuristic (CE-13). The flat chars/4 rule undercounts\n * non-Latin scripts ~4x — real tokenizers emit roughly one token per\n * CJK character (and per kana / hangul syllable), so a 200k-window\n * threshold expressed in \"heuristic tokens\" let CJK conversations blow\n * past the provider limit before compaction ever fired. Characters\n * outside the Latin-ish range count at one token each; Latin text keeps\n * the classic chars/4. Still a heuristic — wire a real `TokenCounter`\n * (e.g. the provider package's JsTiktokenCounter) for production\n * accuracy.\n */\nconst DENSE_SCRIPT_RE =\n // CJK unified + extensions, kana, hangul, CJK punctuation, full-width forms.\n /[ᄀ-ᇿ⺀-〿-ヿ-ㇰ-䶿一-鿿ꥠ-가-豈--]/g;\n\n/**\n * Built-in heuristic counter — chars/4 for Latin-ish text plus one\n * token per dense-script (CJK/kana/hangul) character (CE-13). Stable\n * default when the operator does not pass a real {@link TokenCounter}.\n *\n * @stable\n */\nexport const HEURISTIC_TOKEN_COUNTER: ContextTokenCounter = Object.freeze({\n id: 'graphorin/heuristic@chars-per-4+dense-script',\n async countText(text: string): Promise<number> {\n if (typeof text !== 'string' || text.length === 0) return 0;\n const dense = text.match(DENSE_SCRIPT_RE)?.length ?? 0;\n const latin = text.length - dense;\n return Math.ceil(latin / 4) + dense;\n },\n});\n\n/**\n * Wrap a real {@link TokenCounter} into the narrower\n * {@link ContextTokenCounter} surface. Calls `countText(text)`\n * directly for max precision; falls back to the synthetic\n * single-message bridge when only `count(messages)` is supported.\n *\n * @stable\n */\nexport function adaptTokenCounter(counter: TokenCounter): ContextTokenCounter {\n return Object.freeze({\n id: counter.id,\n async countText(text: string): Promise<number> {\n if (typeof text !== 'string' || text.length === 0) return 0;\n return counter.countText(text);\n },\n });\n}\n\n/**\n * Count tokens across a message list using a {@link TokenCounter}\n * (or the heuristic fallback). Used by the trigger-evaluation hot\n * path of the auto-compaction subsystem (RB-46) at the top of every\n * step.\n *\n * @stable\n */\nexport async function countMessageTokens(\n messages: ReadonlyArray<Message>,\n counter: TokenCounter | ContextTokenCounter,\n): Promise<number> {\n if ('count' in counter && typeof (counter as TokenCounter).count === 'function') {\n return (counter as TokenCounter).count(messages);\n }\n let total = 0;\n for (const message of messages) {\n total += await counter.countText(renderMessageText(message));\n }\n return total;\n}\n\n/**\n * Render a `Message` into a single textual approximation suitable\n * for token counting. Multimodal parts other than `'text'` /\n * `'reasoning'` contribute a constant approximation so the counter\n * does not silently under-count.\n *\n * @stable\n */\nexport function renderMessageText(message: Message): string {\n if (message.role === 'system') return message.content;\n const content = message.content;\n if (typeof content === 'string') return content;\n let out = '';\n for (const part of content) {\n if (part.type === 'text' || part.type === 'reasoning') {\n out += `${part.text}\\n`;\n } else {\n // Approximate non-text parts (image / audio / file) as a small\n // fixed cost so the counter does not silently under-count.\n out += '[non-text-part]\\n';\n }\n }\n return out;\n}\n"],"mappings":";;;;;;;;;;;;AA0CA,MAAM,kBAEJ;;;;;;;;AASF,MAAaA,0BAA+C,OAAO,OAAO;CACxE,IAAI;CACJ,MAAM,UAAU,MAA+B;AAC7C,MAAI,OAAO,SAAS,YAAY,KAAK,WAAW,EAAG,QAAO;EAC1D,MAAM,QAAQ,KAAK,MAAM,gBAAgB,EAAE,UAAU;EACrD,MAAM,QAAQ,KAAK,SAAS;AAC5B,SAAO,KAAK,KAAK,QAAQ,EAAE,GAAG;;CAEjC,CAAC;;;;;;;;;AAUF,SAAgB,kBAAkB,SAA4C;AAC5E,QAAO,OAAO,OAAO;EACnB,IAAI,QAAQ;EACZ,MAAM,UAAU,MAA+B;AAC7C,OAAI,OAAO,SAAS,YAAY,KAAK,WAAW,EAAG,QAAO;AAC1D,UAAO,QAAQ,UAAU,KAAK;;EAEjC,CAAC;;;;;;;;;;AAWJ,eAAsB,mBACpB,UACA,SACiB;AACjB,KAAI,WAAW,WAAW,OAAQ,QAAyB,UAAU,WACnE,QAAQ,QAAyB,MAAM,SAAS;CAElD,IAAI,QAAQ;AACZ,MAAK,MAAM,WAAW,SACpB,UAAS,MAAM,QAAQ,UAAU,kBAAkB,QAAQ,CAAC;AAE9D,QAAO;;;;;;;;;;AAWT,SAAgB,kBAAkB,SAA0B;AAC1D,KAAI,QAAQ,SAAS,SAAU,QAAO,QAAQ;CAC9C,MAAM,UAAU,QAAQ;AACxB,KAAI,OAAO,YAAY,SAAU,QAAO;CACxC,IAAI,MAAM;AACV,MAAK,MAAM,QAAQ,QACjB,KAAI,KAAK,SAAS,UAAU,KAAK,SAAS,YACxC,QAAO,GAAG,KAAK,KAAK;KAIpB,QAAO;AAGX,QAAO"}
|