@tangle-network/agent-provider-cli-bridge 0.7.8 → 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.
@@ -19,7 +19,7 @@ class CliBridgeRunCancelledError extends Error {
19
19
  this.name = "CliBridgeRunCancelledError";
20
20
  }
21
21
  }
22
- export async function* streamCliBridgeTurn(options, turn, requestBody, transport, runId, lastEventId, signal, onResponse, readAuthoritativeRun) {
22
+ export async function* streamCliBridgeTurn(options, turn, requestBody, transport, runId, lastEventId, signal, onResponse, readAuthoritativeRun, validateCanonicalEvent) {
23
23
  const reconnect = requestBody === undefined;
24
24
  const replay = retainedReplayRequest(lastEventId);
25
25
  const response = await transport.fetch(reconnect
@@ -74,7 +74,8 @@ export async function* streamCliBridgeTurn(options, turn, requestBody, transport
74
74
  let textBoundaryPending = false;
75
75
  let sawProtocolEnd = false;
76
76
  let protocol = "unknown";
77
- let canonicalTerminalSeen = false;
77
+ let canonicalTerminalStatus;
78
+ let canonicalTerminalEvent;
78
79
  let terminalFrameSeen = false;
79
80
  let cancelledTerminalSeen = false;
80
81
  let terminalFrame;
@@ -92,7 +93,7 @@ export async function* streamCliBridgeTurn(options, turn, requestBody, transport
92
93
  throw new Error("cli-bridge mixed canonical and OpenAI stream formats");
93
94
  }
94
95
  protocol = "canonical";
95
- if (canonicalTerminalSeen) {
96
+ if (canonicalTerminalStatus !== undefined) {
96
97
  throw new Error("cli-bridge emitted an event after canonical terminal status");
97
98
  }
98
99
  const event = retainedCanonicalEvent(frame, {
@@ -105,8 +106,14 @@ export async function* streamCliBridgeTurn(options, turn, requestBody, transport
105
106
  if (!event) {
106
107
  throw new Error("cli-bridge emitted an empty canonical event");
107
108
  }
108
- canonicalTerminalSeen = isTerminalCanonicalEvent(event);
109
- yield event;
109
+ validateCanonicalEvent?.(event);
110
+ if (isTerminalCanonicalEvent(event)) {
111
+ canonicalTerminalStatus = event.normalized.status;
112
+ canonicalTerminalEvent = event;
113
+ }
114
+ else {
115
+ yield event;
116
+ }
110
117
  continue;
111
118
  }
112
119
  if (protocol === "canonical") {
@@ -253,9 +260,29 @@ export async function* streamCliBridgeTurn(options, turn, requestBody, transport
253
260
  yield* retainedEventsWithIdentity(frameEvents, frame.id, runId, sessionId, turn.executionId, replay.anchor);
254
261
  }
255
262
  if (protocol === "canonical") {
256
- if (!canonicalTerminalSeen) {
263
+ if (canonicalTerminalStatus === undefined) {
257
264
  throw new Error("cli-bridge canonical stream ended without terminal status");
258
265
  }
266
+ const snapshot = await terminalRunSnapshot(runId, readAuthoritativeRun);
267
+ const expectedStatus = canonicalTerminalStatus === "completed"
268
+ ? "done"
269
+ : canonicalTerminalStatus === "failed"
270
+ ? "error"
271
+ : "cancelled";
272
+ if (snapshot.status !== expectedStatus) {
273
+ throw new Error(`cli-bridge canonical terminal status ${JSON.stringify(canonicalTerminalStatus)} contradicts retained run status ${JSON.stringify(snapshot.status)}`);
274
+ }
275
+ if (!canonicalTerminalEvent) {
276
+ throw new Error("cli-bridge canonical terminal event was not retained");
277
+ }
278
+ yield canonicalTerminalEvent;
279
+ return;
280
+ }
281
+ if (!sawProtocolEnd &&
282
+ reconnect &&
283
+ lastEventId !== undefined &&
284
+ protocol === "unknown") {
285
+ await terminalRunSnapshot(runId, readAuthoritativeRun);
259
286
  return;
260
287
  }
261
288
  if (!sawProtocolEnd) {
package/dist/wire.d.ts CHANGED
@@ -1,8 +1,13 @@
1
- import type { TokenUsage } from "@tangle-network/agent-interface";
1
+ import type { AgentProfile, AgentProfileRef, TokenUsage } from "@tangle-network/agent-interface";
2
2
  import type { AgentTurnInput, CreateAgentEnvironmentInput } from "@tangle-network/agent-interface/environment-provider";
3
3
  import type { CliBridgeProviderOptions } from "./provider-options.js";
4
- import type { CliBridgeRunSnapshot } from "./retained-run-state.js";
4
+ import type { CliBridgeRun, CliBridgeRunSnapshot } from "./retained-run-state.js";
5
5
  export declare function toChatCompletionsBody(options: CliBridgeProviderOptions, environmentInput: CreateAgentEnvironmentInput, turn: AgentTurnInput, runId: string): Record<string, unknown>;
6
+ export declare function resolveBridgeModel(options: CliBridgeProviderOptions, environmentInput: CreateAgentEnvironmentInput, turn: AgentTurnInput, profile: AgentProfile | undefined): string;
7
+ export declare function toRetainedSessionBody(options: CliBridgeProviderOptions, environmentInput: CreateAgentEnvironmentInput, profile: AgentProfile | undefined, sessionId: string, model: string): Record<string, unknown>;
8
+ /** Encode the exact retained turn contract without moving posture into metadata. */
9
+ export declare function toRetainedTurnBody(turn: AgentTurnInput, run: CliBridgeRun, providerName: string): Record<string, unknown>;
10
+ export declare function inlineProfile(profile: AgentProfileRef): AgentProfile | undefined;
6
11
  export interface CliBridgeSseFrame {
7
12
  readonly data: string;
8
13
  readonly id?: string;
package/dist/wire.js CHANGED
@@ -1,8 +1,11 @@
1
1
  import { createHash } from "node:crypto";
2
- import { AgentExactRunControlRefSchema } from "@tangle-network/agent-interface";
2
+ import { AgentExactRunControlRefSchema, RequestedInteractionsSchema, } from "@tangle-network/agent-interface";
3
3
  export function toChatCompletionsBody(options, environmentInput, turn, runId) {
4
4
  const profile = inlineProfile(environmentInput.profile);
5
5
  const execution = executionFromInput(options, environmentInput);
6
+ const interactions = turn.interactions === undefined
7
+ ? undefined
8
+ : RequestedInteractionsSchema.parse(turn.interactions);
6
9
  return {
7
10
  model: resolveBridgeModel(options, environmentInput, turn, profile),
8
11
  messages: messagesFromTurn(turn),
@@ -17,6 +20,7 @@ export function toChatCompletionsBody(options, environmentInput, turn, runId) {
17
20
  ...(environmentInput.env ? { env: environmentInput.env } : {}),
18
21
  ...(environmentInput.workspace?.cwd ? { cwd: environmentInput.workspace.cwd } : {}),
19
22
  ...(execution ? { execution } : {}),
23
+ ...(interactions === undefined ? {} : { interactions }),
20
24
  metadata: {
21
25
  ...(environmentInput.metadata ?? {}),
22
26
  ...(turn.context ?? {}),
@@ -24,7 +28,7 @@ export function toChatCompletionsBody(options, environmentInput, turn, runId) {
24
28
  },
25
29
  };
26
30
  }
27
- function resolveBridgeModel(options, environmentInput, turn, profile) {
31
+ export function resolveBridgeModel(options, environmentInput, turn, profile) {
28
32
  const harness = environmentInput.backend ?? profile?.harness;
29
33
  const model = turn.model ?? options.defaultModel ?? profile?.model?.default;
30
34
  const provider = profile?.model?.provider;
@@ -43,6 +47,77 @@ function resolveBridgeModel(options, environmentInput, turn, profile) {
43
47
  return `${harness}/${provider}/${model}`;
44
48
  return `${harness}/${model}`;
45
49
  }
50
+ export function toRetainedSessionBody(options, environmentInput, profile, sessionId, model) {
51
+ const execution = retainedExecutionFromInput(options, environmentInput);
52
+ return {
53
+ id: sessionId,
54
+ model,
55
+ interaction_policy: "interactive",
56
+ ...(options.defaultMode ? { mode: options.defaultMode } : {}),
57
+ ...(environmentInput.workspace?.cwd !== undefined
58
+ ? { cwd: environmentInput.workspace.cwd }
59
+ : {}),
60
+ ...(execution !== undefined ? { execution } : {}),
61
+ ...(profile ? { agent_profile: profile } : {}),
62
+ ...(environmentInput.env !== undefined ? { env: environmentInput.env } : {}),
63
+ ...(environmentInput.metadata !== undefined
64
+ ? { metadata: environmentInput.metadata }
65
+ : {}),
66
+ ...(environmentInput.providerOptions !== undefined
67
+ ? { provider_options: environmentInput.providerOptions }
68
+ : {}),
69
+ };
70
+ }
71
+ /** Encode the exact retained turn contract without moving posture into metadata. */
72
+ export function toRetainedTurnBody(turn, run, providerName) {
73
+ assertRetainedTurnInputSupported(turn);
74
+ const interactions = RequestedInteractionsSchema.parse(turn.interactions ?? {});
75
+ const content = {};
76
+ if (turn.prompt !== undefined) {
77
+ if (turn.prompt.length === 0) {
78
+ throw new Error("cli-bridge retained sessions require a non-empty message");
79
+ }
80
+ content.message = turn.prompt;
81
+ }
82
+ if (turn.parts !== undefined) {
83
+ if (turn.parts.length === 0) {
84
+ throw new Error("cli-bridge retained sessions require non-empty input parts");
85
+ }
86
+ content.parts = turn.parts;
87
+ }
88
+ if (turn.prompt === undefined && turn.parts === undefined) {
89
+ throw new Error("cli-bridge retained sessions require a non-empty message or input parts");
90
+ }
91
+ return {
92
+ ...content,
93
+ turn_id: run.turnId,
94
+ execution_id: run.executionId,
95
+ run_id: run.id,
96
+ provider: providerName,
97
+ environment_id: run.environmentId,
98
+ ...(turn.interactions === undefined ? {} : { interactions }),
99
+ ...(turn.context !== undefined ? { context: turn.context } : {}),
100
+ ...(turn.providerOptions !== undefined
101
+ ? { provider_options: turn.providerOptions }
102
+ : {}),
103
+ };
104
+ }
105
+ function assertRetainedTurnInputSupported(turn) {
106
+ const unsupported = [];
107
+ if (turn.timeoutMs !== undefined)
108
+ unsupported.push("timeoutMs");
109
+ if (turn.controlRef !== undefined)
110
+ unsupported.push("controlRef");
111
+ if (turn.contextTransfer !== undefined)
112
+ unsupported.push("contextTransfer");
113
+ if (turn.nativeContinuation !== undefined)
114
+ unsupported.push("nativeContinuation");
115
+ if (turn.detach === true)
116
+ unsupported.push("detach");
117
+ if (unsupported.length > 0) {
118
+ throw new Error(`cli-bridge retained turns cannot represent ${unsupported.join(", ")}`);
119
+ }
120
+ }
46
121
  /** Keep task messages separate from the profile's harness-owned prompt controls. */
47
122
  function messagesFromTurn(turn) {
48
123
  return [{ role: "user", content: contentFromTurn(turn) }];
@@ -52,7 +127,7 @@ function contentFromTurn(turn) {
52
127
  return turn.parts;
53
128
  return turn.prompt ?? "";
54
129
  }
55
- function inlineProfile(profile) {
130
+ export function inlineProfile(profile) {
56
131
  return typeof profile === "string" ? undefined : profile;
57
132
  }
58
133
  function executionFromInput(options, input) {
@@ -66,6 +141,34 @@ function executionFromInput(options, input) {
66
141
  ...(input.workspace.gitRef ? { gitRef: input.workspace.gitRef } : {}),
67
142
  };
68
143
  }
144
+ function retainedExecutionFromInput(options, input) {
145
+ const unsupported = [];
146
+ if (typeof input.profile === "string")
147
+ unsupported.push("named profile");
148
+ if (input.workspace?.environment !== undefined)
149
+ unsupported.push("workspace.environment");
150
+ if (input.workspace?.image !== undefined)
151
+ unsupported.push("workspace.image");
152
+ if (input.workspace?.repoUrl !== undefined)
153
+ unsupported.push("workspace.repoUrl");
154
+ if (input.workspace?.gitRef !== undefined)
155
+ unsupported.push("workspace.gitRef");
156
+ if (input.workspace?.providerOptions !== undefined) {
157
+ unsupported.push("workspace.providerOptions");
158
+ }
159
+ if (input.resources !== undefined)
160
+ unsupported.push("resources");
161
+ if (input.secrets !== undefined)
162
+ unsupported.push("secrets");
163
+ if (unsupported.length > 0) {
164
+ throw new Error(`cli-bridge retained sessions cannot represent ${unsupported.join(", ")}`);
165
+ }
166
+ const execution = executionFromInput(options, input);
167
+ if (execution?.kind === "sandbox") {
168
+ throw new Error("cli-bridge retained sessions cannot execute in a sandbox; use one-shot execution");
169
+ }
170
+ return execution;
171
+ }
69
172
  export async function* parseSse(body) {
70
173
  const decoder = new TextDecoder();
71
174
  let buffer = "";
@@ -227,7 +330,7 @@ export function runSnapshot(value) {
227
330
  const terminal = parsed.terminal;
228
331
  if (typeof id !== "string" ||
229
332
  (requestDigest !== undefined && !requestDigest.success) ||
230
- !["running", "done", "error", "cancelled"].includes(String(status)) ||
333
+ !["running", "done", "error", "cancelled", "unknown"].includes(String(status)) ||
231
334
  typeof terminal !== "boolean") {
232
335
  throw new Error("cli-bridge run status returned an invalid snapshot");
233
336
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-provider-cli-bridge",
3
- "version": "0.7.8",
4
- "description": "AgentEnvironmentProvider adapter for cli-bridge /v1/chat/completions",
3
+ "version": "0.8.0",
4
+ "description": "AgentEnvironmentProvider adapter for cli-bridge retained sessions and one-shot turns",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "main": "./dist/index.js",
@@ -25,6 +25,12 @@
25
25
  "files": [
26
26
  "dist/index.d.ts",
27
27
  "dist/index.js",
28
+ "dist/environment-identity.d.ts",
29
+ "dist/environment-identity.js",
30
+ "dist/capability-discovery.d.ts",
31
+ "dist/capability-discovery.js",
32
+ "dist/interaction-response.d.ts",
33
+ "dist/interaction-response.js",
28
34
  "dist/observation.d.ts",
29
35
  "dist/observation.js",
30
36
  "dist/provider-options.d.ts",
@@ -39,6 +45,8 @@
39
45
  "dist/retained-event-cursor.js",
40
46
  "dist/retained-execution.d.ts",
41
47
  "dist/retained-execution.js",
48
+ "dist/retained-native.d.ts",
49
+ "dist/retained-native.js",
42
50
  "dist/retained-run-state.d.ts",
43
51
  "dist/retained-run-state.js",
44
52
  "dist/retained-stream.d.ts",
@@ -60,9 +68,10 @@
60
68
  "vitest": "^4.1.5"
61
69
  },
62
70
  "scripts": {
63
- "build": "tsc -p tsconfig.json",
71
+ "build": "pnpm run clean && tsc -p tsconfig.build.json",
64
72
  "check-types": "tsc --noEmit",
65
73
  "clean": "rm -rf dist",
66
- "test": "vitest run"
74
+ "test": "vitest run src",
75
+ "test:integration": "test -n \"$CLI_BRIDGE_INTEGRATION_ROOT\" || { echo 'CLI_BRIDGE_INTEGRATION_ROOT is required; see README' >&2; exit 1; }; vitest run tests/cli-bridge.integration.test.ts"
67
76
  }
68
77
  }