@noetic-tools/types 2.3.0 → 3.1.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/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/types/channel.d.ts +4 -2
- package/dist/types/common.d.ts +26 -10
- package/dist/types/context-cache.d.ts +84 -0
- package/dist/types/context-cache.js +2 -0
- package/dist/types/context-layer.d.ts +51 -0
- package/dist/types/context-parts/layer-usage.d.ts +24 -0
- package/dist/types/context.d.ts +7 -0
- package/dist/types/runtime.d.ts +28 -0
- package/dist/util/content-hash.d.ts +25 -0
- package/dist/util/content-hash.js +65 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export * from './schemas/workflow.js';
|
|
|
10
10
|
export * from './types/channel.js';
|
|
11
11
|
export * from './types/common.js';
|
|
12
12
|
export * from './types/context.js';
|
|
13
|
+
export * from './types/context-cache.js';
|
|
13
14
|
export * from './types/context-layer.js';
|
|
14
15
|
export * from './types/context-parts/item-log.js';
|
|
15
16
|
export * from './types/context-parts/layer-usage.js';
|
|
@@ -29,5 +30,6 @@ export * from './types/sub-harness.js';
|
|
|
29
30
|
export * from './types/subprocess-adapter.js';
|
|
30
31
|
export * from './types/tool.js';
|
|
31
32
|
export * from './types/tool-context.js';
|
|
33
|
+
export * from './util/content-hash.js';
|
|
32
34
|
export * from './util/framework-cast.js';
|
|
33
35
|
export * from './util/message-helpers.js';
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ export * from './schemas/workflow.js';
|
|
|
10
10
|
export * from './types/channel.js';
|
|
11
11
|
export * from './types/common.js';
|
|
12
12
|
export * from './types/context.js';
|
|
13
|
+
export * from './types/context-cache.js';
|
|
13
14
|
export * from './types/context-layer.js';
|
|
14
15
|
export * from './types/context-parts/item-log.js';
|
|
15
16
|
export * from './types/context-parts/layer-usage.js';
|
|
@@ -29,5 +30,6 @@ export * from './types/sub-harness.js';
|
|
|
29
30
|
export * from './types/subprocess-adapter.js';
|
|
30
31
|
export * from './types/tool.js';
|
|
31
32
|
export * from './types/tool-context.js';
|
|
33
|
+
export * from './util/content-hash.js';
|
|
32
34
|
export * from './util/framework-cast.js';
|
|
33
35
|
export * from './util/message-helpers.js';
|
package/dist/types/channel.d.ts
CHANGED
|
@@ -14,11 +14,13 @@ export interface Channel<T> {
|
|
|
14
14
|
readonly capacity?: number;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* A channel
|
|
17
|
+
* A channel reachable from outside the execution tree: writable via
|
|
18
|
+
* `getChannelHandle` (e.g. user input) and readable via `getChannelStream`
|
|
19
|
+
* (e.g. approval requests posted to a chat platform).
|
|
18
20
|
* @public
|
|
19
21
|
*/
|
|
20
22
|
export interface ExternalChannel<T> extends Channel<T> {
|
|
21
|
-
/** Marker indicating the channel accepts
|
|
23
|
+
/** Marker indicating the channel accepts external readers and writers. */
|
|
22
24
|
readonly external: true;
|
|
23
25
|
}
|
|
24
26
|
/** @public Handle for sending messages to a channel and checking its closed state. */
|
package/dist/types/common.d.ts
CHANGED
|
@@ -56,25 +56,41 @@ export interface TokenUsage {
|
|
|
56
56
|
total: number;
|
|
57
57
|
cached?: number;
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* @public Token counts for a single model round.
|
|
61
|
+
*
|
|
62
|
+
* `cachedTokens` and `cacheWriteTokens` are `undefined` — not `0` — when the
|
|
63
|
+
* provider reports no prompt-cache figures at all. Callers that steer on cache
|
|
64
|
+
* behaviour must tell "nothing was cached" apart from "this provider doesn't
|
|
65
|
+
* say", so the distinction is preserved rather than defaulted away.
|
|
66
|
+
*/
|
|
67
|
+
export interface RoundUsage {
|
|
68
|
+
inputTokens: number;
|
|
69
|
+
outputTokens: number;
|
|
70
|
+
/** Prompt tokens served from the provider's cache (a cache *read*). */
|
|
71
|
+
cachedTokens?: number;
|
|
72
|
+
/** Prompt tokens written into the provider's cache (a cache *write*). */
|
|
73
|
+
cacheWriteTokens?: number;
|
|
74
|
+
}
|
|
59
75
|
/** @public Metadata captured from the most recent step execution. */
|
|
60
76
|
export interface StepMeta {
|
|
61
77
|
toolCalls?: FunctionCallItem[];
|
|
62
|
-
usage?:
|
|
63
|
-
inputTokens: number;
|
|
64
|
-
outputTokens: number;
|
|
65
|
-
cachedTokens?: number;
|
|
66
|
-
};
|
|
78
|
+
usage?: RoundUsage;
|
|
67
79
|
cost?: number;
|
|
68
80
|
responseItems?: ReadonlyArray<Item>;
|
|
69
81
|
}
|
|
70
82
|
/** @public Structured response returned by a model adapter after an LLM call. */
|
|
71
83
|
export interface LLMResponse {
|
|
72
84
|
items: Item[];
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
85
|
+
/** Totals across every round of the call. */
|
|
86
|
+
usage: RoundUsage;
|
|
87
|
+
/**
|
|
88
|
+
* Per-round breakdown, oldest first. Rounds after the first replay the same
|
|
89
|
+
* assembled view plus appended tool traffic, so they hit the prompt cache
|
|
90
|
+
* regardless of whether the first round did — only `rounds[0]` answers
|
|
91
|
+
* "was my prefix cached?".
|
|
92
|
+
*/
|
|
93
|
+
rounds?: ReadonlyArray<RoundUsage>;
|
|
78
94
|
cost?: number;
|
|
79
95
|
}
|
|
80
96
|
/** @public Configuration for the LLM provider used by the agent harness. */
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Item } from './items.js';
|
|
2
|
+
/**
|
|
3
|
+
* @public Tuning for the prompt-cache anchoring described on {@link LayerPlacement}.
|
|
4
|
+
*
|
|
5
|
+
* The defaults are chosen so that a layer set with no explicit placements still
|
|
6
|
+
* gets a stable prefix without any configuration.
|
|
7
|
+
*/
|
|
8
|
+
export interface ContextCacheConfig {
|
|
9
|
+
/** Master switch. Default `true`. When off, every layer renders before history, as it did before bands existed. */
|
|
10
|
+
enabled?: boolean;
|
|
11
|
+
/** Re-anchor when the first round reports fewer cached tokens than this. Default 100. */
|
|
12
|
+
minCachedTokens?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Assemblies an epoch must reach before its cache figures are judged. Default 2.
|
|
15
|
+
* The first assembly after a re-anchor writes the cache rather than reading it,
|
|
16
|
+
* so judging it immediately would re-anchor forever and never pin anything.
|
|
17
|
+
*/
|
|
18
|
+
minEpochAssemblies?: number;
|
|
19
|
+
/** Assemblies after which an epoch re-anchors regardless of cache figures. Default 50. */
|
|
20
|
+
maxEpochAssemblies?: number;
|
|
21
|
+
/** Re-anchor once supersedes cost more than this fraction of the anchor band. Default 0.15. */
|
|
22
|
+
deltaBudgetFraction?: number;
|
|
23
|
+
/** An `'auto'` layer changing at least this often moves to the live band. Default 0.5. */
|
|
24
|
+
autoDemoteChurn?: number;
|
|
25
|
+
/** An `'auto'` layer changing at most this often moves back to the anchor band. Default 0.2. */
|
|
26
|
+
autoPromoteChurn?: number;
|
|
27
|
+
/** Assemblies a layer must be watched for before its placement moves. Default 3. */
|
|
28
|
+
minChurnSamples?: number;
|
|
29
|
+
/** Fraction of the churn counters carried across a re-anchor. Default 0.5. */
|
|
30
|
+
churnDecay?: number;
|
|
31
|
+
}
|
|
32
|
+
/** @public Why an epoch re-anchored. */
|
|
33
|
+
export type ReanchorReason = 'cold-start' | 'instructions-changed' | 'cache-miss' | 'delta-pressure' | 'delta-overflow' | 'max-age';
|
|
34
|
+
/** @public One layer's rendered output, held byte-stable for the life of an epoch. */
|
|
35
|
+
export interface AnchorPin {
|
|
36
|
+
layerId: string;
|
|
37
|
+
/** The exact items served every assembly this epoch. */
|
|
38
|
+
items: Item[];
|
|
39
|
+
tokenCount: number;
|
|
40
|
+
contentHash: string;
|
|
41
|
+
/** The layer state as of pinning. Held by reference — see `RenderDeltaParams.prevState`. */
|
|
42
|
+
state: unknown;
|
|
43
|
+
slot: number;
|
|
44
|
+
}
|
|
45
|
+
/** @public How often a layer's rendered output changes, and what that costs. */
|
|
46
|
+
export interface LayerChurn {
|
|
47
|
+
/** Assemblies in which the layer produced output. */
|
|
48
|
+
observed: number;
|
|
49
|
+
/** Assemblies in which its output differed from its pin. */
|
|
50
|
+
changed: number;
|
|
51
|
+
/** Tokens invalidated downstream by those changes, had they not been pinned. */
|
|
52
|
+
rebillTokens: number;
|
|
53
|
+
}
|
|
54
|
+
/** @public One run of assemblies sharing a cacheable prompt prefix. */
|
|
55
|
+
export interface ContextEpoch {
|
|
56
|
+
id: string;
|
|
57
|
+
/** Pinned output, keyed as `${scopeKey}:${layerId}`. */
|
|
58
|
+
pins: Map<string, AnchorPin>;
|
|
59
|
+
/** Band chosen for each `'auto'` layer at the last boundary, same key as `pins`. */
|
|
60
|
+
autoBand: Map<string, 'anchor' | 'live'>;
|
|
61
|
+
/** Hash of the resolved instructions this epoch was anchored against. */
|
|
62
|
+
instructionsHash: string;
|
|
63
|
+
assemblies: number;
|
|
64
|
+
anchorTokens: number;
|
|
65
|
+
deltaTokens: number;
|
|
66
|
+
/**
|
|
67
|
+
* Set once the provider has shown it reports no cache figures, or has missed
|
|
68
|
+
* so persistently that steering on its numbers is pointless. Suppresses the
|
|
69
|
+
* cache-miss trigger; age and delta pressure still apply.
|
|
70
|
+
*/
|
|
71
|
+
cacheBlind: boolean;
|
|
72
|
+
/** Consecutive judged assemblies that came back a miss. */
|
|
73
|
+
misses: number;
|
|
74
|
+
lastReanchorReason: ReanchorReason;
|
|
75
|
+
}
|
|
76
|
+
/** @public Per-harness home for anchoring state, one epoch per cache lineage. */
|
|
77
|
+
export interface ContextCacheStore {
|
|
78
|
+
/** Keyed by cache lineage — a thread at top level, an execution below it. */
|
|
79
|
+
epochs: Map<string, ContextEpoch>;
|
|
80
|
+
/** Churn per layer, outliving epochs so placement is not relearnt each time. */
|
|
81
|
+
churn: Map<string, LayerChurn>;
|
|
82
|
+
/** Re-anchors decided from a response, applied at the next assembly. */
|
|
83
|
+
pendingReanchor: Map<string, ReanchorReason>;
|
|
84
|
+
}
|
|
@@ -179,6 +179,42 @@ export interface StoreParams<TState> {
|
|
|
179
179
|
export interface StoreResult<TState> {
|
|
180
180
|
state: TState;
|
|
181
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* @public Which band of the assembled view a layer's recall output sits in.
|
|
184
|
+
*
|
|
185
|
+
* Prompt caches match on a prefix, so a layer that re-renders every turn
|
|
186
|
+
* invalidates everything after it. The bands let stable content sit ahead of
|
|
187
|
+
* conversation history where it can be cached, and volatile content sit after
|
|
188
|
+
* it where re-rendering is nearly free.
|
|
189
|
+
*
|
|
190
|
+
* - `'anchor'` — before history. Pinned for the epoch: the bytes sent on the
|
|
191
|
+
* first assembly are re-sent unchanged until the next re-anchor, and any
|
|
192
|
+
* change is published as a supersede instead of rewriting the prefix.
|
|
193
|
+
* - `'live'` — after history. Re-rendered freely. Use for content that changes
|
|
194
|
+
* every turn, or whose `recall()` mutates state and so cannot be replayed.
|
|
195
|
+
* - `'auto'` (default) — starts anchored; the runtime may move it to `'live'`
|
|
196
|
+
* at an epoch boundary once it has watched how often the layer changes.
|
|
197
|
+
* An explicit `'anchor'` or `'live'` is never overridden.
|
|
198
|
+
*/
|
|
199
|
+
export type LayerPlacement = 'anchor' | 'live' | 'auto';
|
|
200
|
+
/** @public Parameters passed to a context layer's `renderDelta` hook when its pinned output goes stale. */
|
|
201
|
+
export interface RenderDeltaParams<TState = unknown> {
|
|
202
|
+
/** The items still pinned in the anchor band — what the model can currently see. */
|
|
203
|
+
prev: ReadonlyArray<Item>;
|
|
204
|
+
/** The freshly recalled items that would have replaced them. */
|
|
205
|
+
next: ReadonlyArray<Item>;
|
|
206
|
+
/**
|
|
207
|
+
* The layer state captured when `prev` was pinned. Best-effort: it is held by
|
|
208
|
+
* reference, so a layer that mutates its state in place sees the current
|
|
209
|
+
* object here rather than a snapshot.
|
|
210
|
+
*/
|
|
211
|
+
prevState: TState | undefined;
|
|
212
|
+
/** The layer's current state. */
|
|
213
|
+
state: TState | undefined;
|
|
214
|
+
ctx: ExecutionContext;
|
|
215
|
+
/** Soft token budget for the returned text. */
|
|
216
|
+
budget: number;
|
|
217
|
+
}
|
|
182
218
|
/** @public Parameters passed to a context layer's `onSpawn` hook when a child execution starts. */
|
|
183
219
|
export interface SpawnParams<TState> {
|
|
184
220
|
parentState: TState;
|
|
@@ -297,6 +333,16 @@ export interface ContextLayerHooks<TState = unknown> {
|
|
|
297
333
|
* Use for: capping history, summarising old turns, redacting items.
|
|
298
334
|
*/
|
|
299
335
|
projectHistory?(params: ProjectHistoryParams<TState>): Promise<ProjectHistoryResult>;
|
|
336
|
+
/**
|
|
337
|
+
* Called for an anchored layer whose pinned output has gone stale, to describe
|
|
338
|
+
* the change compactly instead of re-sending the whole block.
|
|
339
|
+
*
|
|
340
|
+
* Return `null` to fall back to the default, which republishes the full new
|
|
341
|
+
* content. Worth implementing only when the layer's output is large and its
|
|
342
|
+
* changes are small — a file set where one file changed, a ledger that gained
|
|
343
|
+
* a row. Never called for layers in the `'live'` band, which re-render anyway.
|
|
344
|
+
*/
|
|
345
|
+
renderDelta?(params: RenderDeltaParams<TState>): Promise<string | null>;
|
|
300
346
|
}
|
|
301
347
|
/**
|
|
302
348
|
* A composable context layer that participates in the recall/store lifecycle.
|
|
@@ -343,6 +389,11 @@ export interface ContextLayer<TState = unknown> {
|
|
|
343
389
|
itemSchemas?: Pick<ItemSchemaExtensions, 'developerMessages' | 'items'>;
|
|
344
390
|
/** Default re-render timing when `onItemAppend` requests a re-render. */
|
|
345
391
|
rerenderTiming?: 'immediate' | 'batched';
|
|
392
|
+
/**
|
|
393
|
+
* Which band of the assembled view this layer's recall output sits in, and so
|
|
394
|
+
* whether it is pinned for the prompt cache. Defaults to `'auto'`.
|
|
395
|
+
*/
|
|
396
|
+
placement?: LayerPlacement;
|
|
346
397
|
}
|
|
347
398
|
/** @public Configuration for how the runtime projects conversation items into the model's context window. */
|
|
348
399
|
export interface ProjectionPolicy {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReanchorReason } from '../context-cache.js';
|
|
1
2
|
import type { Item } from '../items.js';
|
|
2
3
|
/** @public Per-layer contribution to the context window on the most recent LLM call. */
|
|
3
4
|
export interface LayerUsageEntry {
|
|
@@ -5,6 +6,27 @@ export interface LayerUsageEntry {
|
|
|
5
6
|
readonly tokenCount: number;
|
|
6
7
|
/** Items this layer contributed to the context view for the last LLM call. */
|
|
7
8
|
readonly items: ReadonlyArray<Item>;
|
|
9
|
+
/** Which band the layer rendered into. */
|
|
10
|
+
readonly placement: 'anchor' | 'live';
|
|
11
|
+
/** `'pinned'` when the items are a replay of an earlier render, held for the prompt cache. */
|
|
12
|
+
readonly served: 'fresh' | 'pinned';
|
|
13
|
+
/** Whether the layer's fresh output differed from its pin, and so was superseded. */
|
|
14
|
+
readonly changed: boolean;
|
|
15
|
+
/** Share of watched assemblies in which this layer's output changed, 0–1. */
|
|
16
|
+
readonly churnRate: number;
|
|
17
|
+
/** Tokens this layer's changes would have re-billed had it not been pinned. */
|
|
18
|
+
readonly rebillTokens: number;
|
|
19
|
+
}
|
|
20
|
+
/** @public Prompt-cache anchoring state behind the most recent assembly. */
|
|
21
|
+
export interface EpochUsage {
|
|
22
|
+
readonly id: string;
|
|
23
|
+
/** Assemblies served by this epoch, including the one just made. */
|
|
24
|
+
readonly age: number;
|
|
25
|
+
readonly anchorTokens: number;
|
|
26
|
+
readonly liveTokens: number;
|
|
27
|
+
readonly deltaTokens: number;
|
|
28
|
+
/** Set only on an assembly that re-anchored. */
|
|
29
|
+
readonly reanchorReason?: ReanchorReason;
|
|
8
30
|
}
|
|
9
31
|
/** @public Breakdown of the context window as of the most recent LLM call in an execution. */
|
|
10
32
|
export interface LastLayerUsage {
|
|
@@ -15,4 +37,6 @@ export interface LastLayerUsage {
|
|
|
15
37
|
readonly toolsTokens: number;
|
|
16
38
|
readonly historyTokens: number;
|
|
17
39
|
readonly totalUsedTokens: number;
|
|
40
|
+
/** Absent when context caching is switched off. */
|
|
41
|
+
readonly epoch?: EpochUsage;
|
|
18
42
|
}
|
package/dist/types/context.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ZodType } from 'zod';
|
|
|
2
2
|
import type { ItemSchemaRegistry } from '../schemas/item.js';
|
|
3
3
|
import type { Channel, ChannelHandle, ExternalChannel } from './channel.js';
|
|
4
4
|
import type { LLMResponse, ModelParams, StepMeta, TokenUsage } from './common.js';
|
|
5
|
+
import type { ContextCacheConfig, ContextCacheStore } from './context-cache.js';
|
|
5
6
|
import type { ContextData, ContextLayer, ProjectionPolicy, StorageAdapter } from './context-layer.js';
|
|
6
7
|
import type { ItemLog } from './context-parts/item-log.js';
|
|
7
8
|
import type { LastLayerUsage } from './context-parts/layer-usage.js';
|
|
@@ -128,6 +129,8 @@ export interface ContextHarness {
|
|
|
128
129
|
readonly params: Record<string, unknown>;
|
|
129
130
|
/** Harness-wide default projection policy; a step's `projection` overrides it. */
|
|
130
131
|
readonly projection?: ProjectionPolicy;
|
|
132
|
+
/** Tuning for prompt-cache anchoring. */
|
|
133
|
+
readonly contextCache?: ContextCacheConfig;
|
|
131
134
|
};
|
|
132
135
|
readonly fs: FsAdapter;
|
|
133
136
|
readonly shell: ShellAdapter;
|
|
@@ -159,6 +162,8 @@ export interface ContextHarness {
|
|
|
159
162
|
beforeToolCall(layers: ContextLayer[], toolName: string, toolArgs: unknown, ctx: Context): Promise<SteeringDecision>;
|
|
160
163
|
afterModelCall(layers: ContextLayer[], response: LLMResponse, ctx: Context): Promise<SteeringDecision>;
|
|
161
164
|
runAppendPipeline(layers: ContextLayer[], items: import('./items.js').Item[], ctx: Context): Promise<ContextAppendPipelineResult>;
|
|
165
|
+
/** Pinned anchor output and epoch bookkeeping for prompt-cache anchoring. Absent on a harness that predates it. */
|
|
166
|
+
readonly contextCache?: ContextCacheStore;
|
|
162
167
|
recallLayers(layers: ContextLayer[], input: string, ctx: Context): Promise<ContextRecallLayerOutput[]>;
|
|
163
168
|
recallLayersAtomic(layers: ContextLayer[], input: string, ctx: Context, budgets: Map<string, number>): Promise<ContextRecallLayerOutput[]>;
|
|
164
169
|
recallLayersEventual(layers: ContextLayer[], input: string, ctx: Context, budgets: Map<string, number>): Promise<ContextRecallLayerOutput[]>;
|
|
@@ -177,6 +182,8 @@ export interface ContextHarness {
|
|
|
177
182
|
}): Promise<T>;
|
|
178
183
|
tryRecv<T>(channel: Channel<T>, ctx: Context): T | null;
|
|
179
184
|
getChannelHandle<T>(channel: ExternalChannel<T>, executionId: string): ChannelHandle<T>;
|
|
185
|
+
/** Read-side counterpart to `getChannelHandle` — see `AgentHarnessContract.getChannelStream`. */
|
|
186
|
+
getChannelStream<T>(channel: ExternalChannel<T>, executionId: string): AsyncIterable<T>;
|
|
180
187
|
initLayers(layers: ContextLayer[], ctx: Context, storage: StorageAdapter): Promise<void>;
|
|
181
188
|
disposeLayers(layers: ContextLayer[], ctx: Context): Promise<void>;
|
|
182
189
|
checkpoint(ctx: Context): Promise<void>;
|
package/dist/types/runtime.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ZodType } from 'zod';
|
|
|
2
2
|
import type { Channel, ChannelHandle, ExternalChannel } from './channel.js';
|
|
3
3
|
import type { LLMResponse, ModelParams, ServerToolSpec } from './common.js';
|
|
4
4
|
import type { Context, CwdState, RestoreContextOptions } from './context.js';
|
|
5
|
+
import type { ContextCacheConfig, ContextCacheStore } from './context-cache.js';
|
|
5
6
|
import type { ContextData, ContextLayer, ProjectionPolicy, StorageAdapter } from './context-layer.js';
|
|
6
7
|
import type { DetachedHandle } from './detached.js';
|
|
7
8
|
import type { FsAdapter } from './fs-adapter.js';
|
|
@@ -32,6 +33,8 @@ export interface AgentConfig<TParams extends Record<string, unknown> = Record<st
|
|
|
32
33
|
projection?: ProjectionPolicy;
|
|
33
34
|
/** When true, every layer is recalled atomically regardless of its `recallMode` (no eventual/cached recall). */
|
|
34
35
|
forceAtomicRecall?: boolean;
|
|
36
|
+
/** Tuning for prompt-cache anchoring. See `ContextCacheConfig` for the defaults. */
|
|
37
|
+
contextCache?: ContextCacheConfig;
|
|
35
38
|
}
|
|
36
39
|
/** @public How a message submitted during an active turn is delivered to the model.
|
|
37
40
|
*
|
|
@@ -234,6 +237,24 @@ export interface AgentHarnessContract<TParams extends Record<string, unknown> =
|
|
|
234
237
|
}): Promise<T>;
|
|
235
238
|
tryRecv<T>(channel: Channel<T>, ctx: Context): T | null;
|
|
236
239
|
getChannelHandle<T>(channel: ExternalChannel<T>, executionId: string): ChannelHandle<T>;
|
|
240
|
+
/**
|
|
241
|
+
* Read-side counterpart to `getChannelHandle`: subscribe to values the agent
|
|
242
|
+
* sends on an external channel. Delivery is CHANNEL-scoped (state is keyed
|
|
243
|
+
* by channel name per harness) — `executionId` bounds only the
|
|
244
|
+
* subscription's lifetime: the iterable ends when that root execution
|
|
245
|
+
* completes (success, failure, or cancellation), draining buffered values
|
|
246
|
+
* first. An id no execution runs under never closes, giving a
|
|
247
|
+
* harness-lifetime stream ended via `iterator.return()`. The returned
|
|
248
|
+
* iterable owns a single iterator; subscribe again for a second consumer.
|
|
249
|
+
*/
|
|
250
|
+
getChannelStream<T>(channel: ExternalChannel<T>, executionId: string): AsyncIterable<T>;
|
|
251
|
+
/**
|
|
252
|
+
* Pinned anchor output and epoch bookkeeping for prompt-cache anchoring,
|
|
253
|
+
* shared by every thread on this harness. Optional so a hand-written harness
|
|
254
|
+
* stays valid: without it nothing is pinned across turns and layers render
|
|
255
|
+
* fresh every time, as they did before anchoring existed.
|
|
256
|
+
*/
|
|
257
|
+
readonly contextCache?: ContextCacheStore;
|
|
237
258
|
initLayers(layers: ContextLayer[], ctx: Context, storage: StorageAdapter): Promise<void>;
|
|
238
259
|
recallLayers(layers: ContextLayer[], input: string, ctx: Context): Promise<RecallLayerOutput[]>;
|
|
239
260
|
/** Recall only atomic layers (or all layers when the harness forces atomic recall). Blocks until complete. */
|
|
@@ -296,6 +317,13 @@ export interface RecallLayerOutput {
|
|
|
296
317
|
layerId: string;
|
|
297
318
|
items: Item[];
|
|
298
319
|
tokenCount: number;
|
|
320
|
+
/**
|
|
321
|
+
* Set when the layer's `recall` returned new state, meaning the call changed
|
|
322
|
+
* something and cannot be replayed. Such a layer is never pinned — serving an
|
|
323
|
+
* older render would drop the very thing the call committed (the steering
|
|
324
|
+
* layer, for one, drains its queue as it renders).
|
|
325
|
+
*/
|
|
326
|
+
mutatedState?: boolean;
|
|
299
327
|
}
|
|
300
328
|
/** @public A request to re-render the context window, collected from onItemAppend hooks. */
|
|
301
329
|
export interface RerenderRequest {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Item } from '../types/items.js';
|
|
2
|
+
/**
|
|
3
|
+
* @public Hash a string to 16 hex chars (64 bits).
|
|
4
|
+
*
|
|
5
|
+
* Not cryptographic — it exists to answer "is this byte-identical to last
|
|
6
|
+
* time?" cheaply. 64 bits keeps accidental collisions far enough away that a
|
|
7
|
+
* stale-content false match is not a practical concern.
|
|
8
|
+
*
|
|
9
|
+
* Adapted from bryc's cyrb64 (public domain):
|
|
10
|
+
* https://github.com/bryc/code/blob/master/jshash/experimental/cyrb1.js
|
|
11
|
+
*/
|
|
12
|
+
export declare function hashString(input: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* @public Hash rendered items by the content that actually reaches the model.
|
|
15
|
+
*
|
|
16
|
+
* `createMessage` mints a fresh `id` on every render, so hashing items verbatim
|
|
17
|
+
* would report a change every turn even when the text is identical. Message
|
|
18
|
+
* items are sent as `{role, content}` alone, so `id` and `status` are dropped
|
|
19
|
+
* before hashing; every other item type is hashed whole, since their ids can
|
|
20
|
+
* reach the wire.
|
|
21
|
+
*
|
|
22
|
+
* Object keys are sorted, so a layer that builds its items in a different key
|
|
23
|
+
* order between renders still hashes the same.
|
|
24
|
+
*/
|
|
25
|
+
export declare function hashItems(items: ReadonlyArray<Item>): string;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
//#region Public API
|
|
2
|
+
/**
|
|
3
|
+
* @public Hash a string to 16 hex chars (64 bits).
|
|
4
|
+
*
|
|
5
|
+
* Not cryptographic — it exists to answer "is this byte-identical to last
|
|
6
|
+
* time?" cheaply. 64 bits keeps accidental collisions far enough away that a
|
|
7
|
+
* stale-content false match is not a practical concern.
|
|
8
|
+
*
|
|
9
|
+
* Adapted from bryc's cyrb64 (public domain):
|
|
10
|
+
* https://github.com/bryc/code/blob/master/jshash/experimental/cyrb1.js
|
|
11
|
+
*/
|
|
12
|
+
export function hashString(input) {
|
|
13
|
+
let h1 = 0xdeadbeef;
|
|
14
|
+
let h2 = 0x41c6ce57;
|
|
15
|
+
for (let i = 0; i < input.length; i++) {
|
|
16
|
+
const ch = input.charCodeAt(i);
|
|
17
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
18
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
19
|
+
}
|
|
20
|
+
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
|
|
21
|
+
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
|
22
|
+
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
|
|
23
|
+
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
|
24
|
+
return `${(h2 >>> 0).toString(16).padStart(8, '0')}${(h1 >>> 0).toString(16).padStart(8, '0')}`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* @public Hash rendered items by the content that actually reaches the model.
|
|
28
|
+
*
|
|
29
|
+
* `createMessage` mints a fresh `id` on every render, so hashing items verbatim
|
|
30
|
+
* would report a change every turn even when the text is identical. Message
|
|
31
|
+
* items are sent as `{role, content}` alone, so `id` and `status` are dropped
|
|
32
|
+
* before hashing; every other item type is hashed whole, since their ids can
|
|
33
|
+
* reach the wire.
|
|
34
|
+
*
|
|
35
|
+
* Object keys are sorted, so a layer that builds its items in a different key
|
|
36
|
+
* order between renders still hashes the same.
|
|
37
|
+
*/
|
|
38
|
+
export function hashItems(items) {
|
|
39
|
+
return hashString(stableStringify(items.map(wireShape)));
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region Helpers
|
|
43
|
+
/** Drops the fields the provider adapter strips before sending a message item. */
|
|
44
|
+
function wireShape(item) {
|
|
45
|
+
if (item.type !== 'message') {
|
|
46
|
+
return item;
|
|
47
|
+
}
|
|
48
|
+
const { id: _id, status: _status, ...rest } = item;
|
|
49
|
+
return rest;
|
|
50
|
+
}
|
|
51
|
+
/** `JSON.stringify` with object keys sorted at every depth. */
|
|
52
|
+
function stableStringify(value) {
|
|
53
|
+
if (value === null || typeof value !== 'object') {
|
|
54
|
+
return JSON.stringify(value) ?? 'null';
|
|
55
|
+
}
|
|
56
|
+
if (Array.isArray(value)) {
|
|
57
|
+
return `[${value.map(stableStringify).join(',')}]`;
|
|
58
|
+
}
|
|
59
|
+
const entries = Object.entries(value)
|
|
60
|
+
.filter(([, v]) => v !== undefined)
|
|
61
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
62
|
+
.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`);
|
|
63
|
+
return `{${entries.join(',')}}`;
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noetic-tools/types",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Foundational type contracts and primitives shared across the Noetic framework (items, LLM config, adapters, steering, errors).",
|
|
6
6
|
"license": "Apache-2.0",
|