@noetic-tools/types 3.0.0 → 3.2.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 +3 -0
- package/dist/index.js +3 -0
- 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 +10 -3
- package/dist/types/error.d.ts +4 -2
- package/dist/types/runtime.d.ts +24 -2
- package/dist/types/schema.d.ts +62 -0
- package/dist/types/schema.js +63 -0
- package/dist/types/step.d.ts +13 -7
- package/dist/types/tool.d.ts +26 -14
- package/dist/util/content-hash.d.ts +25 -0
- package/dist/util/content-hash.js +65 -0
- package/package.json +2 -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';
|
|
@@ -22,6 +23,7 @@ export * from './types/items.js';
|
|
|
22
23
|
export * from './types/observability.js';
|
|
23
24
|
export * from './types/output-codec.js';
|
|
24
25
|
export * from './types/runtime.js';
|
|
26
|
+
export * from './types/schema.js';
|
|
25
27
|
export * from './types/shell-adapter.js';
|
|
26
28
|
export * from './types/steering.js';
|
|
27
29
|
export * from './types/step.js';
|
|
@@ -29,5 +31,6 @@ export * from './types/sub-harness.js';
|
|
|
29
31
|
export * from './types/subprocess-adapter.js';
|
|
30
32
|
export * from './types/tool.js';
|
|
31
33
|
export * from './types/tool-context.js';
|
|
34
|
+
export * from './util/content-hash.js';
|
|
32
35
|
export * from './util/framework-cast.js';
|
|
33
36
|
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';
|
|
@@ -22,6 +23,7 @@ export * from './types/items.js';
|
|
|
22
23
|
export * from './types/observability.js';
|
|
23
24
|
export * from './types/output-codec.js';
|
|
24
25
|
export * from './types/runtime.js';
|
|
26
|
+
export * from './types/schema.js';
|
|
25
27
|
export * from './types/shell-adapter.js';
|
|
26
28
|
export * from './types/steering.js';
|
|
27
29
|
export * from './types/step.js';
|
|
@@ -29,5 +31,6 @@ export * from './types/sub-harness.js';
|
|
|
29
31
|
export * from './types/subprocess-adapter.js';
|
|
30
32
|
export * from './types/tool.js';
|
|
31
33
|
export * from './types/tool-context.js';
|
|
34
|
+
export * from './util/content-hash.js';
|
|
32
35
|
export * from './util/framework-cast.js';
|
|
33
36
|
export * from './util/message-helpers.js';
|
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
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
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';
|
|
@@ -40,7 +41,9 @@ interface ContextCallModelRequestBase {
|
|
|
40
41
|
items: ReadonlyArray<import('./items.js').Item>;
|
|
41
42
|
instructions?: string;
|
|
42
43
|
params?: ModelParams;
|
|
43
|
-
outputSchema?:
|
|
44
|
+
outputSchema?: StandardSchemaV1;
|
|
45
|
+
/** Explicit non-Zod override/fallback after StandardJSONSchemaV1 conversion. */
|
|
46
|
+
outputJsonSchema?: Record<string, unknown>;
|
|
44
47
|
emit?: boolean | ((eventType: string, data: Record<string, unknown>) => boolean);
|
|
45
48
|
signal?: AbortSignal;
|
|
46
49
|
}
|
|
@@ -111,7 +114,7 @@ export type ContextStep<TContext = ContextData, I = unknown, O = unknown> = {
|
|
|
111
114
|
} | {
|
|
112
115
|
readonly kind: 'llm';
|
|
113
116
|
readonly id: string;
|
|
114
|
-
readonly output?:
|
|
117
|
+
readonly output?: StandardSchemaV1<unknown, O>;
|
|
115
118
|
} | {
|
|
116
119
|
readonly kind: 'loop';
|
|
117
120
|
readonly id: string;
|
|
@@ -128,6 +131,8 @@ export interface ContextHarness {
|
|
|
128
131
|
readonly params: Record<string, unknown>;
|
|
129
132
|
/** Harness-wide default projection policy; a step's `projection` overrides it. */
|
|
130
133
|
readonly projection?: ProjectionPolicy;
|
|
134
|
+
/** Tuning for prompt-cache anchoring. */
|
|
135
|
+
readonly contextCache?: ContextCacheConfig;
|
|
131
136
|
};
|
|
132
137
|
readonly fs: FsAdapter;
|
|
133
138
|
readonly shell: ShellAdapter;
|
|
@@ -159,6 +164,8 @@ export interface ContextHarness {
|
|
|
159
164
|
beforeToolCall(layers: ContextLayer[], toolName: string, toolArgs: unknown, ctx: Context): Promise<SteeringDecision>;
|
|
160
165
|
afterModelCall(layers: ContextLayer[], response: LLMResponse, ctx: Context): Promise<SteeringDecision>;
|
|
161
166
|
runAppendPipeline(layers: ContextLayer[], items: import('./items.js').Item[], ctx: Context): Promise<ContextAppendPipelineResult>;
|
|
167
|
+
/** Pinned anchor output and epoch bookkeeping for prompt-cache anchoring. Absent on a harness that predates it. */
|
|
168
|
+
readonly contextCache?: ContextCacheStore;
|
|
162
169
|
recallLayers(layers: ContextLayer[], input: string, ctx: Context): Promise<ContextRecallLayerOutput[]>;
|
|
163
170
|
recallLayersAtomic(layers: ContextLayer[], input: string, ctx: Context, budgets: Map<string, number>): Promise<ContextRecallLayerOutput[]>;
|
|
164
171
|
recallLayersEventual(layers: ContextLayer[], input: string, ctx: Context, budgets: Map<string, number>): Promise<ContextRecallLayerOutput[]>;
|
package/dist/types/error.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
import type { ZodError } from 'zod';
|
|
2
3
|
/** @public Discriminated union of all structured error kinds raised by the runtime. */
|
|
3
4
|
export type NoeticError = {
|
|
4
5
|
kind: 'step_failed';
|
|
@@ -13,7 +14,8 @@ export type NoeticError = {
|
|
|
13
14
|
kind: 'llm_parse_error';
|
|
14
15
|
stepId: string;
|
|
15
16
|
raw: string;
|
|
16
|
-
schema:
|
|
17
|
+
schema: StandardSchemaV1;
|
|
18
|
+
/** Real `ZodError` for Zod schemas; synthetic `custom` issues adapted from the vendor's Standard Schema issues otherwise. */
|
|
17
19
|
zodError: ZodError;
|
|
18
20
|
} | {
|
|
19
21
|
kind: 'llm_rate_limit';
|
package/dist/types/runtime.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
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
|
*
|
|
@@ -51,7 +54,12 @@ interface CallModelRequestBase {
|
|
|
51
54
|
instructions?: string;
|
|
52
55
|
params?: ModelParams;
|
|
53
56
|
/** When provided, the harness sends a JSON Schema constraint to the model so it returns structured JSON. */
|
|
54
|
-
outputSchema?:
|
|
57
|
+
outputSchema?: StandardSchemaV1;
|
|
58
|
+
/**
|
|
59
|
+
* Explicit raw JSON Schema override for the structured-output constraint.
|
|
60
|
+
* For non-Zod schemas, it takes precedence over StandardJSONSchemaV1 conversion.
|
|
61
|
+
*/
|
|
62
|
+
outputJsonSchema?: Record<string, unknown>;
|
|
55
63
|
/** Controls framework event emission. Defaults to `true`. Passed through from `StepLLM.emit`. */
|
|
56
64
|
emit?: boolean | ((eventType: string, data: Record<string, unknown>) => boolean);
|
|
57
65
|
/**
|
|
@@ -245,6 +253,13 @@ export interface AgentHarnessContract<TParams extends Record<string, unknown> =
|
|
|
245
253
|
* iterable owns a single iterator; subscribe again for a second consumer.
|
|
246
254
|
*/
|
|
247
255
|
getChannelStream<T>(channel: ExternalChannel<T>, executionId: string): AsyncIterable<T>;
|
|
256
|
+
/**
|
|
257
|
+
* Pinned anchor output and epoch bookkeeping for prompt-cache anchoring,
|
|
258
|
+
* shared by every thread on this harness. Optional so a hand-written harness
|
|
259
|
+
* stays valid: without it nothing is pinned across turns and layers render
|
|
260
|
+
* fresh every time, as they did before anchoring existed.
|
|
261
|
+
*/
|
|
262
|
+
readonly contextCache?: ContextCacheStore;
|
|
248
263
|
initLayers(layers: ContextLayer[], ctx: Context, storage: StorageAdapter): Promise<void>;
|
|
249
264
|
recallLayers(layers: ContextLayer[], input: string, ctx: Context): Promise<RecallLayerOutput[]>;
|
|
250
265
|
/** Recall only atomic layers (or all layers when the harness forces atomic recall). Blocks until complete. */
|
|
@@ -307,6 +322,13 @@ export interface RecallLayerOutput {
|
|
|
307
322
|
layerId: string;
|
|
308
323
|
items: Item[];
|
|
309
324
|
tokenCount: number;
|
|
325
|
+
/**
|
|
326
|
+
* Set when the layer's `recall` returned new state, meaning the call changed
|
|
327
|
+
* something and cannot be replayed. Such a layer is never pinned — serving an
|
|
328
|
+
* older render would drop the very thing the call committed (the steering
|
|
329
|
+
* layer, for one, drains its queue as it renders).
|
|
330
|
+
*/
|
|
331
|
+
mutatedState?: boolean;
|
|
310
332
|
}
|
|
311
333
|
/** @public A request to re-render the context window, collected from onItemAppend hooks. */
|
|
312
334
|
export interface RerenderRequest {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { StandardJSONSchemaV1, StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
import type { ZodTypeAny, z } from 'zod';
|
|
3
|
+
import { ZodError } from 'zod';
|
|
4
|
+
export type { StandardJSONSchemaV1, StandardSchemaV1 } from '@standard-schema/spec';
|
|
5
|
+
/**
|
|
6
|
+
* Input type inferred from any Standard Schema. Zod schemas keep their exact
|
|
7
|
+
* `z.input` inference; other vendors use `StandardSchemaV1.InferInput`.
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
export type InferSchemaInput<S extends StandardSchemaV1> = S extends ZodTypeAny ? z.input<S> : StandardSchemaV1.InferInput<S>;
|
|
11
|
+
/**
|
|
12
|
+
* Output type inferred from any Standard Schema. Zod schemas keep their exact
|
|
13
|
+
* `z.output` inference; other vendors use `StandardSchemaV1.InferOutput`.
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
export type InferSchemaOutput<S extends StandardSchemaV1> = S extends ZodTypeAny ? z.output<S> : StandardSchemaV1.InferOutput<S>;
|
|
17
|
+
/**
|
|
18
|
+
* JSON Schema configuration for a tool input schema. Zod and Standard JSON
|
|
19
|
+
* Schema implementations can derive their wire schema; validation-only
|
|
20
|
+
* Standard Schemas require an explicit fallback.
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
export type InputSchemaConfig<S extends StandardSchemaV1> = StandardSchemaV1 extends S ? {
|
|
24
|
+
inputJsonSchema?: Record<string, unknown>;
|
|
25
|
+
} : S extends ZodTypeAny | StandardJSONSchemaV1 ? {
|
|
26
|
+
inputJsonSchema?: Record<string, unknown>;
|
|
27
|
+
} : {
|
|
28
|
+
inputJsonSchema: Record<string, unknown>;
|
|
29
|
+
};
|
|
30
|
+
/** @public Type guard for Zod schemas (detected via the `_zod` internals marker). */
|
|
31
|
+
export declare function isZodSchema(schema: StandardSchemaV1): schema is ZodTypeAny;
|
|
32
|
+
/** @public Type guard for schemas implementing the Standard JSON Schema v1 companion spec. */
|
|
33
|
+
export declare function isStandardJsonSchema(schema: StandardSchemaV1): schema is StandardSchemaV1 & StandardJSONSchemaV1;
|
|
34
|
+
/** @public Successful result of `validateSchema` — carries the parsed/transformed value. */
|
|
35
|
+
export interface SchemaValidationSuccess<T> {
|
|
36
|
+
success: true;
|
|
37
|
+
value: T;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Failed result of `validateSchema`. `issues` are the vendor's issues in
|
|
41
|
+
* Standard Schema form; `zodError` adapts them into a `ZodError` (the
|
|
42
|
+
* original error for Zod schemas, synthetic `custom` issues otherwise) so
|
|
43
|
+
* consumers like `llm_parse_error` keep a single error surface.
|
|
44
|
+
* @public
|
|
45
|
+
*/
|
|
46
|
+
export interface SchemaValidationFailure {
|
|
47
|
+
success: false;
|
|
48
|
+
issues: readonly StandardSchemaV1.Issue[];
|
|
49
|
+
zodError: ZodError;
|
|
50
|
+
}
|
|
51
|
+
/** @public Result of `validateSchema`. */
|
|
52
|
+
export type SchemaValidationResult<T> = SchemaValidationSuccess<T> | SchemaValidationFailure;
|
|
53
|
+
/** @public Adapt Standard Schema issues into a synthetic `ZodError` of `custom` issues. */
|
|
54
|
+
export declare function standardIssuesToZodError(issues: readonly StandardSchemaV1.Issue[]): ZodError;
|
|
55
|
+
/**
|
|
56
|
+
* Validates a value against any Standard Schema. Zod schemas take the
|
|
57
|
+
* `safeParse` fast path; other vendors run `schema['~standard'].validate`,
|
|
58
|
+
* awaiting sync or Promise results. The returned success value is the
|
|
59
|
+
* parsed/transformed output.
|
|
60
|
+
* @public
|
|
61
|
+
*/
|
|
62
|
+
export declare function validateSchema<S extends StandardSchemaV1>(schema: S, value: unknown): Promise<SchemaValidationResult<InferSchemaOutput<S>>>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { ZodError } from 'zod';
|
|
2
|
+
import { frameworkCast } from '../util/framework-cast.js';
|
|
3
|
+
/** @public Type guard for Zod schemas (detected via the `_zod` internals marker). */
|
|
4
|
+
export function isZodSchema(schema) {
|
|
5
|
+
return '_zod' in schema;
|
|
6
|
+
}
|
|
7
|
+
/** @public Type guard for schemas implementing the Standard JSON Schema v1 companion spec. */
|
|
8
|
+
export function isStandardJsonSchema(schema) {
|
|
9
|
+
const standard = frameworkCast(schema['~standard']);
|
|
10
|
+
return typeof standard.jsonSchema?.input === 'function';
|
|
11
|
+
}
|
|
12
|
+
function issuePathToPropertyKeys(path) {
|
|
13
|
+
if (!path) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
return path.map((segment) => typeof segment === 'object' && segment !== null && 'key' in segment ? segment.key : segment);
|
|
17
|
+
}
|
|
18
|
+
/** @public Adapt Standard Schema issues into a synthetic `ZodError` of `custom` issues. */
|
|
19
|
+
export function standardIssuesToZodError(issues) {
|
|
20
|
+
return new ZodError(issues.map((issue) => ({
|
|
21
|
+
code: 'custom',
|
|
22
|
+
message: issue.message,
|
|
23
|
+
path: issuePathToPropertyKeys(issue.path),
|
|
24
|
+
})));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Validates a value against any Standard Schema. Zod schemas take the
|
|
28
|
+
* `safeParse` fast path; other vendors run `schema['~standard'].validate`,
|
|
29
|
+
* awaiting sync or Promise results. The returned success value is the
|
|
30
|
+
* parsed/transformed output.
|
|
31
|
+
* @public
|
|
32
|
+
*/
|
|
33
|
+
export async function validateSchema(schema, value) {
|
|
34
|
+
if (isZodSchema(schema)) {
|
|
35
|
+
const result = schema.safeParse(value);
|
|
36
|
+
if (result.success) {
|
|
37
|
+
return {
|
|
38
|
+
success: true,
|
|
39
|
+
value: frameworkCast(result.data),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
success: false,
|
|
44
|
+
issues: result.error.issues.map((issue) => ({
|
|
45
|
+
message: issue.message,
|
|
46
|
+
path: issue.path,
|
|
47
|
+
})),
|
|
48
|
+
zodError: result.error,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const result = await schema['~standard'].validate(value);
|
|
52
|
+
if (result.issues) {
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
issues: result.issues,
|
|
56
|
+
zodError: standardIssuesToZodError(result.issues),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
success: true,
|
|
61
|
+
value: frameworkCast(result.value),
|
|
62
|
+
};
|
|
63
|
+
}
|
package/dist/types/step.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
2
|
import type { Channel } from './channel.js';
|
|
3
3
|
import type { ModelParams, RetryPolicy, ServerToolSpec, StepMeta } from './common.js';
|
|
4
4
|
import type { Context } from './context.js';
|
|
@@ -104,11 +104,17 @@ export interface StepLLM<TContext = ContextData, _I = unknown, O = unknown> {
|
|
|
104
104
|
*/
|
|
105
105
|
tools?: Lazy<(Tool | ServerToolSpec)[] | undefined, TContext>;
|
|
106
106
|
/**
|
|
107
|
-
* Structured output: a
|
|
107
|
+
* Structured output: a Standard Schema (assistant text is JSON-parsed and
|
|
108
108
|
* validated) or a streaming `OutputCodec` (fed each text delta, produces
|
|
109
|
-
* the typed value at turn end).
|
|
109
|
+
* the typed value at turn end). Non-Zod schemas can derive the model
|
|
110
|
+
* constraint through StandardJSONSchemaV1 or provide `outputJsonSchema`.
|
|
110
111
|
*/
|
|
111
|
-
output?:
|
|
112
|
+
output?: StandardSchemaV1<unknown, O> | OutputCodec<O>;
|
|
113
|
+
/**
|
|
114
|
+
* Explicit raw JSON Schema override sent to the model. For non-Zod schemas,
|
|
115
|
+
* it takes precedence over StandardJSONSchemaV1 conversion.
|
|
116
|
+
*/
|
|
117
|
+
outputJsonSchema?: Record<string, unknown>;
|
|
112
118
|
params?: ModelParams;
|
|
113
119
|
/** Controls framework event emission for this step. Defaults to `true`. Set `false` to suppress all framework events. A filter function receives `(eventType, data)` and returns `boolean`. */
|
|
114
120
|
emit?: boolean | ((eventType: string, data: Record<string, unknown>) => boolean);
|
|
@@ -138,8 +144,8 @@ export interface StepSubHarness<TContext = ContextData, _I = unknown, O = unknow
|
|
|
138
144
|
settings?: SubHarnessSettings;
|
|
139
145
|
/** System instructions applied on the first message of a fresh session. */
|
|
140
146
|
instructions?: Lazy<string | undefined, TContext>;
|
|
141
|
-
/** When set, the assistant text is JSON-parsed and validated against this
|
|
142
|
-
output?:
|
|
147
|
+
/** When set, the assistant text is JSON-parsed and validated against this Standard Schema. */
|
|
148
|
+
output?: StandardSchemaV1<unknown, O>;
|
|
143
149
|
/** Session reuse + teardown policy across steps. */
|
|
144
150
|
session?: SubHarnessSessionPolicy;
|
|
145
151
|
/** Controls framework event emission for this step. Defaults to `true`. */
|
|
@@ -149,7 +155,7 @@ export interface StepSubHarness<TContext = ContextData, _I = unknown, O = unknow
|
|
|
149
155
|
export interface StepTool<_TContext = ContextData, I = unknown, O = unknown> {
|
|
150
156
|
kind: 'tool';
|
|
151
157
|
id: string;
|
|
152
|
-
tool: Tool<
|
|
158
|
+
tool: Tool<StandardSchemaV1<unknown, I>, StandardSchemaV1<unknown, O>>;
|
|
153
159
|
args?: Partial<I>;
|
|
154
160
|
}
|
|
155
161
|
/** @public A step that dynamically selects and executes one of several possible sub-steps. */
|
package/dist/types/tool.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
2
|
import type { FunctionCallItem, FunctionCallOutputItem, Item, ItemSchemaExtensions, ToolResultExtensionItem } from './items.js';
|
|
3
|
-
type
|
|
3
|
+
import type { InferSchemaOutput } from './schema.js';
|
|
4
|
+
type ToolExecutionResult<O extends StandardSchemaV1> = Promise<InferSchemaOutput<O>> | AsyncGenerator<unknown, InferSchemaOutput<O>>;
|
|
4
5
|
/**
|
|
5
6
|
* Declares tool-owned state that the runtime materializes into a ContextLayer.
|
|
6
7
|
* @public
|
|
@@ -39,44 +40,55 @@ export interface UiFragment {
|
|
|
39
40
|
* `tool()` builder and runtime consume.
|
|
40
41
|
* @public
|
|
41
42
|
*/
|
|
42
|
-
export interface ToolUiDeclaration<I extends
|
|
43
|
+
export interface ToolUiDeclaration<I extends StandardSchemaV1 = StandardSchemaV1, O extends StandardSchemaV1 = StandardSchemaV1, E = unknown> {
|
|
43
44
|
/** Rendered as soon as the call streams in — args may be partial. */
|
|
44
|
-
call?(args: Partial<
|
|
45
|
+
call?(args: Partial<InferSchemaOutput<I>>): UiFragment | null;
|
|
45
46
|
/** Re-rendered on each event an AsyncGenerator `execute` yields. Receives all events so far. */
|
|
46
47
|
progress?(events: E[]): UiFragment | null;
|
|
47
48
|
/** Replaces the tool's region on successful completion. */
|
|
48
|
-
result?(output:
|
|
49
|
+
result?(output: InferSchemaOutput<O>, args: InferSchemaOutput<I>): UiFragment | null;
|
|
49
50
|
/** Replaces the tool's region when execution throws. */
|
|
50
|
-
error?(err: unknown, args:
|
|
51
|
+
error?(err: unknown, args: InferSchemaOutput<I>): UiFragment | null;
|
|
51
52
|
}
|
|
52
53
|
/**
|
|
53
54
|
* A tool definition that an LLM can invoke during execution.
|
|
54
55
|
*
|
|
56
|
+
* Input and output schemas accept any Standard Schema v1 implementation
|
|
57
|
+
* (Zod, Valibot, ArkType, …). Zod remains the fast path; other validators
|
|
58
|
+
* can provide JSON Schema through the Standard JSON Schema v1 companion
|
|
59
|
+
* trait or an explicit `inputJsonSchema` override.
|
|
60
|
+
*
|
|
55
61
|
* The runtime passes a `ToolExecutionContext` (from `./tool-context`) as
|
|
56
62
|
* the second argument to `execute`. Callers that need the concrete type
|
|
57
63
|
* should import `Tool` from the package root (`@noetic-tools/core`), which
|
|
58
64
|
* re-exports it with `ToolExecutionContext` substituted in.
|
|
59
65
|
* @public
|
|
60
66
|
*/
|
|
61
|
-
export interface Tool<I extends
|
|
67
|
+
export interface Tool<I extends StandardSchemaV1 = StandardSchemaV1, O extends StandardSchemaV1 = StandardSchemaV1> {
|
|
62
68
|
/** Unique tool name used by the LLM for selection. */
|
|
63
69
|
name: string;
|
|
64
70
|
/** Human-readable description shown to the LLM. */
|
|
65
71
|
description: string;
|
|
66
|
-
/**
|
|
72
|
+
/** Schema validating tool input arguments (any Standard Schema v1). */
|
|
67
73
|
input: I;
|
|
68
|
-
/**
|
|
74
|
+
/** Schema validating tool return value (any Standard Schema v1). */
|
|
69
75
|
output: O;
|
|
70
|
-
/**
|
|
71
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Explicit raw JSON Schema for the tool input, sent to the LLM wire format.
|
|
78
|
+
* For non-Zod schemas, overrides StandardJSONSchemaV1 conversion and serves
|
|
79
|
+
* as the required fallback for validation-only schemas.
|
|
80
|
+
*/
|
|
81
|
+
inputJsonSchema?: Record<string, unknown>;
|
|
82
|
+
/** Optional schema validating streaming events yielded during execution. */
|
|
83
|
+
event?: StandardSchemaV1;
|
|
72
84
|
/** Optional item schemas contributed by this tool for tool call/result extensions. */
|
|
73
85
|
itemSchemas?: Pick<ItemSchemaExtensions, 'toolCalls' | 'toolResults' | 'items'>;
|
|
74
86
|
/** Decorate the harness-created tool result item before it is appended/emitted. */
|
|
75
87
|
decorateResultItem?(params: {
|
|
76
88
|
baseItem: FunctionCallOutputItem;
|
|
77
89
|
callItem: FunctionCallItem;
|
|
78
|
-
args:
|
|
79
|
-
result:
|
|
90
|
+
args: InferSchemaOutput<I>;
|
|
91
|
+
result: InferSchemaOutput<O> | undefined;
|
|
80
92
|
output: string;
|
|
81
93
|
error?: boolean;
|
|
82
94
|
}): Item | ToolResultExtensionItem;
|
|
@@ -86,7 +98,7 @@ export interface Tool<I extends ZodTypeAny = ZodTypeAny, O extends ZodTypeAny =
|
|
|
86
98
|
* this type a dependency leaf. Use `tool()` from `builders/` or cast at
|
|
87
99
|
* the call site to get a typed handle.
|
|
88
100
|
*/
|
|
89
|
-
execute(args:
|
|
101
|
+
execute(args: InferSchemaOutput<I>, toolCtx: unknown): ToolExecutionResult<O>;
|
|
90
102
|
/** When true, execution pauses for human approval before running. */
|
|
91
103
|
needsApproval?: boolean;
|
|
92
104
|
/** Optional context declaration — the runtime generates a ContextLayer from this. */
|
|
@@ -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.
|
|
3
|
+
"version": "3.2.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",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@openrouter/agent": "^0.6.0",
|
|
58
|
+
"@standard-schema/spec": "1.1.0",
|
|
58
59
|
"zod": "^4.0.0"
|
|
59
60
|
},
|
|
60
61
|
"scripts": {
|