@openclaw/radius-provider 2026.11.3 → 2026.11.5-beta.2

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.
@@ -0,0 +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":"b9e22a59b0c292efddf0e3a2834cc1499b12492be97f8ae43781c38b076b97ed","facadeSource":"extensions/radius/catalog.ts","sources":["extensions/radius/catalog.ts"]},{"output":"index.js","sha256":"ffdabc4013c70259a8b82015a480e925538a83ef47b65b9593d8f2aedc9a2ec1","facadeSource":"extensions/radius/index.ts","sources":["extensions/radius/index.ts","extensions/radius/openclaw.plugin.json"]},{"output":"oauth.js","sha256":"b03f9a9a424adc251f5a26c134aadd136b84893d7bfda4f952f599c29cd302ad","facadeSource":"extensions/radius/oauth.ts","sources":["extensions/radius/oauth.ts"]},{"output":"stream.js","sha256":"32c84ade6670f550c0803f52275402580c8dcf351986312b328a90335bc91fa0","facadeSource":"extensions/radius/stream.ts","sources":["extensions/radius/stream.ts"]}],"unrenderedSources":[]}
package/dist/catalog.js CHANGED
@@ -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,
package/dist/index.js CHANGED
@@ -142,16 +142,10 @@ var radius_default = defineSingleProviderPluginEntry({
142
142
  const catalog = await (await loadCatalog()).fetchRadiusCatalog(apiKey);
143
143
  const model = catalog.models.find((entry) => entry.id === ctx.modelId);
144
144
  return model ? {
145
- id: model.id,
146
- name: model.name,
145
+ ...model,
147
146
  provider: "radius",
148
147
  api: "pi-messages",
149
148
  baseUrl: catalog.baseUrl,
150
- reasoning: model.reasoning,
151
- input: model.input,
152
- cost: model.cost,
153
- contextWindow: model.contextWindow,
154
- maxTokens: model.maxTokens,
155
149
  thinkingLevelMap: model.thinkingLevelMap
156
150
  } : void 0;
157
151
  },
package/dist/oauth.js CHANGED
@@ -1,7 +1,7 @@
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
  //#region extensions/radius/oauth.ts
@@ -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,18 @@ 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
159
  //#endregion
157
160
  export { loginRadiusOAuth, refreshRadiusOAuthCredential };
package/dist/stream.js CHANGED
@@ -1,7 +1,8 @@
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";
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";
5
6
  //#region extensions/radius/stream.ts
6
7
  const MAX_SSE_EVENT_BYTES = 16777216;
7
8
  function record(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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/radius-provider",
3
- "version": "2026.11.3",
3
+ "version": "2026.11.5-beta.2",
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.11.3"
21
+ "pluginApi": ">=2026.11.5-beta.2"
22
22
  },
23
23
  "build": {
24
- "openclawVersion": "2026.11.3",
24
+ "openclawVersion": "2026.11.5-beta.2",
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.11.3"
43
+ "openclaw": ">=2026.11.5-beta.2"
44
44
  },
45
45
  "peerDependenciesMeta": {
46
46
  "openclaw": {