pkgsrc-WIP-changes archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
wip/agentcc-gateway: Import agentcc-gateway-1.54.0
Module Name: pkgsrc-wip
Committed By: ci4ic4 <ci4ic4%gmail.com@localhost>
Pushed By: ci4ic4
Date: Sun Oct 25 20:33:05 2026 +0000
Changeset: 1ad1f468796de38c1376cdc0da77fd8e88f07209
Modified Files:
Makefile
Added Files:
agentcc-gateway/COMMIT_MSG
agentcc-gateway/DESCR
agentcc-gateway/Makefile
agentcc-gateway/PLIST
agentcc-gateway/distinfo
agentcc-gateway/files/agentcc-failover.conf
agentcc-gateway/files/agentcc.conf
agentcc-gateway/files/agentcc.sh
agentcc-gateway/go-modules.mk
agentcc-gateway/patches/patch-internal_routing_failover.go
agentcc-gateway/patches/patch-internal_server_handlers.go
Log Message:
wip/agentcc-gateway: Import agentcc-gateway-1.54.0
To see a diff of this commit:
https://wip.pkgsrc.org/cgi-bin/gitweb.cgi?p=pkgsrc-wip.git;a=commitdiff;h=1ad1f468796de38c1376cdc0da77fd8e88f07209
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
diffstat:
Makefile | 1 +
agentcc-gateway/COMMIT_MSG | 23 +++
agentcc-gateway/DESCR | 20 ++
agentcc-gateway/Makefile | 60 ++++++
agentcc-gateway/PLIST | 7 +
agentcc-gateway/distinfo | 106 +++++++++++
agentcc-gateway/files/agentcc-failover.conf | 62 +++++++
agentcc-gateway/files/agentcc.conf | 53 ++++++
agentcc-gateway/files/agentcc.sh | 85 +++++++++
agentcc-gateway/go-modules.mk | 35 ++++
.../patches/patch-internal_routing_failover.go | 203 +++++++++++++++++++++
.../patches/patch-internal_server_handlers.go | 59 ++++++
12 files changed, 714 insertions(+)
diffs:
diff --git a/Makefile b/Makefile
index efd52c1648..d2ffbe14cc 100644
--- a/Makefile
+++ b/Makefile
@@ -149,6 +149,7 @@ SUBDIR+= aegisub
SUBDIR+= aespipe
SUBDIR+= afflib
SUBDIR+= afl-cov
+SUBDIR+= agentcc-gateway
SUBDIR+= agile
SUBDIR+= ags
SUBDIR+= ahir
diff --git a/agentcc-gateway/COMMIT_MSG b/agentcc-gateway/COMMIT_MSG
new file mode 100644
index 0000000000..1c64a136c3
--- /dev/null
+++ b/agentcc-gateway/COMMIT_MSG
@@ -0,0 +1,23 @@
+wip/agentcc-gateway: Import agentcc-gateway-1.54.0
+
+Agent Command Center (agentcc-gateway) is a high-performance,
+OpenAI-compatible LLM gateway written in Go. It sits between an
+application and LLM providers and gives one OpenAI-compatible API
+for every configured provider (OpenAI, Anthropic, Gemini, Bedrock,
+Cohere, Azure, and local endpoints such as vLLM, Ollama and
+LocalAI). It provides routing and failover strategies, caching,
+budgets and rate limits, guardrail scanners, and Prometheus and
+OpenTelemetry metrics.
+
+Only the gateway is built, from the agentcc-gateway/ subdirectory
+of the upstream future-agi monorepo.
+
+The package installs an rc.d script and a minimal agentcc.conf,
+which starts the gateway with no providers. It also installs an
+example two-host round-robin/failover config, plus upstream's
+config.example.yaml and config.license_auth.example.yaml taken
+from the distfile.
+
+Local patches fix biased round-robin and weighted routing when
+failover is enabled. Without them the router was consulted twice
+per request.
diff --git a/agentcc-gateway/DESCR b/agentcc-gateway/DESCR
new file mode 100644
index 0000000000..cbceafe612
--- /dev/null
+++ b/agentcc-gateway/DESCR
@@ -0,0 +1,20 @@
+Agent Command Center (agentcc-gateway) is a high-performance, OpenAI-compatible
+LLM gateway written in Go. It sits between your application and LLM providers,
+providing a single OpenAI-compatible API for every provider you configure.
+
+Features:
+ - One OpenAI-compatible API for OpenAI, Anthropic, Gemini, Bedrock, Cohere,
+ Azure, and local self-hosted endpoints
+ (vLLM, Ollama, LMStudio, TGI, LocalAI)
+ - 15 routing strategies: retries, circuit breakers, failover, hedged/race,
+ latency-aware, cost-optimized, model fallback, shadow/mirror, and more
+ - Cost control: exact + semantic caching, per-key budgets, quotas, virtual
+ keys, rate limits, credits ledger
+ - 18 built-in guardrail scanners (PII, injection, jailbreak, secrets,
+ hallucination, MCP security, content moderation, and more) plus adapters for
+ 15 third-party guardrail vendors
+ - Observability: Prometheus + OpenTelemetry, per-request metrics
+ - Modern protocols: MCP, A2A, Assistants, vector stores, batch, files,
+ realtime WebSocket, responses API
+
+Single binary, single YAML config, no proprietary control plane required.
diff --git a/agentcc-gateway/Makefile b/agentcc-gateway/Makefile
new file mode 100644
index 0000000000..1de8a8c258
--- /dev/null
+++ b/agentcc-gateway/Makefile
@@ -0,0 +1,60 @@
+# $NetBSD$
+
+# The upstream tarball is the whole monorepo (future-agi-<ver>) but this
+# package only builds the gateway (agentcc-gateway). Keep the version in one
+# place (VERSION below); DISTNAME and PKGNAME are derived from it so updating
+# is a one-line change and `make fetch` keeps working.
+VERSION= 1.54.0
+DISTNAME= future-agi-${VERSION}
+PKGNAME= agentcc-gateway-${VERSION}
+CATEGORIES= net
+MASTER_SITES= ${MASTER_SITE_GITHUB:=future-agi/}
+GITHUB_PROJECT= future-agi
+GITHUB_TAG= v${VERSION}
+
+MAINTAINER= pkgsrc-users%NetBSD.org@localhost
+HOMEPAGE= https://github.com/future-agi/future-agi/
+COMMENT= OpenAI-compatible LLM gateway with routing, guardrails, and caching
+LICENSE= apache-2.0
+
+# The gateway is a Go module (github.com/futureagi/agentcc-gateway) that lives
+# inside the future-agi monorepo tarball under agentcc-gateway/.
+WRKSRC= ${WRKDIR}/${DISTNAME}/agentcc-gateway
+
+MAKE_ENV+= CGO_ENABLED=0
+GO_BUILD_PATTERN= ./cmd/agentcc
+
+USE_LANGUAGES= # none
+
+EGDIR= share/examples/agentcc
+
+CONF_FILES+= ${EGDIR}/agentcc.conf ${PKG_SYSCONFDIR}/agentcc.conf
+RCD_SCRIPTS= agentcc
+
+INSTALLATION_DIRS+= ${EGDIR}
+
+post-install:
+ ${INSTALL_DATA} ${FILESDIR}/agentcc.conf \
+ ${DESTDIR}${PREFIX}/${EGDIR}/agentcc.conf
+ ${INSTALL_DATA} ${FILESDIR}/agentcc-failover.conf \
+ ${DESTDIR}${PREFIX}/${EGDIR}/agentcc-failover.conf
+ ${INSTALL_DATA} ${WRKSRC}/config.example.yaml \
+ ${DESTDIR}${PREFIX}/${EGDIR}/config.example.yaml
+ ${INSTALL_DATA} ${WRKSRC}/config.license_auth.example.yaml \
+ ${DESTDIR}${PREFIX}/${EGDIR}/config.license_auth.example.yaml
+
+.include "go-modules.mk"
+
+.include "../../lang/go/go-module.mk"
+.include "../../mk/bsd.pkg.mk"
+
+# PAX tool variable is not being set by go-module.mk's USE_TOOLS+= pax in
+# this pkgsrc version; set it explicitly for the do-install step.
+PAX= /bin/pax
+
+# patches/ contains fixes not yet upstream:
+# - patch-internal_routing_failover.go: round-robin/weighted routing was
+# biased when failover is enabled (router selected twice per request).
+# - patch-internal_server_handlers.go: wire ExecuteWithFirst into the
+# non-stream and streaming failover paths.
+# Regenerate with: diff -u ... > patches/patch-... then `make distinfo`.
diff --git a/agentcc-gateway/PLIST b/agentcc-gateway/PLIST
new file mode 100644
index 0000000000..5727593ff8
--- /dev/null
+++ b/agentcc-gateway/PLIST
@@ -0,0 +1,7 @@
+@comment $NetBSD$
+bin/agentcc
+share/examples/agentcc/agentcc-failover.conf
+share/examples/agentcc/agentcc.conf
+share/examples/agentcc/config.example.yaml
+share/examples/agentcc/config.license_auth.example.yaml
+share/examples/rc.d/agentcc
diff --git a/agentcc-gateway/distinfo b/agentcc-gateway/distinfo
new file mode 100644
index 0000000000..768fd57c91
--- /dev/null
+++ b/agentcc-gateway/distinfo
@@ -0,0 +1,106 @@
+$NetBSD$
+
+BLAKE2s (future-agi-1.54.0.tar.gz) = 606673d66ad84f2c3c70bfdb95787e92fcaf035d3115df96aa8c71f78dbc9332
+SHA512 (future-agi-1.54.0.tar.gz) = 727ce3f1d36671dc2e986de214d3e9b282885e84313f253a1daaf37b0cebd17847243245693eeb582099e64117a3c0401e333f275ebdf63e6de8357849085557
+Size (future-agi-1.54.0.tar.gz) = 81361091 bytes
+BLAKE2s (github.com_cespare_xxhash_v2_@v_v2.3.0.mod) = 03371e0ad30714eb81f3613e1d710ab1771554035bcf8b4cd97e7bc4f2d5eadb
+SHA512 (github.com_cespare_xxhash_v2_@v_v2.3.0.mod) = c1b9f572957bc4ed1ad0c61d22b7b5d6a4c863df2aaeb4cf914e304af21d949c9cfc53dc8eb67aacdf3dd56f339ee6f4a1f5fa464121d9db79b7b3febfb3251b
+Size (github.com_cespare_xxhash_v2_@v_v2.3.0.mod) = 45 bytes
+BLAKE2s (github.com_cespare_xxhash_v2_@v_v2.3.0.zip) = 9f3161460bfb3357c229be7bdc4ed965eeeed637b5d72ab71ae4983bb0e89fc2
+SHA512 (github.com_cespare_xxhash_v2_@v_v2.3.0.zip) = 8a349f6beb7feffcc31582196f84e67876bd95012c825ff9fe7246e354f1518f736952aaa7294abe7d51d2ea834e5b166d2242e22d912f5fefb581b442ac8870
+Size (github.com_cespare_xxhash_v2_@v_v2.3.0.zip) = 17573 bytes
+BLAKE2s (github.com_creack_pty_@v_v1.1.9.mod) = df42a1d34a4df624a6fbab600483cf5afb215874880e2e9e23f5fb03d719eb16
+SHA512 (github.com_creack_pty_@v_v1.1.9.mod) = 9cf1687520933d3d6bbec9d7c51472998d3bdbe1b3d2a7478fca80f9e7944f9040a2b8ec7d49f1458bd46a5121bae4810d314725add41efd413319a5006b944a
+Size (github.com_creack_pty_@v_v1.1.9.mod) = 39 bytes
+BLAKE2s (github.com_dgryski_go-rendezvous_@v_v0.0.0-20200823014737-9f7001d12a5f.mod) = 1db2cc0c8d83ebaf3908112e953c24f94db50d1b49561cdd80df8c2abafaf035
+SHA512 (github.com_dgryski_go-rendezvous_@v_v0.0.0-20200823014737-9f7001d12a5f.mod) = 286b0352105dc2d1278aa0f6a7e38c66e78aa2ad6979c9bc03b4b7df9f9d36f2b2eaa9bcdf5796901d27ec06ff4051b059b5e3bdd85b28f8590923ee0866ac2e
+Size (github.com_dgryski_go-rendezvous_@v_v0.0.0-20200823014737-9f7001d12a5f.mod) = 40 bytes
+BLAKE2s (github.com_dgryski_go-rendezvous_@v_v0.0.0-20200823014737-9f7001d12a5f.zip) = 1e38b6b29694e02f030c22badcfbd0647a0c0aee32095838eea69f7675751c4e
+SHA512 (github.com_dgryski_go-rendezvous_@v_v0.0.0-20200823014737-9f7001d12a5f.zip) = 24e08682b9d86643542571d79f7310b2a311a694d6738beb5a74f155938281f61c1e4bae2bb39d768cd35c0476b9e6ffcbbbf46e2462cd1f6ffe29e6df1aa96d
+Size (github.com_dgryski_go-rendezvous_@v_v0.0.0-20200823014737-9f7001d12a5f.zip) = 2243 bytes
+BLAKE2s (github.com_gorilla_websocket_@v_v1.5.3.mod) = dbf53703a57aa05392a4753b21d683ec3798f5a11d06fc84a30426f87315008d
+SHA512 (github.com_gorilla_websocket_@v_v1.5.3.mod) = 81ef224435efb5cbd429dd4339e8410234855f4a268302a61d34e76c388e89ea22c97ae90d71a6ef910ca00d3178e40d3587eaf484112966050fbd1f1f4843ab
+Size (github.com_gorilla_websocket_@v_v1.5.3.mod) = 45 bytes
+BLAKE2s (github.com_gorilla_websocket_@v_v1.5.3.zip) = 42b72b593f9245118f99a27c92b9651df65a0ee38a80bde7c2df7e941e23db5f
+SHA512 (github.com_gorilla_websocket_@v_v1.5.3.zip) = 4eeff7d05a689155c2331d1caf9ec68d65214f0d0451b1a06c42aa8ad14ccce288a8930def05beef12e1dbe1b702e96c681a6b576d11c75ed0e4224ce456e14d
+Size (github.com_gorilla_websocket_@v_v1.5.3.zip) = 78401 bytes
+BLAKE2s (github.com_kr_pretty_@v_v0.2.1.mod) = c7902e2b218e48371c72208ab3a42754282b425818489f0872c333e1dc01c1f7
+SHA512 (github.com_kr_pretty_@v_v0.2.1.mod) = b920109a2a1f40e2c7bb4dd93d07138228fac3064b780449a38bf5e6cb0630c6b7bd79eaf18b35e0452846e5059b0e192682b0aa93cc563c6120816106b9f11b
+Size (github.com_kr_pretty_@v_v0.2.1.mod) = 72 bytes
+BLAKE2s (github.com_kr_pretty_@v_v0.3.1.mod) = 169a53a3ae668758ead593ad5a305b08c8e1712abb38367775ea80bea6a938b7
+SHA512 (github.com_kr_pretty_@v_v0.3.1.mod) = 46b8cb7f9bdca544fec116dec7257e6009356bfcd2cf1d32ed0115c86e02b4da8cfcd2c7b028d330cd25bc81d72e948297f574ca9d06a03b927a328af37d9ebd
+Size (github.com_kr_pretty_@v_v0.3.1.mod) = 117 bytes
+BLAKE2s (github.com_kr_pretty_@v_v0.3.1.zip) = 6f5314a6db3f01fec03f1b62ce472409b8aa03423494c6b42f4f1bcf97fd7562
+SHA512 (github.com_kr_pretty_@v_v0.3.1.zip) = b3fd8f3e04976414d2423124ada1322002e58eccbe455ba52ce8b89ade612bec9d5597d84f4e492e1330b30bea24ad8a3bf2ddb266b672b0ae8f96d9f4c141a7
+Size (github.com_kr_pretty_@v_v0.3.1.zip) = 13388 bytes
+BLAKE2s (github.com_kr_pty_@v_v1.1.1.mod) = 40cf54480d039c9d4a2692ae500319fdc6cb62b869b60976b3df99163869894d
+SHA512 (github.com_kr_pty_@v_v1.1.1.mod) = 967fcdce835c12afbf3b12bb204a256f32d46a3da535e2a7250159f62781a163ee107dd2a4fa20743b76bbf52b97e627e11e895c8d5ef73aede37d6a89e326eb
+Size (github.com_kr_pty_@v_v1.1.1.mod) = 25 bytes
+BLAKE2s (github.com_kr_text_@v_v0.1.0.mod) = 680f50a44801214a99d92a8314fc050a200f0c8ee971e7c64ba69346c5554425
+SHA512 (github.com_kr_text_@v_v0.1.0.mod) = 11d2502a7d241a0edf4e67bcb651a890a12f49d00f8b944cc9eb8f6e9cf5fbb2b2827e696021649bc795ffa275e95ee700e4a1706e03e88fa9ae079f5b9f48ce
+Size (github.com_kr_text_@v_v0.1.0.mod) = 64 bytes
+BLAKE2s (github.com_kr_text_@v_v0.2.0.mod) = 357461c274200b48eeb7314f6b32cd3720848093c178f79816bef294b59c2ea3
+SHA512 (github.com_kr_text_@v_v0.2.0.mod) = 9fbff125d97ce2c4b9b301021a987edbeb686b37927504df4e5c2f0ddbebc8ed28ee5152de7db2eceb0a0fc5728579823dc4145f36df9ba1961b9c2da1ac4461
+Size (github.com_kr_text_@v_v0.2.0.mod) = 68 bytes
+BLAKE2s (github.com_oklog_ulid_v2_@v_v2.1.0.mod) = cca77fb80c800ead1d644b3180151c1c99112738849b8317029e63cf77d87590
+SHA512 (github.com_oklog_ulid_v2_@v_v2.1.0.mod) = 94aebc02691fa318f126bbdde2ca98b5b1a20d9d14412b38bc19fd8923c13e8aa26beb9918f245b9665a3a79e72c56c2d9771627cc90e46f996428611ae5682c
+Size (github.com_oklog_ulid_v2_@v_v2.1.0.mod) = 111 bytes
+BLAKE2s (github.com_oklog_ulid_v2_@v_v2.1.0.zip) = 8f4d002f63c6092aea636e3b3dcaa5defc1ec45de0f09727723a195a4a753e3f
+SHA512 (github.com_oklog_ulid_v2_@v_v2.1.0.zip) = c5d6a7254735991a1bdc39ac14174114bd729d9a847451443a3d1bb75f615f7fc947f21d432f58dcc5698db7fe59daba32dd599bb0230d18dfbc805be0c9d992
+Size (github.com_oklog_ulid_v2_@v_v2.1.0.zip) = 23744 bytes
+BLAKE2s (github.com_pborman_getopt_@v_v0.0.0-20170112200414-7148bc3a4c30.mod) = 5dce38eb5d2e193e862b7d21e6fb333b5012905fd4f539b551f6299e15e6d562
+SHA512 (github.com_pborman_getopt_@v_v0.0.0-20170112200414-7148bc3a4c30.mod) = 20529318383fec61da3ad57a44f1b1d21011b6db36d5f04ba7ef7008a722724a09bda8d706166620ca3e346cd42c295b1c698d650d5549bef8173546288702d7
+Size (github.com_pborman_getopt_@v_v0.0.0-20170112200414-7148bc3a4c30.mod) = 33 bytes
+BLAKE2s (github.com_pkg_diff_@v_v0.0.0-20210226163009-20ebb0f2a09e.mod) = b578bd0a123f38c80268d6690305d02f8dcddde6185e1df36680f2855f99a4f0
+SHA512 (github.com_pkg_diff_@v_v0.0.0-20210226163009-20ebb0f2a09e.mod) = 8b928ed342c4e92a9a3e36f1a1bb917ec38a145502d8192fd81ed78b4a9c24d1b24a2cb06b0c6cc5bf7a26bff4ea8354066f6b0456f1eafcde0921335afc3975
+Size (github.com_pkg_diff_@v_v0.0.0-20210226163009-20ebb0f2a09e.mod) = 36 bytes
+BLAKE2s (github.com_redis_go-redis_v9_@v_v9.18.0.mod) = 90a79eab1b3d1e7baecfaeafdb069a3babcb6d0cb3a16c7ace11dea60331f0a6
+SHA512 (github.com_redis_go-redis_v9_@v_v9.18.0.mod) = bc2fb3fe724185be5c4af7aaab077d3c372f4746004d2b04e1af0ce79202868270510461ab9d76598e4d9c528f90150ad55cecc143f6502aee6d139265257dde
+Size (github.com_redis_go-redis_v9_@v_v9.18.0.mod) = 759 bytes
+BLAKE2s (github.com_redis_go-redis_v9_@v_v9.18.0.zip) = 831fb1c8cd8eb6fd3d571fd92a112b6d68edbd9dacc1b15343147aa574e2a8b3
+SHA512 (github.com_redis_go-redis_v9_@v_v9.18.0.zip) = 08349cb9f8bb9349fd56747e4f8645055a4724496919b93e015642d657352aab2b460885c19ab603f2e51b38adff3fe6c8cdc4a87cb03ef1c0dab1e7fed2d5fc
+Size (github.com_redis_go-redis_v9_@v_v9.18.0.zip) = 5295784 bytes
+BLAKE2s (github.com_rogpeppe_go-internal_@v_v1.12.0.mod) = abbe9b083db655fe8e922bfc9770333181938b4f5107a1af71e90b6369396f04
+SHA512 (github.com_rogpeppe_go-internal_@v_v1.12.0.mod) = 5b3ffad2cb3713b0b490edffc47c8d22502b68749c156496e0645fa588ce9b7aa3dc56b03b07f436c5489cac0a7af735f7928c3d088227bdf55f8c2a3f7cfa14
+Size (github.com_rogpeppe_go-internal_@v_v1.12.0.mod) = 167 bytes
+BLAKE2s (github.com_rogpeppe_go-internal_@v_v1.12.0.zip) = 315cc3059843f9b87b8732dbce3b109866d39ad9f1403bc4cd27aff656366bfc
+SHA512 (github.com_rogpeppe_go-internal_@v_v1.12.0.zip) = 21a64b9b64eec03de6305de04968a49e096da61cd76b0f76be49a007dea8345640d8a06ccfaa76e5ad8b1bdd0df341de6a69c590ad44d55646ab16ed3e1bda58
+Size (github.com_rogpeppe_go-internal_@v_v1.12.0.zip) = 190596 bytes
+BLAKE2s (github.com_rogpeppe_go-internal_@v_v1.9.0.mod) = a821b267cecc00e91955bdb82a97a7d803d9ef8989d5a55c8559299c26e14591
+SHA512 (github.com_rogpeppe_go-internal_@v_v1.9.0.mod) = a3d30dd629b377513944f59ed204b44f4759e88e20cd70cd9280965d0d010e01af3ad8e5b9f45b33e0ed9a28fbb25575ca0ff2075c970468fcf39c1f1a7f5351
+Size (github.com_rogpeppe_go-internal_@v_v1.9.0.mod) = 112 bytes
+BLAKE2s (go.opentelemetry.io_proto_otlp_@v_v1.5.0.mod) = 418e8e1a4339084fb8e497079e70073b56f154897637ba3e57aad98e79e6ef29
+SHA512 (go.opentelemetry.io_proto_otlp_@v_v1.5.0.mod) = b168aa1c9026d9c687faa56b1b724d83bb5ca491c9c2e27be5d761e196da2a19ffcf583542e2f3242a94f11617c86a926311c04e71cceb5937b546e37f30c9fa
+Size (go.opentelemetry.io_proto_otlp_@v_v1.5.0.mod) = 489 bytes
+BLAKE2s (go.opentelemetry.io_proto_otlp_@v_v1.5.0.zip) = 5f4db2e4e56ae2c55b0209d4e1700c15c8fc6d4dadfc5dc785d5d40db33e06ee
+SHA512 (go.opentelemetry.io_proto_otlp_@v_v1.5.0.zip) = 15e3cde01bddf5a73fcdd640d0d11f26ff09a73b1f1f93b6c88688b61ea997bde7edae50947b0d37d70025c0d6ffac365515bd9950332002cafdbcc7c633c98a
+Size (go.opentelemetry.io_proto_otlp_@v_v1.5.0.zip) = 92996 bytes
+BLAKE2s (go.uber.org_atomic_@v_v1.11.0.mod) = 61951db0109ac51f05c966fb8dc9349cdb549aa8e0d4dcb65f4f5be4f3e53be4
+SHA512 (go.uber.org_atomic_@v_v1.11.0.mod) = 18ffaa53988d328e8f6374ea14b6a8b871b51966518ec3290af595f51991ad65da637596a4d9060048c285f0515a72917449aab71bb4b7fa3da4b55b7fd7dfc8
+Size (go.uber.org_atomic_@v_v1.11.0.mod) = 189 bytes
+BLAKE2s (go.uber.org_atomic_@v_v1.11.0.zip) = 9ed36352a32e4e9e321bf609a3f776c8e212bd04c0f66c4c7127fadb47f2b345
+SHA512 (go.uber.org_atomic_@v_v1.11.0.zip) = de4558f42d73e080af401055ae47953dcac29a501ee3d225b09de90cc421a91e7705a34dc5f85fe4dc66242e5c345694b29a8ec918609f25e2ef208d69c31ff9
+Size (go.uber.org_atomic_@v_v1.11.0.zip) = 77346 bytes
+BLAKE2s (google.golang.org_protobuf_@v_v1.36.1.mod) = 0159acda62aff2baa842478a9d56f754ecae0023a866ae611efa4595631b7789
+SHA512 (google.golang.org_protobuf_@v_v1.36.1.mod) = 6db3e43ea48cfb52d90e0d4648a3a4132dd3758168aa1f165aac5ade29aea6428cd1103c114e38b426726de01e753759bef020532d001f41ba9a2e862838ac5f
+Size (google.golang.org_protobuf_@v_v1.36.1.mod) = 201 bytes
+BLAKE2s (google.golang.org_protobuf_@v_v1.36.1.zip) = b5bb1654c65a6f1141aa19f317a622f1701457b6620a6cd3b6845ed9c99a9214
+SHA512 (google.golang.org_protobuf_@v_v1.36.1.zip) = 339d5d46efacaf84574a17de209ea39ab9fea94d63f2d0c088d692333d33a7ff2a4235879a5951d844ac2bfef6d0278b63e8b81627a0dde2546109345744a2da
+Size (google.golang.org_protobuf_@v_v1.36.1.zip) = 2361679 bytes
+BLAKE2s (gopkg.in_check.v1_@v_v0.0.0-20161208181325-20d25e280405.mod) = 35cd1964700502d06b8c987979fb4dbddc41a5cda3368d037211b89d3587521f
+SHA512 (gopkg.in_check.v1_@v_v0.0.0-20161208181325-20d25e280405.mod) = 9ac0f80ef05881387cb2f48f81560f3207fe586ea16495383662e6d62e43d93fac01dfe72e1d0063d3ab065331dab3a8098d7da4f1830ba1aff6f27f8e1bc1b2
+Size (gopkg.in_check.v1_@v_v0.0.0-20161208181325-20d25e280405.mod) = 25 bytes
+BLAKE2s (gopkg.in_check.v1_@v_v1.0.0-20201130134442-10cb98267c6c.mod) = 535d854a3208e5345eb291d48f401669a53e91c1d94a7a383a10a2cadd71e823
+SHA512 (gopkg.in_check.v1_@v_v1.0.0-20201130134442-10cb98267c6c.mod) = a2f9fd465e2e0c735ca80e04f2002e31cad434c5a3771a3adcdc7f9502f0caa7b27a5f17ec2cd95bcb214e12ac1200a78139db063d4384b864bd290cb6815e87
+Size (gopkg.in_check.v1_@v_v1.0.0-20201130134442-10cb98267c6c.mod) = 71 bytes
+BLAKE2s (gopkg.in_check.v1_@v_v1.0.0-20201130134442-10cb98267c6c.zip) = 0f65407cbdc610b7d793aabb53ce86f87039d306762692fafe04de7c6669d460
+SHA512 (gopkg.in_check.v1_@v_v1.0.0-20201130134442-10cb98267c6c.zip) = b69b95251b63b6fd0ed1953dae01edefa52126e2168246bc2cdf067879ae5f830df4e610da45f5ac5aa4789f70d79da8a9bfa7af5b3786ef8f1e665a8b397e02
+Size (gopkg.in_check.v1_@v_v1.0.0-20201130134442-10cb98267c6c.zip) = 42835 bytes
+BLAKE2s (gopkg.in_yaml.v3_@v_v3.0.1.mod) = 17da54ba1894b4653d4c1ab1ae6e9fe03928a96c69cdc8db6b6ea9e34d673991
+SHA512 (gopkg.in_yaml.v3_@v_v3.0.1.mod) = 307ca9123efc577ca04828996ee9d8edbb51794ccb4b8d9f169ba689e7276aa5f6ae106a04b22b7fab853ffacfebcbf74468b64eaefd57445864c1fbc77fad9d
+Size (gopkg.in_yaml.v3_@v_v3.0.1.mod) = 95 bytes
+BLAKE2s (gopkg.in_yaml.v3_@v_v3.0.1.zip) = 2e80fa896e65b1ae160400012c71aeac70e22a31cac82ec4921c85a93d65774c
+SHA512 (gopkg.in_yaml.v3_@v_v3.0.1.zip) = d57b0d42c71ad6503415e42979b51b0dc7f6344072c728ab2e3d4bab88da7b7d775e7f261868909f990f6b44aed6c533966c97bbe333a0acd65fc8bac9d1d4ff
+Size (gopkg.in_yaml.v3_@v_v3.0.1.zip) = 104623 bytes
+SHA1 (patch-internal_routing_failover.go) = bef1b608b8ddd5175ebfc7979791544405d9360a
+SHA1 (patch-internal_server_handlers.go) = 4c8a428c615d17febc68d2c8e0efa852b03f9670
diff --git a/agentcc-gateway/files/agentcc-failover.conf b/agentcc-gateway/files/agentcc-failover.conf
new file mode 100644
index 0000000000..a6ec535a72
--- /dev/null
+++ b/agentcc-gateway/files/agentcc-failover.conf
@@ -0,0 +1,62 @@
+# Agent Command Center - two-host round-robin + failover example.
+#
+# This is the setup that was tested against two llama.cpp instances serving
+# the same GGUF under different model paths. Clients request the canonical
+# model name ("qwen2.5-0.5b") and the gateway translates it per target via
+# model_override, alternating requests and failing over if one host dies.
+#
+# IMPORTANT: do NOT list the canonical name in model_map. A model_map entry
+# marks the model "explicit" and the gateway then uses SelectExactModel,
+# which skips targets whose model_override differs from the requested model.
+# Without the map entry, plain Select applies the per-target model_override.
+
+server:
+ port: 18080
+ host: "0.0.0.0"
+ read_header_timeout: 5s
+ read_timeout: 60s
+ write_timeout: 300s
+ idle_timeout: 120s
+ shutdown_timeout: 30s
+ max_request_body_size: 10485760
+ default_request_timeout: 120s
+
+providers:
+ llama1:
+ base_url: "http://host1:8089"
+ api_format: "openai"
+ type: "ollama"
+ default_timeout: 120s
+ max_concurrent: 10
+ conn_pool_size: 10
+ llama2:
+ base_url: "http://host2:8080"
+ api_format: "openai"
+ type: "ollama"
+ default_timeout: 120s
+ max_concurrent: 10
+ conn_pool_size: 10
+
+routing:
+ default_strategy: "round-robin" # round-robin | weighted | least-latency | cost-optimized
+ failover:
+ enabled: true
+ max_attempts: 3
+ on_status_codes: [429, 500, 502, 503, 504]
+ on_timeout: true
+ targets:
+ "qwen2.5-0.5b": # canonical name used by clients
+ - provider: llama1
+ weight: 50
+ model_override: "/models/qwen2.5-0.5b-instruct-q4_k_m.gguf"
+ - provider: llama2
+ weight: 50
+ model_override: "/models/qwen2.5-0.5b-instruct-q4_k_m.gguf"
+
+# Needed for browser-based clients hitting the gateway from another origin:
+cors:
+ enabled: true
+ allowed_origins: ["*"]
+
+logging:
+ level: info
\ No newline at end of file
diff --git a/agentcc-gateway/files/agentcc.conf b/agentcc-gateway/files/agentcc.conf
new file mode 100644
index 0000000000..5d1b1d831e
--- /dev/null
+++ b/agentcc-gateway/files/agentcc.conf
@@ -0,0 +1,53 @@
+# Agent Command Center (agentcc-gateway) configuration.
+#
+# This is the default config installed by pkgsrc. It starts the gateway
+# with no providers, which is safe but not useful yet: the /v1/models list
+# will be empty. Add at least one provider (see /usr/pkg/share/examples/
+# agentcc/agentcc-failover.conf for a two-host round-robin/failover setup).
+#
+# See also the full upstream example with every option documented:
+# /usr/pkg/share/examples/agentcc/config.example.yaml
+
+server:
+ port: 18080
+ host: "0.0.0.0"
+ read_header_timeout: 5s
+ read_timeout: 60s
+ write_timeout: 300s
+ idle_timeout: 120s
+ shutdown_timeout: 30s
+ max_request_body_size: 10485760 # 10MB
+ default_request_timeout: 120s
+
+# Uncomment and adjust to enable a provider:
+# providers:
+# llama:
+# base_url: "http://127.0.0.1:8089" # llama-server / Ollama etc.
+# api_format: "openai"
+# type: "ollama" # local endpoint: auto-discovery
+# default_timeout: 120s
+# max_concurrent: 10
+# conn_pool_size: 10
+
+# Uncomment to restrict access with virtual API keys:
+# auth:
+# enabled: true
+# keys:
+# - name: "dev"
+# key: "sk-agentcc-dev-key"
+# owner: "local"
+
+# Uncomment to enable CORS for browser-based clients:
+# cors:
+# enabled: true
+# allowed_origins: ["*"]
+
+# Uncomment to serve Prometheus metrics (needs admin token):
+# admin:
+# token: "change-me"
+# prometheus:
+# enabled: true
+# path: "/-/metrics"
+
+logging:
+ level: info
\ No newline at end of file
diff --git a/agentcc-gateway/files/agentcc.sh b/agentcc-gateway/files/agentcc.sh
new file mode 100644
index 0000000000..0cc4b922c1
--- /dev/null
+++ b/agentcc-gateway/files/agentcc.sh
@@ -0,0 +1,85 @@
+#!@RCD_SCRIPTS_SHELL@
+#
+# $NetBSD$
+#
+# agentcc - Agent Command Center, OpenAI-compatible LLM gateway
+#
+# PROVIDE: agentcc
+# REQUIRE: NETWORK
+# KEYWORD: shutdown
+#
+# You will need to set some variables in @PKG_SYSCONFDIR@/rc.conf to start
+# agentcc:
+#
+# agentcc=YES
+# agentcc_log=@VARBASE@/log/agentcc.log # optional (default)
+
+. @SYSCONFBASE@/rc.subr
+
+name="agentcc"
+rcvar=$name
+
+command="@PREFIX@/bin/agentcc"
+command_args="--config @PKG_SYSCONFDIR@/agentcc.conf"
+pidfile="@VARBASE@/run/${name}.pid"
+required_files="@PKG_SYSCONFDIR@/agentcc.conf"
+
+: ${agentcc_log:=@VARBASE@/log/agentcc.log}
+
+# The gateway does not daemonize or write its own pidfile; run it in the
+# background and record the pid so stop/status/restart work.
+agentcc_start()
+{
+ if [ -f "${pidfile}" ] && checkyesno "${name}"; then
+ if check_pidfile "${pidfile}" "${command}"; then
+ echo "${name} is already running (pid $(cat "${pidfile}"))."
+ return 0
+ fi
+ rm -f "${pidfile}"
+ fi
+
+ local logdir
+ logdir=$(dirname "${agentcc_log}")
+ if [ ! -d "${logdir}" ]; then
+ install -d -o "${agentcc_user:-root}" -g "${agentcc_group:-wheel}" "${logdir}"
+ fi
+
+ echo "Starting ${name}."
+ ${command} ${command_args} >>"${agentcc_log}" 2>&1 &
+ echo $! > "${pidfile}"
+}
+
+agentcc_stop()
+{
+ if [ ! -f "${pidfile}" ]; then
+ echo "${name} is not running."
+ return 0
+ fi
+ echo "Stopping ${name}."
+ kill $(cat "${pidfile}") 2>/dev/null
+ rm -f "${pidfile}"
+}
+
+agentcc_status()
+{
+ if [ -f "${pidfile}" ] && check_pidfile "${pidfile}" "${command}"; then
+ echo "${name} is running as pid $(cat "${pidfile}")."
+ else
+ echo "${name} is not running."
+ fi
+}
+
+start_cmd="agentcc_start"
+stop_cmd="agentcc_stop"
+status_cmd="agentcc_status"
+restart_cmd="agentcc_restart"
+
+agentcc_restart()
+{
+ agentcc_stop
+ sleep 1
+ agentcc_start
+}
+
+load_rc_config $name
+run_rc_command "$1"
diff --git a/agentcc-gateway/go-modules.mk b/agentcc-gateway/go-modules.mk
new file mode 100644
index 0000000000..078723bcd1
--- /dev/null
+++ b/agentcc-gateway/go-modules.mk
@@ -0,0 +1,35 @@
+# $NetBSD$
+
+GO_MODULE_FILES+= github.com/cespare/xxhash/v2/@v/v2.3.0.mod
+GO_MODULE_FILES+= github.com/cespare/xxhash/v2/@v/v2.3.0.zip
+GO_MODULE_FILES+= github.com/creack/pty/@v/v1.1.9.mod
+GO_MODULE_FILES+= github.com/dgryski/go-rendezvous/@v/v0.0.0-20200823014737-9f7001d12a5f.mod
+GO_MODULE_FILES+= github.com/dgryski/go-rendezvous/@v/v0.0.0-20200823014737-9f7001d12a5f.zip
+GO_MODULE_FILES+= github.com/gorilla/websocket/@v/v1.5.3.mod
+GO_MODULE_FILES+= github.com/gorilla/websocket/@v/v1.5.3.zip
+GO_MODULE_FILES+= github.com/kr/pretty/@v/v0.2.1.mod
+GO_MODULE_FILES+= github.com/kr/pretty/@v/v0.3.1.mod
+GO_MODULE_FILES+= github.com/kr/pretty/@v/v0.3.1.zip
+GO_MODULE_FILES+= github.com/kr/pty/@v/v1.1.1.mod
+GO_MODULE_FILES+= github.com/kr/text/@v/v0.1.0.mod
+GO_MODULE_FILES+= github.com/kr/text/@v/v0.2.0.mod
+GO_MODULE_FILES+= github.com/oklog/ulid/v2/@v/v2.1.0.mod
+GO_MODULE_FILES+= github.com/oklog/ulid/v2/@v/v2.1.0.zip
+GO_MODULE_FILES+= github.com/pborman/getopt/@v/v0.0.0-20170112200414-7148bc3a4c30.mod
+GO_MODULE_FILES+= github.com/pkg/diff/@v/v0.0.0-20210226163009-20ebb0f2a09e.mod
+GO_MODULE_FILES+= github.com/redis/go-redis/v9/@v/v9.18.0.mod
+GO_MODULE_FILES+= github.com/redis/go-redis/v9/@v/v9.18.0.zip
+GO_MODULE_FILES+= github.com/rogpeppe/go-internal/@v/v1.12.0.mod
+GO_MODULE_FILES+= github.com/rogpeppe/go-internal/@v/v1.12.0.zip
+GO_MODULE_FILES+= github.com/rogpeppe/go-internal/@v/v1.9.0.mod
+GO_MODULE_FILES+= go.opentelemetry.io/proto/otlp/@v/v1.5.0.mod
+GO_MODULE_FILES+= go.opentelemetry.io/proto/otlp/@v/v1.5.0.zip
+GO_MODULE_FILES+= go.uber.org/atomic/@v/v1.11.0.mod
+GO_MODULE_FILES+= go.uber.org/atomic/@v/v1.11.0.zip
+GO_MODULE_FILES+= google.golang.org/protobuf/@v/v1.36.1.mod
+GO_MODULE_FILES+= google.golang.org/protobuf/@v/v1.36.1.zip
+GO_MODULE_FILES+= gopkg.in/check.v1/@v/v0.0.0-20161208181325-20d25e280405.mod
+GO_MODULE_FILES+= gopkg.in/check.v1/@v/v1.0.0-20201130134442-10cb98267c6c.mod
+GO_MODULE_FILES+= gopkg.in/check.v1/@v/v1.0.0-20201130134442-10cb98267c6c.zip
+GO_MODULE_FILES+= gopkg.in/yaml.v3/@v/v3.0.1.mod
+GO_MODULE_FILES+= gopkg.in/yaml.v3/@v/v3.0.1.zip
diff --git a/agentcc-gateway/patches/patch-internal_routing_failover.go b/agentcc-gateway/patches/patch-internal_routing_failover.go
new file mode 100644
index 0000000000..e5775f3ec6
--- /dev/null
+++ b/agentcc-gateway/patches/patch-internal_routing_failover.go
@@ -0,0 +1,203 @@
+$NetBSD$
+
+Fix round-robin/weighted routing bias when failover is enabled. The router
+was being asked to select a provider twice per request (once during provider
+resolution, once inside the failover loop), so the strategy counter advanced
+twice and traffic was skewed (with 2 targets, always the second). Add
+ExecuteWithFirst to reuse the already-resolved provider for the first failover
+attempt and only re-select from the router on subsequent attempts.
+
+--- internal/routing/failover.go.orig 2026-08-25 14:26:25.000000000 +0100
++++ internal/routing/failover.go 2026-08-28 19:55:56.351603083 +0100
+@@ -89,13 +89,60 @@
+ return false
+ }
+
+-// Execute runs the failover loop, trying providers until one succeeds or all fail.
+-func (f *Failover) Execute(ctx context.Context, model string, call FailoverCallFunc) (*FailoverResult, error) {
++// ExecuteWithFirst runs the failover loop but starts with a pre-resolved
++// provider instead of re-selecting via the router. This avoids consuming the
++// routing strategy's selection twice per request (once in resolveProvider,
++// once in Execute), which would bias round-robin and weighted strategies
++// toward a fixed target. Subsequent attempts re-select from the router,
++// excluding providers already tried.
++func (f *Failover) ExecuteWithFirst(ctx context.Context, model string, firstProvider string, firstOverride string, call FailoverCallFunc) (*FailoverResult, error) {
+ excluded := make(map[string]bool)
+ var lastErr error
+- var originalProvider string
++ originalProvider := firstProvider
+
+ for attempt := 1; attempt <= f.cfg.MaxAttempts; attempt++ {
++ if attempt == 1 && firstProvider != "" {
++ // Use the pre-resolved provider without touching the router.
++ result := &RoutingResult{
++ Target: RoutingTarget{
++ ProviderID: firstProvider,
++ ModelOverride: firstOverride,
++ Healthy: true,
++ },
++ StrategyName: f.router.Strategy(),
++ }
++
++ // Check circuit breaker before calling.
++ if f.cbReg.IsEnabled() {
++ cb := f.cbReg.Get(firstProvider)
++ if !cb.Allow() {
++ excluded[firstProvider] = true
++ continue
++ }
++ }
++
++ callErr := f.doCalls(ctx, call, result, attempt)
++ if callErr != nil {
++ lastErr = callErr
++ excluded[firstProvider] = true
++ if !f.ShouldFailover(callErr) {
++ return nil, callErr
++ }
++ if ctx.Err() != nil {
++ return nil, lastErr
++ }
++ continue
++ }
++ return &FailoverResult{
++ ProviderID: firstProvider,
++ ModelOverride: firstOverride,
++ Attempts: attempt,
++ OriginalProvider: originalProvider,
++ FallbackUsed: false,
++ StrategyName: result.StrategyName,
++ }, nil
++ }
++
+ // Select a target, excluding already-tried providers.
+ var result *RoutingResult
+ var err error
+@@ -124,37 +171,108 @@
+ }
+ }
+
+- // Create per-attempt timeout if configured so later attempts get full time.
+- attemptCtx := ctx
+- var attemptCancel context.CancelFunc
+- if f.cfg.PerAttemptTimeout > 0 {
+- attemptCtx, attemptCancel = context.WithTimeout(ctx, f.cfg.PerAttemptTimeout)
++ callErr := f.doCalls(ctx, call, result, attempt)
++ if callErr == nil {
++ return &FailoverResult{
++ ProviderID: result.Target.ProviderID,
++ ModelOverride: result.Target.ModelOverride,
++ Attempts: attempt,
++ OriginalProvider: originalProvider,
++ FallbackUsed: attempt > 1,
++ StrategyName: result.StrategyName,
++ }, nil
+ }
+
+- // Call the provider (with retry if configured).
+- var callErr error
+- if f.retryer.IsEnabled() {
+- _, callErr = f.retryer.Execute(attemptCtx, func() error {
+- return call(attemptCtx, result.Target.ProviderID, result.Target.ModelOverride)
+- })
++ lastErr = callErr
++ excluded[result.Target.ProviderID] = true
++
++ // Check if we should failover.
++ if !f.ShouldFailover(callErr) {
++ return nil, callErr
++ }
++
++ // Check context cancellation.
++ if ctx.Err() != nil {
++ return nil, lastErr
++ }
++ }
++
++ return nil, lastErr
++}
++
++// doCalls invokes the provider call for a failover attempt, with retry if configured.
++// Returns the call error, or nil on success.
++func (f *Failover) doCalls(ctx context.Context, call FailoverCallFunc, result *RoutingResult, attempt int) error {
++ // Create per-attempt timeout if configured so later attempts get full time.
++ attemptCtx := ctx
++ var attemptCancel context.CancelFunc
++ if f.cfg.PerAttemptTimeout > 0 {
++ attemptCtx, attemptCancel = context.WithTimeout(ctx, f.cfg.PerAttemptTimeout)
++ }
++
++ // Call the provider (with retry if configured).
++ var callErr error
++ if f.retryer.IsEnabled() {
++ _, callErr = f.retryer.Execute(attemptCtx, func() error {
++ return call(attemptCtx, result.Target.ProviderID, result.Target.ModelOverride)
++ })
++ } else {
++ callErr = call(attemptCtx, result.Target.ProviderID, result.Target.ModelOverride)
++ }
++
++ if attemptCancel != nil {
++ attemptCancel()
++ }
++
++ // Record result in circuit breaker.
++ if f.cbReg.IsEnabled() {
++ cb := f.cbReg.Get(result.Target.ProviderID)
++ if callErr == nil {
++ cb.RecordSuccess()
+ } else {
+- callErr = call(attemptCtx, result.Target.ProviderID, result.Target.ModelOverride)
++ cb.RecordFailure(callErr)
+ }
++ }
+
+- if attemptCancel != nil {
+- attemptCancel()
++ return callErr
++}
++
++// Execute runs the failover loop, trying providers until one succeeds or all fail.
++func (f *Failover) Execute(ctx context.Context, model string, call FailoverCallFunc) (*FailoverResult, error) {
++ excluded := make(map[string]bool)
++ var lastErr error
++ var originalProvider string
++
++ for attempt := 1; attempt <= f.cfg.MaxAttempts; attempt++ {
++ // Select a target, excluding already-tried providers.
++ var result *RoutingResult
++ var err error
++ if len(excluded) == 0 {
++ result, err = f.router.Select(model)
++ } else {
++ result, err = f.router.SelectExcluding(model, excluded)
+ }
++ if err != nil {
++ if lastErr != nil {
++ return nil, lastErr // Return the actual provider error, not "no targets"
++ }
++ return nil, err
++ }
+
+- // Record result in circuit breaker.
++ if originalProvider == "" {
++ originalProvider = result.Target.ProviderID
++ }
++
++ // Check circuit breaker before calling.
+ if f.cbReg.IsEnabled() {
+ cb := f.cbReg.Get(result.Target.ProviderID)
+- if callErr == nil {
+- cb.RecordSuccess()
+- } else {
+- cb.RecordFailure(callErr)
++ if !cb.Allow() {
++ excluded[result.Target.ProviderID] = true
++ continue
+ }
+ }
+
++ callErr := f.doCalls(ctx, call, result, attempt)
+ if callErr == nil {
+ return &FailoverResult{
+ ProviderID: result.Target.ProviderID,
diff --git a/agentcc-gateway/patches/patch-internal_server_handlers.go b/agentcc-gateway/patches/patch-internal_server_handlers.go
new file mode 100644
index 0000000000..d122a25ad1
--- /dev/null
+++ b/agentcc-gateway/patches/patch-internal_server_handlers.go
@@ -0,0 +1,59 @@
+$NetBSD$
+
+Wire ExecuteWithFirst into the non-stream and streaming failover paths so the
+provider resolved for the request is used for the first failover attempt
+instead of re-selecting via the router (which consumed the round-robin
+counter twice per request and biased traffic toward one target).
+
+--- internal/server/handlers.go.orig
++++ internal/server/handlers.go
+@@ -1379,7 +1379,7 @@
+ effectiveFailover := h.effectiveFailover(orgCfg)
+
+ if effectiveFailover != nil && effectiveFailover.IsEnabled() && h.registry.Router() != nil && h.registry.Router().HasTargets(rc.Model) {
+- h.handleNonStreamWithFailover(ctx, w, rc, effectiveFailover, orgCfg)
++ h.handleNonStreamWithFailover(ctx, w, rc, effectiveFailover, provider, orgCfg)
+ return
+ }
+
+@@ -1447,7 +1447,7 @@
+ json.NewEncoder(w).Encode(rc.Response)
+ }
+
+-func (h *Handlers) handleNonStreamWithFailover(ctx context.Context, w http.ResponseWriter, rc *models.RequestContext, fo *routing.Failover, orgCfg *tenant.OrgConfig) {
++func (h *Handlers) handleNonStreamWithFailover(ctx context.Context, w http.ResponseWriter, rc *models.RequestContext, fo *routing.Failover, resolvedProvider providers.Provider, orgCfg *tenant.OrgConfig) {
+ originalModel := rc.Request.Model
+ orgID := rc.Metadata["org_id"]
+ var servingProvider providers.Provider
+@@ -1455,7 +1455,16 @@
+ // Wrap the failover loop inside engine.Process so pre-plugins run once,
+ // then the failover retries only the provider call, then post-plugins run once.
+ providerCall := func(ctx context.Context, rc *models.RequestContext) error {
+- foResult, err := fo.Execute(ctx, rc.Model, func(ctx context.Context, providerID string, modelOverride string) error {
++ // Use the provider already resolved for this request as the first
++ // attempt, so the routing strategy is not consumed twice per request.
++ // rc.Request.Model is already the (possibly overridden) model the
++ // resolved provider expects.
++ var firstProvider, firstOverride string
++ if resolvedProvider != nil {
++ firstProvider = resolvedProvider.ID()
++ firstOverride = rc.Request.Model
++ }
++ foResult, err := fo.ExecuteWithFirst(ctx, rc.Model, firstProvider, firstOverride, func(ctx context.Context, providerID string, modelOverride string) error {
+ p, ok := h.registry.GetProvider(providerID)
+ if !ok {
+ return fmt.Errorf("provider %q not found", providerID)
+@@ -1642,7 +1651,12 @@
+ }
+
+ if effectiveFailover != nil && effectiveFailover.IsEnabled() && h.registry.Router() != nil && h.registry.Router().HasTargets(rc.Model) {
+- result, err := effectiveFailover.Execute(ctx, rc.Model, func(callCtx context.Context, providerID string, modelOverride string) error {
++ var firstProvider, firstOverride string
++ if provider != nil {
++ firstProvider = provider.ID()
++ firstOverride = rc.Request.Model
++ }
++ result, err := effectiveFailover.ExecuteWithFirst(ctx, rc.Model, firstProvider, firstOverride, func(callCtx context.Context, providerID string, modelOverride string) error {
+ p, ok := h.registry.GetProvider(providerID)
+ if !ok {
+ return models.ErrUpstreamProvider(http.StatusBadGateway, fmt.Sprintf("provider %q not found", providerID))
Home |
Main Index |
Thread Index |
Old Index