openvibers/openvibe.ai Commits
Expose the Exchange icon search on api.openvibe.exchange (#487)
Changes
@@@ -69,6 +69,20 @@ server {proxy_set_header CF-Connecting-IP $remote_addr;proxy_set_header X-Forwarded-Proto $scheme;}++ # Exchange search: the icon reference route OpenVibe.Design reaches with its first-party service token++ # (exchange.search.icons). POST only, the same public API rate limit and upstream as the rest.++ location = /v1/search/icons {++ limit_req zone=ovai_apps burst=20 nodelay;++ limit_req_status 429;++ limit_except POST { deny all; }++ proxy_pass http://127.0.0.1:4700;++ proxy_http_version 1.1;++ proxy_set_header Host $host;++ proxy_set_header X-Real-IP $remote_addr;++ proxy_set_header X-Forwarded-For $remote_addr;++ proxy_set_header CF-Connecting-IP $remote_addr;++ proxy_set_header X-Forwarded-Proto $scheme;++ }# Exchange's public read API (catalog, a model's offers, aggregate usage) and the OpenAPI document.location ^~ /api/v1/exchange/ {limit_req zone=ovai_apps burst=20 nodelay;@@@ -124,6 +138,6 @@ server {location / {default_type application/json;-- return 404 '{"error":{"message":"Not found. The API is POST /v1/chat/completions, POST /v1/messages and GET /v1/models; docs at https://openvibe.exchange/api","type":"invalid_request_error","code":"not_found"}}';++ return 404 '{"error":{"message":"Not found. The API is POST /v1/chat/completions, POST /v1/messages, POST /v1/search/icons and GET /v1/models; docs at https://openvibe.exchange/api","type":"invalid_request_error","code":"not_found"}}';}}@@@ -74,6 +74,7 @@ t.test('unregistered pricebook rows survive and the public route is cached and passert.strictEqual((await request(h.base,'GET','/api/v1/exchange/market?open_weights=bad')).status,422);const nginx=fs.readFileSync(path.join(__dirname,'../deploy/nginx/api.openvibe.exchange.conf'),'utf8');assert(nginx.includes('location ^~ /api/v1/exchange/'));++ assert(nginx.includes('location = /v1/search/icons'), 'the Exchange icon search reaches OpenVibe.AI from the public API host');let time=100; let reads=0;const market=createMarket({db:h.db,registry:{listProviders:async()=>{reads++;return[];},listModels:async()=>[]},catalog:async()=>({models:[]}),clock:{now:()=>time},metadata:{read:async()=>({entries:[],checked_at:null,stale:true})}});await Promise.all([market.read({limit:1}),market.read({limit:2})]); assert.strictEqual(reads,1);
@@ -69,6 +69,20 @@ server {
@@ -69,6 +69,20 @@ server {
proxy_set_header CF-Connecting-IP $remote_addr;
proxy_set_header CF-Connecting-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
+ # Exchange search: the icon reference route OpenVibe.Design reaches with its first-party service token
+ # (exchange.search.icons). POST only, the same public API rate limit and upstream as the rest.
+ location = /v1/search/icons {
+ limit_req zone=ovai_apps burst=20 nodelay;
+ limit_req_status 429;
+ limit_except POST { deny all; }
+ proxy_pass http://127.0.0.1:4700;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $remote_addr;
+ proxy_set_header CF-Connecting-IP $remote_addr;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
# Exchange's public read API (catalog, a model's offers, aggregate usage) and the OpenAPI document.
# Exchange's public read API (catalog, a model's offers, aggregate usage) and the OpenAPI document.
location ^~ /api/v1/exchange/ {
location ^~ /api/v1/exchange/ {
limit_req zone=ovai_apps burst=20 nodelay;
limit_req zone=ovai_apps burst=20 nodelay;
@@ -124,6 +138,6 @@ server {
@@ -124,6 +138,6 @@ server {
location / {
location / {
default_type application/json;
default_type application/json;
- return 404 '{"error":{"message":"Not found. The API is POST /v1/chat/completions, POST /v1/messages and GET /v1/models; docs at https://openvibe.exchange/api","type":"invalid_request_error","code":"not_found"}}';
+ return 404 '{"error":{"message":"Not found. The API is POST /v1/chat/completions, POST /v1/messages, POST /v1/search/icons and GET /v1/models; docs at https://openvibe.exchange/api","type":"invalid_request_error","code":"not_found"}}';
}
}
}
}
@@ -74,6 +74,7 @@ t.test('unregistered pricebook rows survive and the public route is cached and p
@@ -74,6 +74,7 @@ t.test('unregistered pricebook rows survive and the public route is cached and p
assert.strictEqual((await request(h.base,'GET','/api/v1/exchange/market?open_weights=bad')).status,422);
assert.strictEqual((await request(h.base,'GET','/api/v1/exchange/market?open_weights=bad')).status,422);
const nginx=fs.readFileSync(path.join(__dirname,'../deploy/nginx/api.openvibe.exchange.conf'),'utf8');
const nginx=fs.readFileSync(path.join(__dirname,'../deploy/nginx/api.openvibe.exchange.conf'),'utf8');
assert(nginx.includes('location ^~ /api/v1/exchange/'));
assert(nginx.includes('location ^~ /api/v1/exchange/'));
+ assert(nginx.includes('location = /v1/search/icons'), 'the Exchange icon search reaches OpenVibe.AI from the public API host');
let time=100; let reads=0;
let time=100; let reads=0;
const market=createMarket({db:h.db,registry:{listProviders:async()=>{reads++;return[];},listModels:async()=>[]},catalog:async()=>({models:[]}),clock:{now:()=>time},metadata:{read:async()=>({entries:[],checked_at:null,stale:true})}});
const market=createMarket({db:h.db,registry:{listProviders:async()=>{reads++;return[];},listModels:async()=>[]},catalog:async()=>({models:[]}),clock:{now:()=>time},metadata:{read:async()=>({entries:[],checked_at:null,stale:true})}});
await Promise.all([market.read({limit:1}),market.read({limit:2})]); assert.strictEqual(reads,1);
await Promise.all([market.read({limit:1}),market.read({limit:2})]); assert.strictEqual(reads,1);