@openclaw/radius-provider 2026.10.10-beta.5 → 2026.11.5-beta.1
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"entries":[{"output":"catalog.js","source":"extensions/radius/catalog.ts"},{"output":"index.js","source":"extensions/radius/index.ts"},{"output":"oauth.js","source":"extensions/radius/oauth.ts"},{"output":"stream.js","source":"extensions/radius/stream.ts"}],"chunks":[{"output":"catalog.js","sha256":"
|
|
1
|
+
{"version":3,"entries":[{"output":"catalog.js","source":"extensions/radius/catalog.ts"},{"output":"index.js","source":"extensions/radius/index.ts"},{"output":"oauth.js","source":"extensions/radius/oauth.ts"},{"output":"stream.js","source":"extensions/radius/stream.ts"}],"chunks":[{"output":"catalog.js","sha256":"965e1fa555273d81c1657f23eca541ab4c323c9ac2c7f2b2c8788bf845696326","facadeSource":"extensions/radius/catalog.ts","sources":["extensions/radius/catalog.ts"]},{"output":"index.js","sha256":"0d0f4769de0c2841bc712edc2705e217e9e2542425a3559d28329011529aa45a","facadeSource":"extensions/radius/index.ts","sources":["extensions/radius/index.ts","extensions/radius/openclaw.plugin.json"]},{"output":"oauth.js","sha256":"4c5b7d88510da293f100b77fa77adf80930083913ec8c2a2f6df4f227c638ca1","facadeSource":"extensions/radius/oauth.ts","sources":["extensions/radius/oauth.ts"]},{"output":"stream.js","sha256":"ba158552ca2537e3245837ee8f3b1aa9e30fa82b3146a68797c3a7b6632c0d96","facadeSource":"extensions/radius/stream.ts","sources":["extensions/radius/stream.ts"]}],"unrenderedSources":[]}
|
package/dist/catalog.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fetchLiveProviderModelRows } from "openclaw/plugin-sdk/provider-catalog-live-runtime";
|
|
2
2
|
import { asFiniteNumberInRange, asOptionalRecord, asPositiveSafeInteger, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
3
|
-
|
|
3
|
+
// extensions/radius/catalog.ts
|
|
4
4
|
const RADIUS_BASE_URL = "https://radius.pi.dev/v1";
|
|
5
5
|
const THINKING_LEVELS = [
|
|
6
6
|
"off",
|
|
@@ -61,8 +61,7 @@ function readModel(value) {
|
|
|
61
61
|
const cost = readCost(row?.cost);
|
|
62
62
|
const contextWindow = asPositiveSafeInteger(row?.contextWindow);
|
|
63
63
|
const maxTokens = asPositiveSafeInteger(row?.maxTokens);
|
|
64
|
-
if (!row || !id || !name || !cost || !contextWindow || !maxTokens || row.enabled === false || typeof row.reasoning !== "boolean" || !Array.isArray(row.input) || row.input.length === 0 || row.input.
|
|
65
|
-
const input = row.input.filter((item) => item === "text" || item === "image");
|
|
64
|
+
if (!row || !id || !name || !cost || !contextWindow || !maxTokens || row.enabled === false || typeof row.reasoning !== "boolean" || !Array.isArray(row.input) || row.input.length === 0 || !row.input.every((input) => input === "text" || input === "image")) return;
|
|
66
65
|
let thinkingLevelMap;
|
|
67
66
|
if (row.thinkingLevelMap !== void 0) {
|
|
68
67
|
const levels = asOptionalRecord(row.thinkingLevelMap);
|
|
@@ -80,7 +79,7 @@ function readModel(value) {
|
|
|
80
79
|
id,
|
|
81
80
|
name,
|
|
82
81
|
reasoning: row.reasoning,
|
|
83
|
-
input,
|
|
82
|
+
input: row.input,
|
|
84
83
|
cost,
|
|
85
84
|
contextWindow,
|
|
86
85
|
maxTokens,
|
|
@@ -107,5 +106,4 @@ async function fetchRadiusCatalog(apiKey, signal) {
|
|
|
107
106
|
models: config.models.map(readModel).filter((model) => model !== void 0)
|
|
108
107
|
};
|
|
109
108
|
}
|
|
110
|
-
//#endregion
|
|
111
109
|
export { RADIUS_BASE_URL, fetchRadiusCatalog };
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
|
2
2
|
import { buildOauthProviderAuthResult, resolveOAuthApiKeyMarker } from "openclaw/plugin-sdk/provider-auth";
|
|
3
3
|
import { runLiveProviderCatalog } from "openclaw/plugin-sdk/provider-catalog-live-runtime";
|
|
4
4
|
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
|
|
5
|
-
|
|
5
|
+
// extensions/radius/openclaw.plugin.json
|
|
6
6
|
var openclaw_plugin_default = {
|
|
7
7
|
id: "radius",
|
|
8
8
|
name: "Radius",
|
|
@@ -47,8 +47,7 @@ var openclaw_plugin_default = {
|
|
|
47
47
|
"properties": {}
|
|
48
48
|
}
|
|
49
49
|
};
|
|
50
|
-
|
|
51
|
-
//#region extensions/radius/index.ts
|
|
50
|
+
// extensions/radius/index.ts
|
|
52
51
|
const loadCatalog = createLazyRuntimeModule(() => import("./catalog.js"));
|
|
53
52
|
const loadOAuth = createLazyRuntimeModule(() => import("./oauth.js"));
|
|
54
53
|
const loadStream = createLazyRuntimeModule(() => import("./stream.js"));
|
|
@@ -142,16 +141,10 @@ var radius_default = defineSingleProviderPluginEntry({
|
|
|
142
141
|
const catalog = await (await loadCatalog()).fetchRadiusCatalog(apiKey);
|
|
143
142
|
const model = catalog.models.find((entry) => entry.id === ctx.modelId);
|
|
144
143
|
return model ? {
|
|
145
|
-
|
|
146
|
-
name: model.name,
|
|
144
|
+
...model,
|
|
147
145
|
provider: "radius",
|
|
148
146
|
api: "pi-messages",
|
|
149
147
|
baseUrl: catalog.baseUrl,
|
|
150
|
-
reasoning: model.reasoning,
|
|
151
|
-
input: model.input,
|
|
152
|
-
cost: model.cost,
|
|
153
|
-
contextWindow: model.contextWindow,
|
|
154
|
-
maxTokens: model.maxTokens,
|
|
155
148
|
thinkingLevelMap: model.thinkingLevelMap
|
|
156
149
|
} : void 0;
|
|
157
150
|
},
|
|
@@ -166,5 +159,4 @@ var radius_default = defineSingleProviderPluginEntry({
|
|
|
166
159
|
refreshOAuth: async (credential) => (await loadOAuth()).refreshRadiusOAuthCredential(credential)
|
|
167
160
|
}
|
|
168
161
|
});
|
|
169
|
-
//#endregion
|
|
170
162
|
export { radius_default as default };
|
package/dist/oauth.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { asFiniteNumberInRange } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
2
2
|
import { MAX_TIMER_TIMEOUT_MS, positiveSecondsToSafeMilliseconds, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
|
3
3
|
import { readProviderJsonObjectResponse } from "openclaw/plugin-sdk/provider-http";
|
|
4
|
-
import { throwIfOAuthLoginAborted } from "openclaw/plugin-sdk/provider-oauth-runtime";
|
|
4
|
+
import { createOAuthRefreshError, createOAuthRefreshResponseError, throwIfOAuthLoginAborted } from "openclaw/plugin-sdk/provider-oauth-runtime";
|
|
5
5
|
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
6
6
|
import { sleep } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
7
|
-
|
|
7
|
+
// extensions/radius/oauth.ts
|
|
8
8
|
const OAUTH_BASE_URL = "https://radius.pi.dev/v1/oauth";
|
|
9
9
|
const CLIENT_ID = "pi-gateway";
|
|
10
10
|
const REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -45,7 +45,10 @@ async function postOAuthForm(endpoint, fields, signal, beforeRequest) {
|
|
|
45
45
|
maxBytes: 65536,
|
|
46
46
|
requestHeaders: {}
|
|
47
47
|
});
|
|
48
|
-
if (!response.ok || payload.error !== void 0)
|
|
48
|
+
if (!response.ok || payload.error !== void 0) {
|
|
49
|
+
if (fields.grant_type === "refresh_token") throw createOAuthRefreshResponseError("Radius OAuth refresh", response.status, payload);
|
|
50
|
+
throw new RadiusOAuthError(payload.error, response.status);
|
|
51
|
+
}
|
|
49
52
|
return payload;
|
|
50
53
|
} finally {
|
|
51
54
|
await release();
|
|
@@ -54,31 +57,13 @@ async function postOAuthForm(endpoint, fields, signal, beforeRequest) {
|
|
|
54
57
|
function parseToken(payload, previousRefresh) {
|
|
55
58
|
const lifetimeMs = positiveSecondsToSafeMilliseconds(payload.expires_in);
|
|
56
59
|
const expires = lifetimeMs === void 0 ? void 0 : resolveExpiresAtMsFromDurationMs(lifetimeMs, { bufferMs: Math.min(6e4, Math.floor(lifetimeMs / 2)) });
|
|
57
|
-
const refresh = payload.refresh_token
|
|
60
|
+
const refresh = payload.refresh_token === void 0 ? previousRefresh : payload.refresh_token;
|
|
58
61
|
if (typeof payload.access_token !== "string" || !payload.access_token.trim() || typeof refresh !== "string" || !refresh.trim() || expires === void 0 || payload.scope !== void 0 && typeof payload.scope !== "string") throw new Error("Radius OAuth returned invalid credentials. Retry sign-in.");
|
|
59
62
|
return {
|
|
60
63
|
access: payload.access_token,
|
|
61
64
|
refresh,
|
|
62
|
-
expires
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
function parseDevice(payload) {
|
|
66
|
-
const lifetimeMs = positiveSecondsToSafeMilliseconds(payload.expires_in);
|
|
67
|
-
const expiresAt = lifetimeMs === void 0 ? void 0 : resolveExpiresAtMsFromDurationMs(lifetimeMs);
|
|
68
|
-
const intervalSeconds = payload.interval === void 0 ? 5 : asFiniteNumberInRange(payload.interval, {
|
|
69
|
-
min: Number.MIN_VALUE,
|
|
70
|
-
max: MAX_TIMER_TIMEOUT_MS / 1e3
|
|
71
|
-
});
|
|
72
|
-
const intervalMs = intervalSeconds === void 0 ? void 0 : Math.ceil(intervalSeconds * 1e3);
|
|
73
|
-
if (typeof payload.device_code !== "string" || !payload.device_code.trim() || typeof payload.user_code !== "string" || !payload.user_code.trim() || typeof payload.verification_uri !== "string" || expiresAt === void 0 || intervalMs === void 0 || intervalMs > MAX_TIMER_TIMEOUT_MS) throw new Error("Radius OAuth returned an invalid device authorization response.");
|
|
74
|
-
const url = URL.parse(payload.verification_uri);
|
|
75
|
-
if (!url || url.origin !== "https://radius.earendil.com" || url.username || url.password) throw new Error("Radius OAuth returned an invalid verification URL.");
|
|
76
|
-
return {
|
|
77
|
-
deviceCode: payload.device_code,
|
|
78
|
-
userCode: payload.user_code,
|
|
79
|
-
verificationUri: url.href,
|
|
80
|
-
expiresAt,
|
|
81
|
-
intervalMs
|
|
65
|
+
expires,
|
|
66
|
+
...typeof payload.scope === "string" ? { scope: payload.scope } : {}
|
|
82
67
|
};
|
|
83
68
|
}
|
|
84
69
|
async function loginRadiusOAuth(ctx) {
|
|
@@ -89,35 +74,45 @@ async function loginRadiusOAuth(ctx) {
|
|
|
89
74
|
assertCurrent();
|
|
90
75
|
const payload = await postOAuthForm("device", { scope: "gateway offline_access" }, ctx.signal, assertCurrent);
|
|
91
76
|
assertCurrent();
|
|
92
|
-
const
|
|
77
|
+
const lifetimeMs = positiveSecondsToSafeMilliseconds(payload.expires_in);
|
|
78
|
+
const expiresAt = lifetimeMs === void 0 ? void 0 : resolveExpiresAtMsFromDurationMs(lifetimeMs);
|
|
79
|
+
const intervalSeconds = payload.interval === void 0 ? 5 : asFiniteNumberInRange(payload.interval, {
|
|
80
|
+
min: Number.MIN_VALUE,
|
|
81
|
+
max: MAX_TIMER_TIMEOUT_MS / 1e3
|
|
82
|
+
});
|
|
83
|
+
const pollMs = intervalSeconds === void 0 ? void 0 : Math.ceil(intervalSeconds * 1e3);
|
|
84
|
+
if (typeof payload.device_code !== "string" || !payload.device_code.trim() || typeof payload.user_code !== "string" || !payload.user_code.trim() || typeof payload.verification_uri !== "string" || expiresAt === void 0 || pollMs === void 0 || pollMs > MAX_TIMER_TIMEOUT_MS) throw new Error("Radius OAuth returned an invalid device authorization response.");
|
|
85
|
+
const url = URL.parse(payload.verification_uri);
|
|
86
|
+
if (!url || url.origin !== "https://radius.earendil.com" || url.username || url.password) throw new Error("Radius OAuth returned an invalid verification URL.");
|
|
87
|
+
const { device_code: deviceCode, user_code: userCode } = payload;
|
|
93
88
|
if (ctx.prompter.deviceCode) {
|
|
94
|
-
await ctx.openUrl(
|
|
89
|
+
await ctx.openUrl(url.href);
|
|
95
90
|
await ctx.prompter.deviceCode({
|
|
96
91
|
title: "Radius sign-in",
|
|
97
|
-
code:
|
|
98
|
-
expiresInMinutes: Math.ceil((
|
|
92
|
+
code: userCode,
|
|
93
|
+
expiresInMinutes: Math.ceil((expiresAt - Date.now()) / 6e4),
|
|
99
94
|
message: "Enter this one-time code to sign in to Radius."
|
|
100
95
|
});
|
|
101
|
-
} else await ctx.prompter.note(`Open ${
|
|
96
|
+
} else await ctx.prompter.note(`Open ${url.href} and enter code ${userCode} to sign in to Radius.`, "Radius sign-in");
|
|
102
97
|
assertCurrent();
|
|
103
98
|
if (!ctx.isRemote && !ctx.prompter.deviceCode) {
|
|
104
99
|
try {
|
|
105
|
-
await ctx.openUrl(
|
|
100
|
+
await ctx.openUrl(url.href);
|
|
106
101
|
} catch {}
|
|
107
102
|
assertCurrent();
|
|
108
103
|
}
|
|
109
104
|
const progress = ctx.prompter.progress("Waiting for Radius approval…");
|
|
110
105
|
let completed = false;
|
|
111
106
|
try {
|
|
112
|
-
let intervalMs =
|
|
113
|
-
while (Date.now() <
|
|
114
|
-
await sleep(Math.min(intervalMs,
|
|
107
|
+
let intervalMs = pollMs;
|
|
108
|
+
while (Date.now() < expiresAt) {
|
|
109
|
+
await sleep(Math.min(intervalMs, expiresAt - Date.now()), ctx.signal);
|
|
115
110
|
assertCurrent();
|
|
116
|
-
if (Date.now() >=
|
|
111
|
+
if (Date.now() >= expiresAt) break;
|
|
117
112
|
try {
|
|
118
113
|
const token = await postOAuthForm("token", {
|
|
119
114
|
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
120
|
-
device_code:
|
|
115
|
+
device_code: deviceCode
|
|
121
116
|
}, ctx.signal, assertCurrent);
|
|
122
117
|
assertCurrent();
|
|
123
118
|
const credentials = parseToken(token);
|
|
@@ -148,10 +143,17 @@ async function refreshRadiusOAuthCredential(credential) {
|
|
|
148
143
|
grant_type: "refresh_token",
|
|
149
144
|
refresh_token: credential.refresh
|
|
150
145
|
});
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
146
|
+
try {
|
|
147
|
+
return {
|
|
148
|
+
...credential,
|
|
149
|
+
...parseToken(payload, credential.refresh)
|
|
150
|
+
};
|
|
151
|
+
} catch (cause) {
|
|
152
|
+
throw createOAuthRefreshError("Radius OAuth refresh returned invalid credentials. Sign in again.", {
|
|
153
|
+
version: 1,
|
|
154
|
+
phase: "after-token-response",
|
|
155
|
+
outcome: "unknown"
|
|
156
|
+
}, { cause });
|
|
157
|
+
}
|
|
155
158
|
}
|
|
156
|
-
//#endregion
|
|
157
159
|
export { loginRadiusOAuth, refreshRadiusOAuthCredential };
|
package/dist/stream.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { asOptionalRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
2
2
|
import { createProviderHttpError } from "openclaw/plugin-sdk/provider-http";
|
|
3
3
|
import { createAssistantMessageEventStream, createToolArgumentPreviewSchedule, parseStreamingJson } from "openclaw/plugin-sdk/llm";
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import { omitNullRequestHeaders } from "openclaw/plugin-sdk/provider-request-headers";
|
|
5
|
+
import { buildAssistantMessage, buildGuardedModelFetch, createEmptyTransportUsage, failTransportStream, iterateSseFrames, mergeTransportHeaders, notifyProviderHttpResponse, parseTerminalToolCallArguments } from "openclaw/plugin-sdk/provider-transport-runtime";
|
|
6
|
+
// extensions/radius/stream.ts
|
|
6
7
|
const MAX_SSE_EVENT_BYTES = 16777216;
|
|
7
8
|
function record(value) {
|
|
8
9
|
const parsed = asOptionalRecord(value);
|
|
@@ -17,194 +18,25 @@ function number(value) {
|
|
|
17
18
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) throw new Error("Invalid Radius usage value");
|
|
18
19
|
return value;
|
|
19
20
|
}
|
|
20
|
-
function usage(value) {
|
|
21
|
-
const raw = record(value);
|
|
22
|
-
const cost = record(raw.cost);
|
|
23
|
-
return {
|
|
24
|
-
...raw,
|
|
25
|
-
input: number(raw.input),
|
|
26
|
-
output: number(raw.output),
|
|
27
|
-
cacheRead: number(raw.cacheRead),
|
|
28
|
-
cacheWrite: number(raw.cacheWrite),
|
|
29
|
-
totalTokens: number(raw.totalTokens),
|
|
30
|
-
cost: {
|
|
31
|
-
...cost,
|
|
32
|
-
input: number(cost.input),
|
|
33
|
-
output: number(cost.output),
|
|
34
|
-
cacheRead: number(cost.cacheRead),
|
|
35
|
-
cacheWrite: number(cost.cacheWrite),
|
|
36
|
-
total: number(cost.total)
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
function createEventConverter(partial) {
|
|
41
|
-
const toolJson = /* @__PURE__ */ new Map();
|
|
42
|
-
return (raw) => {
|
|
43
|
-
const event = record(raw);
|
|
44
|
-
const type = string(event.type);
|
|
45
|
-
if (type === "done" || type === "error") {
|
|
46
|
-
partial.usage = usage(event.usage);
|
|
47
|
-
if (event.responseId !== void 0) partial.responseId = string(event.responseId);
|
|
48
|
-
const reason = event.reason;
|
|
49
|
-
if (type === "done" && (reason === "stop" || reason === "length" || reason === "toolUse")) {
|
|
50
|
-
if (toolJson.size > 0) throw new Error("Radius stream ended with an unfinished tool call");
|
|
51
|
-
partial.stopReason = reason;
|
|
52
|
-
return {
|
|
53
|
-
type,
|
|
54
|
-
reason,
|
|
55
|
-
message: partial
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
if (type === "error" && (reason === "error" || reason === "aborted")) {
|
|
59
|
-
for (const block of partial.content) if (block.type === "toolCall") delete block.partialJson;
|
|
60
|
-
partial.stopReason = reason;
|
|
61
|
-
partial.errorMessage = event.errorMessage === void 0 ? "Radius request failed" : string(event.errorMessage);
|
|
62
|
-
return {
|
|
63
|
-
type,
|
|
64
|
-
reason,
|
|
65
|
-
error: partial
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
throw new Error("Invalid Radius terminal reason");
|
|
69
|
-
}
|
|
70
|
-
if (type === "start") return {
|
|
71
|
-
type,
|
|
72
|
-
partial
|
|
73
|
-
};
|
|
74
|
-
const contentIndex = event.contentIndex;
|
|
75
|
-
if (typeof contentIndex !== "number" || !Number.isSafeInteger(contentIndex) || contentIndex < 0) throw new Error("Invalid Radius content index");
|
|
76
|
-
if (type === "text_start" || type === "thinking_start" || type === "toolcall_start") {
|
|
77
|
-
if (contentIndex !== partial.content.length) throw new Error("Out-of-order Radius content block");
|
|
78
|
-
if (type === "text_start") partial.content.push({
|
|
79
|
-
type: "text",
|
|
80
|
-
text: ""
|
|
81
|
-
});
|
|
82
|
-
else if (type === "thinking_start") partial.content.push({
|
|
83
|
-
type: "thinking",
|
|
84
|
-
thinking: ""
|
|
85
|
-
});
|
|
86
|
-
else {
|
|
87
|
-
const toolCall = {
|
|
88
|
-
type: "toolCall",
|
|
89
|
-
id: string(event.id),
|
|
90
|
-
name: string(event.toolName),
|
|
91
|
-
arguments: {},
|
|
92
|
-
partialJson: ""
|
|
93
|
-
};
|
|
94
|
-
partial.content.push(toolCall);
|
|
95
|
-
toolJson.set(contentIndex, createToolArgumentPreviewSchedule());
|
|
96
|
-
}
|
|
97
|
-
return {
|
|
98
|
-
type,
|
|
99
|
-
contentIndex,
|
|
100
|
-
partial
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
const block = partial.content[contentIndex];
|
|
104
|
-
switch (type) {
|
|
105
|
-
case "text_delta": {
|
|
106
|
-
if (block?.type !== "text") break;
|
|
107
|
-
const delta = string(event.delta);
|
|
108
|
-
block.text += delta;
|
|
109
|
-
return {
|
|
110
|
-
type,
|
|
111
|
-
contentIndex,
|
|
112
|
-
delta,
|
|
113
|
-
partial
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
case "text_end":
|
|
117
|
-
if (block?.type !== "text") break;
|
|
118
|
-
block.text = string(event.content);
|
|
119
|
-
if (event.contentSignature !== void 0) block.textSignature = string(event.contentSignature);
|
|
120
|
-
return {
|
|
121
|
-
type,
|
|
122
|
-
contentIndex,
|
|
123
|
-
content: block.text,
|
|
124
|
-
partial
|
|
125
|
-
};
|
|
126
|
-
case "thinking_delta": {
|
|
127
|
-
if (block?.type !== "thinking") break;
|
|
128
|
-
const delta = string(event.delta);
|
|
129
|
-
block.thinking += delta;
|
|
130
|
-
return {
|
|
131
|
-
type,
|
|
132
|
-
contentIndex,
|
|
133
|
-
delta,
|
|
134
|
-
partial
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
case "thinking_end":
|
|
138
|
-
if (block?.type !== "thinking") break;
|
|
139
|
-
block.thinking = string(event.content);
|
|
140
|
-
if (event.contentSignature !== void 0) block.thinkingSignature = string(event.contentSignature);
|
|
141
|
-
if (event.redacted !== void 0 && typeof event.redacted !== "boolean") throw new Error("Invalid Radius redacted thinking flag");
|
|
142
|
-
block.redacted = event.redacted;
|
|
143
|
-
return {
|
|
144
|
-
type,
|
|
145
|
-
contentIndex,
|
|
146
|
-
content: block.thinking,
|
|
147
|
-
partial
|
|
148
|
-
};
|
|
149
|
-
case "toolcall_delta": {
|
|
150
|
-
const pending = toolJson.get(contentIndex);
|
|
151
|
-
if (block?.type !== "toolCall" || !pending) break;
|
|
152
|
-
const delta = string(event.delta);
|
|
153
|
-
const partialJson = (block.partialJson ?? "") + delta;
|
|
154
|
-
block.partialJson = partialJson;
|
|
155
|
-
if (pending(partialJson.length)) block.arguments = parseStreamingJson(partialJson);
|
|
156
|
-
return {
|
|
157
|
-
type,
|
|
158
|
-
contentIndex,
|
|
159
|
-
delta,
|
|
160
|
-
partial
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
case "toolcall_end": {
|
|
164
|
-
if (block?.type !== "toolCall" || !toolJson.has(contentIndex)) break;
|
|
165
|
-
const call = record(event.toolCall);
|
|
166
|
-
if (call.type !== "toolCall" || call.id !== block.id || call.name !== block.name) throw new Error("Radius terminal tool call does not match its start");
|
|
167
|
-
block.arguments = parseTerminalToolCallArguments(call.arguments);
|
|
168
|
-
delete block.partialJson;
|
|
169
|
-
if (call.thoughtSignature !== void 0) block.thoughtSignature = string(call.thoughtSignature);
|
|
170
|
-
toolJson.delete(contentIndex);
|
|
171
|
-
return {
|
|
172
|
-
type,
|
|
173
|
-
contentIndex,
|
|
174
|
-
toolCall: block,
|
|
175
|
-
partial
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
throw new Error("Invalid Radius stream event or content block sequence");
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
21
|
async function* readEvents(body, signal) {
|
|
183
22
|
const reader = body.getReader();
|
|
184
|
-
const decoder = new TextDecoder();
|
|
185
|
-
let buffer = "";
|
|
186
23
|
const cancel = () => {
|
|
187
24
|
reader.cancel(signal?.reason).catch(() => void 0);
|
|
188
25
|
};
|
|
189
26
|
signal?.addEventListener("abort", cancel, { once: true });
|
|
190
27
|
try {
|
|
191
|
-
|
|
28
|
+
for await (const { frame } of iterateSseFrames(async () => {
|
|
192
29
|
signal?.throwIfAborted();
|
|
193
|
-
const
|
|
30
|
+
const chunk = await reader.read();
|
|
194
31
|
signal?.throwIfAborted();
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (data && data !== "[DONE]") yield JSON.parse(data);
|
|
204
|
-
if (!boundary) break;
|
|
205
|
-
}
|
|
206
|
-
if (Buffer.byteLength(buffer, "utf8") > MAX_SSE_EVENT_BYTES) throw new Error("Radius SSE event exceeds 16 MiB");
|
|
207
|
-
if (done) return;
|
|
32
|
+
return chunk;
|
|
33
|
+
}, {
|
|
34
|
+
max: MAX_SSE_EVENT_BYTES,
|
|
35
|
+
unit: "bytes",
|
|
36
|
+
message: "Radius SSE event exceeds 16 MiB"
|
|
37
|
+
})) {
|
|
38
|
+
const data = frame.split(/\r\n|\n|\r/u).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
|
|
39
|
+
if (data && data !== "[DONE]") yield JSON.parse(data);
|
|
208
40
|
}
|
|
209
41
|
} finally {
|
|
210
42
|
signal?.removeEventListener("abort", cancel);
|
|
@@ -222,7 +54,163 @@ function createRadiusStreamFn() {
|
|
|
222
54
|
usage: createEmptyTransportUsage(),
|
|
223
55
|
stopReason: "stop"
|
|
224
56
|
});
|
|
225
|
-
const
|
|
57
|
+
const toolJson = /* @__PURE__ */ new Map();
|
|
58
|
+
const convert = (raw) => {
|
|
59
|
+
const event = record(raw);
|
|
60
|
+
const type = string(event.type);
|
|
61
|
+
if (type === "done" || type === "error") {
|
|
62
|
+
const rawUsage = record(event.usage);
|
|
63
|
+
const cost = record(rawUsage.cost);
|
|
64
|
+
partial.usage = {
|
|
65
|
+
...rawUsage,
|
|
66
|
+
input: number(rawUsage.input),
|
|
67
|
+
output: number(rawUsage.output),
|
|
68
|
+
cacheRead: number(rawUsage.cacheRead),
|
|
69
|
+
cacheWrite: number(rawUsage.cacheWrite),
|
|
70
|
+
totalTokens: number(rawUsage.totalTokens),
|
|
71
|
+
cost: {
|
|
72
|
+
...cost,
|
|
73
|
+
input: number(cost.input),
|
|
74
|
+
output: number(cost.output),
|
|
75
|
+
cacheRead: number(cost.cacheRead),
|
|
76
|
+
cacheWrite: number(cost.cacheWrite),
|
|
77
|
+
total: number(cost.total)
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
if (event.responseId !== void 0) partial.responseId = string(event.responseId);
|
|
81
|
+
const reason = event.reason;
|
|
82
|
+
if (type === "done" && (reason === "stop" || reason === "length" || reason === "toolUse")) {
|
|
83
|
+
if (toolJson.size > 0) throw new Error("Radius stream ended with an unfinished tool call");
|
|
84
|
+
partial.stopReason = reason;
|
|
85
|
+
return {
|
|
86
|
+
type,
|
|
87
|
+
reason,
|
|
88
|
+
message: partial
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (type === "error" && (reason === "error" || reason === "aborted")) {
|
|
92
|
+
for (const block of partial.content) if (block.type === "toolCall") delete block.partialJson;
|
|
93
|
+
partial.stopReason = reason;
|
|
94
|
+
partial.errorMessage = event.errorMessage === void 0 ? "Radius request failed" : string(event.errorMessage);
|
|
95
|
+
return {
|
|
96
|
+
type,
|
|
97
|
+
reason,
|
|
98
|
+
error: partial
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
throw new Error("Invalid Radius terminal reason");
|
|
102
|
+
}
|
|
103
|
+
if (type === "start") return {
|
|
104
|
+
type,
|
|
105
|
+
partial
|
|
106
|
+
};
|
|
107
|
+
const contentIndex = event.contentIndex;
|
|
108
|
+
if (typeof contentIndex !== "number" || !Number.isSafeInteger(contentIndex) || contentIndex < 0) throw new Error("Invalid Radius content index");
|
|
109
|
+
if (type === "text_start" || type === "thinking_start" || type === "toolcall_start") {
|
|
110
|
+
if (contentIndex !== partial.content.length) throw new Error("Out-of-order Radius content block");
|
|
111
|
+
if (type === "text_start") partial.content.push({
|
|
112
|
+
type: "text",
|
|
113
|
+
text: ""
|
|
114
|
+
});
|
|
115
|
+
else if (type === "thinking_start") partial.content.push({
|
|
116
|
+
type: "thinking",
|
|
117
|
+
thinking: ""
|
|
118
|
+
});
|
|
119
|
+
else {
|
|
120
|
+
const toolCall = {
|
|
121
|
+
type: "toolCall",
|
|
122
|
+
id: string(event.id),
|
|
123
|
+
name: string(event.toolName),
|
|
124
|
+
arguments: {},
|
|
125
|
+
partialJson: ""
|
|
126
|
+
};
|
|
127
|
+
partial.content.push(toolCall);
|
|
128
|
+
toolJson.set(contentIndex, createToolArgumentPreviewSchedule());
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
type,
|
|
132
|
+
contentIndex,
|
|
133
|
+
partial
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const block = partial.content[contentIndex];
|
|
137
|
+
switch (type) {
|
|
138
|
+
case "text_delta": {
|
|
139
|
+
if (block?.type !== "text") break;
|
|
140
|
+
const delta = string(event.delta);
|
|
141
|
+
block.text += delta;
|
|
142
|
+
return {
|
|
143
|
+
type,
|
|
144
|
+
contentIndex,
|
|
145
|
+
delta,
|
|
146
|
+
partial
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
case "text_end":
|
|
150
|
+
if (block?.type !== "text") break;
|
|
151
|
+
block.text = string(event.content);
|
|
152
|
+
if (event.contentSignature !== void 0) block.textSignature = string(event.contentSignature);
|
|
153
|
+
return {
|
|
154
|
+
type,
|
|
155
|
+
contentIndex,
|
|
156
|
+
content: block.text,
|
|
157
|
+
partial
|
|
158
|
+
};
|
|
159
|
+
case "thinking_delta": {
|
|
160
|
+
if (block?.type !== "thinking") break;
|
|
161
|
+
const delta = string(event.delta);
|
|
162
|
+
block.thinking += delta;
|
|
163
|
+
return {
|
|
164
|
+
type,
|
|
165
|
+
contentIndex,
|
|
166
|
+
delta,
|
|
167
|
+
partial
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
case "thinking_end":
|
|
171
|
+
if (block?.type !== "thinking") break;
|
|
172
|
+
block.thinking = string(event.content);
|
|
173
|
+
if (event.contentSignature !== void 0) block.thinkingSignature = string(event.contentSignature);
|
|
174
|
+
if (event.redacted !== void 0 && typeof event.redacted !== "boolean") throw new Error("Invalid Radius redacted thinking flag");
|
|
175
|
+
block.redacted = event.redacted;
|
|
176
|
+
return {
|
|
177
|
+
type,
|
|
178
|
+
contentIndex,
|
|
179
|
+
content: block.thinking,
|
|
180
|
+
partial
|
|
181
|
+
};
|
|
182
|
+
case "toolcall_delta": {
|
|
183
|
+
const pending = toolJson.get(contentIndex);
|
|
184
|
+
if (block?.type !== "toolCall" || !pending) break;
|
|
185
|
+
const delta = string(event.delta);
|
|
186
|
+
const partialJson = (block.partialJson ?? "") + delta;
|
|
187
|
+
block.partialJson = partialJson;
|
|
188
|
+
if (pending(partialJson.length)) block.arguments = parseStreamingJson(partialJson);
|
|
189
|
+
return {
|
|
190
|
+
type,
|
|
191
|
+
contentIndex,
|
|
192
|
+
delta,
|
|
193
|
+
partial
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
case "toolcall_end": {
|
|
197
|
+
if (block?.type !== "toolCall" || !toolJson.has(contentIndex)) break;
|
|
198
|
+
const call = record(event.toolCall);
|
|
199
|
+
if (call.type !== "toolCall" || call.id !== block.id || call.name !== block.name) throw new Error("Radius terminal tool call does not match its start");
|
|
200
|
+
block.arguments = parseTerminalToolCallArguments(call.arguments);
|
|
201
|
+
delete block.partialJson;
|
|
202
|
+
if (call.thoughtSignature !== void 0) block.thoughtSignature = string(call.thoughtSignature);
|
|
203
|
+
toolJson.delete(contentIndex);
|
|
204
|
+
return {
|
|
205
|
+
type,
|
|
206
|
+
contentIndex,
|
|
207
|
+
toolCall: block,
|
|
208
|
+
partial
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
throw new Error("Invalid Radius stream event or content block sequence");
|
|
213
|
+
};
|
|
226
214
|
(async () => {
|
|
227
215
|
let response;
|
|
228
216
|
try {
|
|
@@ -245,13 +233,11 @@ function createRadiusStreamFn() {
|
|
|
245
233
|
options.signal?.throwIfAborted();
|
|
246
234
|
response = await buildGuardedModelFetch(model, options.timeoutMs, { sanitizeSse: false })(`${model.baseUrl.replace(/\/+$/u, "")}/messages`, {
|
|
247
235
|
method: "POST",
|
|
248
|
-
headers: {
|
|
236
|
+
headers: omitNullRequestHeaders(mergeTransportHeaders({
|
|
249
237
|
authorization: `Bearer ${options.apiKey}`,
|
|
250
238
|
accept: "text/event-stream",
|
|
251
|
-
"content-type": "application/json"
|
|
252
|
-
|
|
253
|
-
...options.headers
|
|
254
|
-
},
|
|
239
|
+
"content-type": "application/json"
|
|
240
|
+
}, model.headers, options.headers)),
|
|
255
241
|
body: JSON.stringify(payload),
|
|
256
242
|
signal: options.signal
|
|
257
243
|
});
|
|
@@ -286,5 +272,4 @@ function createRadiusStreamFn() {
|
|
|
286
272
|
return stream;
|
|
287
273
|
};
|
|
288
274
|
}
|
|
289
|
-
//#endregion
|
|
290
275
|
export { createRadiusStreamFn };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/radius-provider",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.11.5-beta.1",
|
|
4
4
|
"description": "OpenClaw Radius model gateway provider plugin.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
"minHostVersion": ">=2026.9.4"
|
|
19
19
|
},
|
|
20
20
|
"compat": {
|
|
21
|
-
"pluginApi": ">=2026.
|
|
21
|
+
"pluginApi": ">=2026.11.5-beta.1"
|
|
22
22
|
},
|
|
23
23
|
"build": {
|
|
24
|
-
"openclawVersion": "2026.
|
|
24
|
+
"openclawVersion": "2026.11.5-beta.1",
|
|
25
25
|
"bundledDist": false
|
|
26
26
|
},
|
|
27
27
|
"release": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"assets/activity.svg"
|
|
41
41
|
],
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"openclaw": ">=2026.
|
|
43
|
+
"openclaw": ">=2026.11.5-beta.1"
|
|
44
44
|
},
|
|
45
45
|
"peerDependenciesMeta": {
|
|
46
46
|
"openclaw": {
|