@tangle-network/agent-provider-cli-bridge 0.7.7 → 0.8.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/README.md +41 -0
- package/dist/capability-discovery.d.ts +15 -0
- package/dist/capability-discovery.js +164 -0
- package/dist/environment-identity.d.ts +13 -0
- package/dist/environment-identity.js +87 -0
- package/dist/index.js +83 -12
- package/dist/interaction-response.d.ts +15 -0
- package/dist/interaction-response.js +100 -0
- package/dist/retained-canonical-event.d.ts +18 -0
- package/dist/retained-canonical-event.js +135 -0
- package/dist/retained-control.js +3 -1
- package/dist/retained-environment.d.ts +2 -0
- package/dist/retained-environment.js +49 -7
- package/dist/retained-execution.d.ts +4 -3
- package/dist/retained-execution.js +79 -34
- package/dist/retained-native.d.ts +27 -0
- package/dist/retained-native.js +204 -0
- package/dist/retained-run-state.d.ts +1 -1
- package/dist/retained-run-state.js +4 -0
- package/dist/retained-stream.d.ts +1 -1
- package/dist/retained-stream.js +67 -1
- package/dist/wire.d.ts +8 -2
- package/dist/wire.js +112 -5
- package/package.json +15 -4
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { RuntimeEventEnvelopeSchema, } from "@tangle-network/agent-interface";
|
|
2
|
+
/** Decode one explicitly typed retained-run frame without guessing its wire format. */
|
|
3
|
+
export function retainedCanonicalEvent(frame, context) {
|
|
4
|
+
if (frame.event === undefined)
|
|
5
|
+
return undefined;
|
|
6
|
+
if (frame.event === "error")
|
|
7
|
+
throw retainedCanonicalStreamError(frame.data);
|
|
8
|
+
const envelope = parseEnvelope(frame.data);
|
|
9
|
+
if (envelope.runId !== context.runId) {
|
|
10
|
+
throw new Error("cli-bridge canonical event belongs to another retained run");
|
|
11
|
+
}
|
|
12
|
+
if (frame.event !== envelope.event.type) {
|
|
13
|
+
throw new Error("cli-bridge SSE event type does not match its canonical envelope");
|
|
14
|
+
}
|
|
15
|
+
if (frame.id === undefined) {
|
|
16
|
+
throw new Error("cli-bridge canonical event has no replay cursor");
|
|
17
|
+
}
|
|
18
|
+
if (!isBaseTenInteger(frame.id) ||
|
|
19
|
+
envelope.sequence === 0 ||
|
|
20
|
+
Number(frame.id) !== envelope.sequence) {
|
|
21
|
+
throw new Error("cli-bridge canonical event cursor does not match its sequence");
|
|
22
|
+
}
|
|
23
|
+
const event = envelope.event;
|
|
24
|
+
const usage = usageFromCanonicalEvent(event);
|
|
25
|
+
return {
|
|
26
|
+
type: event.type,
|
|
27
|
+
// AgentSession.events({ since }) accepts a previously emitted event id.
|
|
28
|
+
// The bridge replays by its numeric SSE cursor, while eventId identifies
|
|
29
|
+
// the canonical envelope and is not a replay cursor.
|
|
30
|
+
id: frame.id,
|
|
31
|
+
data: {
|
|
32
|
+
...eventPayload(event),
|
|
33
|
+
eventId: envelope.eventId,
|
|
34
|
+
sequence: envelope.sequence,
|
|
35
|
+
cursor: frame.id,
|
|
36
|
+
runId: context.runId,
|
|
37
|
+
sessionId: context.sessionId,
|
|
38
|
+
...(context.executionId === undefined
|
|
39
|
+
? {}
|
|
40
|
+
: { executionId: context.executionId }),
|
|
41
|
+
...(envelope.occurredAt === undefined
|
|
42
|
+
? {}
|
|
43
|
+
: { occurredAt: envelope.occurredAt }),
|
|
44
|
+
receivedAt: envelope.receivedAt,
|
|
45
|
+
},
|
|
46
|
+
normalized: event,
|
|
47
|
+
...(usage === undefined ? {} : { usage }),
|
|
48
|
+
providerEvent: envelope,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function isTerminalCanonicalEvent(event) {
|
|
52
|
+
return event.normalized?.type === "status" &&
|
|
53
|
+
["completed", "failed", "cancelled"].includes(event.normalized.status);
|
|
54
|
+
}
|
|
55
|
+
function parseEnvelope(data) {
|
|
56
|
+
let value;
|
|
57
|
+
try {
|
|
58
|
+
value = JSON.parse(data);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
throw new Error("cli-bridge canonical event is not valid JSON", { cause: error });
|
|
62
|
+
}
|
|
63
|
+
const parsed = RuntimeEventEnvelopeSchema.safeParse(value);
|
|
64
|
+
if (!parsed.success) {
|
|
65
|
+
throw new Error("cli-bridge emitted an invalid canonical event envelope", {
|
|
66
|
+
cause: parsed.error,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return parsed.data;
|
|
70
|
+
}
|
|
71
|
+
function eventPayload(event) {
|
|
72
|
+
const { type: _type, ...payload } = event;
|
|
73
|
+
return payload;
|
|
74
|
+
}
|
|
75
|
+
function usageFromCanonicalEvent(event) {
|
|
76
|
+
if (event.type !== "raw")
|
|
77
|
+
return undefined;
|
|
78
|
+
const providerEvent = recordValue(event.event);
|
|
79
|
+
if (providerEvent?.type !== "usage")
|
|
80
|
+
return undefined;
|
|
81
|
+
const data = recordValue(providerEvent.data);
|
|
82
|
+
const usage = recordValue(providerEvent.usage) ?? recordValue(data?.usage);
|
|
83
|
+
if (!usage)
|
|
84
|
+
return undefined;
|
|
85
|
+
const inputTokens = nonNegativeInteger(usage.inputTokens);
|
|
86
|
+
const outputTokens = nonNegativeInteger(usage.outputTokens);
|
|
87
|
+
if (inputTokens === undefined || outputTokens === undefined)
|
|
88
|
+
return undefined;
|
|
89
|
+
return {
|
|
90
|
+
inputTokens,
|
|
91
|
+
outputTokens,
|
|
92
|
+
...optionalInteger(usage, "totalTokens"),
|
|
93
|
+
...optionalInteger(usage, "cacheReadInputTokens"),
|
|
94
|
+
...optionalInteger(usage, "cacheCreationInputTokens"),
|
|
95
|
+
...optionalInteger(usage, "reasoningTokens"),
|
|
96
|
+
...optionalNumber(usage, "cost"),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function optionalInteger(source, key) {
|
|
100
|
+
const value = nonNegativeInteger(source[key]);
|
|
101
|
+
return value === undefined ? {} : { [key]: value };
|
|
102
|
+
}
|
|
103
|
+
function optionalNumber(source, key) {
|
|
104
|
+
const value = source[key];
|
|
105
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
106
|
+
? { [key]: value }
|
|
107
|
+
: {};
|
|
108
|
+
}
|
|
109
|
+
function nonNegativeInteger(value) {
|
|
110
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0
|
|
111
|
+
? value
|
|
112
|
+
: undefined;
|
|
113
|
+
}
|
|
114
|
+
function recordValue(value) {
|
|
115
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
116
|
+
? value
|
|
117
|
+
: undefined;
|
|
118
|
+
}
|
|
119
|
+
function isBaseTenInteger(value) {
|
|
120
|
+
return /^(0|[1-9][0-9]*)$/u.test(value) && Number.isSafeInteger(Number(value));
|
|
121
|
+
}
|
|
122
|
+
function retainedCanonicalStreamError(data) {
|
|
123
|
+
let message = "cli-bridge canonical event stream failed";
|
|
124
|
+
try {
|
|
125
|
+
const parsed = recordValue(JSON.parse(data));
|
|
126
|
+
const error = recordValue(parsed?.error);
|
|
127
|
+
if (typeof error?.message === "string" && error.message.length > 0) {
|
|
128
|
+
message = error.message;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// The typed error frame is authoritative even when its detail is malformed.
|
|
133
|
+
}
|
|
134
|
+
return new Error(`cli-bridge: ${message}`);
|
|
135
|
+
}
|
package/dist/retained-control.js
CHANGED
|
@@ -57,7 +57,7 @@ async function performCliBridgeCancellation(options, transport, run) {
|
|
|
57
57
|
throw new Error(`cli-bridge cancel ${response.status}: ${await response.text()}`);
|
|
58
58
|
}
|
|
59
59
|
let snapshot = cancelSnapshot(await response.text());
|
|
60
|
-
assertCliBridgeRunSnapshotIdentity(snapshot, run.id);
|
|
60
|
+
assertCliBridgeRunSnapshotIdentity(snapshot, run.id, run.requestDigest);
|
|
61
61
|
const waitBudgetMs = options.cancelWaitMs ?? 30_000;
|
|
62
62
|
const deadline = Date.now() + waitBudgetMs;
|
|
63
63
|
while (!snapshot.terminal) {
|
|
@@ -166,5 +166,7 @@ export function agentSessionStatusFromRun(snapshot) {
|
|
|
166
166
|
return "failed";
|
|
167
167
|
if (snapshot.status === "cancelled")
|
|
168
168
|
return "cancelled";
|
|
169
|
+
if (snapshot.status === "unknown")
|
|
170
|
+
return "unknown";
|
|
169
171
|
return "running";
|
|
170
172
|
}
|
|
@@ -13,5 +13,7 @@ export interface CreateCliBridgeEnvironmentArgs {
|
|
|
13
13
|
* action the document did not carry.
|
|
14
14
|
*/
|
|
15
15
|
readonly capabilities: AgentEnvironmentCapabilities;
|
|
16
|
+
readonly selectedBackend?: string;
|
|
17
|
+
readonly selectedModel?: string;
|
|
16
18
|
}
|
|
17
19
|
export declare function createCliBridgeEnvironment(args: CreateCliBridgeEnvironmentArgs): AgentEnvironment;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { agentSessionStatusFromRun, cancelCliBridgeRun, cancelExactCliBridgeRun, getCliBridgeRun, } from "./retained-control.js";
|
|
2
2
|
import { addTokenUsage, collectCliBridgeTurnResult, dispatchCliBridgeTurn, streamCliBridgeSessionEvents, streamTrackedCliBridgeTurn, } from "./retained-execution.js";
|
|
3
3
|
import { createExecutionUsageLog, observeCliBridgeEnvironment, } from "./observation.js";
|
|
4
|
+
import { cliBridgeInteractionResponder } from "./interaction-response.js";
|
|
5
|
+
import { assertCliBridgeRequestedInteractions, assertCliBridgeNativeTurnModel, supportsCliBridgeNativeInteractions, } from "./retained-native.js";
|
|
4
6
|
import { assertRunMatchesControlRef, exactControlRefForSession, prepareCliBridgeRun, runFromControlRef, } from "./retained-run-state.js";
|
|
5
7
|
import { createCliBridgeTransport } from "./transport.js";
|
|
6
8
|
export function createCliBridgeEnvironment(args) {
|
|
@@ -8,8 +10,18 @@ export function createCliBridgeEnvironment(args) {
|
|
|
8
10
|
const transport = createCliBridgeTransport(options);
|
|
9
11
|
const runs = new Map();
|
|
10
12
|
const sessions = new Map();
|
|
13
|
+
const nativeSessions = new Map();
|
|
11
14
|
const readers = new Set();
|
|
12
15
|
const usageLog = createExecutionUsageLog();
|
|
16
|
+
const useNativeInteractions = supportsCliBridgeNativeInteractions(args.capabilities, args.selectedBackend);
|
|
17
|
+
const respondToInteraction = useNativeInteractions
|
|
18
|
+
? cliBridgeInteractionResponder({
|
|
19
|
+
options,
|
|
20
|
+
transport,
|
|
21
|
+
providerName,
|
|
22
|
+
environmentId,
|
|
23
|
+
})
|
|
24
|
+
: undefined;
|
|
13
25
|
let destroyed = false;
|
|
14
26
|
let closePromise;
|
|
15
27
|
const stream = async function* (turn) {
|
|
@@ -18,11 +30,15 @@ export function createCliBridgeEnvironment(args) {
|
|
|
18
30
|
if (!args.allowDispatch) {
|
|
19
31
|
throw new Error("a reconstructed cli-bridge environment can only control an existing run");
|
|
20
32
|
}
|
|
21
|
-
|
|
33
|
+
if (useNativeInteractions) {
|
|
34
|
+
assertCliBridgeNativeTurnModel(options, environmentInput, turn, args.selectedModel);
|
|
35
|
+
}
|
|
36
|
+
assertCliBridgeRequestedInteractions(args.capabilities, turn.interactions);
|
|
37
|
+
const prepared = prepareCliBridgeRun(options, environmentInput, turn, environmentId, useNativeInteractions);
|
|
22
38
|
// A streamed run reports usage across its events, so the totals are summed
|
|
23
39
|
// here and recorded as one execution's measurement.
|
|
24
40
|
let streamedUsage;
|
|
25
|
-
for await (const event of streamTrackedCliBridgeTurn(options, environmentInput, prepared, transport, runs, sessions, readers)) {
|
|
41
|
+
for await (const event of streamTrackedCliBridgeTurn(options, environmentInput, providerName, prepared, transport, runs, sessions, readers, nativeSessions, useNativeInteractions)) {
|
|
26
42
|
streamedUsage = addTokenUsage(streamedUsage, event.usage);
|
|
27
43
|
usageLog.record(prepared.run.executionId, streamedUsage);
|
|
28
44
|
yield event;
|
|
@@ -31,6 +47,7 @@ export function createCliBridgeEnvironment(args) {
|
|
|
31
47
|
return {
|
|
32
48
|
id: environmentId,
|
|
33
49
|
provider: providerName,
|
|
50
|
+
capabilities: args.capabilities,
|
|
34
51
|
...(environmentInput.name ? { name: environmentInput.name } : {}),
|
|
35
52
|
status: async (statusOptions) => {
|
|
36
53
|
statusOptions?.signal?.throwIfAborted();
|
|
@@ -43,8 +60,12 @@ export function createCliBridgeEnvironment(args) {
|
|
|
43
60
|
if (!args.allowDispatch) {
|
|
44
61
|
throw new Error("a reconstructed cli-bridge environment cannot dispatch new work");
|
|
45
62
|
}
|
|
63
|
+
if (useNativeInteractions) {
|
|
64
|
+
assertCliBridgeNativeTurnModel(options, environmentInput, turn, args.selectedModel);
|
|
65
|
+
}
|
|
66
|
+
assertCliBridgeRequestedInteractions(args.capabilities, turn.interactions);
|
|
46
67
|
const prepared = prepareCliBridgeRun(options, environmentInput, turn, environmentId, true);
|
|
47
|
-
return dispatchCliBridgeTurn(options, environmentInput, prepared, transport, providerName, runs, sessions);
|
|
68
|
+
return dispatchCliBridgeTurn(options, environmentInput, prepared, transport, providerName, runs, sessions, nativeSessions, useNativeInteractions);
|
|
48
69
|
},
|
|
49
70
|
session(id, sessionOptions) {
|
|
50
71
|
sessionOptions?.signal?.throwIfAborted();
|
|
@@ -58,12 +79,18 @@ export function createCliBridgeEnvironment(args) {
|
|
|
58
79
|
runs,
|
|
59
80
|
sessions,
|
|
60
81
|
readers,
|
|
82
|
+
nativeSessions,
|
|
61
83
|
allowPrompt: args.allowDispatch,
|
|
62
84
|
requestedControlRef: sessionOptions?.controlRef,
|
|
63
85
|
isDestroyed: () => destroyed,
|
|
64
86
|
usageLog,
|
|
87
|
+
capabilities: args.capabilities,
|
|
88
|
+
interactionsEnabled: respondToInteraction !== undefined,
|
|
89
|
+
useNativeInteractions,
|
|
90
|
+
selectedModel: args.selectedModel,
|
|
65
91
|
});
|
|
66
92
|
},
|
|
93
|
+
...(respondToInteraction ? { respondToInteraction } : {}),
|
|
67
94
|
...(args.capabilities.observation
|
|
68
95
|
? {
|
|
69
96
|
async observe(observeOptions) {
|
|
@@ -112,6 +139,7 @@ export function createCliBridgeEnvironment(args) {
|
|
|
112
139
|
}
|
|
113
140
|
await transport.close();
|
|
114
141
|
sessions.clear();
|
|
142
|
+
nativeSessions.clear();
|
|
115
143
|
runs.clear();
|
|
116
144
|
})();
|
|
117
145
|
closePromise = attempt;
|
|
@@ -165,6 +193,15 @@ function createCliBridgeSession(args) {
|
|
|
165
193
|
throw new Error(`cli-bridge session "${args.id}" has no run`);
|
|
166
194
|
return run;
|
|
167
195
|
};
|
|
196
|
+
const respondToInteraction = args.interactionsEnabled
|
|
197
|
+
? cliBridgeInteractionResponder({
|
|
198
|
+
sessionId: args.id,
|
|
199
|
+
options: args.options,
|
|
200
|
+
transport: args.transport,
|
|
201
|
+
providerName: args.providerName,
|
|
202
|
+
environmentId: args.environmentId,
|
|
203
|
+
})
|
|
204
|
+
: undefined;
|
|
168
205
|
return {
|
|
169
206
|
id: args.id,
|
|
170
207
|
get controlRef() {
|
|
@@ -181,11 +218,11 @@ function createCliBridgeSession(args) {
|
|
|
181
218
|
},
|
|
182
219
|
async *events(eventOptions) {
|
|
183
220
|
const run = requireCurrentRun();
|
|
184
|
-
yield* streamCliBridgeSessionEvents(args.options, args.environmentInput, run, args.transport, args.runs, args.readers, eventOptions);
|
|
221
|
+
yield* streamCliBridgeSessionEvents(args.options, args.environmentInput, args.providerName, run, args.transport, args.runs, args.readers, eventOptions);
|
|
185
222
|
},
|
|
186
223
|
async result(resultOptions) {
|
|
187
224
|
const run = requireCurrentRun();
|
|
188
|
-
const result = await collectCliBridgeTurnResult(streamCliBridgeSessionEvents(args.options, args.environmentInput, run, args.transport, args.runs, args.readers, { since: "0", signal: resultOptions?.signal }), run, args.options, args.transport, resultOptions?.signal);
|
|
225
|
+
const result = await collectCliBridgeTurnResult(streamCliBridgeSessionEvents(args.options, args.environmentInput, args.providerName, run, args.transport, args.runs, args.readers, { since: "0", signal: resultOptions?.signal }), run, args.options, args.transport, resultOptions?.signal);
|
|
189
226
|
args.usageLog.record(run.executionId, result.usage);
|
|
190
227
|
return result;
|
|
191
228
|
},
|
|
@@ -196,11 +233,15 @@ function createCliBridgeSession(args) {
|
|
|
196
233
|
if (input.sessionId && input.sessionId !== args.id) {
|
|
197
234
|
throw new Error(`cli-bridge session "${args.id}" cannot prompt session "${input.sessionId}"`);
|
|
198
235
|
}
|
|
236
|
+
if (args.useNativeInteractions) {
|
|
237
|
+
assertCliBridgeNativeTurnModel(args.options, args.environmentInput, input, args.selectedModel);
|
|
238
|
+
}
|
|
239
|
+
assertCliBridgeRequestedInteractions(args.capabilities, input.interactions);
|
|
199
240
|
const prepared = prepareCliBridgeRun(args.options, args.environmentInput, {
|
|
200
241
|
...input,
|
|
201
242
|
sessionId: args.id,
|
|
202
|
-
}, args.environmentId,
|
|
203
|
-
const result = await collectCliBridgeTurnResult(streamTrackedCliBridgeTurn(args.options, args.environmentInput, prepared, args.transport, args.runs, args.sessions, args.readers), prepared.run, args.options, args.transport, input.signal);
|
|
243
|
+
}, args.environmentId, args.useNativeInteractions);
|
|
244
|
+
const result = await collectCliBridgeTurnResult(streamTrackedCliBridgeTurn(args.options, args.environmentInput, args.providerName, prepared, args.transport, args.runs, args.sessions, args.readers, args.nativeSessions, args.useNativeInteractions), prepared.run, args.options, args.transport, input.signal);
|
|
204
245
|
args.usageLog.record(prepared.run.executionId, result.usage);
|
|
205
246
|
return { ...result, sessionId: args.id };
|
|
206
247
|
},
|
|
@@ -212,5 +253,6 @@ function createCliBridgeSession(args) {
|
|
|
212
253
|
const run = requireCurrentRun();
|
|
213
254
|
return cancelExactCliBridgeRun(args.options, args.transport, run, request, cancelOptions?.signal);
|
|
214
255
|
},
|
|
256
|
+
...(respondToInteraction ? { respondToInteraction } : {}),
|
|
215
257
|
};
|
|
216
258
|
}
|
|
@@ -2,10 +2,11 @@ import type { TokenUsage } from "@tangle-network/agent-interface";
|
|
|
2
2
|
import type { AgentEnvironmentEvent, AgentSessionRef, AgentTurnResult, CreateAgentEnvironmentInput } from "@tangle-network/agent-interface/environment-provider";
|
|
3
3
|
import type { CliBridgeProviderOptions } from "./provider-options.js";
|
|
4
4
|
import { type CliBridgeRun, type CliBridgeSessionState, type PreparedCliBridgeRun } from "./retained-run-state.js";
|
|
5
|
+
import { type CliBridgeNativeSessionCache } from "./retained-native.js";
|
|
5
6
|
import type { CliBridgeTransport } from "./transport.js";
|
|
6
|
-
export declare function streamTrackedCliBridgeTurn(options: CliBridgeProviderOptions, environmentInput: CreateAgentEnvironmentInput, prepared: PreparedCliBridgeRun, transport: CliBridgeTransport, runs: Map<string, CliBridgeRun>, sessions: Map<string, CliBridgeSessionState>, readers: Set<AbortController
|
|
7
|
-
export declare function dispatchCliBridgeTurn(options: CliBridgeProviderOptions, environmentInput: CreateAgentEnvironmentInput, prepared: PreparedCliBridgeRun, transport: CliBridgeTransport, providerName: string, runs: Map<string, CliBridgeRun>, sessions: Map<string, CliBridgeSessionState
|
|
8
|
-
export declare function streamCliBridgeSessionEvents(options: CliBridgeProviderOptions, environmentInput: CreateAgentEnvironmentInput, run: CliBridgeRun, transport: CliBridgeTransport, runs: Map<string, CliBridgeRun>, readers: Set<AbortController>, eventOptions?: {
|
|
7
|
+
export declare function streamTrackedCliBridgeTurn(options: CliBridgeProviderOptions, environmentInput: CreateAgentEnvironmentInput, providerName: string, prepared: PreparedCliBridgeRun, transport: CliBridgeTransport, runs: Map<string, CliBridgeRun>, sessions: Map<string, CliBridgeSessionState>, readers: Set<AbortController>, nativeSessions: CliBridgeNativeSessionCache, useNativeInteractions: boolean): AsyncIterable<AgentEnvironmentEvent>;
|
|
8
|
+
export declare function dispatchCliBridgeTurn(options: CliBridgeProviderOptions, environmentInput: CreateAgentEnvironmentInput, prepared: PreparedCliBridgeRun, transport: CliBridgeTransport, providerName: string, runs: Map<string, CliBridgeRun>, sessions: Map<string, CliBridgeSessionState>, nativeSessions: CliBridgeNativeSessionCache, useNativeInteractions: boolean): Promise<AgentSessionRef>;
|
|
9
|
+
export declare function streamCliBridgeSessionEvents(options: CliBridgeProviderOptions, environmentInput: CreateAgentEnvironmentInput, providerName: string, run: CliBridgeRun, transport: CliBridgeTransport, runs: Map<string, CliBridgeRun>, readers: Set<AbortController>, eventOptions?: {
|
|
9
10
|
since?: string;
|
|
10
11
|
executionId?: string;
|
|
11
12
|
signal?: AbortSignal;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { cancelCliBridgeRun, captureCliBridgeRunIdentity, detachCliBridgeReader, getCliBridgeRun, } from "./retained-control.js";
|
|
2
2
|
import { bindCliBridgeRun, bindCliBridgeSession, exactControlRefForRun, restoreCliBridgeRun, restoreCliBridgeSession, } from "./retained-run-state.js";
|
|
3
3
|
import { CliBridgeRequestRejectedError, streamCliBridgeTurn, } from "./retained-stream.js";
|
|
4
|
+
import { beginCliBridgeNativeTurn, } from "./retained-native.js";
|
|
4
5
|
import { requestHeaders, trimSlash } from "./transport.js";
|
|
5
6
|
import { modelRequestCount } from "./wire.js";
|
|
6
|
-
export async function* streamTrackedCliBridgeTurn(options, environmentInput, prepared, transport, runs, sessions, readers) {
|
|
7
|
+
export async function* streamTrackedCliBridgeTurn(options, environmentInput, providerName, prepared, transport, runs, sessions, readers, nativeSessions, useNativeInteractions) {
|
|
7
8
|
const originalTurn = prepared.turn;
|
|
8
9
|
if (originalTurn.detach) {
|
|
9
10
|
throw new Error("cli-bridge provider does not support detached turns");
|
|
@@ -26,9 +27,26 @@ export async function* streamTrackedCliBridgeTurn(options, environmentInput, pre
|
|
|
26
27
|
readers.add(controller);
|
|
27
28
|
let drained = false;
|
|
28
29
|
let threw = false;
|
|
30
|
+
let admitted = false;
|
|
31
|
+
let admissionValidated = false;
|
|
29
32
|
try {
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
if (useNativeInteractions) {
|
|
34
|
+
await beginCliBridgeNativeTurn(options, providerName, environmentInput, turn, run, transport, nativeSessions, signal, () => {
|
|
35
|
+
admitted = true;
|
|
36
|
+
});
|
|
37
|
+
admissionValidated = true;
|
|
38
|
+
for await (const event of streamCliBridgeTurn(options, turn, undefined, transport, run.id, originalTurn.lastEventId, signal, (response) => captureCliBridgeRunIdentity(response, run, false), () => getCliBridgeRun(options, transport, run, 30_000, signal), (event) => assertCanonicalEventBinding(event.normalized, run, providerName))) {
|
|
39
|
+
yield event;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
for await (const event of streamCliBridgeTurn(options, turn, run.requestBody, transport, run.id, originalTurn.lastEventId, signal, (response) => {
|
|
44
|
+
admitted = true;
|
|
45
|
+
captureCliBridgeRunIdentity(response, run, true);
|
|
46
|
+
admissionValidated = true;
|
|
47
|
+
}, () => getCliBridgeRun(options, transport, run, 30_000, signal))) {
|
|
48
|
+
yield event;
|
|
49
|
+
}
|
|
32
50
|
}
|
|
33
51
|
drained = true;
|
|
34
52
|
if (runs.get(run.id) === run)
|
|
@@ -36,7 +54,7 @@ export async function* streamTrackedCliBridgeTurn(options, environmentInput, pre
|
|
|
36
54
|
}
|
|
37
55
|
catch (error) {
|
|
38
56
|
threw = true;
|
|
39
|
-
if (error instanceof CliBridgeRequestRejectedError) {
|
|
57
|
+
if (error instanceof CliBridgeRequestRejectedError && !admitted) {
|
|
40
58
|
restoreCliBridgeRun(run, previousRun, runs);
|
|
41
59
|
restoreCliBridgeSession(run, previousSessionRun, sessions);
|
|
42
60
|
throw error;
|
|
@@ -52,8 +70,13 @@ export async function* streamTrackedCliBridgeTurn(options, environmentInput, pre
|
|
|
52
70
|
if (runs.get(run.id) === run)
|
|
53
71
|
runs.delete(run.id);
|
|
54
72
|
}
|
|
55
|
-
else if (
|
|
56
|
-
|
|
73
|
+
else if ((admitted && !admissionValidated) ||
|
|
74
|
+
originalTurn.signal?.aborted ||
|
|
75
|
+
environmentInput.signal?.aborted) {
|
|
76
|
+
const cancellation = await cancelCliBridgeRun(options, transport, run);
|
|
77
|
+
if (cancellation.requestDigest !== undefined) {
|
|
78
|
+
run.requestDigest = cancellation.requestDigest;
|
|
79
|
+
}
|
|
57
80
|
if (runs.get(run.id) === run)
|
|
58
81
|
runs.delete(run.id);
|
|
59
82
|
}
|
|
@@ -68,7 +91,7 @@ export async function* streamTrackedCliBridgeTurn(options, environmentInput, pre
|
|
|
68
91
|
readers.delete(controller);
|
|
69
92
|
}
|
|
70
93
|
}
|
|
71
|
-
export async function dispatchCliBridgeTurn(options, environmentInput, prepared, transport, providerName, runs, sessions) {
|
|
94
|
+
export async function dispatchCliBridgeTurn(options, environmentInput, prepared, transport, providerName, runs, sessions, nativeSessions, useNativeInteractions) {
|
|
72
95
|
const run = prepared.run;
|
|
73
96
|
const previousRun = bindCliBridgeRun(run, runs);
|
|
74
97
|
const previousSessionRun = bindCliBridgeSession(run, sessions);
|
|
@@ -80,35 +103,44 @@ export async function dispatchCliBridgeTurn(options, environmentInput, prepared,
|
|
|
80
103
|
let accepted = false;
|
|
81
104
|
let responseBody;
|
|
82
105
|
try {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
if (useNativeInteractions) {
|
|
107
|
+
await beginCliBridgeNativeTurn(options, providerName, environmentInput, prepared.turn, run, transport, nativeSessions, signal, () => {
|
|
108
|
+
accepted = true;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
const response = await transport.fetch(`${trimSlash(options.baseUrl)}/v1/chat/completions`, {
|
|
113
|
+
method: "POST",
|
|
114
|
+
headers: {
|
|
115
|
+
...requestHeaders(options),
|
|
116
|
+
accept: "text/event-stream",
|
|
117
|
+
...(run.sessionId ? { "x-session-id": run.sessionId } : {}),
|
|
118
|
+
},
|
|
119
|
+
body: run.requestBody,
|
|
120
|
+
...(signal ? { signal } : {}),
|
|
121
|
+
});
|
|
122
|
+
if (!response.ok) {
|
|
123
|
+
let detail = "request rejected";
|
|
124
|
+
try {
|
|
125
|
+
detail = await response.text();
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// The HTTP status already proves this request was rejected.
|
|
129
|
+
}
|
|
130
|
+
throw new CliBridgeRequestRejectedError(response.status, detail);
|
|
100
131
|
}
|
|
101
|
-
|
|
132
|
+
accepted = true;
|
|
133
|
+
if (!response.body)
|
|
134
|
+
throw new Error("cli-bridge response body is empty");
|
|
135
|
+
responseBody = response.body;
|
|
136
|
+
captureCliBridgeRunIdentity(response, run, true);
|
|
102
137
|
}
|
|
103
|
-
accepted = true;
|
|
104
|
-
if (!response.body)
|
|
105
|
-
throw new Error("cli-bridge response body is empty");
|
|
106
|
-
responseBody = response.body;
|
|
107
|
-
captureCliBridgeRunIdentity(response, run, true);
|
|
108
138
|
const controlRef = exactControlRefForRun(run, providerName);
|
|
109
139
|
run.controlRef = controlRef;
|
|
110
|
-
|
|
111
|
-
|
|
140
|
+
if (responseBody) {
|
|
141
|
+
await detachCliBridgeReader(responseBody);
|
|
142
|
+
responseBody = undefined;
|
|
143
|
+
}
|
|
112
144
|
return {
|
|
113
145
|
id: run.sessionId,
|
|
114
146
|
provider: providerName,
|
|
@@ -151,7 +183,7 @@ export async function dispatchCliBridgeTurn(options, environmentInput, prepared,
|
|
|
151
183
|
throw failure;
|
|
152
184
|
}
|
|
153
185
|
}
|
|
154
|
-
export async function* streamCliBridgeSessionEvents(options, environmentInput, run, transport, runs, readers, eventOptions) {
|
|
186
|
+
export async function* streamCliBridgeSessionEvents(options, environmentInput, providerName, run, transport, runs, readers, eventOptions) {
|
|
155
187
|
if (eventOptions?.executionId !== undefined &&
|
|
156
188
|
eventOptions.executionId !== run.executionId) {
|
|
157
189
|
throw new Error("cli-bridge event request targets another retained execution");
|
|
@@ -171,7 +203,7 @@ export async function* streamCliBridgeSessionEvents(options, environmentInput, r
|
|
|
171
203
|
...(run.sessionId ? { sessionId: run.sessionId } : {}),
|
|
172
204
|
executionId: run.executionId,
|
|
173
205
|
turnId: run.turnId,
|
|
174
|
-
}, undefined, transport, run.id, eventOptions?.since ?? "0", signal, (response) => captureCliBridgeRunIdentity(response, run, false), () => getCliBridgeRun(options, transport, run, 30_000, signal));
|
|
206
|
+
}, undefined, transport, run.id, eventOptions?.since ?? "0", signal, (response) => captureCliBridgeRunIdentity(response, run, false), () => getCliBridgeRun(options, transport, run, 30_000, signal), (event) => assertCanonicalEventBinding(event.normalized, run, providerName));
|
|
175
207
|
drained = true;
|
|
176
208
|
if (runs.get(run.id) === run)
|
|
177
209
|
runs.delete(run.id);
|
|
@@ -184,6 +216,19 @@ export async function* streamCliBridgeSessionEvents(options, environmentInput, r
|
|
|
184
216
|
readers.delete(controller);
|
|
185
217
|
}
|
|
186
218
|
}
|
|
219
|
+
function assertCanonicalEventBinding(event, run, providerName) {
|
|
220
|
+
if (event?.type !== "interaction")
|
|
221
|
+
return;
|
|
222
|
+
const binding = event.request.binding;
|
|
223
|
+
if (binding.runId !== run.id ||
|
|
224
|
+
binding.provider !== providerName ||
|
|
225
|
+
binding.environmentId !== run.environmentId ||
|
|
226
|
+
binding.sessionId !== run.sessionId ||
|
|
227
|
+
binding.executionId !== run.executionId ||
|
|
228
|
+
binding.interactionId !== event.request.id) {
|
|
229
|
+
throw new Error("cli-bridge interaction event does not bind to the retained run");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
187
232
|
export async function collectCliBridgeTurnResult(source, run, options, transport, signal) {
|
|
188
233
|
const events = [];
|
|
189
234
|
let text = "";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AgentEnvironmentCapabilities, AgentTurnInput, CreateAgentEnvironmentInput } from "@tangle-network/agent-interface/environment-provider";
|
|
2
|
+
import { type Sha256Digest, type RequestedInteractions } from "@tangle-network/agent-interface";
|
|
3
|
+
import type { CliBridgeProviderOptions } from "./provider-options.js";
|
|
4
|
+
import type { CliBridgeRun } from "./retained-run-state.js";
|
|
5
|
+
import type { CliBridgeTransport } from "./transport.js";
|
|
6
|
+
/** The server-owned native session identity cached by one provider handle. */
|
|
7
|
+
export interface CliBridgeNativeSession {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly model: string;
|
|
10
|
+
readonly createRequestDigest: Sha256Digest;
|
|
11
|
+
}
|
|
12
|
+
export type CliBridgeNativeSessionCache = Map<string, Promise<CliBridgeNativeSession>>;
|
|
13
|
+
/** Interactions are valid only for the native retained Pi contract. */
|
|
14
|
+
export declare function supportsCliBridgeNativeInteractions(capabilities: AgentEnvironmentCapabilities, selectedBackend?: string): boolean;
|
|
15
|
+
/** Reject posture the selected environment did not advertise before transport use. */
|
|
16
|
+
export declare function assertCliBridgeRequestedInteractions(capabilities: AgentEnvironmentCapabilities, requested: RequestedInteractions | undefined): void;
|
|
17
|
+
/** Keep capability discovery and every native turn on one exact model route. */
|
|
18
|
+
export declare function assertCliBridgeNativeTurnModel(options: CliBridgeProviderOptions, environmentInput: CreateAgentEnvironmentInput, turn: AgentTurnInput, selectedModel: string | undefined): void;
|
|
19
|
+
/**
|
|
20
|
+
* Create or reuse the server's retained native session for one external id.
|
|
21
|
+
*
|
|
22
|
+
* The cache stores the promise so concurrent first turns cannot create two
|
|
23
|
+
* native sessions for the same id.
|
|
24
|
+
*/
|
|
25
|
+
export declare function ensureCliBridgeNativeSession(options: CliBridgeProviderOptions, environmentInput: CreateAgentEnvironmentInput, turn: AgentTurnInput, sessionId: string, transport: CliBridgeTransport, sessions: CliBridgeNativeSessionCache, signal?: AbortSignal): Promise<CliBridgeNativeSession>;
|
|
26
|
+
/** Admit one native retained turn and bind its server digest to the run. */
|
|
27
|
+
export declare function beginCliBridgeNativeTurn(options: CliBridgeProviderOptions, providerName: string, environmentInput: CreateAgentEnvironmentInput, turn: AgentTurnInput, run: CliBridgeRun, transport: CliBridgeTransport, sessions: CliBridgeNativeSessionCache, signal?: AbortSignal, onAdmission?: () => void): Promise<void>;
|