openvibers/openvibe.codes Commits
DeepSeek on the Exchange: keep the agent's own model id (#385)
Changes
@@@ -65,7 +65,7 @@ More: [docs/cli.md](docs/cli.md).| OpenCode | `opencode run --format json` | `opencode` on PATH || Command Code | `cmd --print --output-format json` | `cmd` on PATH || Aider | `aider --message` | `aider` on PATH |--| DeepSeek | the harness's own tool loop, through OpenVibe.Exchange's compatible API with the SDK's Exchange client ([harness/adapters/api.js](harness/adapters/api.js), `openvibe-sdk/exchange`) | `OPENVIBE_EXCHANGE_KEY` (an Exchange key from openvibe.exchange; a person's or the service's) or a Codes run token; `OPENVIBE_EXCHANGE_URL` overrides the endpoint and `OPENVIBE_EXCHANGE_MODEL` the model (the default is the Exchange's id for the agent's DeepSeek chat model, `deepseek/deepseek-flash`). The direct DeepSeek API is used only for a self-hosted run: `OPENVIBE_CODES_DEEPSEEK_DIRECT=1` and your own `DEEPSEEK_API_KEY` (with `DEEPSEEK_BASE_URL` for another compatible server) |++| DeepSeek | the harness's own tool loop, through OpenVibe.Exchange's compatible API with the SDK's Exchange client ([harness/adapters/api.js](harness/adapters/api.js), `openvibe-sdk/exchange`) | `OPENVIBE_EXCHANGE_KEY` (an Exchange key from openvibe.exchange; a person's or the service's) or a Codes run token; `OPENVIBE_EXCHANGE_URL` overrides the endpoint and `OPENVIBE_EXCHANGE_MODEL` names an Exchange catalog id instead of the agent's own model id (the Exchange routes the request to the offer that can run it; the result names the model that answered). The direct DeepSeek API is used only for a self-hosted run: `OPENVIBE_CODES_DEEPSEEK_DIRECT=1` and your own `DEEPSEEK_API_KEY` (with `DEEPSEEK_BASE_URL` for another compatible server) || OpenVibe.AI | the same loop against AI's `/v1/chat/completions`; each answer reports its cost | `OPENVIBE_APP_ID` + `OPENVIBE_APP_SECRET` (an app with `ai.app.run`; tokens are fetched and refreshed), or `OPENVIBE_AI_TOKEN`. App runs on AI's router use free capacity without tools, so name your project's own provider: `OPENVIBE_AI_SECRET` (a project secret granted to `ai` for reveal to the provider's origin), `OPENVIBE_AI_PROVIDER_URL` (default `https://api.openai.com/v1`), `OPENVIBE_AI_PROVIDER_MODEL`. The key stays in OpenVibe.Network, never on the machine || Your own model | the same loop against any OpenAI-compatible server | `OPENVIBE_CODES_BASE_URL`, `OPENVIBE_CODES_MODEL`, optional `OPENVIBE_CODES_API_KEY` |@@@ -94,17 +94,14 @@ function exchangeKeyOf(env) {* A bare hosted run token at OpenVibe.AI (DEEPSEEK_BASE_URL there, DEEPSEEK_API_KEY = run token) goes through* Exchange: api.openvibe.exchange speaks the same compatible API and takes the same person's token.*/--/** The Exchange's catalog ids for the models the deepseek agent names (deepseek-chat is DeepSeek's own alias). */--const EXCHANGE_MODELS = { 'deepseek-chat': 'deepseek/deepseek-flash', 'deepseek-reasoner': 'deepseek/deepseek-v4-pro' };--/** A model the Exchange names itself (a catalog id or a dynamic alias) goes through as it is. */--const EXCHANGE_NAME = /^(openvibe(\/|$)|@preset\/)/;----/** The model the Exchange should run: its own id for the agent's model, a catalog id as given, a dynamic alias as given. */++/**++ * The model the Exchange should run: the agent's own id (deepseek-chat), or an Exchange catalog id when++ * OPENVIBE_EXCHANGE_MODEL names one. The id is sent as it is, the same model id the agent asked for; the Exchange's++ * router chooses the offer that can run the request (today its DeepSeek offer does not declare tools, so a tool loop++ * is answered by a tool-capable offer and the result names the model that ran).++ */function exchangeModelOf(model, env) {-- const named = String((env && env.OPENVIBE_EXCHANGE_MODEL) || model || '').trim();-- if (EXCHANGE_NAME.test(named) || named.includes('/')) return named;-- if (EXCHANGE_MODELS[named]) return EXCHANGE_MODELS[named];-- return `deepseek/${named || 'deepseek-flash'}`;++ return String((env && env.OPENVIBE_EXCHANGE_MODEL) || model || 'deepseek-chat').trim();}function deepseekAdapter(env) {@@@ -122,8 +119,7 @@ function deepseekAdapter(env) {return apiAgent({id: 'deepseek', name: 'DeepSeek', model: 'deepseek-chat',exchange: { baseUrl, ...exchange },-- // The same DeepSeek model under the Exchange's own catalog id (deepseek/deepseek-flash is DeepSeek's-- // V4.1 Flash, the model deepseek-chat names today); OPENVIBE_EXCHANGE_MODEL overrides it.++ // The agent's model id is sent as it is; OPENVIBE_EXCHANGE_MODEL names an Exchange catalog id instead.mapModel: exchangeModelOf,priceIn: 0.00027, priceOut: 0.0011,available: () => ({ available: true, where: baseUrl }),@@@ -65,7 +65,7 @@ const collect = async (run) => { const out = []; for await (const e of run) out.assert.strictEqual(api.requests.length, 2, 'the tool loop ran both turns');assert.strictEqual(api.requests[0].url, 'https://api.openvibe.exchange/v1/chat/completions');assert.strictEqual(api.requests[0].headers.authorization, `Bearer ${EXCHANGE_KEY}`);-- assert.strictEqual(api.requests[0].body.model, 'deepseek/deepseek-flash', 'the Exchange\'s id for the same DeepSeek model');++ assert.strictEqual(api.requests[0].body.model, 'deepseek-chat', 'the same model id the agent asked for');assert.ok(api.requests[0].body.tools.some((t) => t.function.name === 'read_file'), 'the same tools');assert.ok(api.requests[1].body.messages.some((m) => m.role === 'tool' && m.content.includes('const name = 1;')), 'the tool result went back to the model');assert.strictEqual(r.receipt_id, 'rcpt_test', 'the Exchange receipt is on the result');@@@ -73,12 +73,10 @@ const collect = async (run) => { const out = []; for await (const e of run) out.assert.deepStrictEqual([r.usage.input_tokens, r.usage.cache_read_input_tokens, r.usage.output_tokens], [1200, 800, 200]);});-- await check('the agent\'s model ids map to the Exchange\'s catalog ids, and an Exchange id is not mapped again', () => {-- assert.strictEqual(exchangeModelOf('deepseek-chat'), 'deepseek/deepseek-flash');-- assert.strictEqual(exchangeModelOf('deepseek-reasoner'), 'deepseek/deepseek-v4-pro');-- assert.strictEqual(exchangeModelOf('deepseek/deepseek-v4-pro'), 'deepseek/deepseek-v4-pro');-- assert.strictEqual(exchangeModelOf('openvibe'), 'openvibe');-- assert.strictEqual(exchangeModelOf('deepseek-chat', { OPENVIBE_EXCHANGE_MODEL: 'openvibe/auto' }), 'openvibe/auto');++ await check('the agent\'s model id goes as it is; OPENVIBE_EXCHANGE_MODEL can name an Exchange catalog id', () => {++ assert.strictEqual(exchangeModelOf('deepseek-chat'), 'deepseek-chat');++ assert.strictEqual(exchangeModelOf('deepseek-reasoner'), 'deepseek-reasoner');++ assert.strictEqual(exchangeModelOf('deepseek-chat', { OPENVIBE_EXCHANGE_MODEL: 'deepseek/deepseek-v4-pro' }), 'deepseek/deepseek-v4-pro');});await check('a service Exchange key is the fallback, and the Exchange URL can be pointed elsewhere', async () => {
@@ -65,7 +65,7 @@ More: [docs/cli.md](docs/cli.md).
@@ -65,7 +65,7 @@ More: [docs/cli.md](docs/cli.md).
| OpenCode | `opencode run --format json` | `opencode` on PATH |
| OpenCode | `opencode run --format json` | `opencode` on PATH |
| Command Code | `cmd --print --output-format json` | `cmd` on PATH |
| Command Code | `cmd --print --output-format json` | `cmd` on PATH |
| Aider | `aider --message` | `aider` on PATH |
| Aider | `aider --message` | `aider` on PATH |
-| DeepSeek | the harness's own tool loop, through OpenVibe.Exchange's compatible API with the SDK's Exchange client ([harness/adapters/api.js](harness/adapters/api.js), `openvibe-sdk/exchange`) | `OPENVIBE_EXCHANGE_KEY` (an Exchange key from openvibe.exchange; a person's or the service's) or a Codes run token; `OPENVIBE_EXCHANGE_URL` overrides the endpoint and `OPENVIBE_EXCHANGE_MODEL` the model (the default is the Exchange's id for the agent's DeepSeek chat model, `deepseek/deepseek-flash`). The direct DeepSeek API is used only for a self-hosted run: `OPENVIBE_CODES_DEEPSEEK_DIRECT=1` and your own `DEEPSEEK_API_KEY` (with `DEEPSEEK_BASE_URL` for another compatible server) |
+| DeepSeek | the harness's own tool loop, through OpenVibe.Exchange's compatible API with the SDK's Exchange client ([harness/adapters/api.js](harness/adapters/api.js), `openvibe-sdk/exchange`) | `OPENVIBE_EXCHANGE_KEY` (an Exchange key from openvibe.exchange; a person's or the service's) or a Codes run token; `OPENVIBE_EXCHANGE_URL` overrides the endpoint and `OPENVIBE_EXCHANGE_MODEL` names an Exchange catalog id instead of the agent's own model id (the Exchange routes the request to the offer that can run it; the result names the model that answered). The direct DeepSeek API is used only for a self-hosted run: `OPENVIBE_CODES_DEEPSEEK_DIRECT=1` and your own `DEEPSEEK_API_KEY` (with `DEEPSEEK_BASE_URL` for another compatible server) |
| OpenVibe.AI | the same loop against AI's `/v1/chat/completions`; each answer reports its cost | `OPENVIBE_APP_ID` + `OPENVIBE_APP_SECRET` (an app with `ai.app.run`; tokens are fetched and refreshed), or `OPENVIBE_AI_TOKEN`. App runs on AI's router use free capacity without tools, so name your project's own provider: `OPENVIBE_AI_SECRET` (a project secret granted to `ai` for reveal to the provider's origin), `OPENVIBE_AI_PROVIDER_URL` (default `https://api.openai.com/v1`), `OPENVIBE_AI_PROVIDER_MODEL`. The key stays in OpenVibe.Network, never on the machine |
| OpenVibe.AI | the same loop against AI's `/v1/chat/completions`; each answer reports its cost | `OPENVIBE_APP_ID` + `OPENVIBE_APP_SECRET` (an app with `ai.app.run`; tokens are fetched and refreshed), or `OPENVIBE_AI_TOKEN`. App runs on AI's router use free capacity without tools, so name your project's own provider: `OPENVIBE_AI_SECRET` (a project secret granted to `ai` for reveal to the provider's origin), `OPENVIBE_AI_PROVIDER_URL` (default `https://api.openai.com/v1`), `OPENVIBE_AI_PROVIDER_MODEL`. The key stays in OpenVibe.Network, never on the machine |
| Your own model | the same loop against any OpenAI-compatible server | `OPENVIBE_CODES_BASE_URL`, `OPENVIBE_CODES_MODEL`, optional `OPENVIBE_CODES_API_KEY` |
| Your own model | the same loop against any OpenAI-compatible server | `OPENVIBE_CODES_BASE_URL`, `OPENVIBE_CODES_MODEL`, optional `OPENVIBE_CODES_API_KEY` |
@@ -94,17 +94,14 @@ function exchangeKeyOf(env) {
@@ -94,17 +94,14 @@ function exchangeKeyOf(env) {
* A bare hosted run token at OpenVibe.AI (DEEPSEEK_BASE_URL there, DEEPSEEK_API_KEY = run token) goes through
* A bare hosted run token at OpenVibe.AI (DEEPSEEK_BASE_URL there, DEEPSEEK_API_KEY = run token) goes through
* Exchange: api.openvibe.exchange speaks the same compatible API and takes the same person's token.
* Exchange: api.openvibe.exchange speaks the same compatible API and takes the same person's token.
*/
*/
-/** The Exchange's catalog ids for the models the deepseek agent names (deepseek-chat is DeepSeek's own alias). */
-const EXCHANGE_MODELS = { 'deepseek-chat': 'deepseek/deepseek-flash', 'deepseek-reasoner': 'deepseek/deepseek-v4-pro' };
-/** A model the Exchange names itself (a catalog id or a dynamic alias) goes through as it is. */
-const EXCHANGE_NAME = /^(openvibe(\/|$)|@preset\/)/;
-
-/** The model the Exchange should run: its own id for the agent's model, a catalog id as given, a dynamic alias as given. */
+/**
+ * The model the Exchange should run: the agent's own id (deepseek-chat), or an Exchange catalog id when
+ * OPENVIBE_EXCHANGE_MODEL names one. The id is sent as it is, the same model id the agent asked for; the Exchange's
+ * router chooses the offer that can run the request (today its DeepSeek offer does not declare tools, so a tool loop
+ * is answered by a tool-capable offer and the result names the model that ran).
+ */
function exchangeModelOf(model, env) {
function exchangeModelOf(model, env) {
- const named = String((env && env.OPENVIBE_EXCHANGE_MODEL) || model || '').trim();
- if (EXCHANGE_NAME.test(named) || named.includes('/')) return named;
- if (EXCHANGE_MODELS[named]) return EXCHANGE_MODELS[named];
- return `deepseek/${named || 'deepseek-flash'}`;
+ return String((env && env.OPENVIBE_EXCHANGE_MODEL) || model || 'deepseek-chat').trim();
}
}
function deepseekAdapter(env) {
function deepseekAdapter(env) {
@@ -122,8 +119,7 @@ function deepseekAdapter(env) {
@@ -122,8 +119,7 @@ function deepseekAdapter(env) {
return apiAgent({
return apiAgent({
id: 'deepseek', name: 'DeepSeek', model: 'deepseek-chat',
id: 'deepseek', name: 'DeepSeek', model: 'deepseek-chat',
exchange: { baseUrl, ...exchange },
exchange: { baseUrl, ...exchange },
- // The same DeepSeek model under the Exchange's own catalog id (deepseek/deepseek-flash is DeepSeek's
- // V4.1 Flash, the model deepseek-chat names today); OPENVIBE_EXCHANGE_MODEL overrides it.
+ // The agent's model id is sent as it is; OPENVIBE_EXCHANGE_MODEL names an Exchange catalog id instead.
mapModel: exchangeModelOf,
mapModel: exchangeModelOf,
priceIn: 0.00027, priceOut: 0.0011,
priceIn: 0.00027, priceOut: 0.0011,
available: () => ({ available: true, where: baseUrl }),
available: () => ({ available: true, where: baseUrl }),
@@ -65,7 +65,7 @@ const collect = async (run) => { const out = []; for await (const e of run) out.
@@ -65,7 +65,7 @@ const collect = async (run) => { const out = []; for await (const e of run) out.
assert.strictEqual(api.requests.length, 2, 'the tool loop ran both turns');
assert.strictEqual(api.requests.length, 2, 'the tool loop ran both turns');
assert.strictEqual(api.requests[0].url, 'https://api.openvibe.exchange/v1/chat/completions');
assert.strictEqual(api.requests[0].url, 'https://api.openvibe.exchange/v1/chat/completions');
assert.strictEqual(api.requests[0].headers.authorization, `Bearer ${EXCHANGE_KEY}`);
assert.strictEqual(api.requests[0].headers.authorization, `Bearer ${EXCHANGE_KEY}`);
- assert.strictEqual(api.requests[0].body.model, 'deepseek/deepseek-flash', 'the Exchange\'s id for the same DeepSeek model');
+ assert.strictEqual(api.requests[0].body.model, 'deepseek-chat', 'the same model id the agent asked for');
assert.ok(api.requests[0].body.tools.some((t) => t.function.name === 'read_file'), 'the same tools');
assert.ok(api.requests[0].body.tools.some((t) => t.function.name === 'read_file'), 'the same tools');
assert.ok(api.requests[1].body.messages.some((m) => m.role === 'tool' && m.content.includes('const name = 1;')), 'the tool result went back to the model');
assert.ok(api.requests[1].body.messages.some((m) => m.role === 'tool' && m.content.includes('const name = 1;')), 'the tool result went back to the model');
assert.strictEqual(r.receipt_id, 'rcpt_test', 'the Exchange receipt is on the result');
assert.strictEqual(r.receipt_id, 'rcpt_test', 'the Exchange receipt is on the result');
@@ -73,12 +73,10 @@ const collect = async (run) => { const out = []; for await (const e of run) out.
@@ -73,12 +73,10 @@ const collect = async (run) => { const out = []; for await (const e of run) out.
assert.deepStrictEqual([r.usage.input_tokens, r.usage.cache_read_input_tokens, r.usage.output_tokens], [1200, 800, 200]);
assert.deepStrictEqual([r.usage.input_tokens, r.usage.cache_read_input_tokens, r.usage.output_tokens], [1200, 800, 200]);
});
});
- await check('the agent\'s model ids map to the Exchange\'s catalog ids, and an Exchange id is not mapped again', () => {
- assert.strictEqual(exchangeModelOf('deepseek-chat'), 'deepseek/deepseek-flash');
- assert.strictEqual(exchangeModelOf('deepseek-reasoner'), 'deepseek/deepseek-v4-pro');
- assert.strictEqual(exchangeModelOf('deepseek/deepseek-v4-pro'), 'deepseek/deepseek-v4-pro');
- assert.strictEqual(exchangeModelOf('openvibe'), 'openvibe');
- assert.strictEqual(exchangeModelOf('deepseek-chat', { OPENVIBE_EXCHANGE_MODEL: 'openvibe/auto' }), 'openvibe/auto');
+ await check('the agent\'s model id goes as it is; OPENVIBE_EXCHANGE_MODEL can name an Exchange catalog id', () => {
+ assert.strictEqual(exchangeModelOf('deepseek-chat'), 'deepseek-chat');
+ assert.strictEqual(exchangeModelOf('deepseek-reasoner'), 'deepseek-reasoner');
+ assert.strictEqual(exchangeModelOf('deepseek-chat', { OPENVIBE_EXCHANGE_MODEL: 'deepseek/deepseek-v4-pro' }), 'deepseek/deepseek-v4-pro');
});
});
await check('a service Exchange key is the fallback, and the Exchange URL can be pointed elsewhere', async () => {
await check('a service Exchange key is the fallback, and the Exchange URL can be pointed elsewhere', async () => {