@chatpanel/gateway 0.5.3 → 0.5.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatpanel/gateway",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "Local privacy gateway — redacts PII out of OpenAI/Anthropic API traffic before it reaches a model, then restores it in the reply. Point opencode, codex, aider, Claude Code, etc. at it.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,6 +17,9 @@ export function persistConfig(cfg, path = configPath()) {
17
17
  mkdirSync(dirname(path), { recursive: true });
18
18
  const out = {
19
19
  host: cfg.host, port: cfg.port, backend: cfg.backend,
20
+ // Destinations (the configured agents + API models) MUST persist — otherwise a
21
+ // restart drops them and every model falls back to the default OpenAI upstream.
22
+ destinations: cfg.destinations,
20
23
  bridge: cfg.bridge, upstreams: cfg.upstreams, redaction: cfg.redaction,
21
24
  ner: cfg.ner, allowedOrigins: cfg.allowedOrigins, maxBodyBytes: cfg.maxBodyBytes,
22
25
  pro: cfg.pro, logRequests: cfg.logRequests, tools: cfg.tools,
package/src/router.js CHANGED
@@ -13,18 +13,28 @@
13
13
  //
14
14
  // /v1/models aggregates every destination's models so clients can discover them.
15
15
 
16
- // Back-compat: if no destinations are configured, synthesize them from the legacy
17
- // backend/upstreams config so existing installs keep working unchanged.
16
+ // The CLI agents the bridge can drive. A request for any of these ALWAYS routes to
17
+ // the bridge relay the agent runs under ITS OWN login (subscription / enterprise /
18
+ // api, however it's configured), so the gateway never needs an API key for it. This
19
+ // is the whole point: an OpenAI/Anthropic endpoint fronting already-logged-in
20
+ // codex / claude code via the bridge.
21
+ const KNOWN_AGENTS = ['codex', 'claude', 'opencode', 'pi', 'kiro', 'antigravity'];
22
+
23
+ // Build the destination list: explicitly-configured destinations + the known agents
24
+ // (always available as bridge destinations, even with zero saved config) + API
25
+ // fallbacks for non-agent models on the api backend.
18
26
  export function listDestinations(cfg) {
19
- if (Array.isArray(cfg.destinations) && cfg.destinations.length) return cfg.destinations;
20
- if (cfg.backend === 'api') {
21
- return [
22
- { id: 'openai', type: 'api', protocol: 'openai', baseUrl: cfg.upstreams?.openai?.baseUrl, models: [] },
23
- { id: 'anthropic', type: 'api', protocol: 'anthropic', baseUrl: cfg.upstreams?.anthropic?.baseUrl, models: [] },
24
- ];
27
+ const configured = (Array.isArray(cfg.destinations) ? cfg.destinations : []).filter(Boolean);
28
+ const haveId = new Set(configured.map((d) => d.id));
29
+ const out = [...configured];
30
+ for (const a of KNOWN_AGENTS) {
31
+ if (!haveId.has(a)) out.push({ id: a, type: 'agent', agent: a, models: [a] });
32
+ }
33
+ if (cfg.backend === 'api' && !configured.some((d) => d.type === 'api')) {
34
+ out.push({ id: 'openai', type: 'api', protocol: 'openai', baseUrl: cfg.upstreams?.openai?.baseUrl, models: [] });
35
+ out.push({ id: 'anthropic', type: 'api', protocol: 'anthropic', baseUrl: cfg.upstreams?.anthropic?.baseUrl, models: [] });
25
36
  }
26
- const agents = [...new Set([cfg.bridge?.agent || 'codex', 'codex', 'claude', 'opencode', 'pi'])];
27
- return agents.map((a) => ({ id: a, type: 'agent', agent: a, models: [a] }));
37
+ return out;
28
38
  }
29
39
 
30
40
  // Pick the destination that serves `model` (explicit membership → id/agent match →
@@ -32,10 +42,17 @@ export function listDestinations(cfg) {
32
42
  export function resolveDestination(model, cfg, kind) {
33
43
  const dests = listDestinations(cfg);
34
44
  const wantsAnthropic = kind === 'anthropic';
45
+ const protoOk = (d) => (wantsAnthropic ? d.protocol === 'anthropic' : d.protocol !== 'anthropic');
35
46
  return (
47
+ // Explicit: a destination that serves this exact model (a known agent like codex
48
+ // matches its own agent destination here — so it ALWAYS goes to the bridge).
36
49
  (model && dests.find((d) => Array.isArray(d.models) && d.models.includes(model)))
37
50
  || (model && dests.find((d) => d.id === model || d.agent === model))
38
- || dests.find((d) => d.type === 'agent' || (wantsAnthropic ? d.protocol === 'anthropic' : d.protocol !== 'anthropic'))
51
+ // No match: fall back to the BACKEND's natural default an API destination on the
52
+ // api backend, an agent on the bridge backend. Never silently send an unknown
53
+ // model name to a CLI agent (that's why gemma must not hit codex).
54
+ || dests.find((d) => (cfg.backend === 'bridge' ? d.type === 'agent' : (d.type === 'api' && protoOk(d))))
55
+ || dests.find((d) => (cfg.backend === 'bridge' ? d.type === 'agent' : d.type === 'api'))
39
56
  || dests[0]
40
57
  || null
41
58
  );
package/src/server.js CHANGED
@@ -33,7 +33,7 @@ import * as openai from './openai.js';
33
33
  import * as responses from './responses.js';
34
34
  import * as anthropic from './anthropic.js';
35
35
 
36
- export const VERSION = '0.5.3';
36
+ export const VERSION = '0.5.5';
37
37
 
38
38
  const KNOWN_AGENTS = new Set(['codex', 'claude', 'opencode', 'pi', 'kiro', 'antigravity']);
39
39