pi-freeflow 1.15.1 → 1.16.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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.16.0
4
+
5
+ ### Minor Changes
6
+
7
+ - New free model: Union Alpha Free joins the picker. It answers through a different upstream endpoint than the other free models, handled automatically — 262K context, image input, tool calling, and zero cost like the rest of the catalog.
8
+
9
+ ## 1.15.2
10
+
11
+ ### Patch Changes
12
+
13
+ - Relay traffic is now spent only on the free models the pool exists for: requests for other models go straight upstream even with the relay pool on, cutting relay bandwidth. Applies in automatic and always-on modes; relay-off behavior is unchanged.
14
+ - The disabled-deployment rollover now triggers only on genuine hosting verdicts, so ordinary payment and quota refusals always surface immediately without cooling a healthy relay. Relay-list imports also no longer show a negative removal count when the file holds more relays than the current pool.
15
+
3
16
  ## 1.15.1
4
17
 
5
18
  ### Patch Changes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-freeflow",
3
3
  "type": "module",
4
- "version": "1.15.1",
4
+ "version": "1.16.0",
5
5
  "description": "Thin provider for OMP/Pi — model list + dumb relay proxy + log; host pi-ai owns thinking/normalization",
6
6
  "main": "extensions/index.ts",
7
7
  "types": "src/index.ts",
package/src/catalog.ts CHANGED
@@ -77,7 +77,7 @@ export function sanitizeCatalogModels(models: RegisteredModel[]): RegisteredMode
77
77
  }
78
78
  /**
79
79
  * In-memory cache of currently active/available free models.
80
- * Initialized with all 26 verified models for 0ms instant availability.
80
+ * Initialized with all 27 verified models for 0ms instant availability.
81
81
  */
82
82
  let aliveCatalog: RegisteredModel[] = ALL_MODELS.map((m) => ({
83
83
  ...m,
@@ -400,6 +400,6 @@ export async function refreshCatalog(force = false): Promise<RegisteredModel[]>
400
400
  } catch (err) {
401
401
  logDebug("Failed reading stale catalog cache", { error: String(err) });
402
402
  }
403
- // No valid cache — return in-memory static 26 (host will refresh if needed)
403
+ // No valid cache — return in-memory static 27 (host will refresh if needed)
404
404
  return aliveCatalog;
405
405
  }
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * pi-freeflow — Modular, high-resiliency LLM extension for Pi & Oh My Pi (OMP)
3
3
  *
4
- * Provides access to 26 free models (7 OpenCode Zen + 19 KiloCode Gateway) with:
4
+ * Provides access to 27 free models (8 OpenCode Zen + 19 KiloCode Gateway) with:
5
5
  * - Single-port daemon reuse on 28180 across concurrent subagents
6
6
  * - Multi-cloud rolling egress relays (Vercel Edge, Cloudflare, Deno)
7
7
  * - 0ms instant startup with verified static catalog and background live health checks
package/src/models.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Static model definitions and upstream routing catalogs for pi-freeflow
3
3
  *
4
- * Defines the 26 verified free models:
5
- * - 7 OpenCode Zen models (2 Responses API + 5 Chat Completions)
4
+ * Defines the 27 verified free models:
5
+ * - 8 OpenCode Zen models (2 Responses API + 5 Chat Completions + 1 Anthropic Messages)
6
6
  * - 19 KiloCode Keyless Gateway models (18 OpenRouter format + 1 Standard format)
7
7
  */
8
8
 
@@ -127,6 +127,19 @@ export const OPENCODE_MODELS: ModelDef[] = [
127
127
  xhigh: "xhigh",
128
128
  max: null,},
129
129
  },
130
+ {
131
+ id: "union-alpha",
132
+ name: "Union Alpha Free",
133
+ reasoning: true,
134
+ contextWindow: 262_144,
135
+ maxTokens: 131_072,
136
+ api: "anthropic-messages",
137
+ input: ["text", "image"],
138
+ // No thinkingLevelMap: the effort wire values for this endpoint are
139
+ // unknown, so the host sends no effort param. The model answers
140
+ // anyway (proven live 2026-09-17: POST /zen/v1/messages streams real
141
+ // tokens at zero cost). Followup: probe effort values, then add a map.
142
+ },
130
143
  ];
131
144
 
132
145
  /**
@@ -389,7 +402,7 @@ export const KILO_MODEL_IDS = new Set<string>([
389
402
  ]);
390
403
 
391
404
  /**
392
- * Combined list of all 26 static free models (canonical)
405
+ * Combined list of all 27 static free models (canonical)
393
406
  */
394
407
  export const ALL_MODELS: ModelDef[] = [...OPENCODE_MODELS, ...KILO_MODELS];
395
408
 
package/src/proxy.ts CHANGED
@@ -89,6 +89,21 @@ function withRateLimitHint(status: number, data: string): string {
89
89
  return data;
90
90
  }
91
91
 
92
+ /**
93
+ * Relay eligibility: only free-catalog models ride relay egress. Returns true
94
+ * when the model is a non-empty string present in MODEL_MAP or KILO_MODEL_IDS
95
+ * (Kilo traffic leaves via its own branch regardless), and when the model is
96
+ * missing/empty (e.g. GET /v1/models carries no body — preserve routing).
97
+ * Anything else (unknown/paid model ids, non-strings) goes direct upstream.
98
+ */
99
+ export function isRelayEligibleModel(model: unknown): boolean {
100
+ if (model === undefined || model === null) return true;
101
+ if (typeof model !== "string") return false;
102
+ if (model.trim() === "") return true;
103
+ const canonical = resolveCanonicalModelId(model.trim());
104
+ return MODEL_MAP.has(canonical) || KILO_MODEL_IDS.has(canonical);
105
+ }
106
+
92
107
  /**
93
108
  * Extract client IP address from incoming HTTP request.
94
109
  */
@@ -692,12 +707,18 @@ export function startProxy(
692
707
  res.end(data);
693
708
  }
694
709
  } else {
695
- // OpenCode routing — relay when enabled, else direct upstream
710
+ // OpenCode routing — relay egress is for free-catalog models
711
+ // only; unknown/paid models go direct upstream.
696
712
  const relayState = getActiveRelayState();
697
- const shouldUseRelay =
713
+ const relayPoolOn =
698
714
  relayState.mode !== "off" &&
699
715
  relayState.enabled !== false &&
700
716
  Boolean(relayState.url || (relayState.relays && relayState.relays.length > 0));
717
+ const relayEligible = isRelayEligibleModel(parsedBody?.model);
718
+ const shouldUseRelay = relayPoolOn && relayEligible;
719
+ if (relayPoolOn && !relayEligible) {
720
+ log("info", `relay bypass for non-catalog model ${String(parsedBody?.model ?? "?")} — routing direct upstream`, { model: parsedBody?.model }, reqId);
721
+ }
701
722
  if (shouldUseRelay) {
702
723
  const fullUrl = `${UPSTREAM_OPENCODE}${req.url ?? "/"}`;
703
724
  const activeHost = relayState.url