@peer-agent/runtime-sdk 0.1.0-rc.1 → 0.1.0-rc.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/pipeline-contracts.d.ts +12 -2
- package/dist/pipeline.js +59 -25
- package/dist/runtime.js +7 -1
- package/package.json +3 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RuntimeSdkEvent, RuntimeSdkEventInput, RuntimeSdkToolResult } from './contracts.ts';
|
|
2
|
-
export type RuntimePipelineStatus = 'completed' | 'stopped' | 'cancelled' | 'exhausted' | 'failed';
|
|
2
|
+
export type RuntimePipelineStatus = 'completed' | 'stopped' | 'cancelled' | 'yielded' | 'exhausted' | 'failed';
|
|
3
3
|
export interface RuntimePipelineRunInput<TInput = unknown> {
|
|
4
4
|
readonly sessionId: string;
|
|
5
5
|
readonly streamId?: string;
|
|
@@ -9,6 +9,11 @@ export interface RuntimePipelineRunInput<TInput = unknown> {
|
|
|
9
9
|
readonly model?: string;
|
|
10
10
|
readonly input: TInput;
|
|
11
11
|
readonly maxTurns?: number;
|
|
12
|
+
readonly maxToolCalls?: number;
|
|
13
|
+
/** Scheduling allowance; unlike maxToolCalls this can end at a batch boundary. */
|
|
14
|
+
readonly sliceToolCalls?: number;
|
|
15
|
+
readonly yieldAtTurnLimit?: boolean;
|
|
16
|
+
readonly maxToolBatchCalls?: number;
|
|
12
17
|
}
|
|
13
18
|
export interface RuntimePipelineToolCall<TPayload = unknown> {
|
|
14
19
|
readonly toolCallId: string;
|
|
@@ -49,12 +54,17 @@ export interface RuntimePipelineModelAdapter<TInput = unknown, TState = unknown,
|
|
|
49
54
|
initialize(input: RuntimePipelineRunInput<TInput>, context: RuntimePipelineTurnContext<TInput>): TState | Promise<TState>;
|
|
50
55
|
runTurn(state: TState, context: RuntimePipelineTurnContext<TInput>): RuntimePipelineTurnResult<TState, TCall, TOutput> | Promise<RuntimePipelineTurnResult<TState, TCall, TOutput>>;
|
|
51
56
|
applyToolResults(state: TState, executions: readonly RuntimePipelineToolExecution<TCall, TToolResult>[], context: RuntimePipelineTurnContext<TInput>): TState | Promise<TState>;
|
|
57
|
+
/** Critical durable commit after paired tool results; failure ends execution. */
|
|
58
|
+
checkpoint?(state: TState, executions: readonly RuntimePipelineToolExecution<TCall, TToolResult>[], context: RuntimePipelineTurnContext<TInput>): void | Promise<void>;
|
|
52
59
|
onCompleted?(state: TState, output: TOutput | undefined, context: RuntimePipelineTurnContext<TInput>): void | Promise<void>;
|
|
53
60
|
onStopped?(state: TState, executions: readonly RuntimePipelineToolExecution<TCall, TToolResult>[], context: RuntimePipelineTurnContext<TInput>): void | Promise<void>;
|
|
54
61
|
onCancelled?(state: TState | undefined, context: RuntimePipelineTurnContext<TInput>): void | Promise<void>;
|
|
55
|
-
|
|
62
|
+
onYield?(state: TState, context: RuntimePipelineTurnContext<TInput>): void | Promise<void>;
|
|
63
|
+
onExhausted?(state: TState, context: RuntimePipelineTurnContext<TInput>, reason: 'max_turns_exceeded' | 'max_tool_calls_exceeded'): void | Promise<void>;
|
|
56
64
|
}
|
|
57
65
|
export interface RuntimePipelineToolExecutor<TInput = unknown, TCall extends RuntimePipelineToolCall = RuntimePipelineToolCall, TToolResult = RuntimeSdkToolResult> {
|
|
66
|
+
/** Build a paired, explicit non-execution result without dispatching a capability. */
|
|
67
|
+
notExecuted?(call: TCall, reason: string): RuntimePipelineToolExecution<TCall, TToolResult> | Promise<RuntimePipelineToolExecution<TCall, TToolResult>>;
|
|
58
68
|
execute(call: TCall, context: RuntimePipelineTurnContext<TInput> & {
|
|
59
69
|
readonly index: number;
|
|
60
70
|
}): RuntimePipelineToolExecution<TCall, TToolResult> | Promise<RuntimePipelineToolExecution<TCall, TToolResult>>;
|
package/dist/pipeline.js
CHANGED
|
@@ -34,6 +34,10 @@ export function createRuntimePipeline(options) {
|
|
|
34
34
|
throw new Error('RuntimePipeline run requires sessionId.');
|
|
35
35
|
const signal = context.signal;
|
|
36
36
|
const maxTurns = normalizeMaxTurns(input.maxTurns, defaultMaxTurns);
|
|
37
|
+
const maxToolCalls = Number.isFinite(input.maxToolCalls)
|
|
38
|
+
? Math.max(0, Math.floor(input.maxToolCalls)) : Number.POSITIVE_INFINITY;
|
|
39
|
+
const sliceToolCalls = Number.isFinite(input.sliceToolCalls) ? Math.max(1, input.sliceToolCalls) : Infinity;
|
|
40
|
+
const maxBatch = Number.isFinite(input.maxToolBatchCalls) ? Math.max(1, input.maxToolBatchCalls) : Infinity;
|
|
37
41
|
let state;
|
|
38
42
|
let turns = 0;
|
|
39
43
|
let toolCalls = 0;
|
|
@@ -56,6 +60,15 @@ export function createRuntimePipeline(options) {
|
|
|
56
60
|
signal,
|
|
57
61
|
emit,
|
|
58
62
|
});
|
|
63
|
+
const exhaust = async (reason) => {
|
|
64
|
+
await options.model.onExhausted?.(state, turnContext(turns), reason);
|
|
65
|
+
emit({ type: 'runtime.error', ...eventBase, code: reason, message: reason });
|
|
66
|
+
return { status: 'exhausted', state, turns, toolCalls, reason };
|
|
67
|
+
};
|
|
68
|
+
const yieldRun = async () => {
|
|
69
|
+
await options.model.onYield?.(state, turnContext(turns));
|
|
70
|
+
return { status: 'yielded', state, turns, toolCalls, reason: 'scheduling_slice' };
|
|
71
|
+
};
|
|
59
72
|
try {
|
|
60
73
|
throwIfAborted(signal);
|
|
61
74
|
emit({
|
|
@@ -93,30 +106,64 @@ export function createRuntimePipeline(options) {
|
|
|
93
106
|
...(outcome.reason ? { reason: outcome.reason } : {}),
|
|
94
107
|
};
|
|
95
108
|
}
|
|
109
|
+
// Reserve the whole batch before dispatch. Never execute a partial
|
|
110
|
+
// write batch or exceed a caller's remaining budget inside one turn.
|
|
111
|
+
if (outcome.calls.length > maxBatch)
|
|
112
|
+
throw new Error('tool_batch_too_large');
|
|
113
|
+
if (toolCalls + outcome.calls.length > maxToolCalls) {
|
|
114
|
+
if (options.tools.notExecuted) {
|
|
115
|
+
const denied = await Promise.all(outcome.calls.map(call => options.tools.notExecuted(call, 'max_tool_calls_exceeded')));
|
|
116
|
+
state = await options.model.applyToolResults(state, denied, currentContext);
|
|
117
|
+
await options.model.checkpoint?.(state, denied, currentContext);
|
|
118
|
+
}
|
|
119
|
+
return await exhaust('max_tool_calls_exceeded');
|
|
120
|
+
}
|
|
96
121
|
const executions = [];
|
|
97
122
|
if (isParallelSafeLocalToolBatch(outcome.calls)) {
|
|
98
123
|
const batch = await Promise.all(outcome.calls.map(async (call, index) => {
|
|
124
|
+
if (signal?.aborted && options.tools.notExecuted)
|
|
125
|
+
return options.tools.notExecuted(call, 'cancelled');
|
|
99
126
|
throwIfAborted(signal);
|
|
100
|
-
|
|
101
|
-
...currentContext,
|
|
102
|
-
|
|
103
|
-
|
|
127
|
+
try {
|
|
128
|
+
return await options.tools.execute(call, { ...currentContext, index });
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (!options.tools.notExecuted)
|
|
132
|
+
throw error;
|
|
133
|
+
return options.tools.notExecuted(call, isAbortError(error) ? 'cancelled_outcome_unknown' : 'execution_outcome_unknown');
|
|
134
|
+
}
|
|
104
135
|
}));
|
|
105
136
|
executions.push(...batch);
|
|
106
137
|
toolCalls += batch.length;
|
|
107
138
|
}
|
|
108
139
|
else {
|
|
109
140
|
for (const [index, call] of outcome.calls.entries()) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
141
|
+
let execution;
|
|
142
|
+
if (signal?.aborted && options.tools.notExecuted)
|
|
143
|
+
execution = await options.tools.notExecuted(call, 'cancelled');
|
|
144
|
+
else {
|
|
145
|
+
throwIfAborted(signal);
|
|
146
|
+
try {
|
|
147
|
+
execution = await options.tools.execute(call, { ...currentContext, index });
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (!options.tools.notExecuted)
|
|
151
|
+
throw error;
|
|
152
|
+
execution = await options.tools.notExecuted(call, isAbortError(error) ? 'cancelled_outcome_unknown' : 'execution_outcome_unknown');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
115
155
|
executions.push(execution);
|
|
116
156
|
toolCalls += 1;
|
|
157
|
+
if (execution.terminal && options.tools.notExecuted) {
|
|
158
|
+
for (const remaining of outcome.calls.slice(index + 1))
|
|
159
|
+
executions.push(await options.tools.notExecuted(remaining, 'batch_stopped'));
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
117
162
|
}
|
|
118
163
|
}
|
|
119
164
|
state = await options.model.applyToolResults(state, executions, currentContext);
|
|
165
|
+
throwIfAborted(signal);
|
|
166
|
+
await options.model.checkpoint?.(state, executions, currentContext);
|
|
120
167
|
try {
|
|
121
168
|
await options.lifecycle?.toolResultsApplied?.(state, executions, currentContext);
|
|
122
169
|
}
|
|
@@ -143,23 +190,10 @@ export function createRuntimePipeline(options) {
|
|
|
143
190
|
reason: terminalExecution.terminalReason || 'tool_requested_stop',
|
|
144
191
|
};
|
|
145
192
|
}
|
|
193
|
+
if (toolCalls >= sliceToolCalls)
|
|
194
|
+
return await yieldRun();
|
|
146
195
|
}
|
|
147
|
-
|
|
148
|
-
await options.model.onExhausted?.(state, exhaustedContext);
|
|
149
|
-
const exhaustedReason = 'max_turns_exceeded';
|
|
150
|
-
emit({
|
|
151
|
-
type: 'runtime.error',
|
|
152
|
-
...eventBase,
|
|
153
|
-
code: exhaustedReason,
|
|
154
|
-
message: exhaustedReason,
|
|
155
|
-
});
|
|
156
|
-
return {
|
|
157
|
-
status: 'exhausted',
|
|
158
|
-
state,
|
|
159
|
-
turns,
|
|
160
|
-
toolCalls,
|
|
161
|
-
reason: exhaustedReason,
|
|
162
|
-
};
|
|
196
|
+
return input.yieldAtTurnLimit ? await yieldRun() : await exhaust('max_turns_exceeded');
|
|
163
197
|
}
|
|
164
198
|
catch (error) {
|
|
165
199
|
if (signal?.aborted || isAbortError(error)) {
|
package/dist/runtime.js
CHANGED
|
@@ -130,9 +130,15 @@ export function createRuntimeSdk(options) {
|
|
|
130
130
|
decision: mostRestrictiveHookDecision(postRecords),
|
|
131
131
|
records: postRecords,
|
|
132
132
|
});
|
|
133
|
-
|
|
133
|
+
let result = options.host.appendHookEvidence
|
|
134
134
|
? options.host.appendHookEvidence(execution.result, [...preRecords, ...postRecords], preDecision)
|
|
135
135
|
: execution.result;
|
|
136
|
+
// Host-only sideband must survive a Hook Evidence clone without becoming
|
|
137
|
+
// enumerable event/storage data. Model-supplied fields cannot create it.
|
|
138
|
+
const sideband = Object.getOwnPropertyDescriptor(execution.result, 'modelContext');
|
|
139
|
+
if (result !== execution.result && sideband && sideband.enumerable === false) {
|
|
140
|
+
result = Object.defineProperty({ ...result }, 'modelContext', sideband);
|
|
141
|
+
}
|
|
136
142
|
const resolvedExecution = { ...execution, result };
|
|
137
143
|
emit({ type: 'tool.completed', ...eventBase, decision: preDecision, result });
|
|
138
144
|
return resolvedExecution;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peer-agent/runtime-sdk",
|
|
3
|
-
"version": "0.1.0-rc.
|
|
3
|
+
"version": "0.1.0-rc.11",
|
|
4
4
|
"description": "Public host-neutral orchestration SDK for Peer Agent runtimes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"LICENSE"
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@peer-agent/
|
|
38
|
-
"@peer-agent/
|
|
37
|
+
"@peer-agent/runtime-core": "^0.1.0-rc.11",
|
|
38
|
+
"@peer-agent/protocol": "^0.1.0-rc.11"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"typescript": "^5.9.3"
|