@openclaw/chutes-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.
@@ -156,34 +156,6 @@ const CHUTES_MODEL_CATALOG = buildManifestModelProviderConfig({
156
156
  catalog: CHUTES_MANIFEST_CATALOG
157
157
  }).models.map(decorateChutesModelDefinition);
158
158
  const CACHE_TTL = 3e5;
159
- function projectChutesModels(rows) {
160
- const seen = /* @__PURE__ */ new Set();
161
- const models = [];
162
- for (const row of rows) {
163
- if (!row || typeof row !== "object" || Array.isArray(row)) continue;
164
- const entry = row;
165
- const id = normalizeOptionalString(entry.id) ?? "";
166
- if (!id || seen.has(id)) continue;
167
- seen.add(id);
168
- const lowerId = normalizeLowercaseStringOrEmpty(id);
169
- models.push({
170
- id,
171
- name: id,
172
- reasoning: entry.supported_features?.includes("reasoning") || lowerId.includes("r1") || lowerId.includes("thinking") || lowerId.includes("reason") || lowerId.includes("tee"),
173
- input: (entry.input_modalities || ["text"]).filter((item) => item === "text" || item === "image"),
174
- cost: normalizeChutesModelPricing(entry.pricing) ?? {
175
- input: 0,
176
- output: 0,
177
- cacheRead: 0,
178
- cacheWrite: 0
179
- },
180
- contextWindow: asPositiveSafeInteger(entry.context_length) ?? asPositiveSafeInteger(entry.max_model_len) ?? CHUTES_DEFAULT_CONTEXT_WINDOW,
181
- maxTokens: asPositiveSafeInteger(entry.max_output_length) ?? CHUTES_DEFAULT_MAX_TOKENS,
182
- compat: { supportsUsageInStreaming: false }
183
- });
184
- }
185
- return models;
186
- }
187
159
  async function discoverChutesModels(accessToken, options = {}) {
188
160
  return (await buildLiveModelProviderConfig({
189
161
  ...options,
@@ -193,7 +165,7 @@ async function discoverChutesModels(accessToken, options = {}) {
193
165
  baseUrl: CHUTES_BASE_URL,
194
166
  api: "openai-completions"
195
167
  },
196
- models: structuredClone(CHUTES_MODEL_CATALOG),
168
+ models: CHUTES_MODEL_CATALOG,
197
169
  discoveryApiKey: normalizeOptionalString(accessToken),
198
170
  timeoutMs: 1e4,
199
171
  ttlMs: CACHE_TTL,
@@ -201,7 +173,34 @@ async function discoverChutesModels(accessToken, options = {}) {
201
173
  auditContext: "chutes-model-discovery",
202
174
  fetchGuard: (params) => fetchWithSsrFGuard(withTrustedEnvProxyGuardedFetchMode(params)),
203
175
  fallbackToAnonymousOnUnauthorized: true,
204
- projectRows: projectChutesModels
176
+ projectRows: (rows) => {
177
+ const seen = /* @__PURE__ */ new Set();
178
+ const models = [];
179
+ for (const row of rows) {
180
+ if (!row || typeof row !== "object" || Array.isArray(row)) continue;
181
+ const entry = row;
182
+ const id = normalizeOptionalString(entry.id) ?? "";
183
+ if (!id || seen.has(id)) continue;
184
+ seen.add(id);
185
+ const lowerId = normalizeLowercaseStringOrEmpty(id);
186
+ models.push({
187
+ id,
188
+ name: id,
189
+ reasoning: entry.supported_features?.includes("reasoning") || lowerId.includes("r1") || lowerId.includes("thinking") || lowerId.includes("reason") || lowerId.includes("tee"),
190
+ input: (entry.input_modalities || ["text"]).filter((item) => item === "text" || item === "image"),
191
+ cost: normalizeChutesModelPricing(entry.pricing) ?? {
192
+ input: 0,
193
+ output: 0,
194
+ cacheRead: 0,
195
+ cacheWrite: 0
196
+ },
197
+ contextWindow: asPositiveSafeInteger(entry.context_length) ?? asPositiveSafeInteger(entry.max_model_len) ?? CHUTES_DEFAULT_CONTEXT_WINDOW,
198
+ maxTokens: asPositiveSafeInteger(entry.max_output_length) ?? CHUTES_DEFAULT_MAX_TOKENS,
199
+ compat: { supportsUsageInStreaming: false }
200
+ });
201
+ }
202
+ return models;
203
+ }
205
204
  })).models;
206
205
  }
207
206
  //#endregion
@@ -0,0 +1 @@
1
+ {"version":3,"entries":[{"output":"api.js","source":"extensions/chutes/api.ts"},{"output":"index.js","source":"extensions/chutes/index.ts"},{"output":"models.js","source":"extensions/chutes/models.ts"},{"output":"oauth.js","source":"extensions/chutes/oauth.ts"},{"output":"onboard.js","source":"extensions/chutes/onboard.ts"},{"output":"pricing-api.js","source":"extensions/chutes/pricing-api.ts"},{"output":"provider-catalog.js","source":"extensions/chutes/provider-catalog.ts"}],"chunks":[{"output":".setup/models-cgatbipe.mjs","sha256":"b43608d1bf56b96b93dd3e8811ee0f26cacd66b40ef804042f3645359a05cfc0","facadeSource":null,"sources":["extensions/chutes/models.ts","extensions/chutes/openclaw.plugin.json"]},{"output":"api.js","sha256":"166defab109881ce3dbf5336e043ee3d14e50f0a7facd29111a28218caff2669","facadeSource":"extensions/chutes/api.ts","sources":[]},{"output":"index.js","sha256":"40d05af769443f48e5cf27502d6ca7541f43faa7a6d06a99c67ff9c462954435","facadeSource":"extensions/chutes/index.ts","sources":["extensions/chutes/index.ts"]},{"output":"models.js","sha256":"51de8d81c4ff05171493c261176e69cff32fd5b9871cab13f51e486c60d07971","facadeSource":"extensions/chutes/models.ts","sources":[]},{"output":"oauth.js","sha256":"53e807454220263e30cc5c99b403ef4ed50cca57de7743807813a1c4f6d40fe0","facadeSource":"extensions/chutes/oauth.ts","sources":["extensions/chutes/oauth.ts"]},{"output":"onboard.js","sha256":"a3fad4bd4228797b689df28f40e8a0a709072e11fee540058e6599c8b08b961c","facadeSource":"extensions/chutes/onboard.ts","sources":["extensions/chutes/onboard.ts"]},{"output":"pricing-api.js","sha256":"f0a4c76f38ad437f67aa37cb0a7789019a494c6b2fc7ac99998b1cbcc45054d6","facadeSource":"extensions/chutes/pricing-api.ts","sources":["extensions/chutes/pricing-api.ts"]},{"output":"provider-catalog.js","sha256":"42d8ee11f007d7fadcf12de83cd617a9258ae9bfa9fd8d7ec8a955132f962b38","facadeSource":"extensions/chutes/provider-catalog.ts","sources":["extensions/chutes/provider-catalog.ts"]}],"unrenderedSources":["extensions/chutes/api.ts"]}
package/dist/api.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as CHUTES_MODEL_CATALOG, r as discoverChutesModels, t as CHUTES_BASE_URL } from "./.setup/models-mN33VNCv.mjs";
1
+ import { n as CHUTES_MODEL_CATALOG, r as discoverChutesModels, t as CHUTES_BASE_URL } from "./.setup/models-cgatbipe.mjs";
2
2
  import { CHUTES_DEFAULT_MODEL_ID, CHUTES_DEFAULT_MODEL_REF, applyChutesApiKeyConfig, applyChutesConfig, applyChutesProviderConfig } from "./onboard.js";
3
3
  import { buildChutesProvider } from "./provider-catalog.js";
4
4
  export { CHUTES_BASE_URL, CHUTES_DEFAULT_MODEL_ID, CHUTES_DEFAULT_MODEL_REF, CHUTES_MODEL_CATALOG, applyChutesApiKeyConfig, applyChutesConfig, applyChutesProviderConfig, buildChutesProvider, discoverChutesModels };
package/dist/models.js CHANGED
@@ -1,2 +1 @@
1
- import { n as CHUTES_MODEL_CATALOG, r as discoverChutesModels, t as CHUTES_BASE_URL } from "./.setup/models-mN33VNCv.mjs";
2
- export { CHUTES_BASE_URL, CHUTES_MODEL_CATALOG, discoverChutesModels };
1
+ export { t as CHUTES_BASE_URL, n as CHUTES_MODEL_CATALOG, r as discoverChutesModels } from "./.setup/models-cgatbipe.mjs";
package/dist/oauth.js CHANGED
@@ -5,40 +5,13 @@ import { withTrustedEnvProxyGuardedFetchMode } from "openclaw/plugin-sdk/fetch-r
5
5
  import { resolveExpiresAtMsFromDurationSeconds } from "openclaw/plugin-sdk/number-runtime";
6
6
  import { parseOAuthCallbackInput, waitForLocalOAuthCallback } from "openclaw/plugin-sdk/provider-auth-runtime";
7
7
  import { assertOkOrThrowProviderError, readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
8
- import { buildOAuthRequestSignal } from "openclaw/plugin-sdk/provider-oauth-runtime";
8
+ import { buildOAuthRequestSignal, createOAuthRefreshError, createOAuthRefreshResponseError } from "openclaw/plugin-sdk/provider-oauth-runtime";
9
9
  import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
10
10
  //#region extensions/chutes/oauth.ts
11
11
  const CHUTES_AUTHORIZE_ENDPOINT = "https://api.chutes.ai/idp/authorize";
12
12
  const CHUTES_TOKEN_ENDPOINT = "https://api.chutes.ai/idp/token";
13
13
  const CHUTES_USERINFO_ENDPOINT = "https://api.chutes.ai/idp/userinfo";
14
14
  const CHUTES_OAUTH_REQUEST_TIMEOUT_MS = 3e4;
15
- function parseRedirectUri(redirectUri) {
16
- const url = new URL(redirectUri);
17
- if (url.protocol !== "http:") throw new Error(`Chutes OAuth redirect URI must be http:// (got ${redirectUri})`);
18
- const hostname = url.hostname === "[::1]" ? "::1" : url.hostname || "127.0.0.1";
19
- if (hostname !== "localhost" && hostname !== "127.0.0.1" && hostname !== "::1") throw new Error(`Chutes OAuth redirect hostname must be loopback (got ${hostname}). Use http://127.0.0.1:<port>/...`);
20
- return {
21
- hostname,
22
- port: url.port ? Number.parseInt(url.port, 10) : 80,
23
- pathname: url.pathname || "/"
24
- };
25
- }
26
- function parseManualOAuthInput(input, expectedState) {
27
- const parsed = parseOAuthCallbackInput(input, {
28
- invalidInput: "Paste the full redirect URL (must include code + state).",
29
- missingState: "Missing 'state' parameter. Paste the full redirect URL."
30
- });
31
- if ("error" in parsed) throw new Error(parsed.error);
32
- if (parsed.state !== expectedState) throw new Error("OAuth state mismatch - possible CSRF attack. Please retry login.");
33
- return parsed;
34
- }
35
- function resolveChutesExpiresAt(value, now) {
36
- return resolveExpiresAtMsFromDurationSeconds(value, {
37
- nowMs: now,
38
- bufferMs: 3e5,
39
- minRemainingMs: 3e4
40
- });
41
- }
42
15
  async function requestChutesTokenGrant(params) {
43
16
  const { response, release } = await fetchWithSsrFGuard(withTrustedEnvProxyGuardedFetchMode({
44
17
  url: CHUTES_TOKEN_ENDPOINT,
@@ -53,15 +26,37 @@ async function requestChutesTokenGrant(params) {
53
26
  })
54
27
  }));
55
28
  try {
56
- await assertOkOrThrowProviderError(response, `${params.responseLabel} failed`);
29
+ if (params.responseLabel === "Chutes token exchange") await assertOkOrThrowProviderError(response, `${params.responseLabel} failed`);
57
30
  const data = await readProviderJsonResponse(response, params.responseLabel);
31
+ if (!response.ok || data.error !== void 0) throw createOAuthRefreshResponseError(params.responseLabel, response.status, data);
58
32
  const access = normalizeOptionalString(data.access_token);
59
- const expires = resolveChutesExpiresAt(data.expires_in, params.now ?? Date.now());
60
- if (!access) throw new Error(`${params.responseLabel} returned no access_token`);
61
- if (expires === void 0) throw new Error(`${params.responseLabel} returned invalid expires_in`);
33
+ const expires = resolveExpiresAtMsFromDurationSeconds(data.expires_in, {
34
+ nowMs: params.now ?? Date.now(),
35
+ bufferMs: 3e5,
36
+ minRemainingMs: 3e4
37
+ });
38
+ if (!access) throw createOAuthRefreshError(`${params.responseLabel} returned no access_token`, {
39
+ version: 1,
40
+ phase: "after-token-response",
41
+ outcome: "unknown",
42
+ status: response.status
43
+ });
44
+ if (expires === void 0) throw createOAuthRefreshError(`${params.responseLabel} returned invalid expires_in`, {
45
+ version: 1,
46
+ phase: "after-token-response",
47
+ outcome: "unknown",
48
+ status: response.status
49
+ });
50
+ const refresh = normalizeOptionalString(data.refresh_token);
51
+ if (data.refresh_token !== void 0 && !refresh) throw createOAuthRefreshError(`${params.responseLabel} returned invalid refresh_token`, {
52
+ version: 1,
53
+ phase: "after-token-response",
54
+ outcome: "unknown",
55
+ status: response.status
56
+ });
62
57
  return {
63
58
  access,
64
- refresh: normalizeOptionalString(data.refresh_token),
59
+ refresh,
65
60
  expires
66
61
  };
67
62
  } finally {
@@ -85,41 +80,6 @@ async function fetchChutesUserInfo(params) {
85
80
  await release();
86
81
  }
87
82
  }
88
- async function exchangeChutesCodeForTokens(params) {
89
- const now = Date.now();
90
- const body = new URLSearchParams({
91
- grant_type: "authorization_code",
92
- client_id: params.app.clientId,
93
- code: params.code,
94
- redirect_uri: params.app.redirectUri,
95
- code_verifier: params.codeVerifier
96
- });
97
- if (params.app.clientSecret) body.set("client_secret", params.app.clientSecret);
98
- const token = await requestChutesTokenGrant({
99
- body,
100
- responseLabel: "Chutes token exchange",
101
- now,
102
- ...params.signal ? { signal: params.signal } : {}
103
- });
104
- if (!token.refresh) throw new Error("Chutes token exchange returned no refresh_token");
105
- let info = null;
106
- try {
107
- info = await fetchChutesUserInfo({
108
- accessToken: token.access,
109
- ...params.signal ? { signal: params.signal } : {}
110
- });
111
- } catch (error) {
112
- if (params.signal?.aborted) throw error;
113
- }
114
- return {
115
- access: token.access,
116
- refresh: token.refresh,
117
- expires: token.expires,
118
- email: info?.username,
119
- accountId: info?.sub,
120
- clientId: params.app.clientId
121
- };
122
- }
123
83
  /** Refreshes a stored Chutes OAuth credential through the provider token endpoint. */
124
84
  async function refreshChutesOAuthCredential(credential) {
125
85
  const refreshToken = normalizeOptionalString(credential.refresh);
@@ -159,25 +119,39 @@ async function loginChutes(params) {
159
119
  code_challenge_method: "S256"
160
120
  });
161
121
  const url = `${CHUTES_AUTHORIZE_ENDPOINT}?${query}`;
162
- const promptForCode = async () => parseManualOAuthInput(await params.onPrompt({
163
- message: "Paste the redirect URL",
164
- placeholder: `${params.app.redirectUri}?code=...&state=...`
165
- }), state);
122
+ const promptForCode = async () => {
123
+ const parsed = parseOAuthCallbackInput(await params.onPrompt({
124
+ message: "Paste the redirect URL",
125
+ placeholder: `${params.app.redirectUri}?code=...&state=...`
126
+ }), {
127
+ invalidInput: "Paste the full redirect URL (must include code + state).",
128
+ missingState: "Missing 'state' parameter. Paste the full redirect URL."
129
+ });
130
+ if ("error" in parsed) throw new Error(parsed.error);
131
+ if (parsed.state !== state) throw new Error("OAuth state mismatch - possible CSRF attack. Please retry login.");
132
+ return parsed;
133
+ };
166
134
  let codeAndState;
167
135
  if (params.manual) {
168
136
  await params.onAuth({ url });
169
137
  params.onProgress?.("Waiting for redirect URL...");
170
138
  codeAndState = await promptForCode();
171
139
  } else {
172
- const redirect = parseRedirectUri(params.app.redirectUri);
140
+ const redirectUri = params.app.redirectUri;
141
+ const redirect = new URL(redirectUri);
142
+ if (redirect.protocol !== "http:") throw new Error(`Chutes OAuth redirect URI must be http:// (got ${redirectUri})`);
143
+ const hostname = redirect.hostname === "[::1]" ? "::1" : redirect.hostname || "127.0.0.1";
144
+ if (hostname !== "localhost" && hostname !== "127.0.0.1" && hostname !== "::1") throw new Error(`Chutes OAuth redirect hostname must be loopback (got ${hostname}). Use http://127.0.0.1:<port>/...`);
145
+ const port = redirect.port ? Number.parseInt(redirect.port, 10) : 80;
146
+ const pathname = redirect.pathname || "/";
173
147
  const callback = waitForLocalOAuthCallback({
174
148
  expectedState: state,
175
149
  timeoutMs: 18e4,
176
- port: redirect.port,
177
- callbackPath: redirect.pathname,
150
+ port,
151
+ callbackPath: pathname,
178
152
  redirectUri: params.app.redirectUri,
179
153
  successTitle: "Chutes OAuth complete",
180
- hostname: redirect.hostname,
154
+ hostname,
181
155
  onProgress: params.onProgress,
182
156
  ...params.signal ? { signal: params.signal } : {}
183
157
  }).catch(async (error) => {
@@ -189,12 +163,41 @@ async function loginChutes(params) {
189
163
  codeAndState = await callback;
190
164
  }
191
165
  params.onProgress?.("Exchanging code for tokens...");
192
- return await exchangeChutesCodeForTokens({
193
- app: params.app,
166
+ const app = params.app;
167
+ const signal = params.signal;
168
+ const now = Date.now();
169
+ const body = new URLSearchParams({
170
+ grant_type: "authorization_code",
171
+ client_id: app.clientId,
194
172
  code: codeAndState.code,
195
- codeVerifier: verifier,
196
- ...params.signal ? { signal: params.signal } : {}
173
+ redirect_uri: app.redirectUri,
174
+ code_verifier: verifier
175
+ });
176
+ if (app.clientSecret) body.set("client_secret", app.clientSecret);
177
+ const token = await requestChutesTokenGrant({
178
+ body,
179
+ responseLabel: "Chutes token exchange",
180
+ now,
181
+ ...signal ? { signal } : {}
197
182
  });
183
+ if (!token.refresh) throw new Error("Chutes token exchange returned no refresh_token");
184
+ let info = null;
185
+ try {
186
+ info = await fetchChutesUserInfo({
187
+ accessToken: token.access,
188
+ ...signal ? { signal } : {}
189
+ });
190
+ } catch (error) {
191
+ if (signal?.aborted) throw error;
192
+ }
193
+ return {
194
+ access: token.access,
195
+ refresh: token.refresh,
196
+ expires: token.expires,
197
+ email: info?.username,
198
+ accountId: info?.sub,
199
+ clientId: app.clientId
200
+ };
198
201
  }
199
202
  //#endregion
200
203
  export { loginChutes, refreshChutesOAuthCredential };
package/dist/onboard.js CHANGED
@@ -1,4 +1,4 @@
1
- import { i as openclaw_plugin_default, n as CHUTES_MODEL_CATALOG, t as CHUTES_BASE_URL } from "./.setup/models-mN33VNCv.mjs";
1
+ import { i as openclaw_plugin_default, n as CHUTES_MODEL_CATALOG, t as CHUTES_BASE_URL } from "./.setup/models-cgatbipe.mjs";
2
2
  import { readManifestProviderDefaultModelRef } from "openclaw/plugin-sdk/provider-catalog-shared";
3
3
  import { applyAgentDefaultModelPrimary, createModelCatalogPresetAppliers } from "openclaw/plugin-sdk/provider-onboard";
4
4
  //#region extensions/chutes/onboard.ts
@@ -10,7 +10,7 @@ const { applyProviderConfig: applyChutesProviderConfig } = createModelCatalogPre
10
10
  providerId: "chutes",
11
11
  api: "openai-completions",
12
12
  baseUrl: CHUTES_BASE_URL,
13
- catalogModels: cfg.models?.mode === "replace" ? structuredClone(CHUTES_MODEL_CATALOG) : [],
13
+ catalogModels: cfg.models?.mode === "replace" ? CHUTES_MODEL_CATALOG : [],
14
14
  aliases: [
15
15
  ...CHUTES_MODEL_CATALOG.map((model) => `chutes/${model.id}`),
16
16
  {
@@ -1,10 +1,10 @@
1
- import { n as CHUTES_MODEL_CATALOG, r as discoverChutesModels, t as CHUTES_BASE_URL } from "./.setup/models-mN33VNCv.mjs";
1
+ import { n as CHUTES_MODEL_CATALOG, r as discoverChutesModels, t as CHUTES_BASE_URL } from "./.setup/models-cgatbipe.mjs";
2
2
  //#region extensions/chutes/provider-catalog.ts
3
3
  function buildStaticChutesProvider() {
4
4
  return {
5
5
  baseUrl: CHUTES_BASE_URL,
6
6
  api: "openai-completions",
7
- models: structuredClone(CHUTES_MODEL_CATALOG)
7
+ models: CHUTES_MODEL_CATALOG
8
8
  };
9
9
  }
10
10
  async function buildChutesProvider(accessToken, options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/chutes-provider",
3
- "version": "2026.11.3",
3
+ "version": "2026.11.5-beta.2",
4
4
  "description": "OpenClaw Chutes.ai provider plugin.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,10 +18,10 @@
18
18
  "minHostVersion": ">=2026.6.8"
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": {