@konduiteu/openclaw 0.1.0
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/LICENSE +21 -0
- package/README.md +125 -0
- package/assets/icon.png +0 -0
- package/dist/catalog.js +20 -0
- package/dist/index.js +35 -0
- package/dist/usage.js +108 -0
- package/openclaw.plugin.json +360 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 schnaq GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# konduit for OpenClaw
|
|
2
|
+
|
|
3
|
+
European AI inference through [konduit](https://konduit.eu) as an OpenClaw
|
|
4
|
+
provider, with the organisation's live credit balance and rate limits on the
|
|
5
|
+
provider card.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
openclaw plugins install clawhub:@konduiteu/openclaw
|
|
11
|
+
openclaw plugins enable konduit
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
or from npm, which asks you to confirm the source:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
openclaw plugins install npm:@konduiteu/openclaw
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Set `KONDUIT_API_KEY` to a key minted in the [konduit console](https://console.konduit.eu),
|
|
21
|
+
or run `openclaw onboard --konduit-api-key kdt-…`. The key needs the
|
|
22
|
+
`usage:read` scope for the card to show a balance; a key created without any
|
|
23
|
+
scopes is unrestricted and works as it is. A key narrowed to `chat:write` still
|
|
24
|
+
completes, and the card says why it shows nothing.
|
|
25
|
+
|
|
26
|
+
If you had configured konduit by hand under `models.providers.konduit`, keep
|
|
27
|
+
it: OpenClaw merges your entries with the plugin's by model id, and your
|
|
28
|
+
`baseUrl` wins.
|
|
29
|
+
|
|
30
|
+
## What the card shows
|
|
31
|
+
|
|
32
|
+
- **Available credit** — the organisation's `available_micro_eur`, in EUR.
|
|
33
|
+
Every key of an organisation sees the same wallet; it is the number that
|
|
34
|
+
explains a `402`.
|
|
35
|
+
- **Summary** — an open reservation if there is one, then the rate limits the
|
|
36
|
+
key is actually held to: its own, and its organisation's (or konduit's
|
|
37
|
+
deployment default where the organisation set none). `60 RPM · 100K TPM
|
|
38
|
+
(key) · 1M TPM (organisation)`. A limit that is not configured is left out.
|
|
39
|
+
- Errors name their cause: a rejected key, a key without `usage:read`, a
|
|
40
|
+
rate limit, or konduit's ledger being unavailable.
|
|
41
|
+
|
|
42
|
+
OpenClaw caches a snapshot for a minute and refreshes when you look, so the
|
|
43
|
+
card costs one request a minute against your key's rate limit while it is
|
|
44
|
+
open.
|
|
45
|
+
|
|
46
|
+
## Models and prices
|
|
47
|
+
|
|
48
|
+
`openclaw.plugin.json` lists every chat deployment konduit still serves, with
|
|
49
|
+
context window, output cap and price, generated from `GET /v1/models`:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
KONDUIT_API_KEY=kdt-… npm run catalog
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
That includes deployments konduit marks `deprecated`: the status discourages
|
|
56
|
+
them, it does not switch them off, and a model you already have in your config
|
|
57
|
+
should not vanish from the list because of a label. Only `retired` deployments
|
|
58
|
+
are left out. The default model is never a deprecated one.
|
|
59
|
+
|
|
60
|
+
CI checks the committed list against the live catalog. Live discovery is on
|
|
61
|
+
as well, so a deployment konduit adds appears before the next release — at
|
|
62
|
+
cost zero until the generator has written its price.
|
|
63
|
+
|
|
64
|
+
**One caveat.** OpenClaw prices models in US dollars per million tokens and
|
|
65
|
+
has no currency field. konduit prices in euros; the euro figures are stored
|
|
66
|
+
in those fields, so OpenClaw's local *session cost* shows a dollar sign over
|
|
67
|
+
euro-denominated numbers. The balance on the card is not affected: it comes
|
|
68
|
+
from konduit and is formatted as EUR.
|
|
69
|
+
|
|
70
|
+
The two Hetzner deployments are listed at zero. That is a recorded price, not
|
|
71
|
+
a missing one: Hetzner's Inference API is free of charge while it stays
|
|
72
|
+
experimental.
|
|
73
|
+
|
|
74
|
+
## The contract
|
|
75
|
+
|
|
76
|
+
The plugin reads `GET /v1/usage`, described in the konduit repository's
|
|
77
|
+
`docs/openapi/gateway.yaml`. The plugin pins the API it was written against,
|
|
78
|
+
not a konduit commit.
|
|
79
|
+
|
|
80
|
+
Model ids are `provider/model` or `provider/model:variant`. The separator used
|
|
81
|
+
to be `@` and the plugin refuses to publish that spelling, because enough of
|
|
82
|
+
the ecosystem reads `@` as an instance selector to truncate an id in transit.
|
|
83
|
+
|
|
84
|
+
## Develop
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
npm install
|
|
88
|
+
npm test # vitest
|
|
89
|
+
npm run typecheck
|
|
90
|
+
npm run build # dist/, which is what OpenClaw loads
|
|
91
|
+
npm run validate # clawhub package validate
|
|
92
|
+
openclaw plugins install --link . # try it in a local OpenClaw
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`src/openclaw-sdk.d.ts` declares the plugin SDK subpaths that `openclaw`
|
|
96
|
+
ships without types. Delete it whole when a release types its own SDK;
|
|
97
|
+
nothing else has to change.
|
|
98
|
+
|
|
99
|
+
## Publish
|
|
100
|
+
|
|
101
|
+
The first ClawHub publish is manual and creates the package:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
npm exec clawhub -- login
|
|
105
|
+
npm run validate
|
|
106
|
+
npm exec clawhub -- package publish . --dry-run
|
|
107
|
+
npm exec clawhub -- package publish .
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
After it, set the trusted publisher once and the dispatch workflow publishes
|
|
111
|
+
with GitHub OIDC and no stored token:
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
npm exec clawhub -- package trusted-publisher set @konduiteu/openclaw \
|
|
115
|
+
--repository schnaq/openclaw-konduit \
|
|
116
|
+
--workflow-filename clawhub-publish.yml
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
npm publishes on a `v*` tag with provenance. Version, tag, push:
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
npm version minor && git push --follow-tags
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
MIT.
|
package/assets/icon.png
ADDED
|
Binary file
|
package/dist/catalog.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// The manifest is the source of truth for models and auth; this module is how
|
|
2
|
+
// the runtime reads it. It is read with createRequire rather than imported:
|
|
3
|
+
// tsc would otherwise pull the root JSON under rootDir and move dist/ around
|
|
4
|
+
// it. import.meta.url is dist/catalog.js in production and src/catalog.ts under
|
|
5
|
+
// vitest; ../openclaw.plugin.json is the package root from both.
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared";
|
|
8
|
+
export const PROVIDER_ID = "konduit";
|
|
9
|
+
export const manifest = createRequire(import.meta.url)("../openclaw.plugin.json");
|
|
10
|
+
const catalog = manifest.modelCatalog.providers.konduit;
|
|
11
|
+
/** Where konduit's OpenAI-compatible API lives; a user's configured baseUrl overrides it. */
|
|
12
|
+
export const KONDUIT_BASE_URL = catalog.baseUrl;
|
|
13
|
+
/** The provider as OpenClaw's catalog wants it. A copy every call; the manifest is never handed out. */
|
|
14
|
+
export function buildKonduitProvider() {
|
|
15
|
+
return {
|
|
16
|
+
baseUrl: KONDUIT_BASE_URL,
|
|
17
|
+
api: "openai-completions",
|
|
18
|
+
models: structuredClone(buildManifestModelProviderConfig({ providerId: PROVIDER_ID, catalog: catalog }).models),
|
|
19
|
+
};
|
|
20
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// konduit as an OpenClaw provider: European inference over an OpenAI-compatible
|
|
2
|
+
// API, plus the two hooks that put the organisation's balance and limits on the
|
|
3
|
+
// provider card. Shape follows OpenClaw's bundled extensions/deepseek.
|
|
4
|
+
import { defineSingleProviderPluginEntry, } from "openclaw/plugin-sdk/provider-entry";
|
|
5
|
+
import { buildKonduitProvider, manifest, PROVIDER_ID } from "./catalog.js";
|
|
6
|
+
import { fetchKonduitUsage, resolveBaseUrl } from "./usage.js";
|
|
7
|
+
export default defineSingleProviderPluginEntry({
|
|
8
|
+
id: PROVIDER_ID,
|
|
9
|
+
name: "konduit",
|
|
10
|
+
description: "European AI inference through konduit, with live balance and rate limits.",
|
|
11
|
+
// API-key auth and the model catalog are derived from the manifest, so they
|
|
12
|
+
// are declared once, in openclaw.plugin.json, and the generator keeps them.
|
|
13
|
+
manifest: manifest,
|
|
14
|
+
provider: {
|
|
15
|
+
label: "konduit",
|
|
16
|
+
docsPath: "/providers/konduit",
|
|
17
|
+
catalog: {
|
|
18
|
+
buildProvider: buildKonduitProvider,
|
|
19
|
+
buildStaticProvider: buildKonduitProvider,
|
|
20
|
+
// A user who configured models.providers.konduit.baseUrl keeps it.
|
|
21
|
+
allowExplicitBaseUrl: true,
|
|
22
|
+
// GET {baseUrl}/models: a deployment konduit adds appears without a plugin
|
|
23
|
+
// release, at cost zero until the generator writes its price.
|
|
24
|
+
liveModelDiscovery: true,
|
|
25
|
+
discoveryMode: "strict",
|
|
26
|
+
},
|
|
27
|
+
// Both hooks are required for OpenClaw to poll this provider's usage at
|
|
28
|
+
// all; a plugin with only fetchUsageSnapshot is not auto-discovered.
|
|
29
|
+
resolveUsageAuth: async (ctx) => {
|
|
30
|
+
const token = ctx.resolveApiKeyFromConfigAndStore({ envDirect: [ctx.env.KONDUIT_API_KEY] });
|
|
31
|
+
return token ? { token } : null;
|
|
32
|
+
},
|
|
33
|
+
fetchUsageSnapshot: async (ctx) => await fetchKonduitUsage(resolveBaseUrl(ctx.config), ctx.token, ctx.timeoutMs, ctx.fetchFn),
|
|
34
|
+
},
|
|
35
|
+
});
|
package/dist/usage.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// GET /v1/usage → OpenClaw's provider card. The contract is the `Usage` schema
|
|
2
|
+
// in schnaq/konduit's docs/openapi/gateway.yaml; this file is its only reader.
|
|
3
|
+
import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
|
|
4
|
+
import { buildUsageErrorSnapshot, fetchJson, } from "openclaw/plugin-sdk/provider-usage";
|
|
5
|
+
import { KONDUIT_BASE_URL, PROVIDER_ID } from "./catalog.js";
|
|
6
|
+
export const DISPLAY_NAME = "konduit";
|
|
7
|
+
const MICRO_PER_UNIT = 1_000_000;
|
|
8
|
+
const MALFORMED = "Malformed usage response";
|
|
9
|
+
/**
|
|
10
|
+
* The base URL the usage call goes to. A user who pointed models.providers.konduit
|
|
11
|
+
* at another gateway must have their balance read from that gateway, never from
|
|
12
|
+
* the manifest's default; the manifest is the fallback only.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveBaseUrl(config) {
|
|
15
|
+
const configured = config.models?.providers?.[PROVIDER_ID]?.baseUrl?.trim();
|
|
16
|
+
return (configured || KONDUIT_BASE_URL).replace(/\/+$/, "");
|
|
17
|
+
}
|
|
18
|
+
// What a customer reads when the call is refused. The statuses are konduit's
|
|
19
|
+
// documented ones; anything else is shown as its number.
|
|
20
|
+
const REFUSALS = {
|
|
21
|
+
401: "konduit rejected the API key",
|
|
22
|
+
403: "the API key lacks the usage:read scope",
|
|
23
|
+
429: "rate limited by konduit; the card refreshes later",
|
|
24
|
+
503: "konduit's ledger is unavailable; retry shortly",
|
|
25
|
+
};
|
|
26
|
+
export async function fetchKonduitUsage(baseUrl, token, timeoutMs, fetchFn) {
|
|
27
|
+
const response = await fetchJson(`${baseUrl}/usage`, { method: "GET", headers: { Authorization: `Bearer ${token}`, Accept: "application/json" } }, timeoutMs, fetchFn);
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
await response.body?.cancel().catch(() => undefined);
|
|
30
|
+
return buildUsageErrorSnapshot(PROVIDER_ID, REFUSALS[response.status] ?? `HTTP ${response.status}`);
|
|
31
|
+
}
|
|
32
|
+
let data;
|
|
33
|
+
try {
|
|
34
|
+
data = await readProviderJsonResponse(response, "konduit usage");
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return buildUsageErrorSnapshot(PROVIDER_ID, MALFORMED);
|
|
38
|
+
}
|
|
39
|
+
return snapshotFrom(data);
|
|
40
|
+
}
|
|
41
|
+
/** Maps one answer to the card. Pure, so the mapping is tested without a fetch. */
|
|
42
|
+
export function snapshotFrom(data) {
|
|
43
|
+
if (!isUsageResponse(data)) {
|
|
44
|
+
return buildUsageErrorSnapshot(PROVIDER_ID, MALFORMED);
|
|
45
|
+
}
|
|
46
|
+
const { balance, limits } = data;
|
|
47
|
+
return {
|
|
48
|
+
provider: PROVIDER_ID,
|
|
49
|
+
displayName: DISPLAY_NAME,
|
|
50
|
+
windows: [],
|
|
51
|
+
billing: [
|
|
52
|
+
{
|
|
53
|
+
type: "balance",
|
|
54
|
+
label: "Available credit",
|
|
55
|
+
amount: balance.available_micro_eur / MICRO_PER_UNIT,
|
|
56
|
+
unit: balance.currency,
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
summary: summarise(balance, limits),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function summarise(balance, limits) {
|
|
63
|
+
const parts = [];
|
|
64
|
+
if (balance.reserved_micro_eur > 0) {
|
|
65
|
+
parts.push(`Reserved ${money(balance.reserved_micro_eur / MICRO_PER_UNIT, balance.currency)}`);
|
|
66
|
+
}
|
|
67
|
+
parts.push(describeLimit(limits.key, "key"), describeLimit(limits.organisation, "organisation"));
|
|
68
|
+
return parts.filter(Boolean).join(" · ") || "no rate limits configured";
|
|
69
|
+
}
|
|
70
|
+
// null is "no ceiling" and is left out; a limit that is there is shown compactly.
|
|
71
|
+
function describeLimit(limit, scope) {
|
|
72
|
+
const parts = [];
|
|
73
|
+
if (limit.requests_per_minute !== null)
|
|
74
|
+
parts.push(`${compact(limit.requests_per_minute)} RPM`);
|
|
75
|
+
if (limit.tokens_per_minute !== null)
|
|
76
|
+
parts.push(`${compact(limit.tokens_per_minute)} TPM`);
|
|
77
|
+
return parts.length > 0 ? `${parts.join(" · ")} (${scope})` : "";
|
|
78
|
+
}
|
|
79
|
+
function compact(value) {
|
|
80
|
+
return new Intl.NumberFormat("en", { notation: "compact", maximumFractionDigits: 1 }).format(value);
|
|
81
|
+
}
|
|
82
|
+
function money(amount, currency) {
|
|
83
|
+
try {
|
|
84
|
+
return new Intl.NumberFormat("en", { style: "currency", currency }).format(amount);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return `${amount.toFixed(2)} ${currency}`;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function isRecord(value) {
|
|
91
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
92
|
+
}
|
|
93
|
+
function isLimit(value) {
|
|
94
|
+
return (isRecord(value) &&
|
|
95
|
+
(value.requests_per_minute === null || typeof value.requests_per_minute === "number") &&
|
|
96
|
+
(value.tokens_per_minute === null || typeof value.tokens_per_minute === "number"));
|
|
97
|
+
}
|
|
98
|
+
function isUsageResponse(value) {
|
|
99
|
+
if (!isRecord(value) || !isRecord(value.balance) || !isRecord(value.limits))
|
|
100
|
+
return false;
|
|
101
|
+
const { balance, limits } = value;
|
|
102
|
+
return (typeof balance.currency === "string" &&
|
|
103
|
+
typeof balance.booked_micro_eur === "number" &&
|
|
104
|
+
typeof balance.reserved_micro_eur === "number" &&
|
|
105
|
+
typeof balance.available_micro_eur === "number" &&
|
|
106
|
+
isLimit(limits.key) &&
|
|
107
|
+
isLimit(limits.organisation));
|
|
108
|
+
}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "konduit",
|
|
3
|
+
"name": "konduit",
|
|
4
|
+
"description": "European AI inference through konduit, with live balance and rate limits.",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"categories": [
|
|
7
|
+
"models"
|
|
8
|
+
],
|
|
9
|
+
"activation": {
|
|
10
|
+
"onStartup": false
|
|
11
|
+
},
|
|
12
|
+
"enabledByDefault": false,
|
|
13
|
+
"providers": [
|
|
14
|
+
"konduit"
|
|
15
|
+
],
|
|
16
|
+
"contracts": {
|
|
17
|
+
"usageProviders": [
|
|
18
|
+
"konduit"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
"modelCatalog": {
|
|
22
|
+
"providers": {
|
|
23
|
+
"konduit": {
|
|
24
|
+
"baseUrl": "https://api.konduit.eu/v1",
|
|
25
|
+
"api": "openai-completions",
|
|
26
|
+
"defaultModel": "scaleway/gpt-oss-120b",
|
|
27
|
+
"models": [
|
|
28
|
+
{
|
|
29
|
+
"id": "hetzner/qwen3.6-35b-a3b:fp8",
|
|
30
|
+
"name": "Qwen3.6 35B A3B",
|
|
31
|
+
"reasoning": false,
|
|
32
|
+
"input": [
|
|
33
|
+
"text"
|
|
34
|
+
],
|
|
35
|
+
"contextWindow": 262144,
|
|
36
|
+
"maxTokens": 4096,
|
|
37
|
+
"cost": {
|
|
38
|
+
"input": 0,
|
|
39
|
+
"output": 0,
|
|
40
|
+
"cacheRead": 0,
|
|
41
|
+
"cacheWrite": 0
|
|
42
|
+
},
|
|
43
|
+
"compat": {
|
|
44
|
+
"supportsUsageInStreaming": true,
|
|
45
|
+
"supportsTools": true,
|
|
46
|
+
"maxTokensField": "max_tokens"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "hetzner/qwen3.8-27b",
|
|
51
|
+
"name": "Qwen3.8 27B",
|
|
52
|
+
"reasoning": false,
|
|
53
|
+
"input": [
|
|
54
|
+
"text"
|
|
55
|
+
],
|
|
56
|
+
"contextWindow": 262144,
|
|
57
|
+
"maxTokens": 4096,
|
|
58
|
+
"cost": {
|
|
59
|
+
"input": 0,
|
|
60
|
+
"output": 0,
|
|
61
|
+
"cacheRead": 0,
|
|
62
|
+
"cacheWrite": 0
|
|
63
|
+
},
|
|
64
|
+
"compat": {
|
|
65
|
+
"supportsUsageInStreaming": true,
|
|
66
|
+
"supportsTools": true,
|
|
67
|
+
"maxTokensField": "max_tokens"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "scaleway/deepseek-v4-flash-0731",
|
|
72
|
+
"name": "DeepSeek V4 Flash 0731",
|
|
73
|
+
"reasoning": false,
|
|
74
|
+
"input": [
|
|
75
|
+
"text"
|
|
76
|
+
],
|
|
77
|
+
"contextWindow": 256000,
|
|
78
|
+
"maxTokens": 32768,
|
|
79
|
+
"cost": {
|
|
80
|
+
"input": 0.4,
|
|
81
|
+
"output": 0.8,
|
|
82
|
+
"cacheRead": 0,
|
|
83
|
+
"cacheWrite": 0
|
|
84
|
+
},
|
|
85
|
+
"compat": {
|
|
86
|
+
"supportsUsageInStreaming": true,
|
|
87
|
+
"supportsTools": true,
|
|
88
|
+
"maxTokensField": "max_tokens"
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"id": "scaleway/gemma-4-26b-a4b-it",
|
|
93
|
+
"name": "Gemma 4 26B A4B IT",
|
|
94
|
+
"reasoning": false,
|
|
95
|
+
"input": [
|
|
96
|
+
"text"
|
|
97
|
+
],
|
|
98
|
+
"contextWindow": 256000,
|
|
99
|
+
"maxTokens": 32768,
|
|
100
|
+
"cost": {
|
|
101
|
+
"input": 0.25,
|
|
102
|
+
"output": 0.5,
|
|
103
|
+
"cacheRead": 0,
|
|
104
|
+
"cacheWrite": 0
|
|
105
|
+
},
|
|
106
|
+
"compat": {
|
|
107
|
+
"supportsUsageInStreaming": true,
|
|
108
|
+
"supportsTools": true,
|
|
109
|
+
"maxTokensField": "max_tokens"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"id": "scaleway/glm-5.2",
|
|
114
|
+
"name": "GLM-5.2",
|
|
115
|
+
"reasoning": false,
|
|
116
|
+
"input": [
|
|
117
|
+
"text"
|
|
118
|
+
],
|
|
119
|
+
"contextWindow": 256000,
|
|
120
|
+
"maxTokens": 16384,
|
|
121
|
+
"cost": {
|
|
122
|
+
"input": 1.8,
|
|
123
|
+
"output": 5.5,
|
|
124
|
+
"cacheRead": 0,
|
|
125
|
+
"cacheWrite": 0
|
|
126
|
+
},
|
|
127
|
+
"compat": {
|
|
128
|
+
"supportsUsageInStreaming": true,
|
|
129
|
+
"supportsTools": true,
|
|
130
|
+
"maxTokensField": "max_tokens"
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"id": "scaleway/gpt-oss-120b",
|
|
135
|
+
"name": "GPT-OSS 120B",
|
|
136
|
+
"reasoning": false,
|
|
137
|
+
"input": [
|
|
138
|
+
"text"
|
|
139
|
+
],
|
|
140
|
+
"contextWindow": 128000,
|
|
141
|
+
"maxTokens": 32768,
|
|
142
|
+
"cost": {
|
|
143
|
+
"input": 0.15,
|
|
144
|
+
"output": 0.6,
|
|
145
|
+
"cacheRead": 0,
|
|
146
|
+
"cacheWrite": 0
|
|
147
|
+
},
|
|
148
|
+
"compat": {
|
|
149
|
+
"supportsUsageInStreaming": true,
|
|
150
|
+
"supportsTools": true,
|
|
151
|
+
"maxTokensField": "max_tokens"
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"id": "scaleway/llama-3.3-70b-instruct",
|
|
156
|
+
"name": "Llama 3.3 70B Instruct",
|
|
157
|
+
"reasoning": false,
|
|
158
|
+
"input": [
|
|
159
|
+
"text"
|
|
160
|
+
],
|
|
161
|
+
"contextWindow": 100000,
|
|
162
|
+
"maxTokens": 16384,
|
|
163
|
+
"cost": {
|
|
164
|
+
"input": 0.9,
|
|
165
|
+
"output": 0.9,
|
|
166
|
+
"cacheRead": 0,
|
|
167
|
+
"cacheWrite": 0
|
|
168
|
+
},
|
|
169
|
+
"compat": {
|
|
170
|
+
"supportsUsageInStreaming": true,
|
|
171
|
+
"supportsTools": true,
|
|
172
|
+
"maxTokensField": "max_tokens"
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"id": "scaleway/mistral-medium-3.5-128b",
|
|
177
|
+
"name": "Mistral Medium 3.5 128B",
|
|
178
|
+
"reasoning": false,
|
|
179
|
+
"input": [
|
|
180
|
+
"text"
|
|
181
|
+
],
|
|
182
|
+
"contextWindow": 180000,
|
|
183
|
+
"maxTokens": 16384,
|
|
184
|
+
"cost": {
|
|
185
|
+
"input": 1.5,
|
|
186
|
+
"output": 7.5,
|
|
187
|
+
"cacheRead": 0,
|
|
188
|
+
"cacheWrite": 0
|
|
189
|
+
},
|
|
190
|
+
"compat": {
|
|
191
|
+
"supportsUsageInStreaming": true,
|
|
192
|
+
"supportsTools": true,
|
|
193
|
+
"maxTokensField": "max_tokens"
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"id": "scaleway/mistral-small-3.2",
|
|
198
|
+
"name": "Mistral Small 3.2",
|
|
199
|
+
"reasoning": false,
|
|
200
|
+
"input": [
|
|
201
|
+
"text"
|
|
202
|
+
],
|
|
203
|
+
"contextWindow": 128000,
|
|
204
|
+
"maxTokens": 4096,
|
|
205
|
+
"cost": {
|
|
206
|
+
"input": 0.15,
|
|
207
|
+
"output": 0.35,
|
|
208
|
+
"cacheRead": 0,
|
|
209
|
+
"cacheWrite": 0
|
|
210
|
+
},
|
|
211
|
+
"compat": {
|
|
212
|
+
"supportsUsageInStreaming": true,
|
|
213
|
+
"supportsTools": true,
|
|
214
|
+
"maxTokensField": "max_tokens"
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"id": "scaleway/pixtral-12b-2409",
|
|
219
|
+
"name": "Pixtral 12B 2409",
|
|
220
|
+
"reasoning": false,
|
|
221
|
+
"input": [
|
|
222
|
+
"text"
|
|
223
|
+
],
|
|
224
|
+
"contextWindow": 128000,
|
|
225
|
+
"maxTokens": 4096,
|
|
226
|
+
"cost": {
|
|
227
|
+
"input": 0.2,
|
|
228
|
+
"output": 0.2,
|
|
229
|
+
"cacheRead": 0,
|
|
230
|
+
"cacheWrite": 0
|
|
231
|
+
},
|
|
232
|
+
"compat": {
|
|
233
|
+
"supportsUsageInStreaming": true,
|
|
234
|
+
"supportsTools": true,
|
|
235
|
+
"maxTokensField": "max_tokens"
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"id": "scaleway/qwen3-235b-a22b-instruct-2507",
|
|
240
|
+
"name": "Qwen3 235B A22B Instruct 2507",
|
|
241
|
+
"reasoning": false,
|
|
242
|
+
"input": [
|
|
243
|
+
"text"
|
|
244
|
+
],
|
|
245
|
+
"contextWindow": 250000,
|
|
246
|
+
"maxTokens": 16384,
|
|
247
|
+
"cost": {
|
|
248
|
+
"input": 0.75,
|
|
249
|
+
"output": 2.25,
|
|
250
|
+
"cacheRead": 0,
|
|
251
|
+
"cacheWrite": 0
|
|
252
|
+
},
|
|
253
|
+
"compat": {
|
|
254
|
+
"supportsUsageInStreaming": true,
|
|
255
|
+
"supportsTools": true,
|
|
256
|
+
"maxTokensField": "max_tokens"
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"id": "scaleway/qwen3-coder-30b-a3b-instruct",
|
|
261
|
+
"name": "Qwen3 Coder 30B A3B Instruct",
|
|
262
|
+
"reasoning": false,
|
|
263
|
+
"input": [
|
|
264
|
+
"text"
|
|
265
|
+
],
|
|
266
|
+
"contextWindow": 128000,
|
|
267
|
+
"maxTokens": 32768,
|
|
268
|
+
"cost": {
|
|
269
|
+
"input": 0.2,
|
|
270
|
+
"output": 0.8,
|
|
271
|
+
"cacheRead": 0,
|
|
272
|
+
"cacheWrite": 0
|
|
273
|
+
},
|
|
274
|
+
"compat": {
|
|
275
|
+
"supportsUsageInStreaming": true,
|
|
276
|
+
"supportsTools": true,
|
|
277
|
+
"maxTokensField": "max_tokens"
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
"id": "scaleway/qwen3.5-397b-a17b",
|
|
282
|
+
"name": "Qwen3.5 397B A17B",
|
|
283
|
+
"reasoning": false,
|
|
284
|
+
"input": [
|
|
285
|
+
"text"
|
|
286
|
+
],
|
|
287
|
+
"contextWindow": 250000,
|
|
288
|
+
"maxTokens": 16384,
|
|
289
|
+
"cost": {
|
|
290
|
+
"input": 0.6,
|
|
291
|
+
"output": 3.6,
|
|
292
|
+
"cacheRead": 0,
|
|
293
|
+
"cacheWrite": 0
|
|
294
|
+
},
|
|
295
|
+
"compat": {
|
|
296
|
+
"supportsUsageInStreaming": true,
|
|
297
|
+
"supportsTools": true,
|
|
298
|
+
"maxTokensField": "max_tokens"
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
"id": "scaleway/qwen3.6-35b-a3b",
|
|
303
|
+
"name": "Qwen3.6 35B A3B",
|
|
304
|
+
"reasoning": false,
|
|
305
|
+
"input": [
|
|
306
|
+
"text"
|
|
307
|
+
],
|
|
308
|
+
"contextWindow": 262144,
|
|
309
|
+
"maxTokens": 4096,
|
|
310
|
+
"cost": {
|
|
311
|
+
"input": 0.25,
|
|
312
|
+
"output": 1.5,
|
|
313
|
+
"cacheRead": 0,
|
|
314
|
+
"cacheWrite": 0
|
|
315
|
+
},
|
|
316
|
+
"compat": {
|
|
317
|
+
"supportsUsageInStreaming": true,
|
|
318
|
+
"supportsTools": true,
|
|
319
|
+
"maxTokensField": "max_tokens"
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
]
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
"discovery": {
|
|
326
|
+
"konduit": "refreshable"
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
"setup": {
|
|
330
|
+
"providers": [
|
|
331
|
+
{
|
|
332
|
+
"id": "konduit",
|
|
333
|
+
"envVars": [
|
|
334
|
+
"KONDUIT_API_KEY"
|
|
335
|
+
]
|
|
336
|
+
}
|
|
337
|
+
]
|
|
338
|
+
},
|
|
339
|
+
"providerAuthChoices": [
|
|
340
|
+
{
|
|
341
|
+
"provider": "konduit",
|
|
342
|
+
"method": "api-key",
|
|
343
|
+
"choiceId": "konduit-api-key",
|
|
344
|
+
"appGuidedSecret": true,
|
|
345
|
+
"choiceLabel": "konduit API key",
|
|
346
|
+
"groupId": "konduit",
|
|
347
|
+
"groupLabel": "konduit",
|
|
348
|
+
"groupHint": "API key",
|
|
349
|
+
"optionKey": "konduitApiKey",
|
|
350
|
+
"cliFlag": "--konduit-api-key",
|
|
351
|
+
"cliOption": "--konduit-api-key <key>",
|
|
352
|
+
"cliDescription": "konduit API key (kdt-…), minted in the konduit console"
|
|
353
|
+
}
|
|
354
|
+
],
|
|
355
|
+
"configSchema": {
|
|
356
|
+
"type": "object",
|
|
357
|
+
"additionalProperties": false,
|
|
358
|
+
"properties": {}
|
|
359
|
+
}
|
|
360
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@konduiteu/openclaw",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenClaw provider plugin for konduit: European inference, with live balance and rate limits on the provider card.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/schnaq/openclaw-konduit"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://konduit.eu",
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=24"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -p tsconfig.json",
|
|
17
|
+
"prepack": "npm run build",
|
|
18
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
19
|
+
"test": "vitest run --config ./vitest.config.ts",
|
|
20
|
+
"catalog": "node scripts/catalog.ts",
|
|
21
|
+
"catalog:check": "node scripts/catalog.ts --check",
|
|
22
|
+
"validate": "npm run build && clawhub package validate . --out .clawhub-validation"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"openclaw.plugin.json",
|
|
27
|
+
"assets/icon.png",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"openclaw": ">=2026.9.4"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"openclaw": {
|
|
39
|
+
"optional": true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^24.0.0",
|
|
44
|
+
"clawhub": "0.23.3",
|
|
45
|
+
"openclaw": "2026.9.4",
|
|
46
|
+
"typescript": "^5.9.0",
|
|
47
|
+
"vitest": "^3.2.0"
|
|
48
|
+
},
|
|
49
|
+
"openclaw": {
|
|
50
|
+
"extensions": [
|
|
51
|
+
"./dist/index.js"
|
|
52
|
+
],
|
|
53
|
+
"compat": {
|
|
54
|
+
"pluginApi": ">=2026.9.4"
|
|
55
|
+
},
|
|
56
|
+
"build": {
|
|
57
|
+
"openclawVersion": "2026.9.4"
|
|
58
|
+
},
|
|
59
|
+
"install": {
|
|
60
|
+
"clawhubSpec": "clawhub:@konduiteu/openclaw",
|
|
61
|
+
"npmSpec": "@konduiteu/openclaw",
|
|
62
|
+
"defaultChoice": "clawhub",
|
|
63
|
+
"minHostVersion": ">=2026.9.4"
|
|
64
|
+
},
|
|
65
|
+
"release": {
|
|
66
|
+
"publishToClawHub": true,
|
|
67
|
+
"publishToNpm": true
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|