@noetic-tools/types 3.2.2 → 3.4.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/schemas/workflow.d.ts +24 -1
- package/dist/schemas/workflow.js +59 -0
- package/dist/types/common.d.ts +3 -0
- package/dist/types/context-harness.d.ts +18 -6
- package/dist/types/context-layer.d.ts +34 -0
- package/dist/types/decide.d.ts +270 -0
- package/dist/types/decide.js +142 -0
- package/dist/types/harness-judgment.d.ts +18 -0
- package/dist/types/harness-judgment.js +1 -0
- package/dist/types/runtime.d.ts +25 -11
- package/dist/types/steering.d.ts +9 -0
- package/dist/types/step.d.ts +54 -2
- package/dist/types/tool.d.ts +30 -0
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -16,9 +16,11 @@ export * from './types/context-layer.js';
|
|
|
16
16
|
export * from './types/context-parts/item-log.js';
|
|
17
17
|
export * from './types/context-parts/layer-usage.js';
|
|
18
18
|
export * from './types/context-scope.js';
|
|
19
|
+
export * from './types/decide.js';
|
|
19
20
|
export * from './types/detached.js';
|
|
20
21
|
export * from './types/error.js';
|
|
21
22
|
export * from './types/fs-adapter.js';
|
|
23
|
+
export * from './types/harness-judgment.js';
|
|
22
24
|
export * from './types/harness-result.js';
|
|
23
25
|
export * from './types/items.js';
|
|
24
26
|
export * from './types/observability.js';
|
package/dist/index.js
CHANGED
|
@@ -16,9 +16,11 @@ export * from './types/context-layer.js';
|
|
|
16
16
|
export * from './types/context-parts/item-log.js';
|
|
17
17
|
export * from './types/context-parts/layer-usage.js';
|
|
18
18
|
export * from './types/context-scope.js';
|
|
19
|
+
export * from './types/decide.js';
|
|
19
20
|
export * from './types/detached.js';
|
|
20
21
|
export * from './types/error.js';
|
|
21
22
|
export * from './types/fs-adapter.js';
|
|
23
|
+
export * from './types/harness-judgment.js';
|
|
22
24
|
export * from './types/harness-result.js';
|
|
23
25
|
export * from './types/items.js';
|
|
24
26
|
export * from './types/observability.js';
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* document back into live `Step` objects via the existing builders.
|
|
10
10
|
*/
|
|
11
11
|
import { z } from 'zod';
|
|
12
|
+
import type { DecideQuestion } from '../types/decide.js';
|
|
13
|
+
import type { DecideView } from '../types/step.js';
|
|
12
14
|
interface UntilAnyPredicate {
|
|
13
15
|
kind: 'any';
|
|
14
16
|
predicates: UntilPredicate[];
|
|
@@ -234,6 +236,27 @@ export interface ConditionalWorkflowNode extends WorkflowNodeBase {
|
|
|
234
236
|
routes: ConditionalRoute[];
|
|
235
237
|
default?: WorkflowNode;
|
|
236
238
|
}
|
|
239
|
+
/** A node that asks typed questions of a decision model. See spec 32. */
|
|
240
|
+
export interface DecideWorkflowNode extends WorkflowNodeBase {
|
|
241
|
+
kind: 'decide';
|
|
242
|
+
model?: string;
|
|
243
|
+
instructions?: string;
|
|
244
|
+
questions: Record<string, DecideQuestion>;
|
|
245
|
+
view?: DecideView;
|
|
246
|
+
samples?: number;
|
|
247
|
+
}
|
|
248
|
+
/** A node that routes to a case by one question of a decision. See spec 32. */
|
|
249
|
+
export interface RouteSwitchWorkflowNode extends WorkflowNodeBase {
|
|
250
|
+
kind: 'routeSwitch';
|
|
251
|
+
decide: DecideWorkflowNode;
|
|
252
|
+
/** Question id to route by. */
|
|
253
|
+
on: string;
|
|
254
|
+
/** One case per outcome of the `on` question. */
|
|
255
|
+
cases: Record<string, WorkflowNode>;
|
|
256
|
+
minConfidence?: number;
|
|
257
|
+
/** Taken on refusal, or when confidence is below `minConfidence`. */
|
|
258
|
+
uncertain: WorkflowNode;
|
|
259
|
+
}
|
|
237
260
|
export interface InParallelWorkflowNode extends WorkflowNodeBase {
|
|
238
261
|
kind: 'inParallel';
|
|
239
262
|
mode: 'race' | 'all' | 'settle';
|
|
@@ -326,7 +349,7 @@ export interface SubflowWorkflowNode extends WorkflowNodeBase {
|
|
|
326
349
|
input?: string;
|
|
327
350
|
}
|
|
328
351
|
/** @public Discriminated union of all JSON-serialisable workflow node kinds. */
|
|
329
|
-
export type WorkflowNode = CallModelWorkflowNode | InvokeToolWorkflowNode | RunCodeWorkflowNode | ConditionalWorkflowNode | InParallelWorkflowNode | SpawnWorkflowNode | WithContextWorkflowNode | LoopWorkflowNode | SequenceWorkflowNode | ScheduleWorkflowNode | SubflowWorkflowNode | AcpAgentWorkflowNode;
|
|
352
|
+
export type WorkflowNode = CallModelWorkflowNode | InvokeToolWorkflowNode | RunCodeWorkflowNode | ConditionalWorkflowNode | DecideWorkflowNode | RouteSwitchWorkflowNode | InParallelWorkflowNode | SpawnWorkflowNode | WithContextWorkflowNode | LoopWorkflowNode | SequenceWorkflowNode | ScheduleWorkflowNode | SubflowWorkflowNode | AcpAgentWorkflowNode;
|
|
330
353
|
/** @public Zod schema validating a single `WorkflowNode` (any JSON-safe kind). */
|
|
331
354
|
export declare const WorkflowNodeSchema: z.ZodType<WorkflowNode>;
|
|
332
355
|
/** @public Top-level envelope for a JSON workflow definition. */
|
package/dist/schemas/workflow.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* document back into live `Step` objects via the existing builders.
|
|
10
10
|
*/
|
|
11
11
|
import { z } from 'zod';
|
|
12
|
+
import { DecideQuestionsSchema, routeOutcomes } from '../types/decide.js';
|
|
12
13
|
//#endregion
|
|
13
14
|
//#region Until Predicate Schemas
|
|
14
15
|
const MaxStepsPredicateSchema = z.object({
|
|
@@ -245,6 +246,56 @@ const ConditionalNodeSchema = z.object({
|
|
|
245
246
|
routes: z.array(ConditionalRouteSchema).min(1),
|
|
246
247
|
default: WorkflowNodeRef.optional(),
|
|
247
248
|
});
|
|
249
|
+
const DecideNodeSchema = z.object({
|
|
250
|
+
kind: z.literal('decide'),
|
|
251
|
+
...SHARED_FIELDS,
|
|
252
|
+
model: z.string().min(1).optional(),
|
|
253
|
+
instructions: z.string().min(1).optional(),
|
|
254
|
+
questions: DecideQuestionsSchema,
|
|
255
|
+
view: z
|
|
256
|
+
.enum([
|
|
257
|
+
'full',
|
|
258
|
+
'layers',
|
|
259
|
+
'none',
|
|
260
|
+
])
|
|
261
|
+
.optional(),
|
|
262
|
+
samples: z.number().int().positive().optional(),
|
|
263
|
+
});
|
|
264
|
+
const RouteSwitchNodeSchema = z
|
|
265
|
+
.object({
|
|
266
|
+
kind: z.literal('routeSwitch'),
|
|
267
|
+
...SHARED_FIELDS,
|
|
268
|
+
decide: DecideNodeSchema,
|
|
269
|
+
on: z.string().min(1),
|
|
270
|
+
cases: z.record(z.string().min(1), WorkflowNodeRef),
|
|
271
|
+
minConfidence: z.number().min(0).max(1).optional(),
|
|
272
|
+
uncertain: WorkflowNodeRef,
|
|
273
|
+
})
|
|
274
|
+
.superRefine((node, issue) => {
|
|
275
|
+
const question = Object.hasOwn(node.decide.questions, node.on)
|
|
276
|
+
? node.decide.questions[node.on]
|
|
277
|
+
: undefined;
|
|
278
|
+
if (!question) {
|
|
279
|
+
issue.addIssue({
|
|
280
|
+
code: 'custom',
|
|
281
|
+
path: [
|
|
282
|
+
'on',
|
|
283
|
+
],
|
|
284
|
+
message: `routeSwitch 'on' names no question of its decide node: '${node.on}'.`,
|
|
285
|
+
});
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const missing = routeOutcomes(question).filter((o) => !Object.hasOwn(node.cases, o));
|
|
289
|
+
if (missing.length > 0) {
|
|
290
|
+
issue.addIssue({
|
|
291
|
+
code: 'custom',
|
|
292
|
+
path: [
|
|
293
|
+
'cases',
|
|
294
|
+
],
|
|
295
|
+
message: `routeSwitch cases miss outcomes of '${node.on}': ${missing.join(', ')}.`,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
});
|
|
248
299
|
const InParallelNodeSchema = z
|
|
249
300
|
.object({
|
|
250
301
|
kind: z.literal('inParallel'),
|
|
@@ -332,6 +383,8 @@ export const WorkflowNodeSchema = z
|
|
|
332
383
|
InvokeToolNodeSchema,
|
|
333
384
|
RunCodeNodeSchema,
|
|
334
385
|
ConditionalNodeSchema,
|
|
386
|
+
DecideNodeSchema,
|
|
387
|
+
RouteSwitchNodeSchema,
|
|
335
388
|
InParallelNodeSchema,
|
|
336
389
|
SpawnNodeSchema,
|
|
337
390
|
WithContextNodeSchema,
|
|
@@ -390,6 +443,12 @@ function childNodes(node) {
|
|
|
390
443
|
}
|
|
391
444
|
return children;
|
|
392
445
|
}
|
|
446
|
+
case 'routeSwitch':
|
|
447
|
+
return [
|
|
448
|
+
node.decide,
|
|
449
|
+
...Object.values(node.cases),
|
|
450
|
+
node.uncertain,
|
|
451
|
+
];
|
|
393
452
|
case 'subflow':
|
|
394
453
|
// A named ref is a static leaf: the target document is only known at
|
|
395
454
|
// hydration time, so walk/graph/depth cannot see through it.
|
package/dist/types/common.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DecisionMeta } from './decide.js';
|
|
1
2
|
import type { FunctionCallItem, Item } from './items.js';
|
|
2
3
|
/**
|
|
3
4
|
* Policy controlling automatic retry behavior on step failure.
|
|
@@ -90,6 +91,8 @@ export interface StepMeta {
|
|
|
90
91
|
* `max_turn_requests`) from a complete one — both return normally.
|
|
91
92
|
*/
|
|
92
93
|
acpStopReason?: string;
|
|
94
|
+
/** Set by `decide` steps. */
|
|
95
|
+
decision?: DecisionMeta;
|
|
93
96
|
}
|
|
94
97
|
/** @public Structured response returned by a model adapter after an LLM call. */
|
|
95
98
|
export interface LLMResponse {
|
|
@@ -3,7 +3,8 @@ import type { Channel, ChannelHandle, ExternalChannel } from './channel.js';
|
|
|
3
3
|
import type { LLMResponse, ModelParams } from './common.js';
|
|
4
4
|
import type { Context, CwdState, RestoreContextOptions } from './context.js';
|
|
5
5
|
import type { ContextCacheConfig, ContextCacheStore } from './context-cache.js';
|
|
6
|
-
import type { ContextData, ContextLayer, ProjectionPolicy, StorageAdapter } from './context-layer.js';
|
|
6
|
+
import type { ContextData, ContextLayer, ProjectionPolicy, RecallOptions, StorageAdapter } from './context-layer.js';
|
|
7
|
+
import type { DecideRequest, DecideResponse, DecisionRecord } from './decide.js';
|
|
7
8
|
import type { DetachedHandle } from './detached.js';
|
|
8
9
|
import type { FsAdapter } from './fs-adapter.js';
|
|
9
10
|
import type { HarnessResponse, StreamEvent, StreamingItem } from './harness-result.js';
|
|
@@ -42,8 +43,10 @@ interface ContextCallModelRequestWithTools extends ContextCallModelRequestBase {
|
|
|
42
43
|
}
|
|
43
44
|
interface ContextCallModelRequestWithoutTools extends ContextCallModelRequestBase {
|
|
44
45
|
tools?: undefined;
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
/** Accepted without tools — it carries the broadcaster for streaming (see `CallModelRequestWithoutTools`). */
|
|
47
|
+
ctx?: Context;
|
|
48
|
+
layers?: ContextLayer[];
|
|
49
|
+
allowedToolNames?: undefined;
|
|
47
50
|
}
|
|
48
51
|
export type ContextCallModelRequest = ContextCallModelRequestWithTools | ContextCallModelRequestWithoutTools;
|
|
49
52
|
export interface ContextDetachedSpawnOverrides {
|
|
@@ -74,6 +77,10 @@ export type ContextStep<TContext = ContextData, I = unknown, O = unknown> = {
|
|
|
74
77
|
readonly kind: 'callModel';
|
|
75
78
|
readonly id: string;
|
|
76
79
|
readonly output?: StandardSchemaV1<unknown, O>;
|
|
80
|
+
} | {
|
|
81
|
+
readonly kind: 'decide';
|
|
82
|
+
readonly id: string;
|
|
83
|
+
readonly _answers?: O;
|
|
77
84
|
} | {
|
|
78
85
|
readonly kind: 'loop';
|
|
79
86
|
readonly id: string;
|
|
@@ -92,12 +99,17 @@ export interface ContextHarness {
|
|
|
92
99
|
readonly projection?: ProjectionPolicy;
|
|
93
100
|
/** Tuning for prompt-cache anchoring. */
|
|
94
101
|
readonly contextCache?: ContextCacheConfig;
|
|
102
|
+
/** Default model for `decide` steps. */
|
|
103
|
+
readonly decisionModel?: string;
|
|
95
104
|
};
|
|
96
105
|
readonly fs: FsAdapter;
|
|
97
106
|
readonly shell: ShellAdapter;
|
|
98
107
|
readonly subprocess: SubprocessAdapter;
|
|
99
108
|
readonly rootCwdState: CwdState;
|
|
100
109
|
callModel(request: ContextCallModelRequest): Promise<LLMResponse>;
|
|
110
|
+
decide(request: DecideRequest): Promise<DecideResponse>;
|
|
111
|
+
/** Run every layer's `onDecision` hook, in slot order. */
|
|
112
|
+
notifyDecision(layers: ContextLayer[], record: DecisionRecord, ctx: Context): Promise<void>;
|
|
101
113
|
execute(input: ExecuteInput, options?: unknown): Promise<void>;
|
|
102
114
|
getAgentResponse(scope?: unknown): Promise<HarnessResponse>;
|
|
103
115
|
getItemStream(scope?: unknown): AsyncIterable<StreamingItem>;
|
|
@@ -118,14 +130,14 @@ export interface ContextHarness {
|
|
|
118
130
|
setRootCwd(nextCwd: string): void;
|
|
119
131
|
getLayerState<T>(executionId: string, layerId: string): T | undefined;
|
|
120
132
|
setLayerState<T>(executionId: string, layerId: string, state: T): void;
|
|
121
|
-
beforeToolCall(layers: ContextLayer[], toolName: string, toolArgs: unknown, ctx: Context): Promise<SteeringDecision>;
|
|
133
|
+
beforeToolCall(layers: ContextLayer[], toolName: string, toolArgs: unknown, ctx: Context, callId?: string): Promise<SteeringDecision>;
|
|
122
134
|
afterModelCall(layers: ContextLayer[], response: LLMResponse, ctx: Context): Promise<SteeringDecision>;
|
|
123
135
|
runAppendPipeline(layers: ContextLayer[], items: Item[], ctx: Context): Promise<ContextAppendPipelineResult>;
|
|
124
136
|
/** Pinned anchor output and epoch bookkeeping for prompt-cache anchoring. Absent on a harness that predates it. */
|
|
125
137
|
readonly contextCache?: ContextCacheStore;
|
|
126
138
|
recallLayers(layers: ContextLayer[], input: string, ctx: Context): Promise<ContextRecallLayerOutput[]>;
|
|
127
|
-
recallLayersAtomic(layers: ContextLayer[], input: string, ctx: Context, budgets: Map<string, number
|
|
128
|
-
recallLayersEventual(layers: ContextLayer[], input: string, ctx: Context, budgets: Map<string, number
|
|
139
|
+
recallLayersAtomic(layers: ContextLayer[], input: string, ctx: Context, budgets: Map<string, number>, opts?: RecallOptions): Promise<ContextRecallLayerOutput[]>;
|
|
140
|
+
recallLayersEventual(layers: ContextLayer[], input: string, ctx: Context, budgets: Map<string, number>, opts?: RecallOptions): Promise<ContextRecallLayerOutput[]>;
|
|
129
141
|
projectHistory(layers: ContextLayer[], items: ReadonlyArray<Item>, ctx: Context): Promise<ReadonlyArray<Item>>;
|
|
130
142
|
storeLayers(layers: ContextLayer[], response: LLMResponse, ctx: Context): Promise<void>;
|
|
131
143
|
previewRequestItems(scope?: unknown): Promise<ReadonlyArray<Item>>;
|
|
@@ -2,6 +2,7 @@ import type { ZodType } from 'zod';
|
|
|
2
2
|
import type { LLMResponse } from './common.js';
|
|
3
3
|
import type { ItemLog } from './context-parts/item-log.js';
|
|
4
4
|
import type { ContextScope, ExecutionContext, ExecutionOutcome } from './context-scope.js';
|
|
5
|
+
import type { DecisionRecord } from './decide.js';
|
|
5
6
|
import type { Item, ItemSchemaExtensions } from './items.js';
|
|
6
7
|
import type { AfterModelCallParams, AfterModelCallResult, BeforeToolCallParams, BeforeToolCallResult } from './steering.js';
|
|
7
8
|
/** @public Isolation scope controlling how a context layer's state is keyed and shared. */
|
|
@@ -22,6 +23,7 @@ export interface LayerTimeouts {
|
|
|
22
23
|
afterModelCall?: number;
|
|
23
24
|
onItemAppend?: number;
|
|
24
25
|
projectHistory?: number;
|
|
26
|
+
onDecision?: number;
|
|
25
27
|
}
|
|
26
28
|
/** @public A read-only data projection from layer state, accessible via `ctx.context['layerId'].prop`. */
|
|
27
29
|
export interface LayerDataDecl<T = unknown, TState = unknown> {
|
|
@@ -301,6 +303,21 @@ export interface OnItemAppendResult<TState> {
|
|
|
301
303
|
/** Which layers to re-recall (default: 'slot-after'). */
|
|
302
304
|
scope?: RerenderScope;
|
|
303
305
|
}
|
|
306
|
+
/** @public Options for a recall pass. */
|
|
307
|
+
export interface RecallOptions {
|
|
308
|
+
/**
|
|
309
|
+
* Render without leaving a trace: returned state is not committed and
|
|
310
|
+
* eventual-layer output is not cached. A `decide` step peeks so that it never
|
|
311
|
+
* drains state (such as queued steering guidance) meant for the next model call.
|
|
312
|
+
*/
|
|
313
|
+
peek?: boolean;
|
|
314
|
+
}
|
|
315
|
+
/** @public Parameters for the `onDecision` hook. */
|
|
316
|
+
export interface OnDecisionParams<TState> {
|
|
317
|
+
state: TState;
|
|
318
|
+
record: DecisionRecord;
|
|
319
|
+
ctx: ExecutionContext;
|
|
320
|
+
}
|
|
304
321
|
/** @public Lifecycle hook implementations for a context layer. Method syntax enables bivariant assignability for typed layers. */
|
|
305
322
|
export interface ContextLayerHooks<TState = unknown> {
|
|
306
323
|
init?(params: InitParams): Promise<InitResult<TState>>;
|
|
@@ -343,6 +360,13 @@ export interface ContextLayerHooks<TState = unknown> {
|
|
|
343
360
|
* a row. Never called for layers in the `'live'` band, which re-render anyway.
|
|
344
361
|
*/
|
|
345
362
|
renderDelta?(params: RenderDeltaParams<TState>): Promise<string | null>;
|
|
363
|
+
/**
|
|
364
|
+
* Called after every `decide` step completes, in slot order. Return new state
|
|
365
|
+
* to record the decision.
|
|
366
|
+
*/
|
|
367
|
+
onDecision?(params: OnDecisionParams<TState>): Promise<undefined | {
|
|
368
|
+
state: TState;
|
|
369
|
+
}>;
|
|
346
370
|
}
|
|
347
371
|
/**
|
|
348
372
|
* A composable context layer that participates in the recall/store lifecycle.
|
|
@@ -372,6 +396,16 @@ export interface ContextLayer<TState = unknown> {
|
|
|
372
396
|
* (its `recall`/`store`/etc. are skipped). Opt in only for non-critical layers.
|
|
373
397
|
*/
|
|
374
398
|
onInitError?: 'throw' | 'disable';
|
|
399
|
+
/**
|
|
400
|
+
* What a failed `beforeToolCall` hook (throw or timeout) means for the
|
|
401
|
+
* pending tool call.
|
|
402
|
+
* - `'abstain'` (default): log a diagnostic and let the remaining layers /
|
|
403
|
+
* the default decide — correct for observational layers, where a bug must
|
|
404
|
+
* not block every tool.
|
|
405
|
+
* - `'deny'`: the call is denied — correct for permission GATES, where a
|
|
406
|
+
* failed check must fail closed rather than silently approving.
|
|
407
|
+
*/
|
|
408
|
+
onBeforeToolCallError?: 'abstain' | 'deny';
|
|
375
409
|
/**
|
|
376
410
|
* Recall mode controlling whether this layer's `recall()` blocks the model call.
|
|
377
411
|
* - `'atomic'` (default): recall runs synchronously in the hot path; the
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import type { ZodError, ZodType } from 'zod';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import type { Span } from './observability.js';
|
|
4
|
+
/** @public A yes/no question. The answer's `value` is `probability >= threshold`. */
|
|
5
|
+
export interface BooleanQuestion {
|
|
6
|
+
type: 'boolean';
|
|
7
|
+
instructions?: string;
|
|
8
|
+
/** What yes and no mean. Required when `instructions` is absent. */
|
|
9
|
+
criteria?: {
|
|
10
|
+
true?: string;
|
|
11
|
+
false?: string;
|
|
12
|
+
};
|
|
13
|
+
/** Probability at or above which `value` is `true`. Default 0.5. */
|
|
14
|
+
threshold?: number;
|
|
15
|
+
}
|
|
16
|
+
/** @public Pick one of a set of unordered options. */
|
|
17
|
+
export interface ChoiceQuestion<K extends string = string> {
|
|
18
|
+
type: 'choice';
|
|
19
|
+
instructions?: string;
|
|
20
|
+
/** 1–255 options: bare values, or values mapped to descriptions. */
|
|
21
|
+
choices: readonly K[] | Readonly<Record<K, string>>;
|
|
22
|
+
}
|
|
23
|
+
/** @public A level on an ordered scale. */
|
|
24
|
+
export interface ScoreLevel<L extends string = string> {
|
|
25
|
+
label: L;
|
|
26
|
+
description?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* @public Place the state on an ordered scale. Levels run lowest first, and are
|
|
30
|
+
* an array because a record cannot carry order: JavaScript sorts integer-like
|
|
31
|
+
* keys ahead of all others.
|
|
32
|
+
*/
|
|
33
|
+
export interface ScoreQuestion<L extends string = string> {
|
|
34
|
+
type: 'score';
|
|
35
|
+
instructions?: string;
|
|
36
|
+
/** 2–10 levels, lowest first. */
|
|
37
|
+
levels: readonly L[] | ReadonlyArray<ScoreLevel<L>>;
|
|
38
|
+
}
|
|
39
|
+
/** @public Any decide question. */
|
|
40
|
+
export type DecideQuestion = BooleanQuestion | ChoiceQuestion | ScoreQuestion;
|
|
41
|
+
/** @public Question map keyed by question id. */
|
|
42
|
+
export type DecideQuestions = Readonly<Record<string, DecideQuestion>>;
|
|
43
|
+
/** @public Answer to a boolean question. */
|
|
44
|
+
export interface BooleanAnswer {
|
|
45
|
+
type: 'boolean';
|
|
46
|
+
/** `probability >= threshold`. */
|
|
47
|
+
value: boolean;
|
|
48
|
+
/** Probability of yes, in `[0, 1]`. */
|
|
49
|
+
probability: number;
|
|
50
|
+
}
|
|
51
|
+
/** @public Answer to a choice question. */
|
|
52
|
+
export interface ChoiceAnswer<K extends string = string> {
|
|
53
|
+
type: 'choice';
|
|
54
|
+
/** The most probable choice. */
|
|
55
|
+
value: K;
|
|
56
|
+
/** The provider's own certainty measure — not the top probability. */
|
|
57
|
+
confidence: number;
|
|
58
|
+
probabilities: Readonly<Record<K, number>>;
|
|
59
|
+
}
|
|
60
|
+
/** @public Answer to a score question. */
|
|
61
|
+
export interface ScoreAnswer<L extends string = string> {
|
|
62
|
+
type: 'score';
|
|
63
|
+
/** Probability-weighted level index, 0 = lowest. */
|
|
64
|
+
value: number;
|
|
65
|
+
/** The most probable level. Not `value` rounded: a split distribution can differ. */
|
|
66
|
+
level: L;
|
|
67
|
+
/** The provider's own certainty measure — not the top probability. */
|
|
68
|
+
confidence: number;
|
|
69
|
+
probabilities: Readonly<Record<L, number>>;
|
|
70
|
+
}
|
|
71
|
+
/** @public The model declined to answer this question. */
|
|
72
|
+
export interface Refusal {
|
|
73
|
+
type: 'refusal';
|
|
74
|
+
}
|
|
75
|
+
/** @public Any answer, untyped by question. */
|
|
76
|
+
export type DecideAnswer = BooleanAnswer | ChoiceAnswer | ScoreAnswer | Refusal;
|
|
77
|
+
/** @public Choice values declared by a choice question's `choices`. */
|
|
78
|
+
export type ChoiceValues<C> = C extends readonly (infer K extends string)[] ? K : C extends Readonly<Record<infer K extends string | number, string>> ? `${K}` : never;
|
|
79
|
+
/** @public Level labels declared by a score question's `levels`. */
|
|
80
|
+
export type LevelLabels<L> = L extends readonly (infer E)[] ? E extends string ? E : E extends {
|
|
81
|
+
label: infer K extends string;
|
|
82
|
+
} ? K : never : never;
|
|
83
|
+
/** @public The answer type a question produces, refusal excluded. */
|
|
84
|
+
export type AnswerFor<Q> = Q extends {
|
|
85
|
+
type: 'boolean';
|
|
86
|
+
} ? BooleanAnswer : Q extends {
|
|
87
|
+
type: 'choice';
|
|
88
|
+
choices: infer C;
|
|
89
|
+
} ? ChoiceAnswer<ChoiceValues<C>> : Q extends {
|
|
90
|
+
type: 'score';
|
|
91
|
+
levels: infer L;
|
|
92
|
+
} ? ScoreAnswer<LevelLabels<L>> : never;
|
|
93
|
+
/** @public Typed answers for a question map. Any question may be refused. */
|
|
94
|
+
export type DecideAnswers<Q extends DecideQuestions> = {
|
|
95
|
+
readonly [K in keyof Q]: AnswerFor<Q[K]> | Refusal;
|
|
96
|
+
};
|
|
97
|
+
/** @public Case keys a question routes to: its choices, `'true' | 'false'`, or its level labels. */
|
|
98
|
+
export type CaseKeyOf<Q> = Q extends {
|
|
99
|
+
type: 'boolean';
|
|
100
|
+
} ? 'true' | 'false' : Q extends {
|
|
101
|
+
type: 'choice';
|
|
102
|
+
choices: infer C;
|
|
103
|
+
} ? ChoiceValues<C> : Q extends {
|
|
104
|
+
type: 'score';
|
|
105
|
+
levels: infer L;
|
|
106
|
+
} ? LevelLabels<L> : never;
|
|
107
|
+
/** @public Typed answers with refusals removed. */
|
|
108
|
+
export type AnsweredAll<Q extends DecideQuestions> = {
|
|
109
|
+
readonly [K in keyof Q]: AnswerFor<Q[K]>;
|
|
110
|
+
};
|
|
111
|
+
/** @public One piece of the state a decision is made against. */
|
|
112
|
+
export type DecideStatePart = {
|
|
113
|
+
type: 'text';
|
|
114
|
+
text: string;
|
|
115
|
+
name?: string;
|
|
116
|
+
} | {
|
|
117
|
+
type: 'json';
|
|
118
|
+
name: string;
|
|
119
|
+
value: unknown;
|
|
120
|
+
} | {
|
|
121
|
+
type: 'image';
|
|
122
|
+
imageUrl: string;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* @public `native`: a decision model's own distribution. `estimated`: a chat
|
|
126
|
+
* model's self-reported probabilities, which are not calibrated.
|
|
127
|
+
*/
|
|
128
|
+
export type DecisionSource = 'native' | 'estimated';
|
|
129
|
+
/** @public Request for `harness.decide()`, in Noetic's vocabulary. Adapters translate it per provider. */
|
|
130
|
+
export interface DecideRequest {
|
|
131
|
+
model: string;
|
|
132
|
+
instructions?: string;
|
|
133
|
+
state: ReadonlyArray<DecideStatePart>;
|
|
134
|
+
questions: DecideQuestions;
|
|
135
|
+
/** Runs averaged on the estimated path. Ignored on the native path. Default 1. */
|
|
136
|
+
samples?: number;
|
|
137
|
+
signal?: AbortSignal;
|
|
138
|
+
/** @internal Parent span for the decide-call span. */
|
|
139
|
+
parentSpan?: Span;
|
|
140
|
+
}
|
|
141
|
+
/** @public Response from `harness.decide()`. */
|
|
142
|
+
export interface DecideResponse {
|
|
143
|
+
answers: Readonly<Record<string, DecideAnswer>>;
|
|
144
|
+
source: DecisionSource;
|
|
145
|
+
/** Model that answered, as reported by the provider. */
|
|
146
|
+
modelId: string;
|
|
147
|
+
usage: {
|
|
148
|
+
inputTokens: number;
|
|
149
|
+
outputTokens: number;
|
|
150
|
+
};
|
|
151
|
+
cost?: number;
|
|
152
|
+
}
|
|
153
|
+
/** @public Decision metadata recorded on `ctx.lastStepMeta.decision`. */
|
|
154
|
+
export interface DecisionMeta {
|
|
155
|
+
modelId: string;
|
|
156
|
+
source: DecisionSource;
|
|
157
|
+
/** Ids of refused questions. */
|
|
158
|
+
refused: readonly string[];
|
|
159
|
+
}
|
|
160
|
+
/** @public One completed decision, as layers see it through `onDecision`. */
|
|
161
|
+
export interface DecisionRecord {
|
|
162
|
+
stepId: string;
|
|
163
|
+
/** Epoch ms. */
|
|
164
|
+
at: number;
|
|
165
|
+
modelId: string;
|
|
166
|
+
source: DecisionSource;
|
|
167
|
+
answers: Readonly<Record<string, DecideAnswer>>;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* A chat model's estimate did not match the requested shape. The interpreter
|
|
171
|
+
* turns it into `model_parse_error`, which needs the step id this layer lacks.
|
|
172
|
+
* @public
|
|
173
|
+
*/
|
|
174
|
+
export declare class DecideOutputError extends Error {
|
|
175
|
+
readonly raw: string;
|
|
176
|
+
readonly schema: ZodType;
|
|
177
|
+
readonly zodError: ZodError;
|
|
178
|
+
/** Tokens the failed estimate still cost, so the step can count them. */
|
|
179
|
+
readonly usage?: DecideResponse['usage'];
|
|
180
|
+
readonly cost?: number;
|
|
181
|
+
constructor(opts: {
|
|
182
|
+
raw: string;
|
|
183
|
+
schema: ZodType;
|
|
184
|
+
zodError: ZodError;
|
|
185
|
+
usage?: DecideResponse['usage'];
|
|
186
|
+
cost?: number;
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/** Choice and level limits shared by the OpenAI and TypeSafe decision APIs. */
|
|
190
|
+
export declare const DECIDE_LIMITS: {
|
|
191
|
+
readonly maxChoices: 255;
|
|
192
|
+
readonly minLevels: 2;
|
|
193
|
+
readonly maxLevels: 10;
|
|
194
|
+
};
|
|
195
|
+
/** @public Zod schema for a boolean question. */
|
|
196
|
+
export declare const BooleanQuestionSchema: z.ZodObject<{
|
|
197
|
+
type: z.ZodLiteral<"boolean">;
|
|
198
|
+
instructions: z.ZodOptional<z.ZodString>;
|
|
199
|
+
criteria: z.ZodOptional<z.ZodObject<{
|
|
200
|
+
true: z.ZodOptional<z.ZodString>;
|
|
201
|
+
false: z.ZodOptional<z.ZodString>;
|
|
202
|
+
}, z.core.$strip>>;
|
|
203
|
+
threshold: z.ZodOptional<z.ZodNumber>;
|
|
204
|
+
}, z.core.$strip>;
|
|
205
|
+
/** @public Zod schema for a choice question. */
|
|
206
|
+
export declare const ChoiceQuestionSchema: z.ZodObject<{
|
|
207
|
+
type: z.ZodLiteral<"choice">;
|
|
208
|
+
instructions: z.ZodOptional<z.ZodString>;
|
|
209
|
+
choices: z.ZodUnion<readonly [z.ZodArray<z.ZodString>, z.ZodRecord<z.ZodString, z.ZodString>]>;
|
|
210
|
+
}, z.core.$strip>;
|
|
211
|
+
/** @public Zod schema for a score question. */
|
|
212
|
+
export declare const ScoreQuestionSchema: z.ZodObject<{
|
|
213
|
+
type: z.ZodLiteral<"score">;
|
|
214
|
+
instructions: z.ZodOptional<z.ZodString>;
|
|
215
|
+
levels: z.ZodUnion<readonly [z.ZodArray<z.ZodString>, z.ZodArray<z.ZodObject<{
|
|
216
|
+
label: z.ZodString;
|
|
217
|
+
description: z.ZodOptional<z.ZodString>;
|
|
218
|
+
}, z.core.$strip>>]>;
|
|
219
|
+
}, z.core.$strip>;
|
|
220
|
+
/** @public Zod schema for any decide question. */
|
|
221
|
+
export declare const DecideQuestionSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
222
|
+
type: z.ZodLiteral<"boolean">;
|
|
223
|
+
instructions: z.ZodOptional<z.ZodString>;
|
|
224
|
+
criteria: z.ZodOptional<z.ZodObject<{
|
|
225
|
+
true: z.ZodOptional<z.ZodString>;
|
|
226
|
+
false: z.ZodOptional<z.ZodString>;
|
|
227
|
+
}, z.core.$strip>>;
|
|
228
|
+
threshold: z.ZodOptional<z.ZodNumber>;
|
|
229
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
230
|
+
type: z.ZodLiteral<"choice">;
|
|
231
|
+
instructions: z.ZodOptional<z.ZodString>;
|
|
232
|
+
choices: z.ZodUnion<readonly [z.ZodArray<z.ZodString>, z.ZodRecord<z.ZodString, z.ZodString>]>;
|
|
233
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
234
|
+
type: z.ZodLiteral<"score">;
|
|
235
|
+
instructions: z.ZodOptional<z.ZodString>;
|
|
236
|
+
levels: z.ZodUnion<readonly [z.ZodArray<z.ZodString>, z.ZodArray<z.ZodObject<{
|
|
237
|
+
label: z.ZodString;
|
|
238
|
+
description: z.ZodOptional<z.ZodString>;
|
|
239
|
+
}, z.core.$strip>>]>;
|
|
240
|
+
}, z.core.$strip>]>;
|
|
241
|
+
/** @public Zod schema for a question map: at least one question. */
|
|
242
|
+
export declare const DecideQuestionsSchema: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
|
|
243
|
+
type: z.ZodLiteral<"boolean">;
|
|
244
|
+
instructions: z.ZodOptional<z.ZodString>;
|
|
245
|
+
criteria: z.ZodOptional<z.ZodObject<{
|
|
246
|
+
true: z.ZodOptional<z.ZodString>;
|
|
247
|
+
false: z.ZodOptional<z.ZodString>;
|
|
248
|
+
}, z.core.$strip>>;
|
|
249
|
+
threshold: z.ZodOptional<z.ZodNumber>;
|
|
250
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
251
|
+
type: z.ZodLiteral<"choice">;
|
|
252
|
+
instructions: z.ZodOptional<z.ZodString>;
|
|
253
|
+
choices: z.ZodUnion<readonly [z.ZodArray<z.ZodString>, z.ZodRecord<z.ZodString, z.ZodString>]>;
|
|
254
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
255
|
+
type: z.ZodLiteral<"score">;
|
|
256
|
+
instructions: z.ZodOptional<z.ZodString>;
|
|
257
|
+
levels: z.ZodUnion<readonly [z.ZodArray<z.ZodString>, z.ZodArray<z.ZodObject<{
|
|
258
|
+
label: z.ZodString;
|
|
259
|
+
description: z.ZodOptional<z.ZodString>;
|
|
260
|
+
}, z.core.$strip>>]>;
|
|
261
|
+
}, z.core.$strip>]>>;
|
|
262
|
+
/** @public Choice values in declaration order. */
|
|
263
|
+
export declare function choiceValues(choices: ChoiceQuestion['choices']): string[];
|
|
264
|
+
/** @public Score levels, lowest first, in object form. */
|
|
265
|
+
export declare function scoreLevels(levels: ScoreQuestion['levels']): ScoreLevel[];
|
|
266
|
+
/**
|
|
267
|
+
* @public Case keys a question routes to: its choices, `'true'`/`'false'`, or
|
|
268
|
+
* its level labels.
|
|
269
|
+
*/
|
|
270
|
+
export declare function routeOutcomes(question: DecideQuestion): string[];
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
//#endregion
|
|
3
|
+
/**
|
|
4
|
+
* A chat model's estimate did not match the requested shape. The interpreter
|
|
5
|
+
* turns it into `model_parse_error`, which needs the step id this layer lacks.
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
export class DecideOutputError extends Error {
|
|
9
|
+
raw;
|
|
10
|
+
schema;
|
|
11
|
+
zodError;
|
|
12
|
+
/** Tokens the failed estimate still cost, so the step can count them. */
|
|
13
|
+
usage;
|
|
14
|
+
cost;
|
|
15
|
+
constructor(opts) {
|
|
16
|
+
super(`decision estimate did not match its schema: ${opts.zodError.message}`);
|
|
17
|
+
this.name = 'DecideOutputError';
|
|
18
|
+
this.raw = opts.raw;
|
|
19
|
+
this.schema = opts.schema;
|
|
20
|
+
this.zodError = opts.zodError;
|
|
21
|
+
this.usage = opts.usage;
|
|
22
|
+
this.cost = opts.cost;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region Schemas
|
|
27
|
+
/** Choice and level limits shared by the OpenAI and TypeSafe decision APIs. */
|
|
28
|
+
export const DECIDE_LIMITS = {
|
|
29
|
+
maxChoices: 255,
|
|
30
|
+
minLevels: 2,
|
|
31
|
+
maxLevels: 10,
|
|
32
|
+
};
|
|
33
|
+
const InstructionsSchema = z.string().min(1).optional();
|
|
34
|
+
/** @public Zod schema for a boolean question. */
|
|
35
|
+
export const BooleanQuestionSchema = z
|
|
36
|
+
.object({
|
|
37
|
+
type: z.literal('boolean'),
|
|
38
|
+
instructions: InstructionsSchema,
|
|
39
|
+
criteria: z
|
|
40
|
+
.object({
|
|
41
|
+
true: z.string().min(1).optional(),
|
|
42
|
+
false: z.string().min(1).optional(),
|
|
43
|
+
})
|
|
44
|
+
.optional(),
|
|
45
|
+
threshold: z.number().min(0).max(1).optional(),
|
|
46
|
+
})
|
|
47
|
+
.refine((q) => q.instructions !== undefined || q.criteria?.true || q.criteria?.false, {
|
|
48
|
+
message: 'boolean question needs `instructions` or a `criteria` description.',
|
|
49
|
+
});
|
|
50
|
+
/** @public Zod schema for a choice question. */
|
|
51
|
+
export const ChoiceQuestionSchema = z
|
|
52
|
+
.object({
|
|
53
|
+
type: z.literal('choice'),
|
|
54
|
+
instructions: InstructionsSchema,
|
|
55
|
+
choices: z.union([
|
|
56
|
+
z.array(z.string().min(1)).min(1).max(DECIDE_LIMITS.maxChoices),
|
|
57
|
+
z.record(z.string().min(1), z.string()),
|
|
58
|
+
]),
|
|
59
|
+
})
|
|
60
|
+
.refine((q) => choiceValues(q.choices).length >= 1, {
|
|
61
|
+
message: 'choice question needs at least one choice.',
|
|
62
|
+
})
|
|
63
|
+
.refine((q) => choiceValues(q.choices).length <= DECIDE_LIMITS.maxChoices, {
|
|
64
|
+
message: `choice question allows at most ${DECIDE_LIMITS.maxChoices} choices.`,
|
|
65
|
+
})
|
|
66
|
+
.refine((q) => unique(choiceValues(q.choices)), {
|
|
67
|
+
message: 'choice values must be unique.',
|
|
68
|
+
});
|
|
69
|
+
/** @public Zod schema for a score question. */
|
|
70
|
+
export const ScoreQuestionSchema = z
|
|
71
|
+
.object({
|
|
72
|
+
type: z.literal('score'),
|
|
73
|
+
instructions: InstructionsSchema,
|
|
74
|
+
levels: z
|
|
75
|
+
.union([
|
|
76
|
+
z.array(z.string().min(1)),
|
|
77
|
+
z.array(z.object({
|
|
78
|
+
label: z.string().min(1),
|
|
79
|
+
description: z.string().optional(),
|
|
80
|
+
})),
|
|
81
|
+
])
|
|
82
|
+
.refine((l) => l.length >= DECIDE_LIMITS.minLevels && l.length <= DECIDE_LIMITS.maxLevels, {
|
|
83
|
+
message: `score question needs ${DECIDE_LIMITS.minLevels}–${DECIDE_LIMITS.maxLevels} levels.`,
|
|
84
|
+
}),
|
|
85
|
+
})
|
|
86
|
+
.refine((q) => unique(levelLabels(q.levels)), {
|
|
87
|
+
message: 'score level labels must be unique.',
|
|
88
|
+
});
|
|
89
|
+
/** @public Zod schema for any decide question. */
|
|
90
|
+
export const DecideQuestionSchema = z.union([
|
|
91
|
+
BooleanQuestionSchema,
|
|
92
|
+
ChoiceQuestionSchema,
|
|
93
|
+
ScoreQuestionSchema,
|
|
94
|
+
]);
|
|
95
|
+
/** @public Zod schema for a question map: at least one question. */
|
|
96
|
+
export const DecideQuestionsSchema = z
|
|
97
|
+
.record(z.string().min(1), DecideQuestionSchema)
|
|
98
|
+
.refine((q) => Object.keys(q).length > 0, {
|
|
99
|
+
message: 'decide needs at least one question.',
|
|
100
|
+
});
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region Helpers
|
|
103
|
+
/** @public Choice values in declaration order. */
|
|
104
|
+
export function choiceValues(choices) {
|
|
105
|
+
return Array.isArray(choices)
|
|
106
|
+
? [
|
|
107
|
+
...choices,
|
|
108
|
+
]
|
|
109
|
+
: Object.keys(choices);
|
|
110
|
+
}
|
|
111
|
+
/** @public Score levels, lowest first, in object form. */
|
|
112
|
+
export function scoreLevels(levels) {
|
|
113
|
+
return levels.map((l) => typeof l === 'string'
|
|
114
|
+
? {
|
|
115
|
+
label: l,
|
|
116
|
+
}
|
|
117
|
+
: l);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* @public Case keys a question routes to: its choices, `'true'`/`'false'`, or
|
|
121
|
+
* its level labels.
|
|
122
|
+
*/
|
|
123
|
+
export function routeOutcomes(question) {
|
|
124
|
+
switch (question.type) {
|
|
125
|
+
case 'boolean':
|
|
126
|
+
return [
|
|
127
|
+
'true',
|
|
128
|
+
'false',
|
|
129
|
+
];
|
|
130
|
+
case 'choice':
|
|
131
|
+
return choiceValues(question.choices);
|
|
132
|
+
case 'score':
|
|
133
|
+
return levelLabels(question.levels);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function levelLabels(levels) {
|
|
137
|
+
return scoreLevels(levels).map((l) => l.label);
|
|
138
|
+
}
|
|
139
|
+
function unique(values) {
|
|
140
|
+
return new Set(values).size === values.length;
|
|
141
|
+
}
|
|
142
|
+
//#endregion
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { LLMResponse } from './common.js';
|
|
2
|
+
import type { Context } from './context.js';
|
|
3
|
+
import type { ContextLayer } from './context-layer.js';
|
|
4
|
+
import type { DecideRequest, DecideResponse, DecisionRecord } from './decide.js';
|
|
5
|
+
import type { SteeringDecision } from './steering.js';
|
|
6
|
+
/**
|
|
7
|
+
* @public The judgment calls an agent harness makes for a step: the steering
|
|
8
|
+
* hooks around tool and model calls, and typed decisions. Part of
|
|
9
|
+
* `AgentHarnessContract`.
|
|
10
|
+
*/
|
|
11
|
+
export interface HarnessJudgmentContract {
|
|
12
|
+
/** Answer typed questions with a decision model, or estimate with a chat model. See spec 32. */
|
|
13
|
+
decide(request: DecideRequest): Promise<DecideResponse>;
|
|
14
|
+
/** Run every layer's `onDecision` hook, in slot order. */
|
|
15
|
+
notifyDecision(layers: ContextLayer[], record: DecisionRecord, ctx: Context): Promise<void>;
|
|
16
|
+
beforeToolCall(layers: ContextLayer[], toolName: string, toolArgs: unknown, ctx: Context, callId?: string): Promise<SteeringDecision>;
|
|
17
|
+
afterModelCall(layers: ContextLayer[], response: LLMResponse, ctx: Context): Promise<SteeringDecision>;
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/runtime.d.ts
CHANGED
|
@@ -3,14 +3,14 @@ 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
5
|
import type { ContextCacheConfig, ContextCacheStore } from './context-cache.js';
|
|
6
|
-
import type { ContextData, ContextLayer, ProjectionPolicy, StorageAdapter } from './context-layer.js';
|
|
6
|
+
import type { ContextData, ContextLayer, ProjectionPolicy, RecallOptions, StorageAdapter } from './context-layer.js';
|
|
7
7
|
import type { DetachedHandle } from './detached.js';
|
|
8
8
|
import type { FsAdapter } from './fs-adapter.js';
|
|
9
|
+
import type { HarnessJudgmentContract } from './harness-judgment.js';
|
|
9
10
|
import type { HarnessResponse, StreamEvent, StreamingItem } from './harness-result.js';
|
|
10
11
|
import type { ExecuteInput, Item, ItemSchemaExtensions } from './items.js';
|
|
11
12
|
import type { Span, TraceExporter } from './observability.js';
|
|
12
13
|
import type { ShellAdapter } from './shell-adapter.js';
|
|
13
|
-
import type { SteeringDecision } from './steering.js';
|
|
14
14
|
import type { Step } from './step.js';
|
|
15
15
|
import type { SubprocessAdapter } from './subprocess-adapter.js';
|
|
16
16
|
import type { Tool } from './tool.js';
|
|
@@ -40,6 +40,8 @@ export interface AgentConfig<TParams extends Record<string, unknown> = Record<st
|
|
|
40
40
|
forceAtomicRecall?: boolean;
|
|
41
41
|
/** Tuning for prompt-cache anchoring. See `ContextCacheConfig` for the defaults. */
|
|
42
42
|
contextCache?: ContextCacheConfig;
|
|
43
|
+
/** Default model for `decide` steps. */
|
|
44
|
+
decisionModel?: string;
|
|
43
45
|
}
|
|
44
46
|
/** @public How a message submitted during an active turn is delivered to the model.
|
|
45
47
|
*
|
|
@@ -101,12 +103,17 @@ interface CallModelRequestWithTools extends CallModelRequestBase {
|
|
|
101
103
|
/** When set, restricts which tools the model may invoke for this call. */
|
|
102
104
|
allowedToolNames?: string[];
|
|
103
105
|
}
|
|
104
|
-
/**
|
|
105
|
-
*
|
|
106
|
+
/**
|
|
107
|
+
* @public Request shape when no tools are provided. `ctx` is still accepted —
|
|
108
|
+
* it carries the session broadcaster, without which a tool-less call streams
|
|
109
|
+
* nothing to `getFullStream`/`getTextStream` — it just isn't *required* the
|
|
110
|
+
* way the tools variant needs it for execution callbacks.
|
|
111
|
+
*/
|
|
106
112
|
interface CallModelRequestWithoutTools extends CallModelRequestBase {
|
|
107
113
|
tools?: undefined;
|
|
108
|
-
ctx?:
|
|
109
|
-
layers?:
|
|
114
|
+
ctx?: Context;
|
|
115
|
+
layers?: ContextLayer[];
|
|
116
|
+
allowedToolNames?: undefined;
|
|
110
117
|
}
|
|
111
118
|
/** @public Request object for `AgentHarnessContract.callModel()`. Encapsulates all parameters needed for an LLM call using only Noetic types. */
|
|
112
119
|
export type CallModelRequest = CallModelRequestWithTools | CallModelRequestWithoutTools;
|
|
@@ -117,6 +124,13 @@ export interface ExecuteOptions {
|
|
|
117
124
|
state?: unknown;
|
|
118
125
|
/** Context layers to apply to the execution context. Overrides harness-level layers if provided. */
|
|
119
126
|
contextLayers?: ContextLayer[];
|
|
127
|
+
/**
|
|
128
|
+
* Additional context layers appended AFTER the effective layer set
|
|
129
|
+
* (harness-level layers, or `contextLayers` when that override is given).
|
|
130
|
+
* Use this to inject a per-turn layer — a permission gate, an observer —
|
|
131
|
+
* without discarding the layers the harness was built with.
|
|
132
|
+
*/
|
|
133
|
+
extraContextLayers?: ContextLayer[];
|
|
120
134
|
/** Override the harness's default delivery mode for this message only. */
|
|
121
135
|
deliveryMode?: DeliveryMode;
|
|
122
136
|
/**
|
|
@@ -158,7 +172,7 @@ export interface SessionScope {
|
|
|
158
172
|
threadId?: string;
|
|
159
173
|
}
|
|
160
174
|
/** @public Type-level contract for the agent runtime. Used for type annotations throughout the interpreter, context layers, and context. Implemented by `AgentHarness`. */
|
|
161
|
-
export interface AgentHarnessContract<TParams extends Record<string, unknown> = Record<string, unknown>> {
|
|
175
|
+
export interface AgentHarnessContract<TParams extends Record<string, unknown> = Record<string, unknown>> extends HarnessJudgmentContract {
|
|
162
176
|
readonly config: AgentConfig<TParams>;
|
|
163
177
|
/** Filesystem adapter for virtual or real filesystem access. */
|
|
164
178
|
readonly fs: FsAdapter;
|
|
@@ -244,6 +258,8 @@ export interface AgentHarnessContract<TParams extends Record<string, unknown> =
|
|
|
244
258
|
threadId?: string;
|
|
245
259
|
resourceId?: string;
|
|
246
260
|
contextLayers?: ContextLayer[];
|
|
261
|
+
/** Additional layers appended AFTER the effective layer set (see `ExecuteOptions.extraContextLayers`). */
|
|
262
|
+
extraContextLayers?: ContextLayer[];
|
|
247
263
|
/** Override the new context's initial cwd. */
|
|
248
264
|
cwdInit?: string;
|
|
249
265
|
}): Context;
|
|
@@ -284,9 +300,9 @@ export interface AgentHarnessContract<TParams extends Record<string, unknown> =
|
|
|
284
300
|
initLayers(layers: ContextLayer[], ctx: Context, storage: StorageAdapter): Promise<void>;
|
|
285
301
|
recallLayers(layers: ContextLayer[], input: string, ctx: Context): Promise<RecallLayerOutput[]>;
|
|
286
302
|
/** Recall only atomic layers (or all layers when the harness forces atomic recall). Blocks until complete. */
|
|
287
|
-
recallLayersAtomic(layers: ContextLayer[], input: string, ctx: Context, budgets: Map<string, number
|
|
303
|
+
recallLayersAtomic(layers: ContextLayer[], input: string, ctx: Context, budgets: Map<string, number>, opts?: RecallOptions): Promise<RecallLayerOutput[]>;
|
|
288
304
|
/** Recall eventual layers from cache (non-blocking); returns nothing when the harness forces atomic recall. */
|
|
289
|
-
recallLayersEventual(layers: ContextLayer[], input: string, ctx: Context, budgets: Map<string, number
|
|
305
|
+
recallLayersEventual(layers: ContextLayer[], input: string, ctx: Context, budgets: Map<string, number>, opts?: RecallOptions): Promise<RecallLayerOutput[]>;
|
|
290
306
|
/**
|
|
291
307
|
* Compute the items that would be sent to the model on the next turn for a
|
|
292
308
|
* session — its accumulated history plus the harness's context-layer recall
|
|
@@ -318,8 +334,6 @@ export interface AgentHarnessContract<TParams extends Record<string, unknown> =
|
|
|
318
334
|
createSpan(name: string, parent: Span | null): Span;
|
|
319
335
|
getLayerState<T>(executionId: string, layerId: string): T | undefined;
|
|
320
336
|
setLayerState<T>(executionId: string, layerId: string, state: T): void;
|
|
321
|
-
beforeToolCall(layers: ContextLayer[], toolName: string, toolArgs: unknown, ctx: Context): Promise<SteeringDecision>;
|
|
322
|
-
afterModelCall(layers: ContextLayer[], response: LLMResponse, ctx: Context): Promise<SteeringDecision>;
|
|
323
337
|
/**
|
|
324
338
|
* Project the history items through every layer's `projectHistory` hook in
|
|
325
339
|
* slot order. Pure read-side projection — `itemLog` is never mutated.
|
package/dist/types/steering.d.ts
CHANGED
|
@@ -42,6 +42,15 @@ export interface LedgerEntry {
|
|
|
42
42
|
export interface BeforeToolCallParams<TState = unknown> {
|
|
43
43
|
toolName: string;
|
|
44
44
|
toolArgs: unknown;
|
|
45
|
+
/**
|
|
46
|
+
* Correlation id of the pending call — the same id carried by the
|
|
47
|
+
* `tool_call_started` framework event (the model's `callId` on model-driven
|
|
48
|
+
* calls, the step id on a direct `invokeTool`). Lets a gating layer tie its
|
|
49
|
+
* decision to the call it is reported as, e.g. when forwarding the request
|
|
50
|
+
* to an external approver. Optional because a custom caller of the gate may
|
|
51
|
+
* have no call identity; every built-in tool-executing path supplies it.
|
|
52
|
+
*/
|
|
53
|
+
callId?: string;
|
|
45
54
|
ctx: ExecutionContext;
|
|
46
55
|
state: TState;
|
|
47
56
|
}
|
package/dist/types/step.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { Channel } from './channel.js';
|
|
|
4
4
|
import type { ModelParams, RetryPolicy, ServerToolSpec, StepMeta } from './common.js';
|
|
5
5
|
import type { Context } from './context.js';
|
|
6
6
|
import type { ContextConfig, ContextData, ContextLayer, ProjectionPolicy } from './context-layer.js';
|
|
7
|
+
import type { DecideAnswers, DecideQuestions } from './decide.js';
|
|
7
8
|
import type { NoeticError } from './error.js';
|
|
8
9
|
import type { OutputCodec } from './output-codec.js';
|
|
9
10
|
import type { SubprocessAdapter } from './subprocess-adapter.js';
|
|
@@ -73,7 +74,7 @@ export interface SettleResult<O> {
|
|
|
73
74
|
error?: NoeticError;
|
|
74
75
|
}
|
|
75
76
|
/** @public Discriminated union of all step kinds that can be composed into an execution tree. */
|
|
76
|
-
export type Step<TContext = ContextData, I = unknown, O = unknown> = StepRunCode<TContext, I, O> | StepCallModel<TContext, I, O> | StepAcpAgent<TContext, I, O> | StepInvokeTool<TContext, I, O> | StepConditional<TContext, I, O> | StepInParallel<TContext, I, O> | StepSpawn<TContext, I, O> | StepWithContext<TContext, I, O> | StepLoop<TContext, I, O> | StepSchedule<TContext, I, O>;
|
|
77
|
+
export type Step<TContext = ContextData, I = unknown, O = unknown> = StepRunCode<TContext, I, O> | StepCallModel<TContext, I, O> | StepAcpAgent<TContext, I, O> | StepInvokeTool<TContext, I, O> | StepDecide<TContext, I, O> | StepConditional<TContext, I, O> | StepInParallel<TContext, I, O> | StepSpawn<TContext, I, O> | StepWithContext<TContext, I, O> | StepLoop<TContext, I, O> | StepSchedule<TContext, I, O>;
|
|
77
78
|
/** @public A step that executes arbitrary async logic via a user-supplied function. */
|
|
78
79
|
export interface StepRunCode<TContext = ContextData, I = unknown, O = unknown> {
|
|
79
80
|
kind: 'runCode';
|
|
@@ -173,12 +174,63 @@ export interface StepInvokeTool<_TContext = ContextData, I = unknown, O = unknow
|
|
|
173
174
|
tool: Tool<StandardSchemaV1<unknown, I>, StandardSchemaV1<unknown, O>>;
|
|
174
175
|
args?: Partial<I>;
|
|
175
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* A step that asks a model typed questions about the current context and returns
|
|
179
|
+
* typed answers with probabilities. It reads the conversation but never appends
|
|
180
|
+
* to it, and runs no steering hooks. See spec 32.
|
|
181
|
+
* @public
|
|
182
|
+
*/
|
|
183
|
+
export interface StepDecide<TContext = ContextData, _I = unknown, O = unknown> {
|
|
184
|
+
kind: 'decide';
|
|
185
|
+
/** Phantom field carrying the answers type for inference. Never set at runtime. */
|
|
186
|
+
readonly _answers?: O;
|
|
187
|
+
id: string;
|
|
188
|
+
/** Decision model id. Falls back to `harness.config.decisionModel`. */
|
|
189
|
+
model?: Lazy<string, TContext>;
|
|
190
|
+
/** Shared framing for every question. */
|
|
191
|
+
instructions?: Lazy<string | undefined, TContext>;
|
|
192
|
+
questions: DecideQuestions;
|
|
193
|
+
/** Which part of the assembled view the model sees. */
|
|
194
|
+
view: DecideView;
|
|
195
|
+
/** Typed state read from context layers, sent as named state entries. */
|
|
196
|
+
context?: (ctx: Context<TContext>) => Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
197
|
+
/** Budget for the decision's view. Defaults to `DECISION_PROJECTION`. */
|
|
198
|
+
projection?: ProjectionPolicy;
|
|
199
|
+
/** Runs averaged on the estimated path. Ignored on the native path. */
|
|
200
|
+
samples: number;
|
|
201
|
+
/** Controls framework event emission for this step. Defaults to `true`. */
|
|
202
|
+
emit?: boolean | ((eventType: string, data: Record<string, unknown>) => boolean);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* A decide step that keeps its literal question map, so `routeSwitch` can infer
|
|
206
|
+
* the questions, choices, and levels it routes by.
|
|
207
|
+
* @public
|
|
208
|
+
*/
|
|
209
|
+
export type DecideStep<TContext, I, Q extends DecideQuestions> = StepDecide<TContext, I, DecideAnswers<Q>> & {
|
|
210
|
+
readonly questions: Q;
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* What a decide step sees: `full` = layer recall + history, `layers` = layer
|
|
214
|
+
* recall only, `none` = only the input, instructions, and context selector.
|
|
215
|
+
* @public
|
|
216
|
+
*/
|
|
217
|
+
export type DecideView = 'full' | 'layers' | 'none';
|
|
176
218
|
/** @public A step that dynamically selects and executes one of several possible sub-steps. */
|
|
177
219
|
export interface StepConditional<TContext = ContextData, I = unknown, O = unknown> {
|
|
178
220
|
kind: 'conditional';
|
|
179
221
|
id: string;
|
|
180
|
-
|
|
222
|
+
/**
|
|
223
|
+
* Picks the step to run. `run` executes a step through the interpreter, for
|
|
224
|
+
* routes that must run a step (such as a decision) to choose.
|
|
225
|
+
*/
|
|
226
|
+
route: (input: I, ctx: Context<TContext>, run: ExecuteStepFn) => Step<TContext, I, O> | null | Promise<Step<TContext, I, O> | null>;
|
|
181
227
|
_optimizable?: Step<TContext>[];
|
|
228
|
+
/**
|
|
229
|
+
* @internal Rebuild this step around new `_optimizable` children, for
|
|
230
|
+
* conditionals whose route closes over them (`routeSwitch`). The optimizer calls
|
|
231
|
+
* it so a mutated child is the one that runs.
|
|
232
|
+
*/
|
|
233
|
+
_rebuild?: (children: Step<TContext>[]) => StepConditional<TContext, I, O>;
|
|
182
234
|
}
|
|
183
235
|
/** @public Union of inParallel step variants (`race`, `all`, `settle`) for concurrent path execution. */
|
|
184
236
|
export type StepInParallel<TContext = ContextData, I = unknown, O = unknown> = StepInParallelRace<TContext, I, O> | StepInParallelAll<TContext, I, O> | StepInParallelSettle<TContext, I, O>;
|
package/dist/types/tool.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
import type { AcpToolKind } from './acp.js';
|
|
2
3
|
import type { FunctionCallItem, FunctionCallOutputItem, Item, ItemSchemaExtensions, ToolResultExtensionItem } from './items.js';
|
|
3
4
|
import type { InferSchemaOutput } from './schema.js';
|
|
4
5
|
type ToolExecutionResult<O extends StandardSchemaV1> = Promise<InferSchemaOutput<O>> | AsyncGenerator<unknown, InferSchemaOutput<O>>;
|
|
@@ -50,6 +51,33 @@ export interface ToolUiDeclaration<I extends StandardSchemaV1 = StandardSchemaV1
|
|
|
50
51
|
/** Replaces the tool's region when execution throws. */
|
|
51
52
|
error?(err: unknown, args: InferSchemaOutput<I>): UiFragment | null;
|
|
52
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* How a tool call presents in an ACP client's UI when the harness is served
|
|
56
|
+
* as an ACP agent (`toAcpAgent`/`serveAcp` in `@noetic-tools/acp`).
|
|
57
|
+
*
|
|
58
|
+
* Presentation only — it never gates anything. An undeclared tool falls back
|
|
59
|
+
* to kind `other` with its name as the title. Declared as methods (bivariant
|
|
60
|
+
* params) for the same reason as `ToolUiDeclaration`.
|
|
61
|
+
* @public
|
|
62
|
+
*/
|
|
63
|
+
export interface ToolAcpDeclaration<I extends StandardSchemaV1 = StandardSchemaV1> {
|
|
64
|
+
/** ACP tool kind (`read`, `edit`, `execute`, …) for editor rendering. */
|
|
65
|
+
kind?: AcpToolKind;
|
|
66
|
+
/** Human-readable title; a function receives the call's parsed args. */
|
|
67
|
+
title?: string | ToolAcpTitleFn<I>;
|
|
68
|
+
/** File paths the call affects, surfaced as ACP `locations`. */
|
|
69
|
+
locations?(args: InferSchemaOutput<I>): string[];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Bivariant function type for `ToolAcpDeclaration.title` — a union member
|
|
73
|
+
* cannot use method syntax, so the method-bearing object type is indexed to
|
|
74
|
+
* recover the same bivariance `ToolUiDeclaration`'s methods get, keeping a
|
|
75
|
+
* concretely-typed tool assignable to the erased `Tool` form.
|
|
76
|
+
* @public
|
|
77
|
+
*/
|
|
78
|
+
export type ToolAcpTitleFn<I extends StandardSchemaV1 = StandardSchemaV1> = {
|
|
79
|
+
bivariant(args: InferSchemaOutput<I>): string;
|
|
80
|
+
}['bivariant'];
|
|
53
81
|
/**
|
|
54
82
|
* A tool definition that an LLM can invoke during execution.
|
|
55
83
|
*
|
|
@@ -105,5 +133,7 @@ export interface Tool<I extends StandardSchemaV1 = StandardSchemaV1, O extends S
|
|
|
105
133
|
context?: ToolContextDeclaration;
|
|
106
134
|
/** Optional UI declaration — the runtime emits the rendered fragments at call/progress/result points. */
|
|
107
135
|
ui?: ToolUiDeclaration<I, O>;
|
|
136
|
+
/** Optional ACP presentation — how the call renders in an ACP client when the harness is served as an agent. */
|
|
137
|
+
acp?: ToolAcpDeclaration<I>;
|
|
108
138
|
}
|
|
109
139
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noetic-tools/types",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.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",
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@openrouter/agent": "^0.
|
|
44
|
+
"@openrouter/agent": "^0.11.0",
|
|
45
45
|
"bun-types": "latest"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"@openrouter/agent": "^0.
|
|
48
|
+
"@openrouter/agent": "^0.11.0",
|
|
49
49
|
"zod": "^4.0.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependenciesMeta": {
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@openrouter/agent": "^0.
|
|
57
|
+
"@openrouter/agent": "^0.11.0",
|
|
58
58
|
"@standard-schema/spec": "1.1.0",
|
|
59
59
|
"@zed-industries/agent-client-protocol": "^0.4.5",
|
|
60
60
|
"zod": "^4.0.0"
|