@intx/inference 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter.d.ts +4 -2
- package/dist/adapter.js +2 -2
- package/dist/assembly.d.ts +8 -1
- package/dist/assembly.js +2 -1
- package/dist/authz-extension.d.ts +15 -1
- package/dist/authz-extension.js +93 -9
- package/dist/correlation.d.ts +1 -0
- package/dist/correlation.js +8 -1
- package/dist/default-director.d.ts +1 -1
- package/dist/default-director.js +37 -8
- package/dist/gates.d.ts +1 -0
- package/dist/gates.js +24 -1
- package/dist/harness.js +109 -21
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/providers/anthropic.d.ts +5 -1
- package/dist/providers/anthropic.js +264 -17
- package/dist/providers/google-genai.d.ts +3 -1
- package/dist/providers/google-genai.js +224 -215
- package/dist/providers/index.d.ts +3 -3
- package/dist/providers/index.js +3 -3
- package/dist/providers/openai.d.ts +7 -1
- package/dist/providers/openai.js +353 -59
- package/dist/reactor.js +416 -103
- package/dist/transform.d.ts +1 -1
- package/dist/transform.js +19 -4
- package/package.json +5 -4
package/dist/adapter.d.ts
CHANGED
|
@@ -6,18 +6,20 @@ export type BuiltRequest = {
|
|
|
6
6
|
};
|
|
7
7
|
export type RequestBuilder = (messages: ConversationTurn[], model: string, options: InferenceOptions) => BuiltRequest;
|
|
8
8
|
export type ResponseParser = (sseData: string) => InferenceEvent[];
|
|
9
|
+
export type JSONResponseParser = (body: string) => InferenceEvent[];
|
|
9
10
|
export type RetryAfterExtractor = (headers: Headers) => number | undefined;
|
|
10
11
|
export type PacingExtractor = (headers: Headers) => number | undefined;
|
|
11
12
|
export type ProviderAdapter = {
|
|
12
13
|
buildRequest: RequestBuilder;
|
|
13
14
|
parseResponse: ResponseParser;
|
|
15
|
+
parseJSONResponse: JSONResponseParser;
|
|
14
16
|
extractRetryAfterMs?: RetryAfterExtractor;
|
|
15
17
|
extractPacingDelayMs?: PacingExtractor;
|
|
16
18
|
};
|
|
17
|
-
export type AdapterFactory = (source: LastCycleSource) => ProviderAdapter;
|
|
19
|
+
export type AdapterFactory = (source: LastCycleSource, quirks?: unknown) => ProviderAdapter;
|
|
18
20
|
export type AdapterRegistry = {
|
|
19
21
|
has(provider: string): boolean;
|
|
20
|
-
resolve(source: LastCycleSource): ProviderAdapter;
|
|
22
|
+
resolve(source: LastCycleSource, quirks?: unknown): ProviderAdapter;
|
|
21
23
|
};
|
|
22
24
|
/**
|
|
23
25
|
* Builds an adapter registry from a map of provider identifier to adapter
|
package/dist/adapter.js
CHANGED
|
@@ -20,12 +20,12 @@ export function createAdapterRegistry(factories) {
|
|
|
20
20
|
has(provider) {
|
|
21
21
|
return byProvider.has(provider);
|
|
22
22
|
},
|
|
23
|
-
resolve(source) {
|
|
23
|
+
resolve(source, quirks) {
|
|
24
24
|
const factory = byProvider.get(source.provider);
|
|
25
25
|
if (factory === undefined) {
|
|
26
26
|
throw new Error(`Unknown inference provider: ${source.provider}`);
|
|
27
27
|
}
|
|
28
|
-
return factory(source);
|
|
28
|
+
return factory(source, quirks);
|
|
29
29
|
},
|
|
30
30
|
};
|
|
31
31
|
}
|
package/dist/assembly.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type BlobReader, type AuditStore, type BeforeToolExtension, type Compactor, type ContextStore, type ContextTransform, type InferenceSource, type ReactorDirector, type ToolResultTransform, type ToolRunner } from "@intx/types/runtime";
|
|
1
|
+
import { type BlobReader, type AuditStore, type BeforeToolExtension, type Compactor, type ContextStore, type ContextTransform, type InferenceSource, type ReactorDirector, type ToolDefinition, type ToolResultTransform, type ToolRunner } from "@intx/types/runtime";
|
|
2
2
|
import { type AuditCollector } from "./audit-collector.js";
|
|
3
3
|
import { type AuthzExtensionOptions } from "./authz-extension.js";
|
|
4
4
|
import type { CorrelationValidator } from "./correlation.js";
|
|
@@ -31,6 +31,13 @@ export type ReactorAssemblyConfig = {
|
|
|
31
31
|
contextStore: ContextStore;
|
|
32
32
|
onEvent: (event: ReactorEmittedEvent) => void;
|
|
33
33
|
authorize?: AuthzExtensionOptions["authorize"];
|
|
34
|
+
/**
|
|
35
|
+
* Tool definitions forwarded to the authz extension so it can build the
|
|
36
|
+
* approver-facing snapshot at an `ask` suspension. Only consumed when
|
|
37
|
+
* `authorize` is also supplied. Omitting it puts the authz extension in its
|
|
38
|
+
* no-snapshot mode; the production edge always supplies the resolved set.
|
|
39
|
+
*/
|
|
40
|
+
toolDefinitions?: readonly ToolDefinition[];
|
|
34
41
|
auditStore?: AuditStore;
|
|
35
42
|
beforeToolExtensions?: BeforeToolExtension[];
|
|
36
43
|
toolResultTransforms?: ToolResultTransform[];
|
package/dist/assembly.js
CHANGED
|
@@ -24,7 +24,7 @@ const DEFAULT_SIZE_CAP_MAX_CHARS = 10_000;
|
|
|
24
24
|
* different composition.
|
|
25
25
|
*/
|
|
26
26
|
export function createReactorAssembly(config) {
|
|
27
|
-
const { sessionId, director, source, failOverToNextSource, resetToPreferredSource, toolRunner, contextStore, onEvent, authorize, auditStore, beforeToolExtensions: callerBeforeToolExtensions, toolResultTransforms: callerToolResultTransforms, contextTransforms, compactors, sizeCapMaxChars, afterCheckpoint: callerAfterCheckpoint, onShutdown: callerOnShutdown, deps, correlationValidator, inferenceRunner, gateTimeout, shutdownTimeoutMs, } = config;
|
|
27
|
+
const { sessionId, director, source, failOverToNextSource, resetToPreferredSource, toolRunner, contextStore, onEvent, authorize, toolDefinitions, auditStore, beforeToolExtensions: callerBeforeToolExtensions, toolResultTransforms: callerToolResultTransforms, contextTransforms, compactors, sizeCapMaxChars, afterCheckpoint: callerAfterCheckpoint, onShutdown: callerOnShutdown, deps, correlationValidator, inferenceRunner, gateTimeout, shutdownTimeoutMs, } = config;
|
|
28
28
|
// Audit collector is created up-front so the authz extension can route its
|
|
29
29
|
// decisions through `onDecision`. When no auditStore is supplied, no
|
|
30
30
|
// collector is created and authz runs without decision recording.
|
|
@@ -51,6 +51,7 @@ export function createReactorAssembly(config) {
|
|
|
51
51
|
...(auditCollector !== undefined
|
|
52
52
|
? { onDecision: (d) => auditCollector.onDecision(d) }
|
|
53
53
|
: {}),
|
|
54
|
+
...(toolDefinitions !== undefined ? { toolDefinitions } : {}),
|
|
54
55
|
})
|
|
55
56
|
: undefined;
|
|
56
57
|
const composedBeforeToolExtensions = authzExtension !== undefined
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BeforeToolExtension } from "@intx/types/runtime";
|
|
1
|
+
import type { BeforeToolExtension, ToolDefinition } from "@intx/types/runtime";
|
|
2
2
|
import type { Effect } from "@intx/types/authz";
|
|
3
3
|
export type AuthzMatchedGrant = {
|
|
4
4
|
id: string;
|
|
@@ -28,5 +28,19 @@ export type AuthzDecision = {
|
|
|
28
28
|
export type AuthzExtensionOptions<Ctx = unknown> = {
|
|
29
29
|
authorize: (resource: string, action: string, context: Ctx) => Promise<AuthzCallResult>;
|
|
30
30
|
onDecision?: (decision: AuthzDecision) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Deadline applied to an approval suspension, in milliseconds from the
|
|
33
|
+
* moment the `ask` effect is hit. Defaults to `DEFAULT_APPROVAL_TIMEOUT_MS`.
|
|
34
|
+
*/
|
|
35
|
+
approvalTimeoutMs?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Tool definitions the extension can be asked to authorize, used to build the
|
|
38
|
+
* approver-facing snapshot at the `ask` branch. Presence is a contract: when
|
|
39
|
+
* supplied, every tool this extension authorizes must appear here, and an
|
|
40
|
+
* `ask` for a tool that does not is a wiring defect that throws. Omitted
|
|
41
|
+
* entirely, the extension produces no snapshot — a mode for callers that
|
|
42
|
+
* never register a suspension with the hub.
|
|
43
|
+
*/
|
|
44
|
+
toolDefinitions?: readonly ToolDefinition[];
|
|
31
45
|
};
|
|
32
46
|
export declare function createAuthzExtension<Ctx = unknown>(opts: AuthzExtensionOptions<Ctx>): BeforeToolExtension;
|
package/dist/authz-extension.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// Effects:
|
|
8
8
|
// allow → tool proceeds
|
|
9
9
|
// deny → tool blocked
|
|
10
|
-
// ask → tool
|
|
10
|
+
// ask → tool suspended (parked awaiting an external approval decision)
|
|
11
11
|
// null → tool blocked (fail-closed: no grants matched)
|
|
12
12
|
//
|
|
13
13
|
// The action is always "invoke" — all tool calls are invocations. If
|
|
@@ -20,12 +20,15 @@
|
|
|
20
20
|
// The onDecision callback must not throw. If it does, the exception is
|
|
21
21
|
// logged but swallowed so it cannot interfere with the authorization
|
|
22
22
|
// decision or mask the original error.
|
|
23
|
+
// Default deadline for an approval suspension when the caller does not supply
|
|
24
|
+
// one. Matches the reactor's DEFAULT_GATE_TIMEOUT_MS (one hour); the value is
|
|
25
|
+
// duplicated rather than imported to avoid a dependency from the pure-policy
|
|
26
|
+
// extension onto the reactor module.
|
|
27
|
+
const DEFAULT_APPROVAL_TIMEOUT_MS = 3_600_000;
|
|
23
28
|
function formatBlockReason(effect, resource, action) {
|
|
24
29
|
switch (effect) {
|
|
25
30
|
case "deny":
|
|
26
31
|
return `Denied by policy: ${resource}/${action}`;
|
|
27
|
-
case "ask":
|
|
28
|
-
return `Requires approval: ${resource}/${action}`;
|
|
29
32
|
case null:
|
|
30
33
|
return `No matching grants for ${resource}/${action}`;
|
|
31
34
|
}
|
|
@@ -50,7 +53,25 @@ export function createAuthzExtension(opts) {
|
|
|
50
53
|
// safe default at this layer.
|
|
51
54
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- the inference layer has no domain knowledge to construct a Ctx; callers that need a populated context use closure capture on the authorize function (see @intx/workflow's AuthorizeContext)
|
|
52
55
|
const emptyContext = Object.freeze({});
|
|
56
|
+
// One-shot bypass tokens, keyed on ToolCall.id. A token authorizes a single
|
|
57
|
+
// re-dispatch of an already-approved call to skip the `ask` gate it would
|
|
58
|
+
// otherwise re-hit. Held in memory only, within the resumed reactor cycle
|
|
59
|
+
// that grants and consumes it: a durable allow would outlive the cycle and
|
|
60
|
+
// defeat the one-shot intent, and a crash between grant and consume simply
|
|
61
|
+
// re-drives from the durable log and re-grants.
|
|
62
|
+
const approvedOnce = new Set();
|
|
63
|
+
// Name → definition lookup for building the approval snapshot at the `ask`
|
|
64
|
+
// branch. `undefined` (not merely empty) means the caller wired no tool
|
|
65
|
+
// definitions and wants no snapshot; a defined map means every authorizable
|
|
66
|
+
// tool must be present, so a lookup miss is a wiring defect that throws. The
|
|
67
|
+
// sentinel keeps those two contracts distinguishable at the lookup site.
|
|
68
|
+
const toolDefinitionsByName = opts.toolDefinitions !== undefined
|
|
69
|
+
? new Map(opts.toolDefinitions.map((def) => [def.name, def]))
|
|
70
|
+
: undefined;
|
|
53
71
|
return {
|
|
72
|
+
grantOneShot(id) {
|
|
73
|
+
approvedOnce.add(id);
|
|
74
|
+
},
|
|
54
75
|
async beforeTool(call) {
|
|
55
76
|
const resource = `tool:${call.name}`;
|
|
56
77
|
const action = "invoke";
|
|
@@ -75,10 +96,11 @@ export function createAuthzExtension(opts) {
|
|
|
75
96
|
safeOnDecision(opts.onDecision, decision);
|
|
76
97
|
throw cause;
|
|
77
98
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
99
|
+
// An `ask` effect suspends the call rather than blocking it, so it is
|
|
100
|
+
// neither cleanly blocked nor allowed: the decision records
|
|
101
|
+
// `blocked: false` with no block reason. Only `deny`/null (fail-closed)
|
|
102
|
+
// are blocks.
|
|
103
|
+
const blockReason = result.effect === "deny" || result.effect === null
|
|
82
104
|
? formatBlockReason(result.effect, resource, action)
|
|
83
105
|
: undefined;
|
|
84
106
|
const decision = {
|
|
@@ -89,12 +111,74 @@ export function createAuthzExtension(opts) {
|
|
|
89
111
|
effect: result.effect,
|
|
90
112
|
resolvedBy: result.resolvedBy,
|
|
91
113
|
matchingGrants: result.matchingGrants,
|
|
92
|
-
blocked,
|
|
114
|
+
blocked: blockReason !== undefined,
|
|
93
115
|
blockReason,
|
|
94
116
|
error: undefined,
|
|
95
117
|
};
|
|
96
118
|
safeOnDecision(opts.onDecision, decision);
|
|
97
|
-
|
|
119
|
+
// A one-shot token only authorizes bypassing an `ask` gate. If the
|
|
120
|
+
// resolved effect is anything else, the grant changed underneath the
|
|
121
|
+
// token: drop it and let the normal path decide, rather than silently
|
|
122
|
+
// allowing a call the policy no longer parks.
|
|
123
|
+
if (approvedOnce.has(call.id) && result.effect !== "ask") {
|
|
124
|
+
approvedOnce.delete(call.id);
|
|
125
|
+
}
|
|
126
|
+
if (blockReason !== undefined) {
|
|
127
|
+
return { type: "block", reason: blockReason };
|
|
128
|
+
}
|
|
129
|
+
if (result.effect === "ask") {
|
|
130
|
+
// A prior approval authorized this exact call to run once. Consume the
|
|
131
|
+
// token (delete-on-read) and allow it through instead of suspending,
|
|
132
|
+
// so a re-dispatched approved call does not re-park on its own gate.
|
|
133
|
+
if (approvedOnce.has(call.id)) {
|
|
134
|
+
approvedOnce.delete(call.id);
|
|
135
|
+
return { type: "allow" };
|
|
136
|
+
}
|
|
137
|
+
// Mint the correlationId once here so it is the single source of
|
|
138
|
+
// identity for both the gate and the persisted operation. The
|
|
139
|
+
// reactor persists the operation, so this id survives a restart.
|
|
140
|
+
const correlationId = crypto.randomUUID();
|
|
141
|
+
const timeoutAt = Date.now() + (opts.approvalTimeoutMs ?? DEFAULT_APPROVAL_TIMEOUT_MS);
|
|
142
|
+
const gateId = `pending-${correlationId}`;
|
|
143
|
+
// Build the approver-facing snapshot when tool definitions are wired.
|
|
144
|
+
// A wired extension must have a definition for every tool it can
|
|
145
|
+
// authorize, so a miss is a wiring defect rather than a fallback. An
|
|
146
|
+
// unwired extension produces no snapshot: such callers never register
|
|
147
|
+
// the suspension with the hub, so the downstream required-snapshot
|
|
148
|
+
// validator never sees them.
|
|
149
|
+
let approvalSnapshot;
|
|
150
|
+
if (toolDefinitionsByName !== undefined) {
|
|
151
|
+
const def = toolDefinitionsByName.get(call.name);
|
|
152
|
+
if (def === undefined) {
|
|
153
|
+
throw new Error(`Tool "${call.name}" was authorized with effect "ask" but has ` +
|
|
154
|
+
`no definition in the resolved tool set; the approval ` +
|
|
155
|
+
`snapshot cannot be built. This is a wiring defect: every ` +
|
|
156
|
+
`tool the authz extension can authorize must be present in ` +
|
|
157
|
+
`toolDefinitions.`);
|
|
158
|
+
}
|
|
159
|
+
approvalSnapshot = {
|
|
160
|
+
name: call.name,
|
|
161
|
+
description: def.description,
|
|
162
|
+
inputSchema: def.inputSchema,
|
|
163
|
+
arguments: call.arguments,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const pendingOp = {
|
|
167
|
+
correlationId,
|
|
168
|
+
kind: "approval",
|
|
169
|
+
registeredAt: Date.now(),
|
|
170
|
+
gateId,
|
|
171
|
+
timeoutAt,
|
|
172
|
+
suspendedCall: call,
|
|
173
|
+
...(approvalSnapshot !== undefined ? { approvalSnapshot } : {}),
|
|
174
|
+
};
|
|
175
|
+
return {
|
|
176
|
+
type: "suspend",
|
|
177
|
+
gate: { type: "approval", gateId, correlationId, timeoutAt },
|
|
178
|
+
pendingOp,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return { type: "allow" };
|
|
98
182
|
},
|
|
99
183
|
};
|
|
100
184
|
}
|
package/dist/correlation.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export interface CorrelationValidator {
|
|
|
18
18
|
export declare function createCorrelationRegistry(): {
|
|
19
19
|
register: (op: PendingOperation) => void;
|
|
20
20
|
lookup: (correlationId: string) => PendingOperation | undefined;
|
|
21
|
+
findByGateId: (gateId: string) => PendingOperation | undefined;
|
|
21
22
|
remove: (correlationId: string) => boolean;
|
|
22
23
|
all: () => PendingOperation[];
|
|
23
24
|
hasAny: () => boolean;
|
package/dist/correlation.js
CHANGED
|
@@ -19,6 +19,13 @@ export function createCorrelationRegistry() {
|
|
|
19
19
|
function lookup(correlationId) {
|
|
20
20
|
return operations.get(correlationId);
|
|
21
21
|
}
|
|
22
|
+
function findByGateId(gateId) {
|
|
23
|
+
for (const op of operations.values()) {
|
|
24
|
+
if (op.gateId === gateId)
|
|
25
|
+
return op;
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
22
29
|
function remove(correlationId) {
|
|
23
30
|
return operations.delete(correlationId);
|
|
24
31
|
}
|
|
@@ -28,5 +35,5 @@ export function createCorrelationRegistry() {
|
|
|
28
35
|
function hasAny() {
|
|
29
36
|
return operations.size > 0;
|
|
30
37
|
}
|
|
31
|
-
return { register, lookup, remove, all, hasAny };
|
|
38
|
+
return { register, lookup, findByGateId, remove, all, hasAny };
|
|
32
39
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReactorDirector, type ReactorInboundEvent, type ReactorState, type ReactorCapabilities, type ReactorAction, type AssistantTurn, type ToolDefinition } from "@intx/types/runtime";
|
|
2
2
|
/**
|
|
3
3
|
* Decision returned by an `afterInferenceDone` policy hook.
|
|
4
4
|
*
|
package/dist/default-director.js
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
// inference.error → checkpoint + reply (error message to user)
|
|
11
11
|
// abort → done
|
|
12
12
|
// reactor.gate.cleared → checkpoint + infer (resume after gate)
|
|
13
|
+
// resume.execute_tools → execute_tools (re-run a parked approved call)
|
|
14
|
+
// resume.tool_result → checkpoint + infer (parked call denied/timed out)
|
|
13
15
|
//
|
|
14
16
|
// The inference.done branch additionally runs the optional afterInferenceDone
|
|
15
17
|
// policy hook, whose continue/abort/halt decisions route independently of the
|
|
@@ -18,6 +20,7 @@
|
|
|
18
20
|
// The director never throws. Inference errors are surfaced to the user as a
|
|
19
21
|
// reply so the problem is visible, and the agent remains alive for retries.
|
|
20
22
|
import { getLogger } from "@intx/log";
|
|
23
|
+
import { formatSafetyRatingText, } from "@intx/types/runtime";
|
|
21
24
|
const logger = getLogger(["interchange", "inference", "default-director"]);
|
|
22
25
|
function extractToolCalls(turn) {
|
|
23
26
|
const calls = [];
|
|
@@ -33,14 +36,13 @@ function extractToolCalls(turn) {
|
|
|
33
36
|
return calls;
|
|
34
37
|
}
|
|
35
38
|
function extractTextContent(turn) {
|
|
36
|
-
//
|
|
37
|
-
// output
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
// refusal turn-part); the reply path only needs the words.
|
|
39
|
+
// Text, refusal, and safety_rating blocks all carry human-readable
|
|
40
|
+
// output the connector needs to surface. A refusal-only or
|
|
41
|
+
// safety-only turn would otherwise route through the empty-response
|
|
42
|
+
// branch below and never reach the reply path, leaving the human
|
|
43
|
+
// waiting for an answer the model already declined or blocked.
|
|
44
|
+
// Structural part kinds are preserved at the persistence layer;
|
|
45
|
+
// the reply path only needs the words.
|
|
44
46
|
const parts = [];
|
|
45
47
|
for (const block of turn.content) {
|
|
46
48
|
if (block.type === "text") {
|
|
@@ -49,6 +51,9 @@ function extractTextContent(turn) {
|
|
|
49
51
|
else if (block.type === "refusal") {
|
|
50
52
|
parts.push(block.reason);
|
|
51
53
|
}
|
|
54
|
+
else if (block.type === "safety_rating") {
|
|
55
|
+
parts.push(formatSafetyRatingText(block));
|
|
56
|
+
}
|
|
52
57
|
}
|
|
53
58
|
return parts.join("\n").trim();
|
|
54
59
|
}
|
|
@@ -151,6 +156,30 @@ export class DefaultDirector {
|
|
|
151
156
|
// stop (abort), never because the model produced an empty turn.
|
|
152
157
|
return [capabilities.checkpoint("inference-done"), capabilities.wait()];
|
|
153
158
|
}
|
|
159
|
+
case "resume.execute_tools": {
|
|
160
|
+
// A resumed approval re-runs its parked tool call. The reactor drives
|
|
161
|
+
// the execution; this director owns the outstanding-result count, so
|
|
162
|
+
// seed it to the number of calls about to run — exactly as the
|
|
163
|
+
// inference.done branch seeds it for a fresh tool batch. Without this
|
|
164
|
+
// seed the count stays zero and the re-dispatched call's tool.done
|
|
165
|
+
// would decrement to -1 and re-infer off a negative count by accident.
|
|
166
|
+
this.pendingToolResults = event.calls.length;
|
|
167
|
+
return capabilities.executeTools(event.calls, false, true);
|
|
168
|
+
}
|
|
169
|
+
case "resume.tool_result": {
|
|
170
|
+
// A parked approval ended without running its tool (rejected or timed
|
|
171
|
+
// out). The reactor appends the synthetic error result that answers the
|
|
172
|
+
// parked call, then this re-infers once so the model sees the failure
|
|
173
|
+
// and continues. No tool ran, so pendingToolResults is untouched — the
|
|
174
|
+
// counter only gates batches of real executions.
|
|
175
|
+
return [
|
|
176
|
+
capabilities.checkpoint("resume-tool-result"),
|
|
177
|
+
capabilities.infer({
|
|
178
|
+
systemPrompt: this.systemPrompt,
|
|
179
|
+
tools: this.toolDefinitions,
|
|
180
|
+
}),
|
|
181
|
+
];
|
|
182
|
+
}
|
|
154
183
|
case "tool.done": {
|
|
155
184
|
this.pendingToolResults--;
|
|
156
185
|
if (this.pendingToolResults > 0) {
|
package/dist/gates.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export type GateSnapshot = {
|
|
|
19
19
|
export declare function createGateManager(): {
|
|
20
20
|
register: (gateId: string, type: GateType, timeoutMs: number, correlationId: string | undefined, onCleared: (gateId: string, reason: "resolved" | "timeout" | "shutdown") => void) => Promise<"resolved" | "timeout" | "shutdown">;
|
|
21
21
|
clear: (gateId: string) => boolean;
|
|
22
|
+
clearSilently: (gateId: string) => boolean;
|
|
22
23
|
shutdown: () => void;
|
|
23
24
|
findByCorrelationId: (correlationId: string) => GateRecord | undefined;
|
|
24
25
|
snapshot: () => GateSnapshot[];
|
package/dist/gates.js
CHANGED
|
@@ -50,6 +50,21 @@ export function createGateManager() {
|
|
|
50
50
|
gate.onCleared(gateId, "resolved");
|
|
51
51
|
return true;
|
|
52
52
|
}
|
|
53
|
+
// Clear a gate without invoking its onCleared callback. The caller has
|
|
54
|
+
// already decided how the reactor resumes and does not want the standard
|
|
55
|
+
// cleared-event enqueue that onCleared drives. Used by the approval
|
|
56
|
+
// re-dispatch path, which resumes by re-running the parked tool call rather
|
|
57
|
+
// than by re-inferring off a gate-cleared event: firing onCleared there
|
|
58
|
+
// would enqueue a second, spurious continuation.
|
|
59
|
+
function clearSilently(gateId) {
|
|
60
|
+
const gate = gates.get(gateId);
|
|
61
|
+
if (gate === undefined)
|
|
62
|
+
return false;
|
|
63
|
+
clearTimeout(gate.timer);
|
|
64
|
+
gates.delete(gateId);
|
|
65
|
+
gate.resolve("resolved");
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
53
68
|
function shutdown() {
|
|
54
69
|
const entries = Array.from(gates.values());
|
|
55
70
|
gates.clear();
|
|
@@ -76,5 +91,13 @@ export function createGateManager() {
|
|
|
76
91
|
function has(gateId) {
|
|
77
92
|
return gates.has(gateId);
|
|
78
93
|
}
|
|
79
|
-
return {
|
|
94
|
+
return {
|
|
95
|
+
register,
|
|
96
|
+
clear,
|
|
97
|
+
clearSilently,
|
|
98
|
+
shutdown,
|
|
99
|
+
findByCorrelationId,
|
|
100
|
+
snapshot,
|
|
101
|
+
has,
|
|
102
|
+
};
|
|
80
103
|
}
|