@gethelio/proxy 0.1.0 → 0.1.1

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
@@ -249,20 +249,19 @@ Every tool call recorded: timestamp, agent identity, tool name, inputs, policy d
249
249
 
250
250
  ## How Helio Compares
251
251
 
252
- | | Helio | Guild.ai | JetStream | Cerbos | Salus |
253
- | ----------------------------------- | --------------------- | ----------------- | --------------- | --------------- | -------------- |
254
- | **Approach** | Proxy + thin SDK | Platform runtime | Enterprise SaaS | Sidecar library | In-process SDK |
255
- | **Requires migration** | No | Yes | No | No | Code changes |
256
- | **Time to value** | 5 minutes | Weeks | Weeks | Hours | Minutes |
257
- | **Open source** | Apache 2.0 | | | ✅ Apache 2.0 | |
258
- | **Evidence grounding** | ✅ | | ❌ | | |
259
- | **Self-repair feedback** | ✅ | | | ❌ | |
260
- | **Approval workflows** | ✅ | (in runtime) | | ❌ | ❌ |
261
- | **Transaction controls** | ✅ \* | Basic | Cost tracking | ❌ | ❌ |
262
- | **Audit trail** | (incl. downstream) | ✅ (runtime only) | | Decision logs | ❌ |
263
- | **Cross-platform** | ✅ Any MCP agent | ❌ Guild only | | | ✅ Python only |
264
- | **Governs agents you didn't build** | ✅ | | | | |
265
- | **Language agnostic** | ✅ (proxy) | ✅ | ✅ | ✅ | ❌ Python only |
252
+ | | Helio | Obot | Cerbos | Built-in (Anthropic / OpenAI) | Framework (LangChain / CrewAI) |
253
+ | -------------------------------------------- | -------------------------------------- | ------------------------------ | --------------------------------- | ------------------------------------- | ----------------------------------- |
254
+ | **What it governs** | Per-call actions with cross-call state | Which tools/MCPs are reachable | App-level authorization decisions | Agent permissions inside one platform | Agent behavior inside one framework |
255
+ | **Architecture** | Out-of-process MCP proxy | Out-of-process MCP gateway | Sidecar / library | In-platform | In-framework |
256
+ | **Open source** | Apache 2.0 | ✅ Apache 2.0 | ✅ Apache 2.0 | | Varies |
257
+ | **Time to value** | 5 minutes | Setup-dependent | Hours | Built-in | Built-in |
258
+ | **No agent code changes** | ✅ | | ❌ | ✅ (within platform) | |
259
+ | **Governs agents you didn't build** | ✅ Any MCP agent | ✅ Any MCP agent | ✅ (any app) | ❌ One platform only | ❌ One framework only |
260
+ | **Evidence grounding** | ✅ Cumulative across calls | | ❌ | ❌ | Limited |
261
+ | **Self-repair feedback** | ✅ Structured retry hints | | ❌ | ❌ | Limited |
262
+ | **Stateful spend / rate limits** | ✅ Per-tool, per-session¹ | Basic | | ❌ | Limited |
263
+ | **Approval workflows** | ✅ Slack, webhook, dashboard | | | Limited | Limited |
264
+ | **Audit trail (incl. downstream responses)** | ✅ Captures upstream MCP responses | Decision logs | Decision logs | Platform telemetry | Framework logs |
266
265
 
267
266
  \* Per-tool and per-session spend limits ship in v0.1. Cross-tool spend aggregation is planned for v0.2.
268
267
 
package/dist/cli.js CHANGED
@@ -1198,6 +1198,45 @@ async function parseUpstreamResponse(res) {
1198
1198
  return { status: res.status, headers, body };
1199
1199
  }
1200
1200
 
1201
+ // src/upstream/connection-error.ts
1202
+ var UPSTREAM_DOCS_URL = "https://github.com/gethelio/helio/blob/main/docs/getting-started.md";
1203
+ var UNREACHABLE_CODES = /* @__PURE__ */ new Set([
1204
+ "ECONNREFUSED",
1205
+ "ENOTFOUND",
1206
+ "EAI_AGAIN",
1207
+ "ECONNRESET",
1208
+ "EHOSTUNREACH",
1209
+ "ENETUNREACH",
1210
+ "ETIMEDOUT",
1211
+ "EPIPE",
1212
+ "UND_ERR_CONNECT_TIMEOUT",
1213
+ "UND_ERR_SOCKET"
1214
+ ]);
1215
+ function extractErrorCode(error) {
1216
+ let current = error;
1217
+ for (let depth = 0; depth < 5 && current != null; depth += 1) {
1218
+ if (typeof current === "object" && "code" in current) {
1219
+ const code = current.code;
1220
+ if (typeof code === "string") return code;
1221
+ }
1222
+ current = current.cause;
1223
+ }
1224
+ return void 0;
1225
+ }
1226
+ function describeUnreachableUpstream(error, url) {
1227
+ const code = extractErrorCode(error);
1228
+ const isGenericFetchFailure = error instanceof TypeError && error.message === "fetch failed";
1229
+ if (code !== void 0) {
1230
+ if (!UNREACHABLE_CODES.has(code)) return null;
1231
+ } else if (!isGenericFetchFailure) {
1232
+ return null;
1233
+ }
1234
+ const codeSuffix = code ? ` (${code})` : "";
1235
+ return new Error(
1236
+ `Upstream MCP server at ${url} is unreachable${codeSuffix} \u2014 is it running? Helio proxies an existing MCP server: set upstream.url in helio.yaml to a reachable server, or start the server it points at. See ${UPSTREAM_DOCS_URL}`
1237
+ );
1238
+ }
1239
+
1201
1240
  // src/upstream/forwarder.ts
1202
1241
  var UpstreamForwarder = class {
1203
1242
  url;
@@ -1245,7 +1284,7 @@ var UpstreamForwarder = class {
1245
1284
  if (isTimeout) {
1246
1285
  throw new Error(`upstream request timed out after ${String(this.requestTimeoutMs)}ms`);
1247
1286
  }
1248
- throw error;
1287
+ throw describeUnreachableUpstream(error, this.url) ?? error;
1249
1288
  }
1250
1289
  const durationMs = performance.now() - start;
1251
1290
  const contentType = res.headers.get("content-type") ?? "";
@@ -1406,7 +1445,7 @@ var SseUpstreamForwarder = class {
1406
1445
  );
1407
1446
  return;
1408
1447
  }
1409
- reject(asError);
1448
+ reject(describeUnreachableUpstream(err, this.url) ?? asError);
1410
1449
  }
1411
1450
  });
1412
1451
  });
@@ -1452,7 +1491,7 @@ var SseUpstreamForwarder = class {
1452
1491
  `upstream notification POST timed out after ${String(this.requestTimeoutMs)}ms`
1453
1492
  );
1454
1493
  }
1455
- throw asError;
1494
+ throw describeUnreachableUpstream(error, this.postUrl) ?? asError;
1456
1495
  }
1457
1496
  if (!res.ok) {
1458
1497
  throw new Error(`upstream notification POST failed: HTTP ${String(res.status)}`);
@@ -1499,7 +1538,8 @@ var SseUpstreamForwarder = class {
1499
1538
  const asError = error instanceof Error ? error : new Error(String(error));
1500
1539
  const isTimeout = asError.name === "TimeoutError";
1501
1540
  const isAborted = request.signal?.aborted === true;
1502
- const postFailure = isTimeout ? new Error(`upstream request POST timed out after ${String(this.requestTimeoutMs)}ms`) : asError;
1541
+ const networkFailure = describeUnreachableUpstream(error, this.postUrl) ?? asError;
1542
+ const postFailure = isTimeout ? new Error(`upstream request POST timed out after ${String(this.requestTimeoutMs)}ms`) : networkFailure;
1503
1543
  this.pending.reject(
1504
1544
  requestId,
1505
1545
  isAborted ? new Error("request aborted by downstream client") : postFailure