@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":"ce18a0a9552c52fbd7c3926d9287cff3478e0c6d0395fa9bca5b61414f16d274","facadeSource":"extensions/radius/catalog.ts","sources":["extensions/radius/catalog.ts"]},{"output":"index.js","sha256":"d4725b4806728423b4593b1393b2ee0d15deb1910633f62556bea529738991a3","facadeSource":"extensions/radius/index.ts","sources":["extensions/radius/index.ts","extensions/radius/openclaw.plugin.json"]},{"output":"oauth.js","sha256":"ea46c47dc3096b278e3b6a7ccc04491d677d2a1612a8c893e76a8ba02d27af56","facadeSource":"extensions/radius/oauth.ts","sources":["extensions/radius/oauth.ts"]},{"output":"stream.js","sha256":"d101248010817ac67b27cdd39748c67f2a580963c4950492bf14e9e1c84c9f67","facadeSource":"extensions/radius/stream.ts","sources":["extensions/radius/stream.ts"]}],"unrenderedSources":[]}
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
- //#region extensions/radius/catalog.ts
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.some((input) => input !== "text" && input !== "image")) return;
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
- //#region extensions/radius/openclaw.plugin.json
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
- //#endregion
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
- id: model.id,
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
- //#region extensions/radius/oauth.ts
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) throw new RadiusOAuthError(payload.error, response.status);
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 ?? previousRefresh;
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 device = parseDevice(payload);
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(device.verificationUri);
89
+ await ctx.openUrl(url.href);
95
90
  await ctx.prompter.deviceCode({
96
91
  title: "Radius sign-in",
97
- code: device.userCode,
98
- expiresInMinutes: Math.ceil((device.expiresAt - Date.now()) / 6e4),
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 ${device.verificationUri} and enter code ${device.userCode} to sign in to Radius.`, "Radius sign-in");
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(device.verificationUri);
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 = device.intervalMs;
113
- while (Date.now() < device.expiresAt) {
114
- await sleep(Math.min(intervalMs, device.expiresAt - Date.now()), ctx.signal);
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() >= device.expiresAt) break;
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: device.deviceCode
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
- return {
152
- ...credential,
153
- ...parseToken(payload, credential.refresh)
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 { buildAssistantMessage, buildGuardedModelFetch, createEmptyTransportUsage, failTransportStream, notifyProviderHttpResponse, parseTerminalToolCallArguments } from "openclaw/plugin-sdk/provider-transport-runtime";
5
- //#region extensions/radius/stream.ts
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
- while (true) {
28
+ for await (const { frame } of iterateSseFrames(async () => {
192
29
  signal?.throwIfAborted();
193
- const { done, value } = await reader.read();
30
+ const chunk = await reader.read();
194
31
  signal?.throwIfAborted();
195
- buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });
196
- for (;;) {
197
- const boundary = /\r\n\r\n|\n\n|\r\r/u.exec(buffer);
198
- if (!boundary && !done) break;
199
- const frame = boundary ? buffer.slice(0, boundary.index) : buffer;
200
- if (Buffer.byteLength(frame, "utf8") > MAX_SSE_EVENT_BYTES) throw new Error("Radius SSE event exceeds 16 MiB");
201
- buffer = boundary ? buffer.slice(boundary.index + boundary[0].length) : "";
202
- const data = frame.split(/\r\n|\n|\r/u).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
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 convert = createEventConverter(partial);
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
- ...model.headers,
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.10.10-beta.5",
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.10.10-beta.5"
21
+ "pluginApi": ">=2026.11.5-beta.1"
22
22
  },
23
23
  "build": {
24
- "openclawVersion": "2026.10.10-beta.5",
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.10.10-beta.5"
43
+ "openclaw": ">=2026.11.5-beta.1"
44
44
  },
45
45
  "peerDependenciesMeta": {
46
46
  "openclaw": {