@tangle-network/agent-provider-cli-bridge 0.2.27 → 0.2.28

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 CHANGED
@@ -11,3 +11,6 @@ const provider = createCliBridgeProvider({
11
11
  defaultModel: 'codex',
12
12
  })
13
13
  ```
14
+
15
+ Response headers and streamed bodies have no transport timeout by default.
16
+ For unattended runs, set `headersTimeoutMs`, `bodyTimeoutMs`, or an `AbortSignal` so an unresponsive bridge cannot wait forever.
package/dist/index.d.ts CHANGED
@@ -13,6 +13,10 @@ export interface CliBridgeProviderOptions {
13
13
  capability?: string;
14
14
  ttlSeconds?: number;
15
15
  };
16
+ /** Maximum wait for response headers. Defaults to no timeout. */
17
+ headersTimeoutMs?: number;
18
+ /** Maximum idle time between response body chunks. Defaults to no timeout. */
19
+ bodyTimeoutMs?: number;
16
20
  fetch?: typeof fetch;
17
21
  name?: string;
18
22
  capabilities?: AgentEnvironmentCapabilities;
package/dist/index.js CHANGED
@@ -1,29 +1,40 @@
1
+ import { Agent, fetch as undiciFetch } from "undici";
1
2
  export function createCliBridgeProvider(options) {
2
- const fetchImpl = options.fetch ?? globalThis.fetch;
3
- if (!fetchImpl)
4
- throw new Error("createCliBridgeProvider requires fetch");
3
+ assertTimeout(options.headersTimeoutMs, "headersTimeoutMs");
4
+ assertTimeout(options.bodyTimeoutMs, "bodyTimeoutMs");
5
5
  const name = options.name ?? "cli-bridge";
6
6
  return {
7
7
  name,
8
8
  capabilities: () => options.capabilities ?? defaultCliBridgeCapabilities(),
9
9
  async create(input) {
10
+ const transport = createTransport(options);
11
+ let destroyed = false;
12
+ let closePromise;
10
13
  return {
11
14
  id: input.idempotencyKey ?? input.name ?? crypto.randomUUID(),
12
15
  provider: name,
13
16
  ...(input.name ? { name: input.name } : {}),
14
- status: async () => "running",
15
- stream: (turn) => streamCliBridgeTurn(fetchImpl, options, input, turn),
17
+ status: async () => (destroyed ? "stopped" : "running"),
18
+ stream: (turn) => {
19
+ if (destroyed)
20
+ throw new Error("cli-bridge environment is destroyed");
21
+ return streamCliBridgeTurn(options, input, turn, transport);
22
+ },
16
23
  placement: async () => ({
17
24
  kind: options.defaultExecution?.kind === "sandbox" ? "sandbox" : "local",
18
25
  providerMetadata: { baseUrl: options.baseUrl },
19
26
  }),
20
- destroy: async () => { },
27
+ destroy: () => {
28
+ destroyed = true;
29
+ closePromise ??= transport.close();
30
+ return closePromise;
31
+ },
21
32
  };
22
33
  },
23
34
  };
24
35
  }
25
- async function* streamCliBridgeTurn(fetchImpl, options, environmentInput, turn) {
26
- const response = await fetchImpl(`${trimSlash(options.baseUrl)}/v1/chat/completions`, {
36
+ async function* streamCliBridgeTurn(options, environmentInput, turn, transport) {
37
+ const response = await transport.fetch(`${trimSlash(options.baseUrl)}/v1/chat/completions`, {
27
38
  method: "POST",
28
39
  headers: {
29
40
  "content-type": "application/json",
@@ -46,7 +57,7 @@ async function* streamCliBridgeTurn(fetchImpl, options, environmentInput, turn)
46
57
  let completed = false;
47
58
  for await (const event of parseSse(response.body)) {
48
59
  if (event === "[DONE]")
49
- break;
60
+ continue;
50
61
  const parsed = safeJson(event);
51
62
  if (!parsed)
52
63
  continue;
@@ -129,6 +140,33 @@ async function* streamCliBridgeTurn(fetchImpl, options, environmentInput, turn)
129
140
  if (!completed)
130
141
  throw new Error("cli-bridge stream ended without a terminal result");
131
142
  }
143
+ function createTransport(options) {
144
+ if (options.fetch) {
145
+ const fetch = options.fetch;
146
+ return {
147
+ fetch: (input, init) => fetch(input, init),
148
+ close: async () => { },
149
+ };
150
+ }
151
+ const dispatcher = new Agent({
152
+ headersTimeout: options.headersTimeoutMs ?? 0,
153
+ bodyTimeout: options.bodyTimeoutMs ?? 0,
154
+ });
155
+ return {
156
+ fetch: (input, init) => undiciFetch(input, {
157
+ ...init,
158
+ dispatcher,
159
+ }),
160
+ close: async () => {
161
+ await dispatcher.close();
162
+ },
163
+ };
164
+ }
165
+ function assertTimeout(value, name) {
166
+ if (value !== undefined && (!Number.isInteger(value) || value < 0)) {
167
+ throw new Error(`createCliBridgeProvider ${name} must be a non-negative integer`);
168
+ }
169
+ }
132
170
  function toChatCompletionsBody(options, environmentInput, turn) {
133
171
  const profile = inlineProfile(environmentInput.profile);
134
172
  return {
@@ -176,33 +214,24 @@ function executionFromInput(options, input) {
176
214
  };
177
215
  }
178
216
  async function* parseSse(body) {
179
- const reader = body.getReader();
180
217
  const decoder = new TextDecoder();
181
218
  let buffer = "";
182
- try {
183
- while (true) {
184
- const { done, value } = await reader.read();
185
- if (done)
186
- break;
187
- buffer += decoder.decode(value, { stream: true });
188
- let boundary = findFrameBoundary(buffer);
189
- while (boundary) {
190
- const frame = buffer.slice(0, boundary.index);
191
- buffer = buffer.slice(boundary.index + boundary.length);
192
- const data = dataFromFrame(frame);
193
- if (data !== undefined)
194
- yield data;
195
- boundary = findFrameBoundary(buffer);
196
- }
197
- }
198
- if (buffer) {
199
- const data = dataFromFrame(buffer);
219
+ for await (const value of body) {
220
+ buffer += decoder.decode(value, { stream: true });
221
+ let boundary = findFrameBoundary(buffer);
222
+ while (boundary) {
223
+ const frame = buffer.slice(0, boundary.index);
224
+ buffer = buffer.slice(boundary.index + boundary.length);
225
+ const data = dataFromFrame(frame);
200
226
  if (data !== undefined)
201
227
  yield data;
228
+ boundary = findFrameBoundary(buffer);
202
229
  }
203
230
  }
204
- finally {
205
- reader.releaseLock();
231
+ if (buffer) {
232
+ const data = dataFromFrame(buffer);
233
+ if (data !== undefined)
234
+ yield data;
206
235
  }
207
236
  }
208
237
  function findFrameBoundary(value) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-provider-cli-bridge",
3
- "version": "0.2.27",
3
+ "version": "0.2.28",
4
4
  "description": "AgentEnvironmentProvider adapter for cli-bridge /v1/chat/completions",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -29,7 +29,8 @@
29
29
  "LICENSE"
30
30
  ],
31
31
  "dependencies": {
32
- "@tangle-network/agent-interface": "0.34.0"
32
+ "@tangle-network/agent-interface": "0.34.0",
33
+ "undici": "7.28.0"
33
34
  },
34
35
  "devDependencies": {
35
36
  "@types/node": "25.6.0",