@noetic-tools/core 0.0.1 → 0.1.1
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/README.md +31 -11
- package/dist/adapters/in-memory-fs-adapter.d.ts +1 -1
- package/dist/adapters/in-memory-fs-adapter.js +1 -1
- package/dist/adapters/in-memory-shell-adapter.d.ts +1 -1
- package/dist/adapters/in-memory-subprocess/manifest-persistence.d.ts +8 -0
- package/dist/adapters/in-memory-subprocess/manifest-persistence.js +56 -0
- package/dist/adapters/in-memory-subprocess/metadata.d.ts +12 -0
- package/dist/adapters/in-memory-subprocess/metadata.js +58 -0
- package/dist/adapters/in-memory-subprocess/step-completion.d.ts +3 -0
- package/dist/adapters/in-memory-subprocess/step-completion.js +86 -0
- package/dist/adapters/in-memory-subprocess/types.d.ts +88 -0
- package/dist/adapters/in-memory-subprocess/types.js +2 -0
- package/dist/adapters/in-memory-subprocess-adapter.d.ts +12 -4
- package/dist/adapters/in-memory-subprocess-adapter.js +164 -55
- package/dist/adapters/openrouter.d.ts +16 -7
- package/dist/adapters/openrouter.js +68 -7
- package/dist/builders/channel-builder.d.ts +1 -1
- package/dist/builders/control-flow-builders.d.ts +3 -3
- package/dist/builders/control-flow-builders.js +9 -4
- package/dist/builders/every.d.ts +3 -3
- package/dist/builders/every.js +5 -2
- package/dist/builders/layer-provides-builders.d.ts +1 -32
- package/dist/builders/layer-provides-builders.js +1 -33
- package/dist/builders/loop-builder.d.ts +2 -2
- package/dist/builders/loop-builder.js +5 -2
- package/dist/builders/memory-builder.d.ts +1 -1
- package/dist/builders/memory-builder.js +1 -1
- package/dist/builders/provide-builder.d.ts +2 -2
- package/dist/builders/provide-builder.js +5 -2
- package/dist/builders/spawn-builder.d.ts +22 -9
- package/dist/builders/spawn-builder.js +17 -3
- package/dist/builders/step-builders.d.ts +51 -30
- package/dist/builders/step-builders.js +31 -12
- package/dist/builders/tool-builder.d.ts +7 -3
- package/dist/builders/tool-builder.js +3 -1
- package/dist/builders/workflow-hydrator.d.ts +38 -0
- package/dist/builders/workflow-hydrator.js +336 -0
- package/dist/conditions/conditions.d.ts +4 -4
- package/dist/conditions/conditions.js +2 -2
- package/dist/errors/noetic-error.d.ts +1 -1
- package/dist/errors/noetic-error.js +2 -0
- package/dist/{runtime → harness}/agent-harness.d.ts +65 -45
- package/dist/harness/agent-harness.js +576 -0
- package/dist/harness/deps/adapters.d.ts +4 -0
- package/dist/harness/deps/adapters.js +4 -0
- package/dist/harness/deps/interpreter.d.ts +5 -0
- package/dist/harness/deps/interpreter.js +4 -0
- package/dist/harness/deps/memory.d.ts +5 -0
- package/dist/harness/deps/memory.js +4 -0
- package/dist/harness/deps/runtime.d.ts +10 -0
- package/dist/harness/deps/runtime.js +7 -0
- package/dist/harness/deps/types.d.ts +17 -0
- package/dist/harness/deps/types.js +1 -0
- package/dist/harness/model-call.d.ts +50 -0
- package/dist/harness/model-call.js +694 -0
- package/dist/harness/model-schema.d.ts +19 -0
- package/dist/harness/model-schema.js +46 -0
- package/dist/harness/step-bootstrap.d.ts +25 -0
- package/dist/harness/step-bootstrap.js +170 -0
- package/dist/index.d.ts +114 -76
- package/dist/index.js +74 -50
- package/dist/internal-test.d.ts +4 -4
- package/dist/internal-test.js +4 -4
- package/dist/interpreter/action-deps.d.ts +12 -0
- package/dist/interpreter/action-deps.js +10 -0
- package/dist/interpreter/action-types.d.ts +8 -0
- package/dist/interpreter/action-types.js +1 -0
- package/dist/interpreter/collect-tools.d.ts +2 -2
- package/dist/interpreter/collect-tools.js +6 -2
- package/dist/interpreter/execute-action.d.ts +22 -0
- package/dist/interpreter/execute-action.js +527 -0
- package/dist/interpreter/{execute-every.d.ts → execute-control.d.ts} +8 -2
- package/dist/interpreter/execute-control.js +530 -0
- package/dist/interpreter/execute.d.ts +16 -3
- package/dist/interpreter/execute.js +154 -51
- package/dist/interpreter/message-helpers.d.ts +1 -9
- package/dist/interpreter/message-helpers.js +1 -42
- package/dist/interpreter/step-dispatcher.d.ts +61 -0
- package/dist/interpreter/step-dispatcher.js +67 -0
- package/dist/interpreter/typeguards.d.ts +8 -15
- package/dist/interpreter/typeguards.js +4 -21
- package/dist/memory/budget.d.ts +2 -2
- package/dist/memory/budget.js +1 -1
- package/dist/{runtime → memory}/exec-context-factory.d.ts +2 -2
- package/dist/{runtime → memory}/exec-context-factory.js +1 -1
- package/dist/memory/flow-schema.d.ts +52 -0
- package/dist/memory/flow-schema.js +102 -0
- package/dist/memory/function-call-utils.d.ts +1 -1
- package/dist/memory/function-call-utils.js +4 -2
- package/dist/memory/layer-api.d.ts +4 -4
- package/dist/memory/layer-api.js +2 -2
- package/dist/memory/layer-lifecycle.d.ts +6 -6
- package/dist/memory/layer-lifecycle.js +6 -6
- package/dist/memory/layer-provides.d.ts +32 -0
- package/dist/memory/layer-provides.js +33 -0
- package/dist/memory/layer-usage.d.ts +4 -3
- package/dist/memory/layer-usage.js +9 -3
- package/dist/memory/layers/durable-task-state.d.ts +8 -8
- package/dist/memory/layers/durable-task-state.js +2 -2
- package/dist/memory/layers/file-reference.d.ts +1 -1
- package/dist/memory/layers/file-reference.js +303 -247
- package/dist/memory/layers/history-window.d.ts +1 -1
- package/dist/memory/layers/history-window.js +4 -4
- package/dist/memory/layers/observational-memory.d.ts +5 -5
- package/dist/memory/layers/observational-memory.js +2 -3
- package/dist/memory/layers/plan.d.ts +2 -2
- package/dist/memory/layers/plan.js +15 -10
- package/dist/memory/layers/static-content.d.ts +2 -2
- package/dist/memory/layers/static-content.js +1 -1
- package/dist/memory/layers/steering.d.ts +7 -7
- package/dist/memory/layers/steering.js +5 -5
- package/dist/memory/layers/tool-memory-layer.d.ts +2 -2
- package/dist/memory/layers/tool-memory-layer.js +1 -1
- package/dist/memory/layers/working-memory.d.ts +7 -7
- package/dist/memory/layers/working-memory.js +4 -4
- package/dist/memory/projector.d.ts +2 -2
- package/dist/memory/scope.d.ts +1 -1
- package/dist/memory/strip-unresolved.d.ts +1 -1
- package/dist/observability/span-impl.d.ts +1 -1
- package/dist/observability/trace-exporter.d.ts +2 -2
- package/dist/observability/trace-exporter.js +1 -1
- package/dist/patterns/dynamic-workflow.d.ts +55 -0
- package/dist/patterns/dynamic-workflow.js +194 -0
- package/dist/patterns/flow.d.ts +2 -52
- package/dist/patterns/flow.js +1 -103
- package/dist/patterns/interview.d.ts +2 -2
- package/dist/patterns/interview.js +5 -5
- package/dist/patterns/plans.d.ts +6 -6
- package/dist/patterns/plans.js +4 -4
- package/dist/patterns/ralph-wiggum.d.ts +4 -4
- package/dist/patterns/ralph-wiggum.js +5 -5
- package/dist/patterns/react.d.ts +3 -3
- package/dist/patterns/react.js +5 -5
- package/dist/portable/adapters.d.ts +6 -0
- package/dist/portable/adapters.js +6 -0
- package/dist/portable/ask-user.d.ts +4 -0
- package/dist/portable/ask-user.js +2 -0
- package/dist/portable/builders.d.ts +12 -0
- package/dist/portable/builders.js +12 -0
- package/dist/portable/memory-layers.d.ts +12 -0
- package/dist/portable/memory-layers.js +12 -0
- package/dist/portable/runtime.d.ts +4 -0
- package/dist/portable/runtime.js +4 -0
- package/dist/portable/until.d.ts +4 -0
- package/dist/portable/until.js +4 -0
- package/dist/portable.d.ts +8 -26
- package/dist/portable.js +8 -34
- package/dist/runtime/broadcaster-utils.d.ts +7 -3
- package/dist/runtime/broadcaster-utils.js +11 -6
- package/dist/runtime/channel-store.d.ts +1 -1
- package/dist/runtime/channel-store.js +2 -2
- package/dist/runtime/context-deps.d.ts +4 -0
- package/dist/runtime/context-deps.js +2 -0
- package/dist/runtime/context-impl.d.ts +42 -12
- package/dist/runtime/context-impl.js +52 -5
- package/dist/runtime/context-type-deps.d.ts +14 -0
- package/dist/runtime/context-type-deps.js +1 -0
- package/dist/runtime/cwd-helpers.d.ts +1 -1
- package/dist/runtime/detached-handle.d.ts +52 -4
- package/dist/runtime/detached-handle.js +218 -14
- package/dist/runtime/durable/checkpoint-store.d.ts +38 -0
- package/dist/runtime/durable/checkpoint-store.js +101 -0
- package/dist/runtime/durable/detached-signal.d.ts +37 -0
- package/dist/runtime/durable/detached-signal.js +52 -0
- package/dist/runtime/durable/harness-checkpoints.d.ts +51 -0
- package/dist/runtime/durable/harness-checkpoints.js +104 -0
- package/dist/runtime/durable/index.d.ts +10 -0
- package/dist/runtime/durable/index.js +5 -0
- package/dist/runtime/durable/runnable-loop.d.ts +100 -0
- package/dist/runtime/durable/runnable-loop.js +90 -0
- package/dist/runtime/durable/session-seed.d.ts +28 -0
- package/dist/runtime/durable/session-seed.js +27 -0
- package/dist/runtime/durable/stall-nudge.d.ts +82 -0
- package/dist/runtime/durable/stall-nudge.js +87 -0
- package/dist/runtime/event-broadcaster.d.ts +1 -1
- package/dist/runtime/in-memory-storage.d.ts +1 -1
- package/dist/runtime/in-memory-storage.js +1 -1
- package/dist/runtime/item-log-impl.d.ts +3 -3
- package/dist/runtime/item-log-impl.js +1 -1
- package/dist/runtime/message-queue.d.ts +2 -2
- package/dist/runtime/session-runner.d.ts +7 -7
- package/dist/runtime/session-runner.js +3 -3
- package/dist/runtime/session-streams.d.ts +3 -3
- package/dist/runtime/session-streams.js +1 -1
- package/dist/runtime/step-registry.d.ts +42 -0
- package/dist/runtime/step-registry.js +77 -0
- package/dist/runtime/tool-memory.d.ts +3 -3
- package/dist/schemas/item.d.ts +1 -1
- package/dist/schemas/workflow.d.ts +139 -0
- package/dist/schemas/workflow.js +232 -0
- package/dist/types/ask-user-types.d.ts +57 -0
- package/dist/types/ask-user-types.js +74 -0
- package/dist/types/checkpoint.d.ts +98 -0
- package/dist/types/checkpoint.js +81 -0
- package/dist/types/common.d.ts +1 -50
- package/dist/types/context-parts/item-log.d.ts +6 -0
- package/dist/types/context-parts/item-log.js +1 -0
- package/dist/types/context-parts/layer-usage.d.ts +18 -0
- package/dist/types/context-parts/layer-usage.js +1 -0
- package/dist/types/context.d.ts +140 -31
- package/dist/types/error.d.ts +15 -0
- package/dist/types/harness-result.d.ts +2 -2
- package/dist/types/items.d.ts +18 -2
- package/dist/types/memory-context.d.ts +39 -0
- package/dist/types/memory-context.js +1 -0
- package/dist/types/memory.d.ts +38 -68
- package/dist/types/mutable-context.d.ts +3 -3
- package/dist/types/runtime.d.ts +14 -13
- package/dist/types/steering.d.ts +2 -2
- package/dist/types/step-registrar.d.ts +46 -0
- package/dist/types/step-registrar.js +56 -0
- package/dist/types/step.d.ts +34 -9
- package/dist/types/subprocess-adapter.d.ts +119 -3
- package/dist/types/subprocess-adapter.js +2 -0
- package/dist/types/tool-context.d.ts +6 -6
- package/dist/types/tool.d.ts +64 -0
- package/dist/types/tool.js +1 -0
- package/dist/unstable.d.ts +9 -9
- package/dist/unstable.js +7 -7
- package/dist/until/combinators.d.ts +1 -1
- package/dist/until/never.d.ts +1 -1
- package/dist/until/predicates.d.ts +13 -4
- package/dist/until/predicates.js +16 -2
- package/dist/util/message-helpers.d.ts +17 -0
- package/dist/util/message-helpers.js +64 -0
- package/package.json +6 -4
- package/dist/adapters/local-fs-adapter.d.ts +0 -3
- package/dist/adapters/local-fs-adapter.js +0 -43
- package/dist/adapters/local-shell-adapter.d.ts +0 -51
- package/dist/adapters/local-shell-adapter.js +0 -167
- package/dist/adapters/local-subprocess-adapter.d.ts +0 -16
- package/dist/adapters/local-subprocess-adapter.js +0 -187
- package/dist/conditions/index.d.ts +0 -3
- package/dist/conditions/index.js +0 -2
- package/dist/interpreter/execute-branch.d.ts +0 -3
- package/dist/interpreter/execute-branch.js +0 -10
- package/dist/interpreter/execute-every.js +0 -143
- package/dist/interpreter/execute-fork.d.ts +0 -3
- package/dist/interpreter/execute-fork.js +0 -215
- package/dist/interpreter/execute-llm.d.ts +0 -4
- package/dist/interpreter/execute-llm.js +0 -217
- package/dist/interpreter/execute-loop.d.ts +0 -3
- package/dist/interpreter/execute-loop.js +0 -171
- package/dist/interpreter/execute-provide.d.ts +0 -10
- package/dist/interpreter/execute-provide.js +0 -64
- package/dist/interpreter/execute-run.d.ts +0 -3
- package/dist/interpreter/execute-run.js +0 -39
- package/dist/interpreter/execute-spawn.d.ts +0 -11
- package/dist/interpreter/execute-spawn.js +0 -113
- package/dist/interpreter/execute-tool.d.ts +0 -5
- package/dist/interpreter/execute-tool.js +0 -81
- package/dist/runtime/agent-harness.js +0 -1170
- /package/dist/{interpreter → util}/framework-cast.d.ts +0 -0
- /package/dist/{interpreter → util}/framework-cast.js +0 -0
|
@@ -1,1170 +0,0 @@
|
|
|
1
|
-
import { OpenRouter } from '@openrouter/agent';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { createInMemoryFsAdapter } from '../adapters/in-memory-fs-adapter';
|
|
4
|
-
import { createInMemoryShellAdapter } from '../adapters/in-memory-shell-adapter';
|
|
5
|
-
import { createInMemorySubprocessAdapter } from '../adapters/in-memory-subprocess-adapter';
|
|
6
|
-
import { convertTools, executeToolCall, extractOutputItems, extractSystemInstruction, extractUsage, itemsToInput, } from '../adapters/openrouter';
|
|
7
|
-
import { NoeticConfigError } from '../errors/noetic-config-error';
|
|
8
|
-
import { collectAllTools, deduplicateTools } from '../interpreter/collect-tools';
|
|
9
|
-
import { execute } from '../interpreter/execute';
|
|
10
|
-
import { frameworkCast } from '../interpreter/framework-cast';
|
|
11
|
-
import { isFunctionCall } from '../interpreter/typeguards';
|
|
12
|
-
import { resolveLayerTools } from '../memory/layer-api';
|
|
13
|
-
import { afterModelCallLayers, beforeToolCallLayers, createLayerStateStore, disposeLayers, executeRerender, initLayers, projectHistoryLayers, recallLayers, resolveLayerBudgets, runAppendPipeline, storeLayers, } from '../memory/layer-lifecycle';
|
|
14
|
-
import { assembleView } from '../memory/projector';
|
|
15
|
-
import { SpanImpl } from '../observability/span-impl';
|
|
16
|
-
import { NoopExporter } from '../observability/trace-exporter';
|
|
17
|
-
import { ItemSchemaRegistry, mergeExtensions } from '../schemas/item';
|
|
18
|
-
import { SteeringAction } from '../types/steering';
|
|
19
|
-
import { emitFrameworkEvent, getBroadcaster, shouldEmit } from './broadcaster-utils';
|
|
20
|
-
import { ChannelStore } from './channel-store';
|
|
21
|
-
import { ContextImpl } from './context-impl';
|
|
22
|
-
import { snapshotCwdState } from './cwd-helpers';
|
|
23
|
-
import { DetachedHandleImpl } from './detached-handle';
|
|
24
|
-
import { contextToExecCtx } from './exec-context-factory';
|
|
25
|
-
import { createInMemoryStorage } from './in-memory-storage';
|
|
26
|
-
import { SessionRunner } from './session-runner';
|
|
27
|
-
import { buildItemStream, filterReasoningStream, filterTextStream } from './session-streams';
|
|
28
|
-
//#endregion
|
|
29
|
-
const MAX_TOOL_ROUNDS = 32;
|
|
30
|
-
const MAX_RECOVERY_CONTINUATIONS = 3;
|
|
31
|
-
const EPHEMERAL_CONTINUE_INPUT = 'continue';
|
|
32
|
-
const DEFAULT_THREAD_ID = '__default__';
|
|
33
|
-
/** Default idle-timeout for a single model call's streaming response. Chosen to be
|
|
34
|
-
* long enough that slow models aren't falsely aborted, but short enough that a
|
|
35
|
-
* stalled SSE becomes a visible error rather than a silent hang. */
|
|
36
|
-
const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 120e3;
|
|
37
|
-
//#region Helpers
|
|
38
|
-
/**
|
|
39
|
-
* Pick the right `cwdState` for a new Context.
|
|
40
|
-
*
|
|
41
|
-
* - `cwdInit` explicitly overrides everything (worktree isolation).
|
|
42
|
-
* - Parent snapshot if present (snapshot, not shared reference — child `cd`
|
|
43
|
-
* does not leak to parent).
|
|
44
|
-
* - Otherwise share the harness `rootCwdState` reference so successive root
|
|
45
|
-
* contexts see TUI/agent `cd`s carry across runs.
|
|
46
|
-
*/
|
|
47
|
-
function resolveContextCwdState(rootCwdState, parent, cwdInit) {
|
|
48
|
-
if (cwdInit !== undefined) {
|
|
49
|
-
return {
|
|
50
|
-
cwd: cwdInit,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
if (parent) {
|
|
54
|
-
return snapshotCwdState(parent);
|
|
55
|
-
}
|
|
56
|
-
return rootCwdState;
|
|
57
|
-
}
|
|
58
|
-
function createClient(config) {
|
|
59
|
-
const processValue = 'process' in globalThis ? globalThis.process : undefined;
|
|
60
|
-
const envValue = typeof processValue === 'object' && processValue !== null && 'env' in processValue
|
|
61
|
-
? processValue.env
|
|
62
|
-
: undefined;
|
|
63
|
-
const openRouterApiKey = typeof envValue === 'object' && envValue !== null && 'OPENROUTER_API_KEY' in envValue
|
|
64
|
-
? envValue.OPENROUTER_API_KEY
|
|
65
|
-
: undefined;
|
|
66
|
-
const apiKey = config?.apiKey ?? (typeof openRouterApiKey === 'string' ? openRouterApiKey : undefined);
|
|
67
|
-
if (!apiKey) {
|
|
68
|
-
return undefined;
|
|
69
|
-
}
|
|
70
|
-
return new OpenRouter({
|
|
71
|
-
apiKey,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
function mergeItemSchemaExtensions(extensions) {
|
|
75
|
-
let result = {
|
|
76
|
-
items: [],
|
|
77
|
-
developerMessages: [],
|
|
78
|
-
toolCalls: [],
|
|
79
|
-
toolResults: [],
|
|
80
|
-
};
|
|
81
|
-
for (const ext of extensions) {
|
|
82
|
-
if (ext) {
|
|
83
|
-
result = mergeExtensions(result, ext);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return result;
|
|
87
|
-
}
|
|
88
|
-
function collectLayerItemSchemaExtensions(layers) {
|
|
89
|
-
return mergeItemSchemaExtensions(layers?.map((layer) => layer.itemSchemas) ?? []);
|
|
90
|
-
}
|
|
91
|
-
function collectToolItemSchemaExtensions(tools) {
|
|
92
|
-
return mergeItemSchemaExtensions(tools?.map((tool) => tool.itemSchemas) ?? []);
|
|
93
|
-
}
|
|
94
|
-
function buildItemSchemaRegistry({ base, layers, tools, }) {
|
|
95
|
-
return base
|
|
96
|
-
.extend(collectLayerItemSchemaExtensions(layers))
|
|
97
|
-
.extend(collectToolItemSchemaExtensions(tools));
|
|
98
|
-
}
|
|
99
|
-
function createToolResultItem({ output, callId, roundItemSchemas, tool, callItem, args, result, error, }) {
|
|
100
|
-
const baseItem = {
|
|
101
|
-
id: crypto.randomUUID(),
|
|
102
|
-
status: 'completed',
|
|
103
|
-
type: 'function_call_output',
|
|
104
|
-
callId,
|
|
105
|
-
output,
|
|
106
|
-
};
|
|
107
|
-
const decorated = tool?.decorateResultItem && callItem?.type === 'function_call'
|
|
108
|
-
? tool.decorateResultItem({
|
|
109
|
-
baseItem,
|
|
110
|
-
callItem,
|
|
111
|
-
args,
|
|
112
|
-
result,
|
|
113
|
-
output,
|
|
114
|
-
error,
|
|
115
|
-
})
|
|
116
|
-
: baseItem;
|
|
117
|
-
return roundItemSchemas.parseWithCategory(decorated, 'toolResults');
|
|
118
|
-
}
|
|
119
|
-
function buildTextFormat(schema) {
|
|
120
|
-
const jsonSchema = z.toJSONSchema(schema);
|
|
121
|
-
return {
|
|
122
|
-
format: {
|
|
123
|
-
type: 'json_schema',
|
|
124
|
-
name: 'output',
|
|
125
|
-
schema: jsonSchema,
|
|
126
|
-
},
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
function isStreamRecord(value) {
|
|
130
|
-
return typeof value === 'object' && value !== null;
|
|
131
|
-
}
|
|
132
|
-
function providerTerminalError(response) {
|
|
133
|
-
if (!isStreamRecord(response) || response.status === undefined) {
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
const status = String(response.status);
|
|
137
|
-
if (status === 'completed') {
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
if (status === 'incomplete') {
|
|
141
|
-
const details = response.incompleteDetails;
|
|
142
|
-
const reason = isStreamRecord(details) && typeof details.reason === 'string' ? `: ${details.reason}` : '';
|
|
143
|
-
return {
|
|
144
|
-
status,
|
|
145
|
-
message: `LLM response incomplete${reason}`,
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
if (status === 'failed') {
|
|
149
|
-
const error = response.error;
|
|
150
|
-
const message = isStreamRecord(error) && typeof error.message === 'string' ? `: ${error.message}` : '';
|
|
151
|
-
return {
|
|
152
|
-
status,
|
|
153
|
-
message: `LLM response failed${message}`,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
return {
|
|
157
|
-
status,
|
|
158
|
-
message: `LLM response ended with status '${status}'`,
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
function hasUsableResponseOutput(response, items) {
|
|
162
|
-
if (items.length > 0) {
|
|
163
|
-
return true;
|
|
164
|
-
}
|
|
165
|
-
return (isStreamRecord(response) &&
|
|
166
|
-
typeof response.outputText === 'string' &&
|
|
167
|
-
response.outputText.length > 0);
|
|
168
|
-
}
|
|
169
|
-
function withEphemeralContinueInput(input) {
|
|
170
|
-
return [
|
|
171
|
-
...frameworkCast(input),
|
|
172
|
-
frameworkCast({
|
|
173
|
-
type: 'message',
|
|
174
|
-
role: 'user',
|
|
175
|
-
content: EPHEMERAL_CONTINUE_INPUT,
|
|
176
|
-
}),
|
|
177
|
-
];
|
|
178
|
-
}
|
|
179
|
-
/** Race a promise against an AbortSignal so callers (e.g. `SessionRunner.abort`)
|
|
180
|
-
* can break out of a long `await` without waiting for the underlying call to
|
|
181
|
-
* settle. When the signal fires, the returned promise rejects with
|
|
182
|
-
* `signal.reason` (an `Error`) or a generic `Error('aborted')` fallback. */
|
|
183
|
-
function awaitWithAbort(p, signal) {
|
|
184
|
-
if (!signal) {
|
|
185
|
-
return p;
|
|
186
|
-
}
|
|
187
|
-
if (signal.aborted) {
|
|
188
|
-
return Promise.reject(signal.reason instanceof Error ? signal.reason : new Error('aborted'));
|
|
189
|
-
}
|
|
190
|
-
return new Promise((resolve, reject) => {
|
|
191
|
-
const onAbort = () => {
|
|
192
|
-
reject(signal.reason instanceof Error ? signal.reason : new Error('aborted'));
|
|
193
|
-
};
|
|
194
|
-
signal.addEventListener('abort', onAbort, {
|
|
195
|
-
once: true,
|
|
196
|
-
});
|
|
197
|
-
p.then((v) => {
|
|
198
|
-
signal.removeEventListener('abort', onAbort);
|
|
199
|
-
resolve(v);
|
|
200
|
-
}, (e) => {
|
|
201
|
-
signal.removeEventListener('abort', onAbort);
|
|
202
|
-
reject(e);
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
async function pipeStreamEventsToBroadcaster(opts) {
|
|
207
|
-
const { stream, broadcaster, agentName, signal, onEvent } = opts;
|
|
208
|
-
try {
|
|
209
|
-
for await (const event of stream) {
|
|
210
|
-
if (signal?.aborted) {
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
onEvent?.();
|
|
214
|
-
if (!isStreamRecord(event)) {
|
|
215
|
-
continue;
|
|
216
|
-
}
|
|
217
|
-
broadcaster?.emit({
|
|
218
|
-
source: 'sdk',
|
|
219
|
-
type: typeof event.type === 'string' ? event.type : 'unknown',
|
|
220
|
-
data: event,
|
|
221
|
-
outputIndex: typeof event.outputIndex === 'number' ? event.outputIndex : undefined,
|
|
222
|
-
contentIndex: typeof event.contentIndex === 'number' ? event.contentIndex : undefined,
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
catch (err) {
|
|
227
|
-
if (broadcaster) {
|
|
228
|
-
emitFrameworkEvent({
|
|
229
|
-
broadcaster,
|
|
230
|
-
agentName,
|
|
231
|
-
eventType: 'stream_pipe_error',
|
|
232
|
-
data: {
|
|
233
|
-
error: err instanceof Error ? err.message : String(err),
|
|
234
|
-
},
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
throw err;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
/** @internal Create a watchdog that aborts `controller` when no
|
|
241
|
-
* {@link StreamIdleWatchdog.reset reset} is called within `timeoutMs`. When
|
|
242
|
-
* `timeoutMs <= 0`, returns an inert no-op so callers can always call `.reset()`
|
|
243
|
-
* / `.stop()` without a branch. Starts armed: the caller is responsible for
|
|
244
|
-
* `.stop()` in a `finally`. `onTimeout` runs before the abort so observers can
|
|
245
|
-
* emit a framework event with the original cause. Exported only for unit tests. */
|
|
246
|
-
export function createStreamIdleWatchdog(timeoutMs, controller, onTimeout) {
|
|
247
|
-
if (timeoutMs <= 0) {
|
|
248
|
-
return {
|
|
249
|
-
reset: () => { },
|
|
250
|
-
stop: () => { },
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
let timer;
|
|
254
|
-
let stopped = false;
|
|
255
|
-
const arm = () => {
|
|
256
|
-
timer = setTimeout(() => {
|
|
257
|
-
if (stopped) {
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
const reason = new Error(`llm stream idle timeout after ${timeoutMs}ms`);
|
|
261
|
-
onTimeout?.();
|
|
262
|
-
controller.abort(reason);
|
|
263
|
-
}, timeoutMs);
|
|
264
|
-
};
|
|
265
|
-
arm();
|
|
266
|
-
return {
|
|
267
|
-
reset: () => {
|
|
268
|
-
if (stopped || !timer) {
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
clearTimeout(timer);
|
|
272
|
-
arm();
|
|
273
|
-
},
|
|
274
|
-
stop: () => {
|
|
275
|
-
stopped = true;
|
|
276
|
-
if (timer) {
|
|
277
|
-
clearTimeout(timer);
|
|
278
|
-
timer = undefined;
|
|
279
|
-
}
|
|
280
|
-
},
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
function hasSessionQueue(ctx) {
|
|
284
|
-
const maybe = frameworkCast(ctx);
|
|
285
|
-
return (maybe._sessionBetweenRounds === true &&
|
|
286
|
-
maybe._sessionQueue !== undefined &&
|
|
287
|
-
typeof maybe._sessionRunnerAgentName === 'string');
|
|
288
|
-
}
|
|
289
|
-
//#endregion
|
|
290
|
-
//#region AgentHarness
|
|
291
|
-
/**
|
|
292
|
-
* Default agent harness for executing agent steps with built-in channel, memory, and trace support.
|
|
293
|
-
* Provides channel store, memory layer lifecycle, and trace export with no external dependencies.
|
|
294
|
-
*
|
|
295
|
-
* Messages submitted via `execute()` are enqueued on a per-thread session and
|
|
296
|
-
* processed by a `SessionRunner`. Consumers observe responses via the session-
|
|
297
|
-
* scoped accessors: `getAgentResponse`, `getItemStream`, etc.
|
|
298
|
-
*
|
|
299
|
-
* @public
|
|
300
|
-
*/
|
|
301
|
-
export class AgentHarness {
|
|
302
|
-
config;
|
|
303
|
-
fs;
|
|
304
|
-
shell;
|
|
305
|
-
subprocess;
|
|
306
|
-
initialStep;
|
|
307
|
-
_memory;
|
|
308
|
-
client;
|
|
309
|
-
channelStore;
|
|
310
|
-
callModelOverride;
|
|
311
|
-
defaultDeliveryMode;
|
|
312
|
-
streamIdleTimeoutMs;
|
|
313
|
-
sessions = new Map();
|
|
314
|
-
layerStateStore;
|
|
315
|
-
traceExporter;
|
|
316
|
-
/**
|
|
317
|
-
* Long-lived shared cwd state. The same reference is seeded into every
|
|
318
|
-
* root Context this harness creates, so successive `run()` calls — and the
|
|
319
|
-
* TUI — observe each other's `cd`s.
|
|
320
|
-
*/
|
|
321
|
-
rootCwdState;
|
|
322
|
-
itemSchemas;
|
|
323
|
-
constructor(opts) {
|
|
324
|
-
const validatedParams = opts.paramsSchema ? opts.paramsSchema.parse(opts.params) : opts.params;
|
|
325
|
-
this.config = {
|
|
326
|
-
name: opts.name,
|
|
327
|
-
storage: opts.storage ?? createInMemoryStorage(),
|
|
328
|
-
hooks: opts.hooks,
|
|
329
|
-
params: validatedParams,
|
|
330
|
-
itemSchemas: opts.itemSchemas,
|
|
331
|
-
strictItemSchemas: opts.strictItemSchemas ?? true,
|
|
332
|
-
};
|
|
333
|
-
this.fs = opts.fs ?? createInMemoryFsAdapter();
|
|
334
|
-
this.shell = opts.shell ?? createInMemoryShellAdapter();
|
|
335
|
-
this.subprocess = opts.subprocess ?? createInMemorySubprocessAdapter();
|
|
336
|
-
this.initialStep = opts.initialStep;
|
|
337
|
-
this._memory = opts.memory;
|
|
338
|
-
this.callModelOverride = opts._testCallModel;
|
|
339
|
-
this.client = opts._testCallModel ? undefined : createClient(opts.llm);
|
|
340
|
-
this.channelStore = new ChannelStore();
|
|
341
|
-
this.traceExporter = opts.traceExporter ?? new NoopExporter();
|
|
342
|
-
this.layerStateStore = opts.layerStateStore ?? createLayerStateStore();
|
|
343
|
-
this.defaultDeliveryMode = opts.defaultDeliveryMode ?? 'next-turn';
|
|
344
|
-
this.streamIdleTimeoutMs = opts.streamIdleTimeoutMs ?? DEFAULT_STREAM_IDLE_TIMEOUT_MS;
|
|
345
|
-
this.itemSchemas = new ItemSchemaRegistry(opts.itemSchemas, {
|
|
346
|
-
strictUnknownExtensions: opts.strictItemSchemas ?? true,
|
|
347
|
-
});
|
|
348
|
-
this.rootCwdState = {
|
|
349
|
-
cwd: opts.initialCwd ?? '/',
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* Update the harness-wide root cwd. The TUI calls this in response to a
|
|
354
|
-
* user-issued `! cd`, so the next root Context (and any tool inspecting
|
|
355
|
-
* `harness.rootCwdState.cwd`) observes the new value. Caller is responsible
|
|
356
|
-
* for passing an absolute, validated path.
|
|
357
|
-
*/
|
|
358
|
-
setRootCwd(nextCwd) {
|
|
359
|
-
if (nextCwd === this.rootCwdState.cwd) {
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
this.rootCwdState.previousCwd = this.rootCwdState.cwd;
|
|
363
|
-
this.rootCwdState.cwd = nextCwd;
|
|
364
|
-
}
|
|
365
|
-
//#region Session Accessors
|
|
366
|
-
execute(input, options) {
|
|
367
|
-
if (!this.initialStep) {
|
|
368
|
-
return Promise.reject(new NoeticConfigError({
|
|
369
|
-
code: 'NO_STEP_CONFIGURED',
|
|
370
|
-
message: 'No initialStep configured on this harness.',
|
|
371
|
-
hint: 'Pass `initialStep` in constructor options, or use run() directly.',
|
|
372
|
-
}));
|
|
373
|
-
}
|
|
374
|
-
const threadId = options?.threadId ?? DEFAULT_THREAD_ID;
|
|
375
|
-
const deliveryMode = options?.deliveryMode ?? this.defaultDeliveryMode;
|
|
376
|
-
const session = this.getOrCreateSession(threadId);
|
|
377
|
-
const message = {
|
|
378
|
-
id: options?.messageId ?? `msg-${crypto.randomUUID()}`,
|
|
379
|
-
input,
|
|
380
|
-
deliveryMode,
|
|
381
|
-
options: options ?? {},
|
|
382
|
-
enqueuedAt: Date.now(),
|
|
383
|
-
};
|
|
384
|
-
if (deliveryMode === 'interrupt' && session.runner.getStatus().kind === 'generating') {
|
|
385
|
-
session.runner.queue.prepend(message);
|
|
386
|
-
// Abort kicks the runner via queue subscription after the in-flight turn settles.
|
|
387
|
-
void session.runner.abort('interrupt');
|
|
388
|
-
return Promise.resolve();
|
|
389
|
-
}
|
|
390
|
-
session.runner.queue.enqueue(message);
|
|
391
|
-
return Promise.resolve();
|
|
392
|
-
}
|
|
393
|
-
getAgentResponse(scope) {
|
|
394
|
-
const session = this.requireSession(scope);
|
|
395
|
-
return session.runner.getAgentResponse();
|
|
396
|
-
}
|
|
397
|
-
getItemStream(scope) {
|
|
398
|
-
const session = this.requireSession(scope);
|
|
399
|
-
return buildItemStream(session.runner.broadcaster, this.itemSchemas);
|
|
400
|
-
}
|
|
401
|
-
getTextStream(scope) {
|
|
402
|
-
const session = this.requireSession(scope);
|
|
403
|
-
return filterTextStream(session.runner.broadcaster);
|
|
404
|
-
}
|
|
405
|
-
getReasoningStream(scope) {
|
|
406
|
-
const session = this.requireSession(scope);
|
|
407
|
-
return filterReasoningStream(session.runner.broadcaster);
|
|
408
|
-
}
|
|
409
|
-
getFullStream(scope) {
|
|
410
|
-
const session = this.requireSession(scope);
|
|
411
|
-
return session.runner.broadcaster;
|
|
412
|
-
}
|
|
413
|
-
async abort(scope) {
|
|
414
|
-
const threadId = scope?.threadId ?? DEFAULT_THREAD_ID;
|
|
415
|
-
const session = this.sessions.get(threadId);
|
|
416
|
-
if (!session) {
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
await session.runner.abort(scope?.reason);
|
|
420
|
-
}
|
|
421
|
-
getStatus(scope) {
|
|
422
|
-
const threadId = scope?.threadId ?? DEFAULT_THREAD_ID;
|
|
423
|
-
const session = this.sessions.get(threadId);
|
|
424
|
-
if (!session) {
|
|
425
|
-
return {
|
|
426
|
-
kind: 'idle',
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
return session.runner.getStatus();
|
|
430
|
-
}
|
|
431
|
-
getQueueSize(scope) {
|
|
432
|
-
const threadId = scope?.threadId ?? DEFAULT_THREAD_ID;
|
|
433
|
-
const session = this.sessions.get(threadId);
|
|
434
|
-
return session ? session.runner.queue.size : 0;
|
|
435
|
-
}
|
|
436
|
-
seedSessionHistory(threadId, items) {
|
|
437
|
-
const session = this.getOrCreateSession(threadId);
|
|
438
|
-
session.accumulatedItems = [
|
|
439
|
-
...items,
|
|
440
|
-
];
|
|
441
|
-
}
|
|
442
|
-
getOrCreateSession(threadId) {
|
|
443
|
-
const existing = this.sessions.get(threadId);
|
|
444
|
-
if (existing) {
|
|
445
|
-
return existing;
|
|
446
|
-
}
|
|
447
|
-
const session = {
|
|
448
|
-
accumulatedItems: [],
|
|
449
|
-
runner: new SessionRunner({
|
|
450
|
-
threadId,
|
|
451
|
-
agentName: this.config.name,
|
|
452
|
-
// The first queued message in a batch establishes `resourceId` /
|
|
453
|
-
// `state` / `memory` for the turn. If multiple messages are drained
|
|
454
|
-
// together (queue flush), later messages' values for these fields
|
|
455
|
-
// are ignored. `deliveryMode` is resolved per-message at enqueue
|
|
456
|
-
// time and doesn't apply here.
|
|
457
|
-
createContext: (items, _turnId, messages) => {
|
|
458
|
-
const perTurnOptions = messages[0]?.options ?? {};
|
|
459
|
-
const allItems = [
|
|
460
|
-
...session.accumulatedItems,
|
|
461
|
-
...items,
|
|
462
|
-
];
|
|
463
|
-
const ctx = this.createContext({
|
|
464
|
-
items: allItems,
|
|
465
|
-
threadId,
|
|
466
|
-
resourceId: perTurnOptions.resourceId,
|
|
467
|
-
state: perTurnOptions.state,
|
|
468
|
-
memory: perTurnOptions.memory,
|
|
469
|
-
_broadcaster: session.runner.broadcaster,
|
|
470
|
-
});
|
|
471
|
-
const ext = frameworkCast(ctx);
|
|
472
|
-
ext._sessionQueue = session.runner.queue;
|
|
473
|
-
ext._sessionBetweenRounds = true;
|
|
474
|
-
ext._sessionRunnerAgentName = this.config.name;
|
|
475
|
-
if (this.initialStep) {
|
|
476
|
-
this.setUnifiedTools(ctx, collectAllTools(this.initialStep));
|
|
477
|
-
}
|
|
478
|
-
return ctx;
|
|
479
|
-
},
|
|
480
|
-
runTurn: async (ctx, _turn, signal) => {
|
|
481
|
-
if (!this.initialStep) {
|
|
482
|
-
throw new NoeticConfigError({
|
|
483
|
-
code: 'NO_STEP_CONFIGURED',
|
|
484
|
-
message: 'No initialStep configured on this harness.',
|
|
485
|
-
hint: 'Pass `initialStep` in constructor options.',
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
// Wire signal-abort to context-abort so the interpreter bails cleanly.
|
|
489
|
-
if (signal.aborted) {
|
|
490
|
-
ctx.abort(signal.reason ? String(signal.reason) : 'aborted');
|
|
491
|
-
}
|
|
492
|
-
else {
|
|
493
|
-
signal.addEventListener('abort', () => {
|
|
494
|
-
ctx.abort(signal.reason ? String(signal.reason) : 'aborted');
|
|
495
|
-
}, {
|
|
496
|
-
once: true,
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
const result = await this.initAndRun(this.initialStep, '', ctx);
|
|
500
|
-
// Snapshot final items into session history for the next turn.
|
|
501
|
-
session.accumulatedItems = [
|
|
502
|
-
...ctx.itemLog.items,
|
|
503
|
-
];
|
|
504
|
-
return result;
|
|
505
|
-
},
|
|
506
|
-
}),
|
|
507
|
-
};
|
|
508
|
-
this.sessions.set(threadId, session);
|
|
509
|
-
return session;
|
|
510
|
-
}
|
|
511
|
-
requireSession(scope) {
|
|
512
|
-
const threadId = scope?.threadId ?? DEFAULT_THREAD_ID;
|
|
513
|
-
let session = this.sessions.get(threadId);
|
|
514
|
-
if (session) {
|
|
515
|
-
return session;
|
|
516
|
-
}
|
|
517
|
-
// Lazily create so consumers can attach stream listeners before the first execute.
|
|
518
|
-
session = this.getOrCreateSession(threadId);
|
|
519
|
-
return session;
|
|
520
|
-
}
|
|
521
|
-
//#endregion
|
|
522
|
-
//#region callModel
|
|
523
|
-
async callModel(request) {
|
|
524
|
-
if (this.callModelOverride) {
|
|
525
|
-
const response = await this.callModelOverride(request);
|
|
526
|
-
const itemSchemas = buildItemSchemaRegistry({
|
|
527
|
-
base: this.itemSchemas,
|
|
528
|
-
layers: request.layers,
|
|
529
|
-
tools: request.tools,
|
|
530
|
-
});
|
|
531
|
-
return {
|
|
532
|
-
...response,
|
|
533
|
-
items: itemSchemas.parseMany(response.items),
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
if (!this.client) {
|
|
537
|
-
throw new NoeticConfigError({
|
|
538
|
-
code: 'NO_LLM_PROVIDER',
|
|
539
|
-
message: 'No LLM provider configured on this harness.',
|
|
540
|
-
hint: 'Pass `llm: { provider: "openrouter", apiKey: "..." }` in constructor options or set OPENROUTER_API_KEY.',
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
const { instructions: extractedInstructions, remaining } = extractSystemInstruction(request.items);
|
|
544
|
-
const instructions = [
|
|
545
|
-
request.instructions,
|
|
546
|
-
extractedInstructions,
|
|
547
|
-
]
|
|
548
|
-
.filter(Boolean)
|
|
549
|
-
.join('\n\n') || undefined;
|
|
550
|
-
const broadcaster = getBroadcaster(request.ctx);
|
|
551
|
-
const agentName = this.config.name;
|
|
552
|
-
const signal = request.signal;
|
|
553
|
-
const emitIfAllowed = (eventType, data) => {
|
|
554
|
-
if (shouldEmit(request.emit, eventType, data)) {
|
|
555
|
-
emitFrameworkEvent({
|
|
556
|
-
broadcaster,
|
|
557
|
-
agentName,
|
|
558
|
-
eventType,
|
|
559
|
-
data,
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
};
|
|
563
|
-
const allowedNamesSet = request.tools && 'allowedToolNames' in request && request.allowedToolNames
|
|
564
|
-
? new Set(request.allowedToolNames)
|
|
565
|
-
: undefined;
|
|
566
|
-
const filteredTools = allowedNamesSet
|
|
567
|
-
? request.tools?.filter((t) => allowedNamesSet.has(t.name))
|
|
568
|
-
: request.tools;
|
|
569
|
-
let sdkTools;
|
|
570
|
-
if (filteredTools && filteredTools.length > 0) {
|
|
571
|
-
sdkTools = convertTools({
|
|
572
|
-
tools: filteredTools,
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
|
-
const allItems = [];
|
|
576
|
-
const totalUsage = {
|
|
577
|
-
inputTokens: 0,
|
|
578
|
-
outputTokens: 0,
|
|
579
|
-
cachedTokens: 0,
|
|
580
|
-
};
|
|
581
|
-
let totalCost = 0;
|
|
582
|
-
const conversationInput = itemsToInput(remaining);
|
|
583
|
-
const textFormat = request.outputSchema ? buildTextFormat(request.outputSchema) : undefined;
|
|
584
|
-
let round = 0;
|
|
585
|
-
let invalidRecoveryContinuations = 0;
|
|
586
|
-
let toolLimitRecoveryContinuations = 0;
|
|
587
|
-
let useEphemeralContinue = false;
|
|
588
|
-
while (!signal?.aborted) {
|
|
589
|
-
const recoveryContinuation = useEphemeralContinue;
|
|
590
|
-
useEphemeralContinue = false;
|
|
591
|
-
// Inject between-rounds inbox messages (mode: 'between-rounds') before
|
|
592
|
-
// the next LLM call. Mirrors Claude Code's teammate-attachment path.
|
|
593
|
-
// Also append to ctx.itemLog so the session's post-turn snapshot
|
|
594
|
-
// carries the injected messages into next turn's history.
|
|
595
|
-
if (round > 0 && request.ctx && hasSessionQueue(request.ctx)) {
|
|
596
|
-
const injected = this.drainBetweenRoundsMessages(request.ctx._sessionQueue);
|
|
597
|
-
if (injected.length > 0) {
|
|
598
|
-
for (const msg of injected) {
|
|
599
|
-
const text = itemToText(msg);
|
|
600
|
-
const userItem = {
|
|
601
|
-
id: `user-${crypto.randomUUID()}`,
|
|
602
|
-
type: 'message',
|
|
603
|
-
role: 'user',
|
|
604
|
-
status: 'completed',
|
|
605
|
-
content: [
|
|
606
|
-
{
|
|
607
|
-
type: 'input_text',
|
|
608
|
-
text,
|
|
609
|
-
},
|
|
610
|
-
],
|
|
611
|
-
};
|
|
612
|
-
conversationInput.push({
|
|
613
|
-
type: 'message',
|
|
614
|
-
role: 'user',
|
|
615
|
-
content: [
|
|
616
|
-
{
|
|
617
|
-
type: 'input_text',
|
|
618
|
-
text,
|
|
619
|
-
},
|
|
620
|
-
],
|
|
621
|
-
});
|
|
622
|
-
request.ctx.itemLog.append(userItem);
|
|
623
|
-
}
|
|
624
|
-
emitIfAllowed('inbox_injected', {
|
|
625
|
-
round,
|
|
626
|
-
count: injected.length,
|
|
627
|
-
messageIds: injected.map((i) => i.id),
|
|
628
|
-
});
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
// Build a round-scoped controller that also aborts when the caller's
|
|
632
|
-
// signal fires — `AbortSignal.any` handles listener lifecycle for us
|
|
633
|
-
// (no manual cleanup required on normal completion).
|
|
634
|
-
const roundController = new AbortController();
|
|
635
|
-
const roundSignal = signal
|
|
636
|
-
? AbortSignal.any([
|
|
637
|
-
signal,
|
|
638
|
-
roundController.signal,
|
|
639
|
-
])
|
|
640
|
-
: roundController.signal;
|
|
641
|
-
let firstEventSeen = false;
|
|
642
|
-
let idleStalled = false;
|
|
643
|
-
const watchdog = createStreamIdleWatchdog(this.streamIdleTimeoutMs, roundController, () => {
|
|
644
|
-
idleStalled = true;
|
|
645
|
-
emitIfAllowed('llm_call_stalled', {
|
|
646
|
-
round,
|
|
647
|
-
idleTimeoutMs: this.streamIdleTimeoutMs,
|
|
648
|
-
});
|
|
649
|
-
});
|
|
650
|
-
const modelInput = recoveryContinuation
|
|
651
|
-
? withEphemeralContinueInput(conversationInput)
|
|
652
|
-
: frameworkCast(conversationInput);
|
|
653
|
-
emitIfAllowed('llm_call_started', {
|
|
654
|
-
round,
|
|
655
|
-
messageCount: modelInput.length,
|
|
656
|
-
toolCount: sdkTools?.length ?? 0,
|
|
657
|
-
recoveryContinuation,
|
|
658
|
-
});
|
|
659
|
-
const callResult = this.client.callModel({
|
|
660
|
-
model: request.model,
|
|
661
|
-
input: modelInput,
|
|
662
|
-
instructions,
|
|
663
|
-
tools: sdkTools,
|
|
664
|
-
temperature: request.params?.temperature,
|
|
665
|
-
maxOutputTokens: request.params?.maxTokens,
|
|
666
|
-
topP: request.params?.topP,
|
|
667
|
-
...(textFormat
|
|
668
|
-
? {
|
|
669
|
-
text: textFormat,
|
|
670
|
-
}
|
|
671
|
-
: {}),
|
|
672
|
-
}, {
|
|
673
|
-
signal: roundSignal,
|
|
674
|
-
});
|
|
675
|
-
const onStreamEvent = () => {
|
|
676
|
-
watchdog.reset();
|
|
677
|
-
if (!firstEventSeen) {
|
|
678
|
-
firstEventSeen = true;
|
|
679
|
-
emitIfAllowed('llm_call_first_event', {
|
|
680
|
-
round,
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
};
|
|
684
|
-
// Always consume the SDK stream so the watchdog gets reset per event
|
|
685
|
-
// even when no broadcaster is attached (headless / test harness runs).
|
|
686
|
-
// Emission into the broadcaster is optional inside the pipe function.
|
|
687
|
-
const pipePromise = pipeStreamEventsToBroadcaster({
|
|
688
|
-
stream: callResult.getFullResponsesStream(),
|
|
689
|
-
broadcaster,
|
|
690
|
-
agentName,
|
|
691
|
-
signal: roundSignal,
|
|
692
|
-
onEvent: onStreamEvent,
|
|
693
|
-
});
|
|
694
|
-
let sdkResponse;
|
|
695
|
-
try {
|
|
696
|
-
sdkResponse = await awaitWithAbort(callResult.getResponse(), roundSignal);
|
|
697
|
-
// Stop the watchdog synchronously the instant the response resolves,
|
|
698
|
-
// closing the race where a pending setTimeout could fire in the
|
|
699
|
-
// microtask gap between this await and the `finally` below.
|
|
700
|
-
watchdog.stop();
|
|
701
|
-
await awaitWithAbort(pipePromise, roundSignal);
|
|
702
|
-
}
|
|
703
|
-
catch (err) {
|
|
704
|
-
if (idleStalled) {
|
|
705
|
-
// roundSignal.reason is the Error the watchdog passed to controller.abort().
|
|
706
|
-
throw roundSignal.reason instanceof Error
|
|
707
|
-
? roundSignal.reason
|
|
708
|
-
: new Error(`llm stream idle timeout after ${this.streamIdleTimeoutMs}ms`);
|
|
709
|
-
}
|
|
710
|
-
// Parent signal cancelled the round — exit gracefully rather than
|
|
711
|
-
// propagating the abort error as an exception.
|
|
712
|
-
if (signal?.aborted) {
|
|
713
|
-
break;
|
|
714
|
-
}
|
|
715
|
-
throw err;
|
|
716
|
-
}
|
|
717
|
-
finally {
|
|
718
|
-
watchdog.stop();
|
|
719
|
-
}
|
|
720
|
-
if (signal?.aborted) {
|
|
721
|
-
break;
|
|
722
|
-
}
|
|
723
|
-
const terminalError = providerTerminalError(sdkResponse);
|
|
724
|
-
if (terminalError) {
|
|
725
|
-
emitIfAllowed('llm_call_failed', {
|
|
726
|
-
round,
|
|
727
|
-
status: terminalError.status,
|
|
728
|
-
error: terminalError.message,
|
|
729
|
-
recoverable: invalidRecoveryContinuations < MAX_RECOVERY_CONTINUATIONS,
|
|
730
|
-
});
|
|
731
|
-
if (invalidRecoveryContinuations >= MAX_RECOVERY_CONTINUATIONS) {
|
|
732
|
-
throw new Error(terminalError.message);
|
|
733
|
-
}
|
|
734
|
-
invalidRecoveryContinuations += 1;
|
|
735
|
-
useEphemeralContinue = true;
|
|
736
|
-
emitIfAllowed('llm_call_recovery_continue', {
|
|
737
|
-
round,
|
|
738
|
-
status: terminalError.status,
|
|
739
|
-
attempt: invalidRecoveryContinuations,
|
|
740
|
-
maxAttempts: MAX_RECOVERY_CONTINUATIONS,
|
|
741
|
-
});
|
|
742
|
-
continue;
|
|
743
|
-
}
|
|
744
|
-
const roundItemSchemas = buildItemSchemaRegistry({
|
|
745
|
-
base: this.itemSchemas,
|
|
746
|
-
layers: request.layers,
|
|
747
|
-
tools: request.tools,
|
|
748
|
-
});
|
|
749
|
-
const roundItems = roundItemSchemas.parseMany(extractOutputItems(sdkResponse));
|
|
750
|
-
if (!hasUsableResponseOutput(sdkResponse, roundItems)) {
|
|
751
|
-
const message = 'LLM response completed with no output items';
|
|
752
|
-
emitIfAllowed('llm_call_failed', {
|
|
753
|
-
round,
|
|
754
|
-
status: 'completed',
|
|
755
|
-
error: message,
|
|
756
|
-
recoverable: invalidRecoveryContinuations < MAX_RECOVERY_CONTINUATIONS,
|
|
757
|
-
});
|
|
758
|
-
if (invalidRecoveryContinuations >= MAX_RECOVERY_CONTINUATIONS) {
|
|
759
|
-
throw new Error(message);
|
|
760
|
-
}
|
|
761
|
-
invalidRecoveryContinuations += 1;
|
|
762
|
-
useEphemeralContinue = true;
|
|
763
|
-
emitIfAllowed('llm_call_recovery_continue', {
|
|
764
|
-
round,
|
|
765
|
-
status: 'completed',
|
|
766
|
-
attempt: invalidRecoveryContinuations,
|
|
767
|
-
maxAttempts: MAX_RECOVERY_CONTINUATIONS,
|
|
768
|
-
});
|
|
769
|
-
continue;
|
|
770
|
-
}
|
|
771
|
-
invalidRecoveryContinuations = 0;
|
|
772
|
-
emitIfAllowed('llm_call_completed', {
|
|
773
|
-
round,
|
|
774
|
-
itemCount: sdkResponse.output?.length ?? 0,
|
|
775
|
-
});
|
|
776
|
-
const roundUsage = extractUsage(sdkResponse.usage);
|
|
777
|
-
totalUsage.inputTokens += roundUsage.inputTokens;
|
|
778
|
-
totalUsage.outputTokens += roundUsage.outputTokens;
|
|
779
|
-
totalUsage.cachedTokens += roundUsage.cachedTokens ?? 0;
|
|
780
|
-
totalCost += sdkResponse.usage?.cost ?? 0;
|
|
781
|
-
allItems.push(...roundItems);
|
|
782
|
-
const functionCalls = roundItems.filter(isFunctionCall);
|
|
783
|
-
if (functionCalls.length === 0 || !request.tools) {
|
|
784
|
-
break;
|
|
785
|
-
}
|
|
786
|
-
emitIfAllowed('tool_round_started', {
|
|
787
|
-
round,
|
|
788
|
-
toolCount: functionCalls.length,
|
|
789
|
-
});
|
|
790
|
-
for (const fc of functionCalls) {
|
|
791
|
-
conversationInput.push({
|
|
792
|
-
type: 'function_call',
|
|
793
|
-
callId: fc.callId,
|
|
794
|
-
id: fc.id ?? crypto.randomUUID(),
|
|
795
|
-
name: fc.name,
|
|
796
|
-
arguments: fc.arguments,
|
|
797
|
-
});
|
|
798
|
-
}
|
|
799
|
-
for (const fc of functionCalls) {
|
|
800
|
-
if (signal?.aborted) {
|
|
801
|
-
break;
|
|
802
|
-
}
|
|
803
|
-
emitIfAllowed('tool_call_started', {
|
|
804
|
-
name: fc.name,
|
|
805
|
-
callId: fc.callId,
|
|
806
|
-
});
|
|
807
|
-
let parsedArgs;
|
|
808
|
-
try {
|
|
809
|
-
parsedArgs = JSON.parse(fc.arguments);
|
|
810
|
-
}
|
|
811
|
-
catch {
|
|
812
|
-
const errorOutput = `Error: malformed JSON in tool arguments: ${fc.arguments}`;
|
|
813
|
-
const toolForCall = request.tools.find((t) => t.name === fc.name);
|
|
814
|
-
const outputItem = createToolResultItem({
|
|
815
|
-
output: errorOutput,
|
|
816
|
-
callId: fc.callId,
|
|
817
|
-
roundItemSchemas,
|
|
818
|
-
tool: toolForCall,
|
|
819
|
-
callItem: fc,
|
|
820
|
-
error: true,
|
|
821
|
-
});
|
|
822
|
-
allItems.push(outputItem);
|
|
823
|
-
conversationInput.push({
|
|
824
|
-
type: 'function_call_output',
|
|
825
|
-
callId: fc.callId,
|
|
826
|
-
output: errorOutput,
|
|
827
|
-
});
|
|
828
|
-
emitIfAllowed('tool_call_completed', {
|
|
829
|
-
name: fc.name,
|
|
830
|
-
callId: fc.callId,
|
|
831
|
-
error: true,
|
|
832
|
-
});
|
|
833
|
-
continue;
|
|
834
|
-
}
|
|
835
|
-
const toolForCall = request.tools.find((t) => t.name === fc.name);
|
|
836
|
-
const toolResult = await executeToolCall({
|
|
837
|
-
toolName: fc.name,
|
|
838
|
-
args: parsedArgs,
|
|
839
|
-
tools: request.tools,
|
|
840
|
-
context: request.ctx,
|
|
841
|
-
harness: this,
|
|
842
|
-
layers: request.layers,
|
|
843
|
-
});
|
|
844
|
-
const { output } = toolResult;
|
|
845
|
-
const outputItem = createToolResultItem({
|
|
846
|
-
output,
|
|
847
|
-
callId: fc.callId,
|
|
848
|
-
roundItemSchemas,
|
|
849
|
-
tool: toolForCall,
|
|
850
|
-
callItem: fc,
|
|
851
|
-
args: parsedArgs,
|
|
852
|
-
result: toolResult.result,
|
|
853
|
-
error: toolResult.error,
|
|
854
|
-
});
|
|
855
|
-
allItems.push(outputItem);
|
|
856
|
-
conversationInput.push({
|
|
857
|
-
type: 'function_call_output',
|
|
858
|
-
callId: fc.callId,
|
|
859
|
-
output,
|
|
860
|
-
});
|
|
861
|
-
emitIfAllowed('tool_call_completed', {
|
|
862
|
-
name: fc.name,
|
|
863
|
-
callId: fc.callId,
|
|
864
|
-
error: false,
|
|
865
|
-
});
|
|
866
|
-
}
|
|
867
|
-
emitIfAllowed('tool_round_completed', {
|
|
868
|
-
round,
|
|
869
|
-
toolCount: functionCalls.length,
|
|
870
|
-
});
|
|
871
|
-
round += 1;
|
|
872
|
-
if (round >= MAX_TOOL_ROUNDS) {
|
|
873
|
-
emitIfAllowed('tool_round_limit_exceeded', {
|
|
874
|
-
maxToolRounds: MAX_TOOL_ROUNDS,
|
|
875
|
-
attempt: toolLimitRecoveryContinuations + 1,
|
|
876
|
-
maxAttempts: MAX_RECOVERY_CONTINUATIONS,
|
|
877
|
-
});
|
|
878
|
-
if (toolLimitRecoveryContinuations >= MAX_RECOVERY_CONTINUATIONS) {
|
|
879
|
-
throw new Error(`LLM exceeded maximum tool rounds (${MAX_TOOL_ROUNDS})`);
|
|
880
|
-
}
|
|
881
|
-
toolLimitRecoveryContinuations += 1;
|
|
882
|
-
useEphemeralContinue = true;
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
return {
|
|
886
|
-
items: allItems,
|
|
887
|
-
usage: totalUsage,
|
|
888
|
-
cost: totalCost > 0 ? totalCost : undefined,
|
|
889
|
-
};
|
|
890
|
-
}
|
|
891
|
-
/** @internal Drain only messages tagged `between-rounds` from the queue. */
|
|
892
|
-
drainBetweenRoundsMessages(queue) {
|
|
893
|
-
const pending = queue.peekAll();
|
|
894
|
-
const toInject = [];
|
|
895
|
-
const keep = [];
|
|
896
|
-
for (const msg of pending) {
|
|
897
|
-
if (msg.deliveryMode === 'between-rounds') {
|
|
898
|
-
toInject.push(msg);
|
|
899
|
-
continue;
|
|
900
|
-
}
|
|
901
|
-
keep.push(msg);
|
|
902
|
-
}
|
|
903
|
-
if (toInject.length === 0) {
|
|
904
|
-
return [];
|
|
905
|
-
}
|
|
906
|
-
// Replace queue contents: re-prepend kept messages in order after draining.
|
|
907
|
-
queue.drainAll();
|
|
908
|
-
for (const msg of keep) {
|
|
909
|
-
queue.enqueue(msg);
|
|
910
|
-
}
|
|
911
|
-
return toInject;
|
|
912
|
-
}
|
|
913
|
-
//#endregion
|
|
914
|
-
async run(s, input, ctx) {
|
|
915
|
-
return execute(s, input, ctx);
|
|
916
|
-
}
|
|
917
|
-
/** Run all layer `init` hooks before the first step executes. Per spec 11,
|
|
918
|
-
* init MUST complete before any recall fires so layer state is populated. */
|
|
919
|
-
async initAndRun(s, input, ctx) {
|
|
920
|
-
const layers = ctx.layers;
|
|
921
|
-
const storage = this.config.storage;
|
|
922
|
-
if (layers && layers.length > 0 && storage) {
|
|
923
|
-
await this.initLayers(layers, ctx, storage);
|
|
924
|
-
}
|
|
925
|
-
return execute(s, input, ctx);
|
|
926
|
-
}
|
|
927
|
-
detachedSpawn(s, input, parentCtx, overrides) {
|
|
928
|
-
const childCtx = this.createContext({
|
|
929
|
-
parent: parentCtx,
|
|
930
|
-
threadId: overrides?.threadId ?? parentCtx.threadId,
|
|
931
|
-
resourceId: overrides?.resourceId ?? parentCtx.resourceId,
|
|
932
|
-
cwdInit: overrides?.cwdInit,
|
|
933
|
-
});
|
|
934
|
-
const promise = this.run(s, input, childCtx);
|
|
935
|
-
return new DetachedHandleImpl(childCtx.id, promise);
|
|
936
|
-
}
|
|
937
|
-
createContext(opts) {
|
|
938
|
-
const { memory: memoryLayers, cwdInit, ...rest } = opts ?? {};
|
|
939
|
-
const effectiveMemory = memoryLayers ?? this._memory;
|
|
940
|
-
const itemSchemas = buildItemSchemaRegistry({
|
|
941
|
-
base: this.itemSchemas,
|
|
942
|
-
layers: effectiveMemory,
|
|
943
|
-
});
|
|
944
|
-
return new ContextImpl({
|
|
945
|
-
...rest,
|
|
946
|
-
harness: this,
|
|
947
|
-
channelStore: this.channelStore,
|
|
948
|
-
layers: effectiveMemory,
|
|
949
|
-
itemSchemas,
|
|
950
|
-
cwdState: resolveContextCwdState(this.rootCwdState, opts?.parent, cwdInit),
|
|
951
|
-
});
|
|
952
|
-
}
|
|
953
|
-
send(channel, value, _ctx) {
|
|
954
|
-
this.channelStore.send(channel, value);
|
|
955
|
-
}
|
|
956
|
-
recv(channel, _ctx, opts) {
|
|
957
|
-
return this.channelStore.recv(channel, opts?.timeout);
|
|
958
|
-
}
|
|
959
|
-
tryRecv(channel, _ctx) {
|
|
960
|
-
return this.channelStore.tryRecv(channel);
|
|
961
|
-
}
|
|
962
|
-
getChannelHandle(channel, executionId) {
|
|
963
|
-
return this.channelStore.getHandle(channel, executionId);
|
|
964
|
-
}
|
|
965
|
-
/** Resolves layer-provided tools and merges with step tools into ctx.unifiedTools. */
|
|
966
|
-
setUnifiedTools(ctx, stepTools) {
|
|
967
|
-
const layers = ctx.layers;
|
|
968
|
-
const layerTools = layers && layers.length > 0 ? resolveLayerTools(layers, this, ctx) : [];
|
|
969
|
-
const allTools = deduplicateTools([
|
|
970
|
-
...stepTools,
|
|
971
|
-
...layerTools,
|
|
972
|
-
]);
|
|
973
|
-
if (allTools.length > 0) {
|
|
974
|
-
const impl = frameworkCast(ctx);
|
|
975
|
-
impl.unifiedTools = allTools;
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
toExecCtx(ctx) {
|
|
979
|
-
return contextToExecCtx(ctx, {
|
|
980
|
-
callModel: (request) => this.callModel(request),
|
|
981
|
-
});
|
|
982
|
-
}
|
|
983
|
-
async initLayers(layers, ctx, storage) {
|
|
984
|
-
await initLayers({
|
|
985
|
-
layers,
|
|
986
|
-
ctx: this.toExecCtx(ctx),
|
|
987
|
-
storage,
|
|
988
|
-
store: this.layerStateStore,
|
|
989
|
-
});
|
|
990
|
-
}
|
|
991
|
-
async recallLayers(layers, input, ctx) {
|
|
992
|
-
return recallLayers({
|
|
993
|
-
layers,
|
|
994
|
-
query: input,
|
|
995
|
-
ctx: this.toExecCtx(ctx),
|
|
996
|
-
log: ctx.itemLog,
|
|
997
|
-
budgets: resolveLayerBudgets(layers),
|
|
998
|
-
store: this.layerStateStore,
|
|
999
|
-
itemSchemas: this.itemSchemas,
|
|
1000
|
-
});
|
|
1001
|
-
}
|
|
1002
|
-
/**
|
|
1003
|
-
* Compute the items array that would be sent to the model on the next turn —
|
|
1004
|
-
* the same arrangement `executeLLM` builds: harness-level memory layers'
|
|
1005
|
-
* recall outputs concatenated with the session's accumulated history.
|
|
1006
|
-
*
|
|
1007
|
-
* Read-mostly: `recallLayers` writes layer-state snapshots to
|
|
1008
|
-
* `layerStateStore` exactly as a real turn would, so successive previews
|
|
1009
|
-
* remain consistent with what the next real turn produces.
|
|
1010
|
-
*/
|
|
1011
|
-
async previewRequestItems(scope) {
|
|
1012
|
-
const threadId = scope?.threadId ?? DEFAULT_THREAD_ID;
|
|
1013
|
-
// Read-only: if the session doesn't exist, treat history as empty rather
|
|
1014
|
-
// than allocating a SessionRunner for a debug/preview call.
|
|
1015
|
-
const existingSession = this.sessions.get(threadId);
|
|
1016
|
-
const historyItems = existingSession
|
|
1017
|
-
? [
|
|
1018
|
-
...existingSession.accumulatedItems,
|
|
1019
|
-
]
|
|
1020
|
-
: [];
|
|
1021
|
-
const ctx = this.createContext({
|
|
1022
|
-
items: historyItems,
|
|
1023
|
-
threadId,
|
|
1024
|
-
memory: this._memory,
|
|
1025
|
-
});
|
|
1026
|
-
const layers = ctx.layers ?? [];
|
|
1027
|
-
if (layers.length === 0) {
|
|
1028
|
-
return historyItems;
|
|
1029
|
-
}
|
|
1030
|
-
const recallResults = await this.recallLayers(layers, '', ctx);
|
|
1031
|
-
const layerOutputItems = recallResults.flatMap((r) => r.items);
|
|
1032
|
-
if (layerOutputItems.length === 0) {
|
|
1033
|
-
return historyItems;
|
|
1034
|
-
}
|
|
1035
|
-
return assembleView({
|
|
1036
|
-
systemPromptItems: [],
|
|
1037
|
-
layerOutputItems,
|
|
1038
|
-
historyItems,
|
|
1039
|
-
});
|
|
1040
|
-
}
|
|
1041
|
-
async storeLayers(layers, response, ctx) {
|
|
1042
|
-
const storage = this.config.storage;
|
|
1043
|
-
if (!storage) {
|
|
1044
|
-
return;
|
|
1045
|
-
}
|
|
1046
|
-
await storeLayers({
|
|
1047
|
-
layers,
|
|
1048
|
-
response,
|
|
1049
|
-
ctx: this.toExecCtx(ctx),
|
|
1050
|
-
log: ctx.itemLog,
|
|
1051
|
-
store: this.layerStateStore,
|
|
1052
|
-
storage,
|
|
1053
|
-
});
|
|
1054
|
-
}
|
|
1055
|
-
async disposeLayers(layers, ctx) {
|
|
1056
|
-
await disposeLayers({
|
|
1057
|
-
layers,
|
|
1058
|
-
ctx: this.toExecCtx(ctx),
|
|
1059
|
-
store: this.layerStateStore,
|
|
1060
|
-
});
|
|
1061
|
-
}
|
|
1062
|
-
async checkpoint(_ctx) { }
|
|
1063
|
-
async restore(_executionId) {
|
|
1064
|
-
return null;
|
|
1065
|
-
}
|
|
1066
|
-
async cancel(_ctx, _reason) { }
|
|
1067
|
-
createSpan(name, parent) {
|
|
1068
|
-
return new SpanImpl(name, parent);
|
|
1069
|
-
}
|
|
1070
|
-
getLayerState(executionId, layerId) {
|
|
1071
|
-
return this.layerStateStore.get(executionId, layerId);
|
|
1072
|
-
}
|
|
1073
|
-
setLayerState(executionId, layerId, state) {
|
|
1074
|
-
this.layerStateStore.set(executionId, layerId, state);
|
|
1075
|
-
}
|
|
1076
|
-
async beforeToolCall(layers, toolName, toolArgs, ctx) {
|
|
1077
|
-
const hasHook = layers.some((l) => l.hooks.beforeToolCall);
|
|
1078
|
-
if (!hasHook) {
|
|
1079
|
-
return {
|
|
1080
|
-
action: SteeringAction.Allow,
|
|
1081
|
-
};
|
|
1082
|
-
}
|
|
1083
|
-
return beforeToolCallLayers({
|
|
1084
|
-
layers,
|
|
1085
|
-
toolName,
|
|
1086
|
-
toolArgs,
|
|
1087
|
-
ctx: this.toExecCtx(ctx),
|
|
1088
|
-
store: this.layerStateStore,
|
|
1089
|
-
});
|
|
1090
|
-
}
|
|
1091
|
-
async afterModelCall(layers, response, ctx) {
|
|
1092
|
-
const hasHook = layers.some((l) => l.hooks.afterModelCall);
|
|
1093
|
-
if (!hasHook) {
|
|
1094
|
-
return {
|
|
1095
|
-
action: SteeringAction.Allow,
|
|
1096
|
-
};
|
|
1097
|
-
}
|
|
1098
|
-
return afterModelCallLayers({
|
|
1099
|
-
layers,
|
|
1100
|
-
response,
|
|
1101
|
-
ctx: this.toExecCtx(ctx),
|
|
1102
|
-
store: this.layerStateStore,
|
|
1103
|
-
});
|
|
1104
|
-
}
|
|
1105
|
-
async projectHistory(layers, items, ctx) {
|
|
1106
|
-
const hasHook = layers.some((l) => l.hooks.projectHistory);
|
|
1107
|
-
if (!hasHook) {
|
|
1108
|
-
return items;
|
|
1109
|
-
}
|
|
1110
|
-
return projectHistoryLayers({
|
|
1111
|
-
layers,
|
|
1112
|
-
items,
|
|
1113
|
-
ctx: this.toExecCtx(ctx),
|
|
1114
|
-
store: this.layerStateStore,
|
|
1115
|
-
});
|
|
1116
|
-
}
|
|
1117
|
-
async runAppendPipeline(layers, items, ctx) {
|
|
1118
|
-
const hasHook = layers.some((l) => l.hooks.onItemAppend);
|
|
1119
|
-
if (!hasHook) {
|
|
1120
|
-
return {
|
|
1121
|
-
items,
|
|
1122
|
-
rerenderRequests: [],
|
|
1123
|
-
};
|
|
1124
|
-
}
|
|
1125
|
-
return runAppendPipeline({
|
|
1126
|
-
layers,
|
|
1127
|
-
items,
|
|
1128
|
-
ctx: this.toExecCtx(ctx),
|
|
1129
|
-
log: ctx.itemLog,
|
|
1130
|
-
store: this.layerStateStore,
|
|
1131
|
-
});
|
|
1132
|
-
}
|
|
1133
|
-
async executeRerender(requests, layers, ctx, budgets, query) {
|
|
1134
|
-
return executeRerender({
|
|
1135
|
-
requests,
|
|
1136
|
-
layers,
|
|
1137
|
-
ctx: this.toExecCtx(ctx),
|
|
1138
|
-
log: ctx.itemLog,
|
|
1139
|
-
budgets,
|
|
1140
|
-
store: this.layerStateStore,
|
|
1141
|
-
query,
|
|
1142
|
-
itemSchemas: this.itemSchemas,
|
|
1143
|
-
});
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
//#endregion
|
|
1147
|
-
//#region Text extraction helper
|
|
1148
|
-
function itemToText(msg) {
|
|
1149
|
-
if (typeof msg.input === 'string') {
|
|
1150
|
-
return msg.input;
|
|
1151
|
-
}
|
|
1152
|
-
const items = Array.isArray(msg.input)
|
|
1153
|
-
? msg.input
|
|
1154
|
-
: [
|
|
1155
|
-
msg.input,
|
|
1156
|
-
];
|
|
1157
|
-
const texts = [];
|
|
1158
|
-
for (const item of items) {
|
|
1159
|
-
if (item.type !== 'message') {
|
|
1160
|
-
continue;
|
|
1161
|
-
}
|
|
1162
|
-
for (const part of item.content) {
|
|
1163
|
-
if (part.type === 'input_text' || part.type === 'output_text') {
|
|
1164
|
-
texts.push(part.text);
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
return texts.join('\n');
|
|
1169
|
-
}
|
|
1170
|
-
//#endregion
|