openvibers/openvibe.ai Commits
openvibe-sdk v0.35.0 (was v0.26.0) (#26)
Changes
@@@ -43,7 +43,7 @@ owns publication truth: Wiki, Blog, News, Live, … decide what to publish.- OpenVibe.Media (recordings `media.analyze` reads, over allow-listed https URLs or signed URLs)- provider APIs configured by the operator (OpenAI-compatible, OpenAI Responses, Anthropic, the HTTP seam), whisper.cppand the local model server `openvibe-llm.service` on this host--- `openvibe-contracts` v0.86.0, `openvibe-sdk` v0.26.0 (service tokens, events outbox),++- `openvibe-contracts` v0.86.0, `openvibe-sdk` v0.35.0 (service tokens, events outbox),`openvibe-shared` v2.3.1, pinned by release tarball## Capabilities@@@ -483,6 +483,6 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [<!-- versions:start -->- openvibe-contracts: v0.112.0--- openvibe-sdk: v0.26.0++- openvibe-sdk: v0.35.0- openvibe-shared: v2.13.0<!-- versions:end -->@@@ -8,7 +8,7 @@"port": 4700,"domain": "ai.openvibe.network still serves the OpenVibe.Sites placeholder; the service runs on openvibe-ovh (/opt/openvibe.ai, unit openvibe-ai) on 127.0.0.1:4700, loopback only, used by OpenVibe.Live and OpenVibe.Network","contracts": "openvibe-contracts v0.112.0",-- "sdk": "openvibe-sdk v0.26.0",++ "sdk": "openvibe-sdk v0.35.0","shared": "openvibe-shared v2.13.0","capabilities": ["ai.run.create",@@@ -14,7 +14,7 @@"express": "^4.21.2","iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.112.0",-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0","openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.13.0","pg": "^8.23.0"},@@@ -1281,9 +1281,9 @@}},"node_modules/openvibe-sdk": {-- "version": "0.26.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",-- "integrity": "sha512-+RK89ZG4WAKIRh89yr3ieIvnyJVNSOI+dsbBqGNcr9UHsJStdxtM75zSDq8cUNpBPQ1X6ElatZrjU9S3s0aEkg==",++ "version": "0.35.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0",++ "integrity": "sha512-IF7XB97tU7Q+ccwssGx6QXXcfDPcVpl+mX3BjIr7C6NJlcmoqj9DbHiKrBxlVav0eG44Jf8zuXEwokrHDq3J0w==","license": "MIT","engines": {"node": ">=20"@@@ -26,7 +26,7 @@"express": "^4.21.2","iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.112.0",-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0","openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.13.0","pg": "^8.23.0"},@@@ -80,7 +80,7 @@ function createTiers({ config, valkey = null, clock = { now: () => Date.now() },if (owned) valkey = createValkey({ url: g.valkeyUrl, prefix: g.valkeyPrefix, log });if (!valkey && config.isProduction) throw new Error('AI_GOVERN_TIERS=1 needs VALKEY_URL: the tier budgets are counted in Valkey');if (!valkey) log.warn('[govern] no VALKEY_URL: tier budgets are counted in this process only');-- const gov = createGovernor({ policy: g.policy, valkey, now: () => clock.now() });++ const gov = createGovernor({ service: 'ai', policy: g.policy, valkey, now: () => clock.now(), log });const free = freeCounters(valkey);const tierOf = (...keys) => { for (const k of keys) if (k && g.tiers[k]) return g.tiers[k]; return g.defaultTier; };let lastError = null;@@@ -160,7 +160,7 @@ function createTiers({ config, valkey = null, clock = { now: () => Date.now() },*/function createMeter() {const seen = new Map();-- const gov = createGovernor({ onUsage: (e) => { if (seen.has(e.idempotency_key)) seen.set(e.idempotency_key, e); } });++ const gov = createGovernor({ service: 'ai', onUsage: (e) => { if (seen.has(e.idempotency_key)) seen.set(e.idempotency_key, e); } });return async function reading({ subject, quantity, key }) {if (seen.has(key)) return null;seen.set(key, null);@@@ -54,7 +54,7 @@ function sampleOf({ runId, attempt, kind, workflowKey, requester, project = null}/** The reading: govern's onUsage record (id, subject, quantity) on the attempt's own fields. */function sampleFromUsage(e, fields) {-- return { ...sampleOf(fields), id: e.idempotency_key, idempotency_key: e.idempotency_key, subject: e.subject, quantity: e.amount };++ return { ...sampleOf(fields), id: e.idempotency_key, idempotency_key: e.idempotency_key, subject: e.subject, quantity: e.quantity };}/*** MUST be awaited inside the transaction that accounts the run. `readings` are the run's per-attempt,@@@ -140,7 +140,7 @@ t.test('the meter: one onUsage record per idempotency key at once', async () =>const reading = createMeter();const got = await Promise.all([reading({ subject: 'service:live', quantity: 5, key: 'ai:run_m:tokens' }), reading({ subject: 'service:live', quantity: 5, key: 'ai:run_m:tokens' })]);assert.strictEqual(got.filter(Boolean).length, 1);-- assert.deepStrictEqual([got.find(Boolean).idempotency_key, got.find(Boolean).amount, got.find(Boolean).subject], ['ai:run_m:tokens', 5, 'service:live']);++ assert.deepStrictEqual([got.find(Boolean).idempotency_key, got.find(Boolean).quantity, got.find(Boolean).subject, got.find(Boolean).service], ['ai:run_m:tokens', 5, 'service:live', 'ai']);});// On the containers' Valkey (OV_TEST_VALKEY_URL, set by openvibe-sdk/scripts/test-services.sh): govern's Lua and the
@@ -43,7 +43,7 @@ owns publication truth: Wiki, Blog, News, Live, … decide what to publish.
@@ -43,7 +43,7 @@ owns publication truth: Wiki, Blog, News, Live, … decide what to publish.
- OpenVibe.Media (recordings `media.analyze` reads, over allow-listed https URLs or signed URLs)
- OpenVibe.Media (recordings `media.analyze` reads, over allow-listed https URLs or signed URLs)
- provider APIs configured by the operator (OpenAI-compatible, OpenAI Responses, Anthropic, the HTTP seam), whisper.cpp
- provider APIs configured by the operator (OpenAI-compatible, OpenAI Responses, Anthropic, the HTTP seam), whisper.cpp
and the local model server `openvibe-llm.service` on this host
and the local model server `openvibe-llm.service` on this host
-- `openvibe-contracts` v0.86.0, `openvibe-sdk` v0.26.0 (service tokens, events outbox),
+- `openvibe-contracts` v0.86.0, `openvibe-sdk` v0.35.0 (service tokens, events outbox),
`openvibe-shared` v2.3.1, pinned by release tarball
`openvibe-shared` v2.3.1, pinned by release tarball
## Capabilities
## Capabilities
@@ -483,6 +483,6 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [
@@ -483,6 +483,6 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [
<!-- versions:start -->
<!-- versions:start -->
- openvibe-contracts: v0.112.0
- openvibe-contracts: v0.112.0
-- openvibe-sdk: v0.26.0
+- openvibe-sdk: v0.35.0
- openvibe-shared: v2.13.0
- openvibe-shared: v2.13.0
<!-- versions:end -->
<!-- versions:end -->
@@ -8,7 +8,7 @@
@@ -8,7 +8,7 @@
"port": 4700,
"port": 4700,
"domain": "ai.openvibe.network still serves the OpenVibe.Sites placeholder; the service runs on openvibe-ovh (/opt/openvibe.ai, unit openvibe-ai) on 127.0.0.1:4700, loopback only, used by OpenVibe.Live and OpenVibe.Network",
"domain": "ai.openvibe.network still serves the OpenVibe.Sites placeholder; the service runs on openvibe-ovh (/opt/openvibe.ai, unit openvibe-ai) on 127.0.0.1:4700, loopback only, used by OpenVibe.Live and OpenVibe.Network",
"contracts": "openvibe-contracts v0.112.0",
"contracts": "openvibe-contracts v0.112.0",
- "sdk": "openvibe-sdk v0.26.0",
+ "sdk": "openvibe-sdk v0.35.0",
"shared": "openvibe-shared v2.13.0",
"shared": "openvibe-shared v2.13.0",
"capabilities": [
"capabilities": [
"ai.run.create",
"ai.run.create",
@@ -14,7 +14,7 @@
@@ -14,7 +14,7 @@
"express": "^4.21.2",
"express": "^4.21.2",
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.112.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.112.0",
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.13.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.13.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
@@ -1281,9 +1281,9 @@
@@ -1281,9 +1281,9 @@
}
}
},
},
"node_modules/openvibe-sdk": {
"node_modules/openvibe-sdk": {
- "version": "0.26.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",
- "integrity": "sha512-+RK89ZG4WAKIRh89yr3ieIvnyJVNSOI+dsbBqGNcr9UHsJStdxtM75zSDq8cUNpBPQ1X6ElatZrjU9S3s0aEkg==",
+ "version": "0.35.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0",
+ "integrity": "sha512-IF7XB97tU7Q+ccwssGx6QXXcfDPcVpl+mX3BjIr7C6NJlcmoqj9DbHiKrBxlVav0eG44Jf8zuXEwokrHDq3J0w==",
"license": "MIT",
"license": "MIT",
"engines": {
"engines": {
"node": ">=20"
"node": ">=20"
@@ -26,7 +26,7 @@
@@ -26,7 +26,7 @@
"express": "^4.21.2",
"express": "^4.21.2",
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.112.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.112.0",
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.13.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.13.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
@@ -80,7 +80,7 @@ function createTiers({ config, valkey = null, clock = { now: () => Date.now() },
@@ -80,7 +80,7 @@ function createTiers({ config, valkey = null, clock = { now: () => Date.now() },
if (owned) valkey = createValkey({ url: g.valkeyUrl, prefix: g.valkeyPrefix, log });
if (owned) valkey = createValkey({ url: g.valkeyUrl, prefix: g.valkeyPrefix, log });
if (!valkey && config.isProduction) throw new Error('AI_GOVERN_TIERS=1 needs VALKEY_URL: the tier budgets are counted in Valkey');
if (!valkey && config.isProduction) throw new Error('AI_GOVERN_TIERS=1 needs VALKEY_URL: the tier budgets are counted in Valkey');
if (!valkey) log.warn('[govern] no VALKEY_URL: tier budgets are counted in this process only');
if (!valkey) log.warn('[govern] no VALKEY_URL: tier budgets are counted in this process only');
- const gov = createGovernor({ policy: g.policy, valkey, now: () => clock.now() });
+ const gov = createGovernor({ service: 'ai', policy: g.policy, valkey, now: () => clock.now(), log });
const free = freeCounters(valkey);
const free = freeCounters(valkey);
const tierOf = (...keys) => { for (const k of keys) if (k && g.tiers[k]) return g.tiers[k]; return g.defaultTier; };
const tierOf = (...keys) => { for (const k of keys) if (k && g.tiers[k]) return g.tiers[k]; return g.defaultTier; };
let lastError = null;
let lastError = null;
@@ -160,7 +160,7 @@ function createTiers({ config, valkey = null, clock = { now: () => Date.now() },
@@ -160,7 +160,7 @@ function createTiers({ config, valkey = null, clock = { now: () => Date.now() },
*/
*/
function createMeter() {
function createMeter() {
const seen = new Map();
const seen = new Map();
- const gov = createGovernor({ onUsage: (e) => { if (seen.has(e.idempotency_key)) seen.set(e.idempotency_key, e); } });
+ const gov = createGovernor({ service: 'ai', onUsage: (e) => { if (seen.has(e.idempotency_key)) seen.set(e.idempotency_key, e); } });
return async function reading({ subject, quantity, key }) {
return async function reading({ subject, quantity, key }) {
if (seen.has(key)) return null;
if (seen.has(key)) return null;
seen.set(key, null);
seen.set(key, null);
@@ -54,7 +54,7 @@ function sampleOf({ runId, attempt, kind, workflowKey, requester, project = null
@@ -54,7 +54,7 @@ function sampleOf({ runId, attempt, kind, workflowKey, requester, project = null
}
}
/** The reading: govern's onUsage record (id, subject, quantity) on the attempt's own fields. */
/** The reading: govern's onUsage record (id, subject, quantity) on the attempt's own fields. */
function sampleFromUsage(e, fields) {
function sampleFromUsage(e, fields) {
- return { ...sampleOf(fields), id: e.idempotency_key, idempotency_key: e.idempotency_key, subject: e.subject, quantity: e.amount };
+ return { ...sampleOf(fields), id: e.idempotency_key, idempotency_key: e.idempotency_key, subject: e.subject, quantity: e.quantity };
}
}
/**
/**
* MUST be awaited inside the transaction that accounts the run. `readings` are the run's per-attempt,
* MUST be awaited inside the transaction that accounts the run. `readings` are the run's per-attempt,
@@ -140,7 +140,7 @@ t.test('the meter: one onUsage record per idempotency key at once', async () =>
@@ -140,7 +140,7 @@ t.test('the meter: one onUsage record per idempotency key at once', async () =>
const reading = createMeter();
const reading = createMeter();
const got = await Promise.all([reading({ subject: 'service:live', quantity: 5, key: 'ai:run_m:tokens' }), reading({ subject: 'service:live', quantity: 5, key: 'ai:run_m:tokens' })]);
const got = await Promise.all([reading({ subject: 'service:live', quantity: 5, key: 'ai:run_m:tokens' }), reading({ subject: 'service:live', quantity: 5, key: 'ai:run_m:tokens' })]);
assert.strictEqual(got.filter(Boolean).length, 1);
assert.strictEqual(got.filter(Boolean).length, 1);
- assert.deepStrictEqual([got.find(Boolean).idempotency_key, got.find(Boolean).amount, got.find(Boolean).subject], ['ai:run_m:tokens', 5, 'service:live']);
+ assert.deepStrictEqual([got.find(Boolean).idempotency_key, got.find(Boolean).quantity, got.find(Boolean).subject, got.find(Boolean).service], ['ai:run_m:tokens', 5, 'service:live', 'ai']);
});
});
// On the containers' Valkey (OV_TEST_VALKEY_URL, set by openvibe-sdk/scripts/test-services.sh): govern's Lua and the
// On the containers' Valkey (OV_TEST_VALKEY_URL, set by openvibe-sdk/scripts/test-services.sh): govern's Lua and the