@noetic-tools/core 5.5.0 → 5.7.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.
Files changed (39) hide show
  1. package/dist/builders/control-flow-builders.d.ts +6 -4
  2. package/dist/builders/control-flow-builders.js +3 -2
  3. package/dist/builders/decide-builders.d.ts +86 -0
  4. package/dist/builders/decide-builders.js +230 -0
  5. package/dist/builders/dynamic-workflow.js +1 -0
  6. package/dist/builders/workflow-hydrator.js +81 -2
  7. package/dist/conditions/conditions.d.ts +9 -3
  8. package/dist/conditions/conditions.js +84 -25
  9. package/dist/harness/agent-harness.d.ts +18 -3
  10. package/dist/harness/agent-harness.js +63 -6
  11. package/dist/harness/decide-call.d.ts +19 -0
  12. package/dist/harness/decide-call.js +483 -0
  13. package/dist/harness/deps/adapters.d.ts +1 -0
  14. package/dist/harness/deps/adapters.js +1 -0
  15. package/dist/harness/deps/context.d.ts +1 -1
  16. package/dist/harness/deps/context.js +1 -1
  17. package/dist/harness/deps/types.d.ts +1 -1
  18. package/dist/index.d.ts +14 -6
  19. package/dist/index.js +6 -2
  20. package/dist/interpreter/action-deps.d.ts +1 -1
  21. package/dist/interpreter/action-deps.js +1 -1
  22. package/dist/interpreter/action-types.d.ts +1 -1
  23. package/dist/interpreter/collect-tools.js +2 -0
  24. package/dist/interpreter/execute-action.js +3 -20
  25. package/dist/interpreter/execute-control.js +1 -1
  26. package/dist/interpreter/execute-decide.d.ts +10 -0
  27. package/dist/interpreter/execute-decide.js +417 -0
  28. package/dist/interpreter/execute.js +8 -3
  29. package/dist/observability/genai-attributes.d.ts +6 -0
  30. package/dist/observability/genai-attributes.js +6 -0
  31. package/dist/portable/context-layers.d.ts +1 -1
  32. package/dist/portable/context-layers.js +1 -1
  33. package/dist/runtime/context-impl.d.ts +3 -0
  34. package/dist/runtime/context-impl.js +2 -0
  35. package/dist/schemas/workflow.d.ts +1 -1
  36. package/dist/util/status-code.d.ts +2 -0
  37. package/dist/util/status-code.js +7 -0
  38. package/package.json +7 -6
  39. package/schema/noetic-workflow.schema.json +396 -0
@@ -0,0 +1,483 @@
1
+ import { choiceValues, createMessage, DecideOutputError, extractAssistantText, frameworkCast, scoreLevels, } from '@noetic-tools/types';
2
+ import { z } from 'zod';
3
+ import { statusOf } from '../util/status-code.js';
4
+ //#endregion
5
+ //#region Public API
6
+ /**
7
+ * Answer a decide request. Decision models (those whose OpenRouter output
8
+ * modality is `decisions`) answer natively with their own distributions; any
9
+ * other model answers through a structured-output call that estimates them.
10
+ *
11
+ * @internal
12
+ */
13
+ export async function callDecide(request, deps) {
14
+ if (deps.client && (await isDecisionModel(request.model, deps))) {
15
+ return callNative(request, deps.client);
16
+ }
17
+ return callEstimated(request, deps);
18
+ }
19
+ //#endregion
20
+ //#region Model detection
21
+ function isDecisionModel(model, deps) {
22
+ const cached = deps.nativeModels.get(model);
23
+ if (cached) {
24
+ return cached.then((native) => native ?? false);
25
+ }
26
+ const lookup = lookupDecisionModel(model, deps.client);
27
+ deps.nativeModels.set(model, lookup);
28
+ // A failed lookup says nothing about the model, so the next call asks again.
29
+ void lookup.then((native) => {
30
+ if (native === undefined) {
31
+ deps.nativeModels.delete(model);
32
+ }
33
+ });
34
+ return lookup.then((native) => native ?? false);
35
+ }
36
+ /** `undefined` when the lookup itself failed, as opposed to the model not being one. */
37
+ async function lookupDecisionModel(model, client) {
38
+ const slash = model.indexOf('/');
39
+ if (!client || slash <= 0) {
40
+ return false;
41
+ }
42
+ try {
43
+ const { data } = await client.models.get({
44
+ author: model.slice(0, slash),
45
+ slug: model.slice(slash + 1),
46
+ });
47
+ return data.architecture.outputModalities.includes('decisions');
48
+ }
49
+ catch (e) {
50
+ // An unknown model is not a decision model: the estimated path surfaces the
51
+ // provider's own error for it. Anything else may pass, so it is not cached.
52
+ return statusOf(e) === 404 ? false : undefined;
53
+ }
54
+ }
55
+ //#endregion
56
+ //#region Native path
57
+ async function callNative(request, client) {
58
+ const questions = {};
59
+ for (const [id, question] of Object.entries(request.questions)) {
60
+ questions[id] = toNativeQuestion(question);
61
+ }
62
+ const response = await client.alpha.decisions.create({
63
+ decisionsRequest: {
64
+ model: request.model,
65
+ state: toNativeState(request.instructions, request.state),
66
+ questions,
67
+ },
68
+ }, {
69
+ signal: request.signal,
70
+ });
71
+ const answers = {};
72
+ for (const [id, question] of Object.entries(request.questions)) {
73
+ const answer = fromNativeAnswer(question, response.answers[id]);
74
+ if (answer) {
75
+ answers[id] = answer;
76
+ }
77
+ }
78
+ return {
79
+ answers,
80
+ source: 'native',
81
+ modelId: response.model,
82
+ usage: {
83
+ inputTokens: response.usage.inputTokens,
84
+ outputTokens: response.usage.outputTokens,
85
+ },
86
+ cost: response.usage.cost,
87
+ };
88
+ }
89
+ /**
90
+ * The native APIs require `instructions` on every question, while Noetic lets a
91
+ * boolean question carry its meaning in `criteria` alone; and TypeSafe needs
92
+ * both criteria sides or neither.
93
+ */
94
+ function toNativeQuestion(question) {
95
+ switch (question.type) {
96
+ case 'boolean':
97
+ return toNativeBoolean(question);
98
+ case 'choice': {
99
+ const criteria = {};
100
+ for (const value of choiceValues(question.choices)) {
101
+ criteria[value] = describeChoice(question, value) ?? null;
102
+ }
103
+ return {
104
+ type: 'choice',
105
+ instructions: question.instructions ?? 'Pick the option that best fits the state.',
106
+ criteria,
107
+ };
108
+ }
109
+ case 'score':
110
+ return {
111
+ type: 'score',
112
+ instructions: question.instructions ?? 'Place the state on this scale.',
113
+ criteria: scoreLevels(question.levels).map((l) => l.description ? `${l.label}: ${l.description}` : l.label),
114
+ };
115
+ }
116
+ }
117
+ function toNativeBoolean(question) {
118
+ const yes = question.criteria?.true;
119
+ const no = question.criteria?.false;
120
+ if (yes && no) {
121
+ const instructions = question.instructions ?? yes;
122
+ return {
123
+ type: 'noul',
124
+ instructions,
125
+ criteria: {
126
+ true: yes,
127
+ false: no,
128
+ },
129
+ };
130
+ }
131
+ // One side alone cannot be sent as criteria, so it joins the instructions.
132
+ const side = yes ? `Yes means: ${yes}` : no ? `No means: ${no}` : '';
133
+ return {
134
+ type: 'noul',
135
+ instructions: [
136
+ question.instructions,
137
+ side,
138
+ ]
139
+ .filter(Boolean)
140
+ .join(' '),
141
+ };
142
+ }
143
+ function describeChoice(question, value) {
144
+ if (Array.isArray(question.choices)) {
145
+ return undefined;
146
+ }
147
+ return frameworkCast(question.choices)[value];
148
+ }
149
+ /** Named parts become keys; unnamed text and images run in order under `conversation`. */
150
+ function toNativeState(instructions, parts) {
151
+ const state = {};
152
+ if (instructions) {
153
+ state.instructions = instructions;
154
+ }
155
+ const conversation = [];
156
+ for (const part of parts) {
157
+ if (part.type === 'image') {
158
+ conversation.push({
159
+ type: 'image_url',
160
+ image_url: {
161
+ url: part.imageUrl,
162
+ },
163
+ });
164
+ continue;
165
+ }
166
+ if (part.type === 'json') {
167
+ state[part.name] = part.value;
168
+ continue;
169
+ }
170
+ if (part.name) {
171
+ state[part.name] = part.text;
172
+ continue;
173
+ }
174
+ conversation.push(part.text);
175
+ }
176
+ if (conversation.length > 0) {
177
+ state.conversation = conversation;
178
+ }
179
+ return state;
180
+ }
181
+ /**
182
+ * Map one provider answer. Only an explicit `refusal` becomes a `Refusal`; a
183
+ * missing or mistyped answer comes back `undefined` so validation raises
184
+ * `model_parse_error` instead of passing a provider fault off as the model
185
+ * declining.
186
+ */
187
+ function fromNativeAnswer(question, answer) {
188
+ if (isRefusal(answer)) {
189
+ return {
190
+ type: 'refusal',
191
+ };
192
+ }
193
+ if (question.type === 'boolean' && answer?.type === 'noul') {
194
+ return booleanAnswer(question, answer.noul);
195
+ }
196
+ if (question.type === 'choice' && answer?.type === 'choice') {
197
+ const probabilities = everyOutcome(choiceValues(question.choices), answer.probabilities ?? {
198
+ [answer.choice]: 1,
199
+ });
200
+ return {
201
+ type: 'choice',
202
+ value: answer.choice,
203
+ confidence: answer.confidence ?? top(probabilities),
204
+ probabilities,
205
+ };
206
+ }
207
+ if (question.type === 'score' && answer?.type === 'score') {
208
+ const labels = scoreLevels(question.levels).map((l) => l.label);
209
+ const rounded = labels[Math.round(answer.score)] ?? '';
210
+ // Score probabilities arrive keyed by level index ('0', '1', …). Without
211
+ // a distribution, all the weight goes on the level the score rounds to.
212
+ const byIndex = answer.probabilities ?? {
213
+ [String(Math.round(answer.score))]: 1,
214
+ };
215
+ const probabilities = everyOutcome(labels, Object.fromEntries(labels.map((label, i) => [
216
+ label,
217
+ byIndex[String(i)] ?? 0,
218
+ ])));
219
+ return {
220
+ type: 'score',
221
+ value: answer.score,
222
+ level: answer.probabilities ? (argmax(probabilities) ?? rounded) : rounded,
223
+ confidence: answer.confidence ?? top(probabilities),
224
+ probabilities,
225
+ };
226
+ }
227
+ return undefined;
228
+ }
229
+ //#endregion
230
+ //#region Estimated path
231
+ /**
232
+ * Ask a chat model for a probability per outcome, `samples` times, and average.
233
+ * Self-reported probabilities are not calibrated — fit for routing thresholds,
234
+ * not for measurement.
235
+ */
236
+ async function callEstimated(request, deps) {
237
+ const schema = estimateSchema(request.questions);
238
+ const samples = Math.max(1, request.samples ?? 1);
239
+ const settled = await Promise.allSettled(Array.from({
240
+ length: samples,
241
+ }, () => deps.callModel({
242
+ model: request.model,
243
+ instructions: estimateInstructions(request),
244
+ items: [
245
+ stateMessage(request.state),
246
+ ],
247
+ // Averaging only helps when the runs can differ.
248
+ params: {
249
+ temperature: samples > 1 ? 0.7 : 0,
250
+ },
251
+ outputSchema: schema,
252
+ signal: request.signal,
253
+ parentSpan: request.parentSpan,
254
+ })));
255
+ const runs = settled.flatMap((r) => r.status === 'fulfilled'
256
+ ? [
257
+ r.value,
258
+ ]
259
+ : []);
260
+ const failed = settled.find((r) => r.status === 'rejected');
261
+ if (runs.length === 0 && failed) {
262
+ throw failed.reason;
263
+ }
264
+ const usage = {
265
+ inputTokens: sum(runs.map((r) => r.usage.inputTokens)),
266
+ outputTokens: sum(runs.map((r) => r.usage.outputTokens)),
267
+ };
268
+ const cost = runs.some((r) => r.cost !== undefined)
269
+ ? sum(runs.map((r) => r.cost ?? 0))
270
+ : undefined;
271
+ // Samples run hot, so one bad reply should cost only itself; fail only when none parse.
272
+ const results = runs.map((run) => parseEstimate(schema, extractAssistantText(run.items)));
273
+ const parsed = results.filter((r) => !(r instanceof DecideOutputError));
274
+ const firstError = results.find((r) => r instanceof DecideOutputError);
275
+ if (parsed.length === 0 && firstError) {
276
+ throw new DecideOutputError({
277
+ raw: firstError.raw,
278
+ schema: firstError.schema,
279
+ zodError: firstError.zodError,
280
+ usage,
281
+ cost,
282
+ });
283
+ }
284
+ const answers = {};
285
+ for (const [id, question] of Object.entries(request.questions)) {
286
+ answers[id] = averageAnswer(question, parsed.map((p) => p[id]));
287
+ }
288
+ return {
289
+ answers,
290
+ source: 'estimated',
291
+ modelId: request.model,
292
+ usage,
293
+ cost,
294
+ };
295
+ }
296
+ function parseEstimate(schema, raw) {
297
+ let json;
298
+ try {
299
+ json = JSON.parse(raw);
300
+ }
301
+ catch {
302
+ json = undefined;
303
+ }
304
+ const result = schema.safeParse(json);
305
+ if (!result.success) {
306
+ return new DecideOutputError({
307
+ raw,
308
+ schema,
309
+ zodError: result.error,
310
+ });
311
+ }
312
+ return result.data;
313
+ }
314
+ function estimateSchema(questions) {
315
+ const shape = {};
316
+ for (const [id, question] of Object.entries(questions)) {
317
+ const keys = outcomeKeys(question);
318
+ shape[id] = z.object(Object.fromEntries(keys.map((k) => [
319
+ k,
320
+ z.number().min(0).max(1),
321
+ ])));
322
+ }
323
+ return frameworkCast(z.object(shape));
324
+ }
325
+ /** Booleans estimate `{ yes }`; choices and scores estimate one probability per outcome. */
326
+ function outcomeKeys(question) {
327
+ switch (question.type) {
328
+ case 'boolean':
329
+ return [
330
+ 'yes',
331
+ ];
332
+ case 'choice':
333
+ return choiceValues(question.choices);
334
+ case 'score':
335
+ return scoreLevels(question.levels).map((l) => l.label);
336
+ }
337
+ }
338
+ function estimateInstructions(request) {
339
+ const lines = [
340
+ 'You answer questions about the state you are given. For each question, return a probability for every outcome.',
341
+ 'For a yes/no question, return the probability of yes. For other questions, the probabilities should sum to 1.',
342
+ ];
343
+ if (request.instructions) {
344
+ lines.push('', request.instructions);
345
+ }
346
+ lines.push('', 'Questions:');
347
+ for (const [id, question] of Object.entries(request.questions)) {
348
+ lines.push(`- ${id}: ${describeQuestion(question)}`);
349
+ }
350
+ return lines.join('\n');
351
+ }
352
+ function describeQuestion(question) {
353
+ const ask = question.instructions ?? '';
354
+ switch (question.type) {
355
+ case 'boolean': {
356
+ const yes = question.criteria?.true ? ` Yes means: ${question.criteria.true}.` : '';
357
+ const no = question.criteria?.false ? ` No means: ${question.criteria.false}.` : '';
358
+ return `(yes/no) ${ask}${yes}${no}`.trim();
359
+ }
360
+ case 'choice': {
361
+ const options = choiceValues(question.choices).map((v) => {
362
+ const description = describeChoice(question, v);
363
+ return description ? `${v} (${description})` : v;
364
+ });
365
+ return `(one of: ${options.join('; ')}) ${ask}`.trim();
366
+ }
367
+ case 'score':
368
+ return `(scale, lowest first: ${describeLevels(question)}) ${ask}`.trim();
369
+ }
370
+ }
371
+ function describeLevels(question) {
372
+ return scoreLevels(question.levels)
373
+ .map((l) => (l.description ? `${l.label} (${l.description})` : l.label))
374
+ .join('; ');
375
+ }
376
+ function stateMessage(parts) {
377
+ const content = parts.map((part) => {
378
+ if (part.type === 'image') {
379
+ return {
380
+ type: 'input_image',
381
+ imageUrl: part.imageUrl,
382
+ };
383
+ }
384
+ if (part.type === 'json') {
385
+ return {
386
+ type: 'input_text',
387
+ text: `${part.name}: ${JSON.stringify(part.value)}`,
388
+ };
389
+ }
390
+ return {
391
+ type: 'input_text',
392
+ text: part.name ? `${part.name}: ${part.text}` : part.text,
393
+ };
394
+ });
395
+ return {
396
+ ...createMessage('', 'user'),
397
+ content,
398
+ };
399
+ }
400
+ function averageAnswer(question, samples) {
401
+ const present = samples.filter((s) => s !== undefined);
402
+ if (present.length === 0) {
403
+ return {
404
+ type: 'refusal',
405
+ };
406
+ }
407
+ const keys = outcomeKeys(question);
408
+ const mean = {};
409
+ for (const key of keys) {
410
+ mean[key] = sum(present.map((s) => s[key] ?? 0)) / present.length;
411
+ }
412
+ if (question.type === 'boolean') {
413
+ return booleanAnswer(question, mean.yes ?? 0);
414
+ }
415
+ const probabilities = normalize(mean);
416
+ const best = argmax(probabilities) ?? keys[0] ?? '';
417
+ if (question.type === 'choice') {
418
+ return {
419
+ type: 'choice',
420
+ value: best,
421
+ confidence: top(probabilities),
422
+ probabilities,
423
+ };
424
+ }
425
+ return {
426
+ type: 'score',
427
+ value: sum(keys.map((k, i) => i * (probabilities[k] ?? 0))),
428
+ level: best,
429
+ confidence: top(probabilities),
430
+ probabilities,
431
+ };
432
+ }
433
+ //#endregion
434
+ //#region Helpers
435
+ function booleanAnswer(question, probability) {
436
+ return {
437
+ type: 'boolean',
438
+ value: probability >= (question.threshold ?? 0.5),
439
+ probability,
440
+ };
441
+ }
442
+ /** The SDK surfaces answer types it does not model as `UNKNOWN`, keeping the payload in `raw`. */
443
+ function isRefusal(answer) {
444
+ if (answer?.type !== 'UNKNOWN' || !('raw' in answer)) {
445
+ return false;
446
+ }
447
+ const raw = answer.raw;
448
+ return typeof raw === 'object' && raw !== null && 'type' in raw && raw.type === 'refusal';
449
+ }
450
+ /** One entry per outcome, so a caller can index any choice or level; missing ones read 0. */
451
+ function everyOutcome(outcomes, given) {
452
+ return Object.fromEntries(outcomes.map((o) => [
453
+ o,
454
+ Object.hasOwn(given, o) ? (given[o] ?? 0) : 0,
455
+ ]));
456
+ }
457
+ function normalize(probabilities) {
458
+ const keys = Object.keys(probabilities);
459
+ const total = sum(Object.values(probabilities));
460
+ const out = {};
461
+ for (const key of keys) {
462
+ out[key] = total > 0 ? (probabilities[key] ?? 0) / total : 1 / keys.length;
463
+ }
464
+ return out;
465
+ }
466
+ function argmax(probabilities) {
467
+ let best;
468
+ let bestP = -1;
469
+ for (const [key, p] of Object.entries(probabilities)) {
470
+ if (p > bestP) {
471
+ best = key;
472
+ bestP = p;
473
+ }
474
+ }
475
+ return best;
476
+ }
477
+ function top(probabilities) {
478
+ return Math.max(0, ...Object.values(probabilities));
479
+ }
480
+ function sum(values) {
481
+ return values.reduce((a, b) => a + b, 0);
482
+ }
483
+ //#endregion
@@ -1,4 +1,5 @@
1
1
  export { OpenRouter } from '@openrouter/agent';
2
+ export { OpenRouter as OpenRouterSdk } from '@openrouter/sdk';
2
3
  export { createInMemoryFsAdapter } from '../../adapters/in-memory-fs-adapter.js';
3
4
  export { createInMemoryShellAdapter } from '../../adapters/in-memory-shell-adapter.js';
4
5
  export { createInMemorySubprocessAdapter } from '../../adapters/in-memory-subprocess-adapter.js';
@@ -1,4 +1,5 @@
1
1
  export { OpenRouter } from '@openrouter/agent';
2
+ export { OpenRouter as OpenRouterSdk } from '@openrouter/sdk';
2
3
  export { createInMemoryFsAdapter } from '../../adapters/in-memory-fs-adapter.js';
3
4
  export { createInMemoryShellAdapter } from '../../adapters/in-memory-shell-adapter.js';
4
5
  export { createInMemorySubprocessAdapter } from '../../adapters/in-memory-subprocess-adapter.js';
@@ -1,2 +1,2 @@
1
1
  export type { LayerStateStore, RecallCache } from '@noetic-tools/context';
2
- export { afterModelCallLayers, allocateBudgets, assembleView, beforeToolCallLayers, completeLayers, contextToExecCtx, createContextCacheStore, createLayerStateStore, createRecallCache, DEFAULT_PROJECTION, disposeLayers, executeRerender, initLayers, projectHistoryLayers, recallLayers, recallLayersAtomic, recallLayersEventual, resolveLayerTools, runAppendPipeline, storeLayers, } from '@noetic-tools/context';
2
+ export { afterModelCallLayers, allocateBudgets, assembleView, beforeToolCallLayers, completeLayers, contextToExecCtx, createContextCacheStore, createLayerStateStore, createRecallCache, DEFAULT_PROJECTION, disposeLayers, executeRerender, initLayers, notifyDecision, projectHistoryLayers, recallLayers, recallLayersAtomic, recallLayersEventual, resolveLayerTools, runAppendPipeline, storeLayers, } from '@noetic-tools/context';
@@ -1 +1 @@
1
- export { afterModelCallLayers, allocateBudgets, assembleView, beforeToolCallLayers, completeLayers, contextToExecCtx, createContextCacheStore, createLayerStateStore, createRecallCache, DEFAULT_PROJECTION, disposeLayers, executeRerender, initLayers, projectHistoryLayers, recallLayers, recallLayersAtomic, recallLayersEventual, resolveLayerTools, runAppendPipeline, storeLayers, } from '@noetic-tools/context';
1
+ export { afterModelCallLayers, allocateBudgets, assembleView, beforeToolCallLayers, completeLayers, contextToExecCtx, createContextCacheStore, createLayerStateStore, createRecallCache, DEFAULT_PROJECTION, disposeLayers, executeRerender, initLayers, notifyDecision, projectHistoryLayers, recallLayers, recallLayersAtomic, recallLayersEventual, resolveLayerTools, runAppendPipeline, storeLayers, } from '@noetic-tools/context';
@@ -1,4 +1,4 @@
1
1
  export type { ContextCacheConfig, ContextCacheStore, ContextData, ContextLayer, ExecutionContext, ProjectionPolicy, StorageAdapter, } from '@noetic-tools/context';
2
- export type { AcpKeepAlive, AcpLiveSession, AcpSessionInfo, AgentConfig, AgentHarnessContract, AgentHooks, CallModelRequest, Channel, ChannelHandle, Context, CwdState, DeliveryMode, DetachedHandle, ExecuteInput, ExecuteOptions, ExternalChannel, FsAdapter, HarnessResponse, HarnessStatus, Item, ItemSchemaConfig, ItemSchemaExtensions, LLMResponse, LlmProviderConfig, RecallLayerOutput, SessionScope, SessionUsage, ShellAdapter, Span, SteeringDecision, Step, StreamEvent, StreamingItem, SubprocessAdapter, Tool, TraceExporter, } from '@noetic-tools/types';
2
+ export type { AcpKeepAlive, AcpLiveSession, AcpSessionInfo, AgentConfig, AgentHarnessContract, AgentHooks, CallModelRequest, Channel, ChannelHandle, Context, CwdState, DecideRequest, DecideResponse, DecisionRecord, DeliveryMode, DetachedHandle, ExecuteInput, ExecuteOptions, ExternalChannel, FsAdapter, HarnessResponse, HarnessStatus, Item, ItemSchemaConfig, ItemSchemaExtensions, LLMResponse, LlmProviderConfig, RecallLayerOutput, RecallOptions, SessionScope, SessionUsage, ShellAdapter, Span, SteeringDecision, Step, StreamEvent, StreamingItem, SubprocessAdapter, Tool, TraceExporter, } from '@noetic-tools/types';
3
3
  export { SteeringAction } from '@noetic-tools/types';
4
4
  export type { ZodType } from 'zod';
package/dist/index.d.ts CHANGED
@@ -17,6 +17,8 @@ export { context } from './builders/context-builder.js';
17
17
  /** @public */
18
18
  export { conditional, inParallel } from './builders/control-flow-builders.js';
19
19
  /** @public */
20
+ export { decide, routeSwitch } from './builders/decide-builders.js';
21
+ /** @public */
20
22
  export type { ScheduleOptions } from './builders/every.js';
21
23
  /** @public */
22
24
  export { schedule } from './builders/every.js';
@@ -65,7 +67,7 @@ export { execute } from './interpreter/execute.js';
65
67
  /** @public */
66
68
  /** @public */
67
69
  /** @public */
68
- export type { FactExtractor, FactSearcher, HistoryConfig, ObservationsConfig, ObservationsState, PlanConfig, PlanEnterSessionCallback, PlanExecutionEntry, PlanExitCallback, PlanState, ScratchpadConfig, ScratchpadState, TaskState, TemporalConfig, TemporalFact, TemporalSearchResult, } from '@noetic-tools/context';
70
+ export type { DecisionsHistoryConfig, DecisionsHistoryLayer, DecisionsHistoryState, FactExtractor, FactSearcher, HistoryConfig, ObservationsConfig, ObservationsState, PlanConfig, PlanEnterSessionCallback, PlanExecutionEntry, PlanExitCallback, PlanState, ScratchpadConfig, ScratchpadState, TaskState, TemporalConfig, TemporalFact, TemporalSearchResult, } from '@noetic-tools/context';
69
71
  /** @public */
70
72
  /** @public */
71
73
  /** @public */
@@ -78,9 +80,9 @@ export type { FactExtractor, FactSearcher, HistoryConfig, ObservationsConfig, Ob
78
80
  /** @public */
79
81
  /** @public */
80
82
  /** @public */
81
- export { filesystem, findFunctionCall, history, instructions, observations, PlanPhase, PlanStyle, plan, scratchpad, steering, storageGetMany, stripUnresolvedToolCalls, taskState, temporal, toolCalls, } from '@noetic-tools/context';
83
+ export { DECISION_PROJECTION, decisionsHistoryLayer, filesystem, findFunctionCall, history, instructions, observations, PlanPhase, PlanStyle, plan, scratchpad, steering, storageGetMany, stripUnresolvedToolCalls, taskState, temporal, toolCalls, } from '@noetic-tools/context';
82
84
  /** @public */
83
- export { createMessage, estimateTokens } from '@noetic-tools/types';
85
+ export { createMessage, estimateTokens, recordUsage } from '@noetic-tools/types';
84
86
  /** @public */
85
87
  export { GenAI, NoeticAttr, ToolAttr } from './observability/genai-attributes.js';
86
88
  /** @public */
@@ -104,14 +106,14 @@ export { getRegistry, lookupStep, registerStep } from './runtime/step-registry.j
104
106
  /** @public */
105
107
  export { defaultItemSchemaRegistry, ItemSchema, ItemSchemaRegistry } from '@noetic-tools/types';
106
108
  /** @public */
107
- export type { AcpAgentWorkflowNode, CallModelWorkflowNode, ConditionalRoute, ConditionalWorkflowNode, InParallelWorkflowNode, InvokeToolWorkflowNode, LoopWorkflowNode, MergeStrategy, ScheduleWorkflowNode, SequenceWorkflowNode, SpawnWorkflowNode, SubflowWorkflowNode, UntilPredicate, WithContextWorkflowNode, WorkflowDocument, WorkflowNode, } from './schemas/workflow.js';
109
+ export type { AcpAgentWorkflowNode, CallModelWorkflowNode, ConditionalRoute, ConditionalWorkflowNode, DecideWorkflowNode, InParallelWorkflowNode, InvokeToolWorkflowNode, LoopWorkflowNode, MergeStrategy, RouteSwitchWorkflowNode, ScheduleWorkflowNode, SequenceWorkflowNode, SpawnWorkflowNode, SubflowWorkflowNode, UntilPredicate, WithContextWorkflowNode, WorkflowDocument, WorkflowNode, } from './schemas/workflow.js';
108
110
  /** @public */
109
111
  export { MergeStrategySchema, UntilPredicateSchema, validateWorkflow, WorkflowDocumentSchema, WorkflowNodeSchema, walkWorkflow, workflowDepth, } from './schemas/workflow.js';
110
112
  /** @public */
111
113
  export type { Channel, ChannelHandle, ExternalChannel } from '@noetic-tools/types';
112
114
  /** @public */
113
115
  /** @public */
114
- export type { InferSchemaInput, InferSchemaOutput, InputSchemaConfig, LLMResponse, LlmProviderConfig, ModelParams, RetryPolicy, RoundUsage, SchemaValidationFailure, SchemaValidationResult, SchemaValidationSuccess, ServerToolSpec, StandardJSONSchemaV1, StandardSchemaV1, StepMeta, TokenUsage, Tool, ToolContextDeclaration, } from '@noetic-tools/types';
116
+ export type { InferSchemaInput, InferSchemaOutput, InputSchemaConfig, LLMResponse, LlmProviderConfig, ModelParams, RetryPolicy, RoundUsage, SchemaValidationFailure, SchemaValidationResult, SchemaValidationSuccess, ServerToolSpec, StandardJSONSchemaV1, StandardSchemaV1, StepMeta, TokenUsage, Tool, ToolContextDeclaration, UsageCharge, } from '@noetic-tools/types';
115
117
  /** @public */
116
118
  export { isServerToolSpec, isStandardJsonSchema, isZodSchema, standardIssuesToZodError, validateSchema, } from '@noetic-tools/types';
117
119
  /** @public */
@@ -145,7 +147,7 @@ export type { LayerTraceSpan, Span, TraceExporter } from '@noetic-tools/types';
145
147
  /** @public */
146
148
  export type { FrameworkStreamEvent, HarnessResponse, SdkStreamEvent, StreamEvent, StreamingItem, } from '@noetic-tools/types';
147
149
  /** @public */
148
- export type { AgentConfig, AgentHarnessContract, AgentHooks, CallModelRequest, ContextCacheConfig, ContextCacheStore, ContextEpoch, DeliveryMode, ExecuteOptions, HarnessStatus, ItemSchemaConfig, ReanchorReason, RecallLayerOutput, SessionScope, SessionUsage, } from '@noetic-tools/types';
150
+ export type { AgentConfig, AgentHarnessContract, AgentHooks, CallModelRequest, ContextCacheConfig, ContextCacheStore, ContextEpoch, DeliveryMode, ExecuteOptions, HarnessJudgmentContract, HarnessStatus, ItemSchemaConfig, ReanchorReason, RecallLayerOutput, SessionScope, SessionUsage, } from '@noetic-tools/types';
149
151
  /** @public */
150
152
  export type { AfterModelCallParams, AfterModelCallResult, BeforeToolCallParams, BeforeToolCallResult, LedgerEntry, SteeringConfig, SteeringDecision, SteeringRule, SteeringState, } from '@noetic-tools/types';
151
153
  /** @public */
@@ -153,6 +155,12 @@ export { LedgerEntryKind, SteeringAction } from '@noetic-tools/types';
153
155
  /** @public */
154
156
  export type { ExecuteStepFn, SettleResult, Snapshot, Step, StepAcpAgent, StepCallModel, StepConditional, StepInParallel, StepInParallelAll, StepInParallelRace, StepInParallelSettle, StepInvokeTool, StepLoop, StepRunCode, StepSpawn, StepWithContext, Until, Verdict, } from '@noetic-tools/types';
155
157
  /** @public */
158
+ export type { AnsweredAll, AnswerFor, BooleanAnswer, BooleanQuestion, CaseKeyOf, ChoiceAnswer, ChoiceQuestion, ChoiceValues, DecideAnswer, DecideAnswers, DecideQuestion, DecideQuestions, DecideRequest, DecideResponse, DecideStatePart, DecideStep, DecideView, DecisionMeta, DecisionRecord, DecisionSource, LevelLabels, OnDecisionParams, RecallOptions, ScoreAnswer, ScoreLevel, ScoreQuestion, StepDecide, } from '@noetic-tools/types';
159
+ /** @public */
160
+ export { BooleanQuestionSchema, ChoiceQuestionSchema, choiceValues, DECIDE_LIMITS, DecideOutputError, DecideQuestionSchema, DecideQuestionsSchema, routeOutcomes, ScoreQuestionSchema, scoreLevels, } from '@noetic-tools/types';
161
+ /** @public */
162
+ export type { RouteSwitchByFunctionOpts, RouteSwitchByKeyOpts, StepDecideOpts, StepInput, StepOutput, } from './builders/decide-builders.js';
163
+ /** @public */
156
164
  export type { AcpAgent, AcpAgentCapabilities, AcpAgentConnection, AcpAuthMethod, AcpAvailableCommand, AcpBoundPermissionHandler, AcpClientCapabilityConfig, AcpClientHost, AcpConnectOptions, AcpContentBlock, AcpKeepAlive, AcpLiveSession, AcpLoadSessionOptions, AcpMcpServer, AcpNewSessionOptions, AcpPermissionHandler, AcpPermissionOption, AcpPermissionOutcome, AcpPermissionPolicy, AcpPermissionRequestInfo, AcpPermissionRule, AcpPermissionSteerer, AcpPlanEntry, AcpPromptCapabilities, AcpPromptOptions, AcpRequestPermissionRequest, AcpSession, AcpSessionDisposer, AcpSessionInfo, AcpSessionMode, AcpSessionModeState, AcpSessionNotification, AcpSessionPolicy, AcpStopReason, AcpToolCallContent, AcpToolCallStatus, AcpToolKind, AcpTransport, AcpTransportFactory, AcpTransportOptions, AcpTurnResult, } from '@noetic-tools/types';
157
165
  /** @public */
158
166
  export { ACP_AGENT_STEP_KIND, AcpCapabilityError, AcpConnectError, AcpPermissionDecision, isAcpCapabilityError, isAcpConnectError, } from '@noetic-tools/types';
package/dist/index.js CHANGED
@@ -16,6 +16,8 @@ export { context } from './builders/context-builder.js';
16
16
  /** @public */
17
17
  export { conditional, inParallel } from './builders/control-flow-builders.js';
18
18
  /** @public */
19
+ export { decide, routeSwitch } from './builders/decide-builders.js';
20
+ /** @public */
19
21
  export { schedule } from './builders/every.js';
20
22
  /** @public */
21
23
  export { layerData, layerFunction } from './builders/layer-provides-builders.js';
@@ -60,11 +62,11 @@ export { execute } from './interpreter/execute.js';
60
62
  /** @public */
61
63
  /** @public */
62
64
  /** @public */
63
- export { filesystem, findFunctionCall, history, instructions, observations, PlanPhase, PlanStyle, plan, scratchpad, steering, storageGetMany, stripUnresolvedToolCalls, taskState, temporal, toolCalls, } from '@noetic-tools/context';
65
+ export { DECISION_PROJECTION, decisionsHistoryLayer, filesystem, findFunctionCall, history, instructions, observations, PlanPhase, PlanStyle, plan, scratchpad, steering, storageGetMany, stripUnresolvedToolCalls, taskState, temporal, toolCalls, } from '@noetic-tools/context';
64
66
  //#endregion
65
67
  //#region Message Utilities
66
68
  /** @public */
67
- export { createMessage, estimateTokens } from '@noetic-tools/types';
69
+ export { createMessage, estimateTokens, recordUsage } from '@noetic-tools/types';
68
70
  //#endregion
69
71
  //#region Observability
70
72
  /** @public */
@@ -100,6 +102,8 @@ export { Slot } from '@noetic-tools/context';
100
102
  /** @public */
101
103
  export { LedgerEntryKind, SteeringAction } from '@noetic-tools/types';
102
104
  /** @public */
105
+ export { BooleanQuestionSchema, ChoiceQuestionSchema, choiceValues, DECIDE_LIMITS, DecideOutputError, DecideQuestionSchema, DecideQuestionsSchema, routeOutcomes, ScoreQuestionSchema, scoreLevels, } from '@noetic-tools/types';
106
+ /** @public */
103
107
  export { ACP_AGENT_STEP_KIND, AcpCapabilityError, AcpConnectError, AcpPermissionDecision, isAcpCapabilityError, isAcpConnectError, } from '@noetic-tools/types';
104
108
  /** @public */
105
109
  export { TIMEOUT_ERROR_PREFIX } from '@noetic-tools/types';
@@ -1,5 +1,5 @@
1
1
  export type { LayerServeInfo, LayerStateStore } from '@noetic-tools/context';
2
- export { allocateBudgets, assembleView, churnFor, churnRate, commitLayerUsage, computeLayerUsage, contextToExecCtx, createContextCacheStore, DEFAULT_PROJECTION, lineageKey, noteCacheOutcome, openEpoch, pin, pinKey, reanchorReason, resolveCacheConfig, resolveLayerTools, returnLayers, spawnLayers, } from '@noetic-tools/context';
2
+ export { allocateBudgets, assembleView, churnFor, churnRate, commitLayerUsage, computeLayerUsage, contextToExecCtx, createContextCacheStore, DECISION_PROJECTION, DEFAULT_PROJECTION, lineageKey, noteCacheOutcome, openEpoch, pin, pinKey, reanchorReason, resolveCacheConfig, resolveLayerTools, returnLayers, spawnLayers, } from '@noetic-tools/context';
3
3
  export type { ItemSchemaRegistry } from '@noetic-tools/types';
4
4
  export { defaultItemSchemaRegistry } from '@noetic-tools/types';
5
5
  export { emitFrameworkEvent, getBroadcaster, shouldEmit } from '../runtime/broadcaster-utils.js';
@@ -1,4 +1,4 @@
1
- export { allocateBudgets, assembleView, churnFor, churnRate, commitLayerUsage, computeLayerUsage, contextToExecCtx, createContextCacheStore, DEFAULT_PROJECTION, lineageKey, noteCacheOutcome, openEpoch, pin, pinKey, reanchorReason, resolveCacheConfig, resolveLayerTools, returnLayers, spawnLayers, } from '@noetic-tools/context';
1
+ export { allocateBudgets, assembleView, churnFor, churnRate, commitLayerUsage, computeLayerUsage, contextToExecCtx, createContextCacheStore, DECISION_PROJECTION, DEFAULT_PROJECTION, lineageKey, noteCacheOutcome, openEpoch, pin, pinKey, reanchorReason, resolveCacheConfig, resolveLayerTools, returnLayers, spawnLayers, } from '@noetic-tools/context';
2
2
  export { defaultItemSchemaRegistry } from '@noetic-tools/types';
3
3
  export { emitFrameworkEvent, getBroadcaster, shouldEmit } from '../runtime/broadcaster-utils.js';
4
4
  export { ContextImpl } from '../runtime/context-impl.js';
@@ -1,3 +1,3 @@
1
1
  export type { ContextConfig, ContextData, ContextLayer, ExecutionContext, } from '@noetic-tools/context';
2
- export type { AgentHarnessContract, CallModelRequest, Context, ContextRerenderRequest, ExecuteStepFn, FunctionCallItem, InputMessageItem, Item, LLMResponse, ProjectionPolicy, RecallLayerOutput, RetryPolicy, ServerToolSpec, StepCallModel, StepInvokeTool, StepMeta, StepRunCode, StepSpawn, StepWithContext, Tool, } from '@noetic-tools/types';
2
+ export type { AgentHarnessContract, CallModelRequest, Context, ContextRerenderRequest, ExecuteStepFn, FunctionCallItem, InputMessageItem, Item, LLMResponse, ProjectionPolicy, RecallLayerOutput, RetryPolicy, ServerToolSpec, StepCallModel, StepDecide, StepInvokeTool, StepMeta, StepRunCode, StepSpawn, StepWithContext, Tool, } from '@noetic-tools/types';
3
3
  export { isServerToolSpec, SteeringAction } from '@noetic-tools/types';
@@ -43,6 +43,8 @@ function walkStep(step, out) {
43
43
  return;
44
44
  case 'runCode':
45
45
  case 'invokeTool':
46
+ // Decide steps ask questions; they never call tools.
47
+ case 'decide':
46
48
  // An ACP agent runs its own tools inside the external agent process; it
47
49
  // contributes nothing to the host unified tool set.
48
50
  case 'acp-agent':