Merge remote-tracking branch 'upstream/main' into feat/gigachat

This commit is contained in:
Alexander Davydov 2026-03-20 19:20:13 +03:00
commit c32ff67919
81 changed files with 385 additions and 400 deletions

View File

@ -974,6 +974,9 @@ Compatibility note:
helper is only needed by a bundled extension, keep it behind the extension's
local `api.js` or `runtime-api.js` seam instead of promoting it into
`openclaw/plugin-sdk/<extension>`.
- Channel-branded bundled bars such as `feishu`, `googlechat`, `irc`, `line`,
`nostr`, `twitch`, and `zalo` stay private unless they are explicitly added
back to the public contract.
- Capability-specific subpaths such as `image-generation`,
`media-understanding`, and `speech` exist because bundled/native plugins use
them today. Their presence does not by itself mean every exported helper is a

View File

@ -100,6 +100,7 @@ import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pair
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
import { createOptionalChannelSetupSurface } from "openclaw/plugin-sdk/channel-setup";
import { resolveChannelGroupRequireMention } from "openclaw/plugin-sdk/channel-policy";
import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-oauth";
// Wrong: monolithic root (lint will reject this)
import { ... } from "openclaw/plugin-sdk";
@ -120,6 +121,7 @@ Common subpaths:
| `plugin-sdk/runtime-store` | Persistent plugin storage |
| `plugin-sdk/allow-from` | Allowlist resolution |
| `plugin-sdk/reply-payload` | Message reply types |
| `plugin-sdk/provider-oauth` | OAuth login + PKCE helpers |
| `plugin-sdk/provider-onboard` | Provider onboarding config patches |
| `plugin-sdk/testing` | Test utilities |

View File

@ -1 +1 @@
export * from "openclaw/plugin-sdk/acpx";
export * from "../../src/plugin-sdk/acpx.js";

View File

@ -1,12 +1,12 @@
import { definePluginEntry } from "openclaw/plugin-sdk/core";
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import {
buildOauthProviderAuthResult,
createProviderApiKeyAuthMethod,
resolveOAuthApiKeyMarker,
type ProviderAuthContext,
type ProviderAuthResult,
} from "openclaw/plugin-sdk/provider-auth";
import { loginChutes } from "openclaw/plugin-sdk/provider-auth-login";
import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-oauth";
import {
CHUTES_DEFAULT_MODEL_REF,
applyChutesApiKeyConfig,

View File

@ -1 +1,8 @@
export * from "openclaw/plugin-sdk/device-pair";
export {
approveDevicePairing,
issueDeviceBootstrapToken,
listDevicePairing,
} from "openclaw/plugin-sdk/device-bootstrap";
export { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
export { resolveGatewayBindUrl, resolveTailnetHostWithRunner } from "openclaw/plugin-sdk/core";
export { runPluginCommandWithTimeout } from "openclaw/plugin-sdk/sandbox";

View File

@ -1 +1,4 @@
export * from "openclaw/plugin-sdk/feishu";
// Private runtime barrel for the bundled Feishu extension.
// Keep this barrel thin and aligned with the local extension surface.
export * from "../../src/plugin-sdk/feishu.js";

View File

@ -3,7 +3,7 @@ import type {
ProviderAuthContext,
ProviderFetchUsageSnapshotContext,
} from "openclaw/plugin-sdk/plugin-entry";
import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-auth";
import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-oauth";
import { fetchGeminiUsage } from "openclaw/plugin-sdk/provider-usage";
import { isModernGoogleModel, resolveGoogle31ForwardCompatModel } from "./provider-models.js";

View File

@ -1 +1 @@
export * from "openclaw/plugin-sdk/google";
export * from "../../src/plugin-sdk/google.js";

View File

@ -1,4 +1,4 @@
// Private runtime barrel for the bundled Google Chat extension.
// Keep this barrel thin and aligned with the curated plugin-sdk/googlechat surface.
// Keep this barrel thin and aligned with the local extension surface.
export * from "openclaw/plugin-sdk/googlechat";
export * from "../../src/plugin-sdk/googlechat.js";

View File

@ -1 +1,4 @@
export * from "openclaw/plugin-sdk/irc";
// Private runtime barrel for the bundled IRC extension.
// Keep this barrel thin and aligned with the local extension surface.
export * from "../../../src/plugin-sdk/irc.js";

View File

@ -1,2 +1,2 @@
export * from "openclaw/plugin-sdk/line";
export * from "./runtime-api.js";
export * from "./setup-api.js";

View File

@ -1 +1,12 @@
export * from "openclaw/plugin-sdk/line-core";
// Private runtime barrel for the bundled LINE extension.
// Keep this barrel thin and aligned with the local extension surface.
export * from "../../src/plugin-sdk/line.js";
export { resolveExactLineGroupConfigKey } from "../../src/plugin-sdk/line-core.js";
export {
formatDocsLink,
setSetupChannelEnabled,
splitSetupEntries,
type ChannelSetupDmPolicy,
type ChannelSetupWizard,
} from "../../src/plugin-sdk/line-core.js";

View File

@ -1 +1 @@
export * from "openclaw/plugin-sdk/lobster";
export * from "../../src/plugin-sdk/lobster.js";

View File

@ -1 +1,4 @@
export * from "openclaw/plugin-sdk/mattermost";
// Private runtime barrel for the bundled Mattermost extension.
// Keep this barrel thin and aligned with the local extension surface.
export * from "../../src/plugin-sdk/mattermost.js";

View File

@ -1,16 +1,16 @@
import {
buildOauthProviderAuthResult,
definePluginEntry,
type ProviderAuthContext,
type ProviderAuthResult,
type ProviderCatalogContext,
} from "openclaw/plugin-sdk/minimax-portal-auth";
} from "openclaw/plugin-sdk/plugin-entry";
import {
MINIMAX_OAUTH_MARKER,
createProviderApiKeyAuthMethod,
ensureAuthProfileStore,
listProfilesForProvider,
} from "openclaw/plugin-sdk/provider-auth";
import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-oauth";
import { fetchMinimaxUsage } from "openclaw/plugin-sdk/provider-usage";
import {
minimaxMediaUnderstandingProvider,

View File

@ -2,7 +2,7 @@ import { randomBytes, randomUUID } from "node:crypto";
import {
generatePkceVerifierChallenge,
toFormUrlEncoded,
} from "openclaw/plugin-sdk/minimax-portal-auth";
} from "openclaw/plugin-sdk/provider-oauth";
export type MiniMaxRegion = "cn" | "global";

View File

@ -1 +1,4 @@
export * from "openclaw/plugin-sdk/msteams";
// Private runtime barrel for the bundled Microsoft Teams extension.
// Keep this barrel thin and aligned with the local extension surface.
export * from "../../src/plugin-sdk/msteams.js";

View File

@ -1 +1,4 @@
export * from "openclaw/plugin-sdk/nextcloud-talk";
// Private runtime barrel for the bundled Nextcloud Talk extension.
// Keep this barrel thin and aligned with the local extension surface.
export * from "../../src/plugin-sdk/nextcloud-talk.js";

View File

@ -1 +1 @@
export * from "openclaw/plugin-sdk/nostr";
export * from "./runtime-api.js";

View File

@ -1 +1,4 @@
export * from "openclaw/plugin-sdk/nostr";
// Private runtime barrel for the bundled Nostr extension.
// Keep this barrel thin and aligned with the local extension surface.
export * from "../../src/plugin-sdk/nostr.js";

View File

@ -3,7 +3,6 @@ import type {
ProviderResolveDynamicModelContext,
ProviderRuntimeModel,
} from "openclaw/plugin-sdk/plugin-entry";
import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-auth";
import {
CODEX_CLI_PROFILE_ID,
ensureAuthProfileStore,
@ -17,6 +16,7 @@ import {
normalizeProviderId,
type ProviderPlugin,
} from "openclaw/plugin-sdk/provider-models";
import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-oauth";
import { createOpenAIAttributionHeadersWrapper } from "openclaw/plugin-sdk/provider-stream";
import { fetchCodexUsage } from "openclaw/plugin-sdk/provider-usage";
import { buildOpenAICodexProvider } from "./openai-codex-catalog.js";

View File

@ -2,6 +2,6 @@ export { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
export type {
OpenClawPluginApi,
OpenClawPluginCommandDefinition,
OpenClawPluginService,
PluginCommandContext,
} from "openclaw/plugin-sdk/core";
OpenClawPluginService,
} from "openclaw/plugin-sdk/plugin-entry";

View File

@ -1,9 +1,10 @@
import { ensureAuthProfileStore, listProfilesForProvider } from "openclaw/plugin-sdk/agent-runtime";
import { QWEN_OAUTH_MARKER } from "openclaw/plugin-sdk/agent-runtime";
import { buildQwenPortalProvider, QWEN_PORTAL_BASE_URL } from "./provider-catalog.js";
import {
buildOauthProviderAuthResult,
definePluginEntry,
ensureAuthProfileStore,
listProfilesForProvider,
QWEN_OAUTH_MARKER,
refreshQwenPortalCredentials,
type ProviderAuthContext,
type ProviderCatalogContext,

View File

@ -0,0 +1,135 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { refreshQwenPortalCredentials } from "./refresh.js";
function expiredCredentials() {
return {
type: "oauth" as const,
provider: "qwen-portal",
access: "expired-access",
refresh: "refresh-token",
expires: Date.now() - 60_000,
};
}
describe("refreshQwenPortalCredentials", () => {
const originalFetch = globalThis.fetch;
afterEach(() => {
globalThis.fetch = originalFetch;
vi.restoreAllMocks();
});
const runRefresh = async () => await refreshQwenPortalCredentials(expiredCredentials());
it("refreshes oauth credentials and preserves existing refresh token when absent", async () => {
globalThis.fetch = vi.fn(async () => {
return new Response(
JSON.stringify({
access_token: "new-access",
expires_in: 3600,
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
},
);
}) as typeof fetch;
const result = await runRefresh();
expect(result.access).toBe("new-access");
expect(result.refresh).toBe("refresh-token");
expect(result.expires).toBeGreaterThan(Date.now());
expect(globalThis.fetch).toHaveBeenCalledTimes(1);
expect(globalThis.fetch).toHaveBeenCalledWith(
"https://chat.qwen.ai/api/v1/oauth2/token",
expect.objectContaining({
method: "POST",
body: expect.any(URLSearchParams),
}),
);
});
it("replaces the refresh token when the server rotates it", async () => {
globalThis.fetch = vi.fn(async () => {
return new Response(
JSON.stringify({
access_token: "new-access",
refresh_token: "rotated-refresh",
expires_in: 1200,
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
},
);
}) as typeof fetch;
const result = await runRefresh();
expect(result.refresh).toBe("rotated-refresh");
});
it("rejects invalid expires_in payloads", async () => {
globalThis.fetch = vi.fn(async () => {
return new Response(
JSON.stringify({
access_token: "new-access",
expires_in: 0,
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
},
);
}) as typeof fetch;
await expect(runRefresh()).rejects.toThrow(
"Qwen OAuth refresh response missing or invalid expires_in",
);
});
it("turns 400 responses into a re-authenticate hint", async () => {
globalThis.fetch = vi.fn(
async () => new Response("bad refresh", { status: 400 }),
) as typeof fetch;
await expect(runRefresh()).rejects.toThrow("Qwen OAuth refresh token expired or invalid");
});
it("requires a refresh token", async () => {
await expect(
refreshQwenPortalCredentials({
type: "oauth",
provider: "qwen-portal",
access: "expired-access",
refresh: "",
expires: Date.now() - 60_000,
}),
).rejects.toThrow("Qwen OAuth refresh token missing");
});
it("rejects missing access tokens", async () => {
globalThis.fetch = vi.fn(async () => {
return new Response(
JSON.stringify({
expires_in: 3600,
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
},
);
}) as typeof fetch;
await expect(runRefresh()).rejects.toThrow("Qwen OAuth refresh response missing access token");
});
it("surfaces non-400 refresh failures", async () => {
globalThis.fetch = vi.fn(
async () => new Response("gateway down", { status: 502 }),
) as typeof fetch;
await expect(runRefresh()).rejects.toThrow("Qwen OAuth refresh failed: gateway down");
});
});

View File

@ -1,5 +1,5 @@
import type { OAuthCredentials } from "@mariozechner/pi-ai";
import { formatCliCommand } from "../cli/command-format.js";
import { formatCliCommand } from "openclaw/plugin-sdk/setup-tools";
const QWEN_OAUTH_BASE_URL = "https://chat.qwen.ai";
const QWEN_OAUTH_TOKEN_ENDPOINT = `${QWEN_OAUTH_BASE_URL}/api/v1/oauth2/token`;
@ -54,9 +54,9 @@ export async function refreshQwenPortalCredentials(
return {
...credentials,
access: accessToken,
// RFC 6749 section 6: new refresh token is optional; if present, replace old.
refresh: newRefreshToken || refreshToken,
access: accessToken,
expires: Date.now() + expiresIn * 1000,
};
}

View File

@ -1 +1,10 @@
export * from "openclaw/plugin-sdk/qwen-portal-auth";
export { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-oauth";
export { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
export type { ProviderAuthContext, ProviderCatalogContext } from "openclaw/plugin-sdk/plugin-entry";
export { ensureAuthProfileStore, listProfilesForProvider } from "openclaw/plugin-sdk/provider-auth";
export { QWEN_OAUTH_MARKER } from "openclaw/plugin-sdk/agent-runtime";
export {
generatePkceVerifierChallenge,
toFormUrlEncoded,
} from "openclaw/plugin-sdk/provider-oauth";
export { refreshQwenPortalCredentials } from "./refresh.js";

View File

@ -4,7 +4,7 @@ import {
resolveAccountEntry,
type OpenClawConfig,
} from "openclaw/plugin-sdk/account-resolution";
import type { SignalAccountConfig } from "openclaw/plugin-sdk/signal-core";
import type { SignalAccountConfig } from "./runtime-api.js";
export type ResolvedSignalAccount = {
accountId: string;

View File

@ -1,3 +1,3 @@
import { buildChannelConfigSchema, SignalConfigSchema } from "openclaw/plugin-sdk/signal-core";
import { buildChannelConfigSchema, SignalConfigSchema } from "./runtime-api.js";
export const SignalChannelConfigSchema = buildChannelConfigSchema(SignalConfigSchema);

View File

@ -1 +1,4 @@
export * from "openclaw/plugin-sdk/signal";
// Private runtime barrel for the bundled Signal extension.
// Keep this barrel thin and aligned with the local extension surface.
export * from "../../../src/plugin-sdk/signal.js";

View File

@ -1,2 +0,0 @@
export * from "openclaw/plugin-sdk/synology-chat";
export * from "./setup-api.js";

View File

@ -27,20 +27,37 @@ async function readRequestBodyWithLimitForTest(req: IncomingMessage): Promise<st
});
}
vi.mock("../api.js", () => ({
DEFAULT_ACCOUNT_ID: "default",
setAccountEnabledInConfigSection: vi.fn((_opts: unknown) => ({})),
registerPluginHttpRoute: registerPluginHttpRouteMock,
buildChannelConfigSchema: vi.fn((schema: unknown) => ({ schema })),
readRequestBodyWithLimit: vi.fn(readRequestBodyWithLimitForTest),
isRequestBodyLimitError: vi.fn(() => false),
requestBodyErrorToText: vi.fn(() => "Request body too large"),
createFixedWindowRateLimiter: vi.fn(() => ({
isRateLimited: vi.fn(() => false),
size: vi.fn(() => 0),
clear: vi.fn(),
})),
}));
vi.mock("openclaw/plugin-sdk/setup", async () => {
const actual = await vi.importActual<object>("openclaw/plugin-sdk/setup");
return {
...actual,
DEFAULT_ACCOUNT_ID: "default",
};
});
vi.mock("openclaw/plugin-sdk/channel-config-schema", async () => {
const actual = await vi.importActual<object>("openclaw/plugin-sdk/channel-config-schema");
return {
...actual,
buildChannelConfigSchema: vi.fn((schema: unknown) => ({ schema })),
};
});
vi.mock("openclaw/plugin-sdk/webhook-ingress", async () => {
const actual = await vi.importActual<object>("openclaw/plugin-sdk/webhook-ingress");
return {
...actual,
registerPluginHttpRoute: registerPluginHttpRouteMock,
readRequestBodyWithLimit: vi.fn(readRequestBodyWithLimitForTest),
isRequestBodyLimitError: vi.fn(() => false),
requestBodyErrorToText: vi.fn(() => "Request body too large"),
createFixedWindowRateLimiter: vi.fn(() => ({
isRateLimited: vi.fn(() => false),
size: vi.fn(() => 0),
clear: vi.fn(),
})),
};
});
vi.mock("./client.js", () => ({
sendMessage: vi.fn().mockResolvedValue(true),

View File

@ -8,6 +8,7 @@ import {
createHybridChannelConfigAdapter,
createScopedDmSecurityResolver,
} from "openclaw/plugin-sdk/channel-config-helpers";
import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
import {
createConditionalWarningCollector,
projectWarningCollector,
@ -17,8 +18,9 @@ import {
createEmptyChannelDirectoryAdapter,
createTextPairingAdapter,
} from "openclaw/plugin-sdk/channel-runtime";
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/setup";
import { registerPluginHttpRoute } from "openclaw/plugin-sdk/webhook-ingress";
import { z } from "zod";
import { DEFAULT_ACCOUNT_ID, registerPluginHttpRoute, buildChannelConfigSchema } from "../api.js";
import { listAccountIds, resolveAccount } from "./accounts.js";
import { sendMessage, sendFileUrl } from "./client.js";
import { getSynologyRuntime } from "./runtime.js";

View File

@ -1,4 +1,4 @@
import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
import { z } from "zod";
import { buildChannelConfigSchema } from "../api.js";
export const SynologyChatChannelConfigSchema = buildChannelConfigSchema(z.object({}).passthrough());

View File

@ -1,5 +1,5 @@
import type { PluginRuntime } from "openclaw/plugin-sdk/core";
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
import type { PluginRuntime } from "../api.js";
const { setRuntime: setSynologyRuntime, getRuntime: getSynologyRuntime } =
createPluginRuntimeStore<PluginRuntime>(

View File

@ -3,7 +3,10 @@
*/
import * as crypto from "node:crypto";
import { createFixedWindowRateLimiter, type FixedWindowRateLimiter } from "../api.js";
import {
createFixedWindowRateLimiter,
type FixedWindowRateLimiter,
} from "openclaw/plugin-sdk/webhook-ingress";
export type DmAuthorizationResult =
| { allowed: true }

View File

@ -9,7 +9,7 @@ import {
isRequestBodyLimitError,
readRequestBodyWithLimit,
requestBodyErrorToText,
} from "../api.js";
} from "openclaw/plugin-sdk/webhook-ingress";
import { sendMessage, resolveChatUserId } from "./client.js";
import { validateToken, authorizeUserForDm, sanitizeInput, RateLimiter } from "./security.js";
import type { SynologyWebhookPayload, ResolvedSynologyChatAccount } from "./types.js";

View File

@ -1 +1 @@
export * from "openclaw/plugin-sdk/tlon";
export * from "./runtime-api.js";

View File

@ -0,0 +1,4 @@
// Private runtime barrel for the bundled Tlon extension.
// Keep this barrel thin and aligned with the local extension surface.
export * from "../../src/plugin-sdk/tlon.js";

View File

@ -1 +1 @@
export * from "openclaw/plugin-sdk/twitch";
export * from "./runtime-api.js";

View File

@ -1 +1,4 @@
export * from "openclaw/plugin-sdk/twitch";
// Private runtime barrel for the bundled Twitch extension.
// Keep this barrel thin and aligned with the local extension surface.
export * from "../../src/plugin-sdk/twitch.js";

View File

@ -1 +1 @@
export * from "openclaw/plugin-sdk/voice-call";
export * from "./runtime-api.js";

View File

@ -0,0 +1,4 @@
// Private runtime barrel for the bundled Voice Call extension.
// Keep this barrel thin and aligned with the local extension surface.
export * from "../../src/plugin-sdk/voice-call.js";

View File

@ -1 +1 @@
export * from "openclaw/plugin-sdk/zai";
export * from "../../src/plugin-sdk/zai.js";

View File

@ -1 +1,4 @@
export * from "openclaw/plugin-sdk/zalo";
// Private runtime barrel for the bundled Zalo extension.
// Keep this barrel thin and aligned with the local extension surface.
export * from "../../src/plugin-sdk/zalo.js";

View File

@ -1 +1,4 @@
export * from "openclaw/plugin-sdk/zalouser";
// Private runtime barrel for the bundled Zalo Personal extension.
// Keep this barrel thin and aligned with the local extension surface.
export * from "../../src/plugin-sdk/zalouser.js";

View File

@ -173,10 +173,6 @@
"types": "./dist/plugin-sdk/acp-runtime.d.ts",
"default": "./dist/plugin-sdk/acp-runtime.js"
},
"./plugin-sdk/acpx": {
"types": "./dist/plugin-sdk/acpx.d.ts",
"default": "./dist/plugin-sdk/acpx.js"
},
"./plugin-sdk/telegram": {
"types": "./dist/plugin-sdk/telegram.d.ts",
"default": "./dist/plugin-sdk/telegram.js"
@ -193,46 +189,10 @@
"types": "./dist/plugin-sdk/discord-core.d.ts",
"default": "./dist/plugin-sdk/discord-core.js"
},
"./plugin-sdk/feishu": {
"types": "./dist/plugin-sdk/feishu.d.ts",
"default": "./dist/plugin-sdk/feishu.js"
},
"./plugin-sdk/google": {
"types": "./dist/plugin-sdk/google.d.ts",
"default": "./dist/plugin-sdk/google.js"
},
"./plugin-sdk/googlechat": {
"types": "./dist/plugin-sdk/googlechat.d.ts",
"default": "./dist/plugin-sdk/googlechat.js"
},
"./plugin-sdk/irc": {
"types": "./dist/plugin-sdk/irc.d.ts",
"default": "./dist/plugin-sdk/irc.js"
},
"./plugin-sdk/line-core": {
"types": "./dist/plugin-sdk/line-core.d.ts",
"default": "./dist/plugin-sdk/line-core.js"
},
"./plugin-sdk/lobster": {
"types": "./dist/plugin-sdk/lobster.d.ts",
"default": "./dist/plugin-sdk/lobster.js"
},
"./plugin-sdk/matrix": {
"types": "./dist/plugin-sdk/matrix.d.ts",
"default": "./dist/plugin-sdk/matrix.js"
},
"./plugin-sdk/mattermost": {
"types": "./dist/plugin-sdk/mattermost.d.ts",
"default": "./dist/plugin-sdk/mattermost.js"
},
"./plugin-sdk/msteams": {
"types": "./dist/plugin-sdk/msteams.d.ts",
"default": "./dist/plugin-sdk/msteams.js"
},
"./plugin-sdk/nextcloud-talk": {
"types": "./dist/plugin-sdk/nextcloud-talk.d.ts",
"default": "./dist/plugin-sdk/nextcloud-talk.js"
},
"./plugin-sdk/slack": {
"types": "./dist/plugin-sdk/slack.d.ts",
"default": "./dist/plugin-sdk/slack.js"
@ -249,10 +209,6 @@
"types": "./dist/plugin-sdk/imessage-core.d.ts",
"default": "./dist/plugin-sdk/imessage-core.js"
},
"./plugin-sdk/signal": {
"types": "./dist/plugin-sdk/signal.d.ts",
"default": "./dist/plugin-sdk/signal.js"
},
"./plugin-sdk/whatsapp": {
"types": "./dist/plugin-sdk/whatsapp.d.ts",
"default": "./dist/plugin-sdk/whatsapp.js"
@ -313,9 +269,9 @@
"types": "./dist/plugin-sdk/boolean-param.d.ts",
"default": "./dist/plugin-sdk/boolean-param.js"
},
"./plugin-sdk/device-pair": {
"types": "./dist/plugin-sdk/device-pair.d.ts",
"default": "./dist/plugin-sdk/device-pair.js"
"./plugin-sdk/device-bootstrap": {
"types": "./dist/plugin-sdk/device-bootstrap.d.ts",
"default": "./dist/plugin-sdk/device-bootstrap.js"
},
"./plugin-sdk/diagnostics-otel": {
"types": "./dist/plugin-sdk/diagnostics-otel.d.ts",
@ -369,10 +325,6 @@
"types": "./dist/plugin-sdk/keyed-async-queue.d.ts",
"default": "./dist/plugin-sdk/keyed-async-queue.js"
},
"./plugin-sdk/line": {
"types": "./dist/plugin-sdk/line.d.ts",
"default": "./dist/plugin-sdk/line.js"
},
"./plugin-sdk/llm-task": {
"types": "./dist/plugin-sdk/llm-task.d.ts",
"default": "./dist/plugin-sdk/llm-task.js"
@ -381,14 +333,14 @@
"types": "./dist/plugin-sdk/memory-lancedb.d.ts",
"default": "./dist/plugin-sdk/memory-lancedb.js"
},
"./plugin-sdk/minimax-portal-auth": {
"types": "./dist/plugin-sdk/minimax-portal-auth.d.ts",
"default": "./dist/plugin-sdk/minimax-portal-auth.js"
},
"./plugin-sdk/provider-auth": {
"types": "./dist/plugin-sdk/provider-auth.d.ts",
"default": "./dist/plugin-sdk/provider-auth.js"
},
"./plugin-sdk/provider-oauth": {
"types": "./dist/plugin-sdk/provider-oauth.d.ts",
"default": "./dist/plugin-sdk/provider-oauth.js"
},
"./plugin-sdk/provider-auth-api-key": {
"types": "./dist/plugin-sdk/provider-auth-api-key.d.ts",
"default": "./dist/plugin-sdk/provider-auth-api-key.js"
@ -429,10 +381,6 @@
"types": "./dist/plugin-sdk/image-generation.d.ts",
"default": "./dist/plugin-sdk/image-generation.js"
},
"./plugin-sdk/nostr": {
"types": "./dist/plugin-sdk/nostr.d.ts",
"default": "./dist/plugin-sdk/nostr.js"
},
"./plugin-sdk/reply-history": {
"types": "./dist/plugin-sdk/reply-history.d.ts",
"default": "./dist/plugin-sdk/reply-history.js"
@ -453,10 +401,6 @@
"types": "./dist/plugin-sdk/request-url.d.ts",
"default": "./dist/plugin-sdk/request-url.js"
},
"./plugin-sdk/qwen-portal-auth": {
"types": "./dist/plugin-sdk/qwen-portal-auth.d.ts",
"default": "./dist/plugin-sdk/qwen-portal-auth.js"
},
"./plugin-sdk/webhook-ingress": {
"types": "./dist/plugin-sdk/webhook-ingress.d.ts",
"default": "./dist/plugin-sdk/webhook-ingress.js"
@ -473,46 +417,14 @@
"types": "./dist/plugin-sdk/secret-input.d.ts",
"default": "./dist/plugin-sdk/secret-input.js"
},
"./plugin-sdk/signal-core": {
"types": "./dist/plugin-sdk/signal-core.d.ts",
"default": "./dist/plugin-sdk/signal-core.js"
},
"./plugin-sdk/synology-chat": {
"types": "./dist/plugin-sdk/synology-chat.d.ts",
"default": "./dist/plugin-sdk/synology-chat.js"
},
"./plugin-sdk/thread-ownership": {
"types": "./dist/plugin-sdk/thread-ownership.d.ts",
"default": "./dist/plugin-sdk/thread-ownership.js"
},
"./plugin-sdk/tlon": {
"types": "./dist/plugin-sdk/tlon.d.ts",
"default": "./dist/plugin-sdk/tlon.js"
},
"./plugin-sdk/twitch": {
"types": "./dist/plugin-sdk/twitch.d.ts",
"default": "./dist/plugin-sdk/twitch.js"
},
"./plugin-sdk/voice-call": {
"types": "./dist/plugin-sdk/voice-call.d.ts",
"default": "./dist/plugin-sdk/voice-call.js"
},
"./plugin-sdk/web-media": {
"types": "./dist/plugin-sdk/web-media.d.ts",
"default": "./dist/plugin-sdk/web-media.js"
},
"./plugin-sdk/zai": {
"types": "./dist/plugin-sdk/zai.d.ts",
"default": "./dist/plugin-sdk/zai.js"
},
"./plugin-sdk/zalo": {
"types": "./dist/plugin-sdk/zalo.d.ts",
"default": "./dist/plugin-sdk/zalo.js"
},
"./plugin-sdk/zalouser": {
"types": "./dist/plugin-sdk/zalouser.d.ts",
"default": "./dist/plugin-sdk/zalouser.js"
},
"./plugin-sdk/speech": {
"types": "./dist/plugin-sdk/speech.d.ts",
"default": "./dist/plugin-sdk/speech.js"

View File

@ -33,26 +33,15 @@
"hook-runtime",
"process-runtime",
"acp-runtime",
"acpx",
"telegram",
"telegram-core",
"discord",
"discord-core",
"feishu",
"google",
"googlechat",
"irc",
"line-core",
"lobster",
"matrix",
"mattermost",
"msteams",
"nextcloud-talk",
"slack",
"slack-core",
"imessage",
"imessage-core",
"signal",
"whatsapp",
"whatsapp-shared",
"whatsapp-action-runtime",
@ -68,7 +57,7 @@
"allowlist-resolution",
"allowlist-config-edit",
"boolean-param",
"device-pair",
"device-bootstrap",
"diagnostics-otel",
"diffs",
"extension-shared",
@ -82,11 +71,10 @@
"directory-runtime",
"json-store",
"keyed-async-queue",
"line",
"llm-task",
"memory-lancedb",
"minimax-portal-auth",
"provider-auth",
"provider-oauth",
"provider-auth-api-key",
"provider-auth-login",
"plugin-entry",
@ -97,27 +85,17 @@
"provider-usage",
"provider-web-search",
"image-generation",
"nostr",
"reply-history",
"media-understanding",
"secret-input-runtime",
"secret-input-schema",
"request-url",
"qwen-portal-auth",
"webhook-ingress",
"webhook-path",
"runtime-store",
"secret-input",
"signal-core",
"synology-chat",
"thread-ownership",
"tlon",
"twitch",
"voice-call",
"web-media",
"zai",
"zalo",
"zalouser",
"speech",
"state-paths",
"tool-send"

View File

@ -1,4 +1,4 @@
// Public ACPX runtime backend helpers.
// Private ACPX runtime backend helpers for bundled extensions.
// Keep this surface narrow and limited to the ACP runtime/backend contract.
export type { AcpRuntimeErrorCode } from "../acp/runtime/errors.js";

View File

@ -91,7 +91,6 @@ export {
parseOptionalDelimitedEntries,
} from "../channels/plugins/helpers.js";
export { getChatChannelMeta } from "../channels/registry.js";
export { buildOauthProviderAuthResult } from "./provider-auth-result.js";
export {
channelTargetSchema,
channelTargetsSchema,

View File

@ -0,0 +1,4 @@
// Shared bootstrap/pairing helpers for plugins that provision remote devices.
export { approveDevicePairing, listDevicePairing } from "../infra/device-pairing.js";
export { issueDeviceBootstrapToken } from "../infra/device-bootstrap.js";

View File

@ -1,10 +0,0 @@
// Narrow plugin-sdk surface for the bundled device-pair plugin.
// Keep this list additive and scoped to symbols used under extensions/device-pair.
export { definePluginEntry } from "./core.js";
export { approveDevicePairing, listDevicePairing } from "../infra/device-pairing.js";
export { issueDeviceBootstrapToken } from "../infra/device-bootstrap.js";
export type { OpenClawPluginApi } from "../plugins/types.js";
export { resolveGatewayBindUrl } from "../shared/gateway-bind-url.js";
export { resolveTailnetHostWithRunner } from "../shared/tailscale-status.js";
export { runPluginCommandWithTimeout } from "./run-command.js";

View File

@ -1,4 +1,4 @@
// Narrow plugin-sdk surface for the bundled feishu plugin.
// Private helper surface for the bundled feishu plugin.
// Keep this list additive and scoped to symbols used under extensions/feishu.
export type { HistoryEntry } from "../auto-reply/reply/history.js";

View File

@ -1,4 +1,4 @@
// Public Google-specific helpers used by bundled Google plugins.
// Private Google-specific helpers used by bundled Google plugins.
export { normalizeGoogleModelId } from "../agents/model-id-normalization.js";
export { parseGeminiAuth } from "../infra/gemini-auth.js";

View File

@ -1,4 +1,4 @@
// Narrow plugin-sdk surface for the bundled googlechat plugin.
// Private helper surface for the bundled googlechat plugin.
// Keep this list additive and scoped to symbols used under extensions/googlechat.
import { resolveChannelGroupRequireMention } from "./channel-policy.js";

View File

@ -1,4 +1,4 @@
// Narrow plugin-sdk surface for the bundled irc plugin.
// Private helper surface for the bundled irc plugin.
// Keep this list additive and scoped to symbols used under extensions/irc.
export { resolveControlCommandGate } from "../channels/command-gating.js";

View File

@ -1,4 +1,4 @@
// Public Lobster plugin helpers.
// Private Lobster plugin helpers for bundled extensions.
// Keep this surface narrow and limited to the Lobster workflow/tool contract.
export { definePluginEntry } from "./core.js";

View File

@ -1,4 +1,4 @@
// Narrow plugin-sdk surface for the bundled mattermost plugin.
// Private helper surface for the bundled mattermost plugin.
// Keep this list additive and scoped to symbols used under extensions/mattermost.
export { formatInboundFromLabel } from "../auto-reply/envelope.js";

View File

@ -1,12 +0,0 @@
// Narrow plugin-sdk surface for MiniMax OAuth helpers used by the bundled minimax plugin.
// Keep this list additive and scoped to MiniMax OAuth support code.
export { definePluginEntry } from "./core.js";
export { buildOauthProviderAuthResult } from "./provider-auth-result.js";
export type {
OpenClawPluginApi,
ProviderAuthContext,
ProviderCatalogContext,
ProviderAuthResult,
} from "../plugins/types.js";
export { generatePkceVerifierChallenge, toFormUrlEncoded } from "./oauth-utils.js";

View File

@ -1,4 +1,4 @@
// Narrow plugin-sdk surface for the bundled msteams plugin.
// Private helper surface for the bundled msteams plugin.
// Keep this list additive and scoped to symbols used under extensions/msteams.
import { createOptionalChannelSetupSurface } from "./channel-setup.js";

View File

@ -1,4 +1,4 @@
// Narrow plugin-sdk surface for the bundled nextcloud-talk plugin.
// Private helper surface for the bundled nextcloud-talk plugin.
// Keep this list additive and scoped to symbols used under extensions/nextcloud-talk.
export { logInboundDrop } from "../channels/logging.js";

View File

@ -1,4 +1,4 @@
// Narrow plugin-sdk surface for the bundled nostr plugin.
// Private helper surface for the bundled nostr plugin.
// Keep this list additive and scoped to symbols used under extensions/nostr.
import { createOptionalChannelSetupSurface } from "./channel-setup.js";

View File

@ -11,6 +11,7 @@ export type {
AnyAgentTool,
MediaUnderstandingProviderPlugin,
OpenClawPluginApi,
PluginCommandContext,
OpenClawPluginConfigSchema,
ProviderDiscoveryContext,
ProviderCatalogContext,

View File

@ -5,7 +5,6 @@ export type { SecretInput } from "../config/types.secrets.js";
export type { ProviderAuthResult } from "../plugins/types.js";
export type { ProviderAuthContext } from "../plugins/types.js";
export type { AuthProfileStore, OAuthCredential } from "../agents/auth-profiles/types.js";
export { buildOauthProviderAuthResult } from "./provider-auth-result.js";
export {
CLAUDE_CLI_PROFILE_ID,

View File

@ -0,0 +1,4 @@
// Focused OAuth helpers for provider plugins.
export { buildOauthProviderAuthResult } from "./provider-auth-result.js";
export { generatePkceVerifierChallenge, toFormUrlEncoded } from "./oauth-utils.js";

View File

@ -1,14 +0,0 @@
// Narrow plugin-sdk surface for the bundled qwen-portal-auth plugin.
// Keep this list additive and scoped to symbols used under extensions/qwen-portal-auth.
export { definePluginEntry } from "./core.js";
export { buildOauthProviderAuthResult } from "./provider-auth-result.js";
export type {
OpenClawPluginApi,
ProviderAuthContext,
ProviderCatalogContext,
} from "../plugins/types.js";
export { ensureAuthProfileStore, listProfilesForProvider } from "../agents/auth-profiles.js";
export { QWEN_OAUTH_MARKER } from "../agents/model-auth-markers.js";
export { refreshQwenPortalCredentials } from "../providers/qwen-portal-oauth.js";
export { generatePkceVerifierChallenge, toFormUrlEncoded } from "./oauth-utils.js";

View File

@ -34,13 +34,13 @@ const RUNTIME_API_EXPORT_GUARDS: Record<string, readonly string[]> = {
'export { probeIMessage } from "./src/probe.js";',
'export { sendMessageIMessage } from "./src/send.js";',
],
"extensions/googlechat/runtime-api.ts": ['export * from "openclaw/plugin-sdk/googlechat";'],
"extensions/googlechat/runtime-api.ts": ['export * from "../../src/plugin-sdk/googlechat.js";'],
"extensions/matrix/runtime-api.ts": [
'export * from "./src/auth-precedence.js";',
'export * from "./helper-api.js";',
],
"extensions/nextcloud-talk/runtime-api.ts": [
'export * from "openclaw/plugin-sdk/nextcloud-talk";',
'export * from "../../src/plugin-sdk/nextcloud-talk.js";',
],
"extensions/signal/runtime-api.ts": ['export * from "./src/runtime-api.js";'],
"extensions/slack/runtime-api.ts": [

View File

@ -1,3 +1,6 @@
// Private helper surface for the bundled signal plugin.
// Keep this list additive and scoped to symbols used under extensions/signal.
export type { SignalAccountConfig } from "../config/types.js";
export type { ChannelPlugin } from "./channel-plugin-common.js";
export {

View File

@ -1,3 +1,6 @@
// Private helper surface for the bundled signal plugin.
// Keep this list additive and scoped to symbols used under extensions/signal.
export type { ChannelMessageActionAdapter } from "../channels/plugins/types.js";
export type { OpenClawConfig } from "../config/config.js";
export type { SignalAccountConfig } from "../config/types.js";

View File

@ -16,7 +16,9 @@ import * as imessageSdk from "openclaw/plugin-sdk/imessage";
import * as imessageCoreSdk from "openclaw/plugin-sdk/imessage-core";
import * as lazyRuntimeSdk from "openclaw/plugin-sdk/lazy-runtime";
import * as ollamaSetupSdk from "openclaw/plugin-sdk/ollama-setup";
import * as providerAuthSdk from "openclaw/plugin-sdk/provider-auth";
import * as providerModelsSdk from "openclaw/plugin-sdk/provider-models";
import * as providerOauthSdk from "openclaw/plugin-sdk/provider-oauth";
import * as providerSetupSdk from "openclaw/plugin-sdk/provider-setup";
import * as replyPayloadSdk from "openclaw/plugin-sdk/reply-payload";
import * as routingSdk from "openclaw/plugin-sdk/routing";
@ -56,10 +58,33 @@ const allowlistEditSdk = await import("openclaw/plugin-sdk/allowlist-config-edit
describe("plugin-sdk subpath exports", () => {
it("keeps the curated public list free of internal implementation subpaths", () => {
expect(pluginSdkSubpaths).not.toContain("acpx");
expect(pluginSdkSubpaths).not.toContain("compat");
expect(pluginSdkSubpaths).not.toContain("device-pair");
expect(pluginSdkSubpaths).not.toContain("feishu");
expect(pluginSdkSubpaths).not.toContain("google");
expect(pluginSdkSubpaths).not.toContain("googlechat");
expect(pluginSdkSubpaths).not.toContain("irc");
expect(pluginSdkSubpaths).not.toContain("line");
expect(pluginSdkSubpaths).not.toContain("line-core");
expect(pluginSdkSubpaths).not.toContain("lobster");
expect(pluginSdkSubpaths).not.toContain("mattermost");
expect(pluginSdkSubpaths).not.toContain("msteams");
expect(pluginSdkSubpaths).not.toContain("nextcloud-talk");
expect(pluginSdkSubpaths).not.toContain("nostr");
expect(pluginSdkSubpaths).not.toContain("pairing-access");
expect(pluginSdkSubpaths).not.toContain("qwen-portal-auth");
expect(pluginSdkSubpaths).not.toContain("reply-prefix");
expect(pluginSdkSubpaths).not.toContain("signal");
expect(pluginSdkSubpaths).not.toContain("signal-core");
expect(pluginSdkSubpaths).not.toContain("synology-chat");
expect(pluginSdkSubpaths).not.toContain("tlon");
expect(pluginSdkSubpaths).not.toContain("twitch");
expect(pluginSdkSubpaths).not.toContain("typing");
expect(pluginSdkSubpaths).not.toContain("voice-call");
expect(pluginSdkSubpaths).not.toContain("zalo");
expect(pluginSdkSubpaths).not.toContain("zai");
expect(pluginSdkSubpaths).not.toContain("zalouser");
expect(pluginSdkSubpaths).not.toContain("provider-model-definitions");
});
@ -91,6 +116,13 @@ describe("plugin-sdk subpath exports", () => {
expect(typeof accountHelpersSdk.createAccountListHelpers).toBe("function");
});
it("exports device bootstrap helpers from the dedicated subpath", async () => {
const deviceBootstrapSdk = await import("openclaw/plugin-sdk/device-bootstrap");
expect(typeof deviceBootstrapSdk.approveDevicePairing).toBe("function");
expect(typeof deviceBootstrapSdk.issueDeviceBootstrapToken).toBe("function");
expect(typeof deviceBootstrapSdk.listDevicePairing).toBe("function");
});
it("exports allowlist edit helpers from the dedicated subpath", () => {
expect(typeof allowlistEditSdk.buildDmGroupAccountAllowlistAdapter).toBe("function");
expect(typeof allowlistEditSdk.createNestedAllowlistOverrideResolver).toBe("function");
@ -139,6 +171,14 @@ describe("plugin-sdk subpath exports", () => {
expect(typeof providerSetupSdk.discoverOpenAICompatibleSelfHostedProvider).toBe("function");
});
it("exports oauth helpers from the dedicated provider oauth subpath", () => {
expect(typeof providerOauthSdk.buildOauthProviderAuthResult).toBe("function");
expect(typeof providerOauthSdk.generatePkceVerifierChallenge).toBe("function");
expect(typeof providerOauthSdk.toFormUrlEncoded).toBe("function");
expect("buildOauthProviderAuthResult" in asExports(coreSdk)).toBe(false);
expect("buildOauthProviderAuthResult" in asExports(providerAuthSdk)).toBe(false);
});
it("keeps provider models focused on shared provider primitives", () => {
expect(typeof providerModelsSdk.applyOpenAIConfig).toBe("function");
expect(typeof providerModelsSdk.buildKilocodeModelDefinition).toBe("function");
@ -187,8 +227,11 @@ describe("plugin-sdk subpath exports", () => {
});
it("exports webhook ingress helpers from the dedicated subpath", () => {
expect(typeof webhookIngressSdk.registerPluginHttpRoute).toBe("function");
expect(typeof webhookIngressSdk.resolveWebhookPath).toBe("function");
expect(typeof webhookIngressSdk.readRequestBodyWithLimit).toBe("function");
expect(typeof webhookIngressSdk.readJsonWebhookBodyOrReject).toBe("function");
expect(typeof webhookIngressSdk.requestBodyErrorToText).toBe("function");
expect(typeof webhookIngressSdk.withResolvedWebhookRequestPipeline).toBe("function");
});

View File

@ -1,23 +0,0 @@
// Narrow plugin-sdk surface for the bundled synology-chat plugin.
// Keep this list additive and scoped to symbols used under extensions/synology-chat.
export { setAccountEnabledInConfigSection } from "../channels/plugins/config-helpers.js";
export { buildChannelConfigSchema } from "../channels/plugins/config-schema.js";
export type { ChannelSetupAdapter } from "../channels/plugins/types.adapters.js";
export {
isRequestBodyLimitError,
readRequestBodyWithLimit,
requestBodyErrorToText,
} from "../infra/http-body.js";
export { emptyPluginConfigSchema } from "../plugins/config-schema.js";
export { registerPluginHttpRoute } from "../plugins/http-registry.js";
export type { OpenClawConfig } from "../config/config.js";
export type { PluginRuntime } from "../plugins/runtime/types.js";
export type { OpenClawPluginApi } from "../plugins/types.js";
export { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js";
export type { FixedWindowRateLimiter } from "./webhook-memory-guards.js";
export { createFixedWindowRateLimiter } from "./webhook-memory-guards.js";
export {
synologyChatSetupAdapter,
synologyChatSetupWizard,
} from "../../extensions/synology-chat/setup-api.js";

View File

@ -1,4 +1,4 @@
// Narrow plugin-sdk surface for the bundled tlon plugin.
// Private helper surface for the bundled tlon plugin.
// Keep this list additive and scoped to symbols used under extensions/tlon.
import { createOptionalChannelSetupSurface } from "./channel-setup.js";

View File

@ -1,4 +1,4 @@
// Narrow plugin-sdk surface for the bundled twitch plugin.
// Private helper surface for the bundled twitch plugin.
// Keep this list additive and scoped to symbols used under extensions/twitch.
import { createOptionalChannelSetupSurface } from "./channel-setup.js";

View File

@ -1,4 +1,4 @@
// Public Voice Call plugin helpers.
// Private helper surface for the bundled voice-call plugin.
// Keep this surface narrow and limited to the voice-call feature contract.
export { definePluginEntry } from "./core.js";

View File

@ -14,14 +14,18 @@ export {
beginWebhookRequestPipelineOrReject,
createWebhookInFlightLimiter,
isJsonContentType,
isRequestBodyLimitError,
readRequestBodyWithLimit,
readJsonWebhookBodyOrReject,
readWebhookBodyOrReject,
requestBodyErrorToText,
WEBHOOK_BODY_READ_DEFAULTS,
WEBHOOK_IN_FLIGHT_DEFAULTS,
type WebhookBodyReadProfile,
type WebhookInFlightLimiter,
} from "./webhook-request-guards.js";
export {
registerPluginHttpRoute,
registerWebhookTarget,
registerWebhookTargetWithPluginRoute,
resolveSingleWebhookTarget,

View File

@ -10,6 +10,12 @@ import type { FixedWindowRateLimiter } from "./webhook-memory-guards.js";
export type WebhookBodyReadProfile = "pre-auth" | "post-auth";
export {
isRequestBodyLimitError,
readRequestBodyWithLimit,
requestBodyErrorToText,
} from "../infra/http-body.js";
export const WEBHOOK_BODY_READ_DEFAULTS = Object.freeze({
preAuth: {
maxBytes: 64 * 1024,

View File

@ -19,6 +19,8 @@ export type RegisterWebhookTargetOptions<T extends { path: string }> = {
type RegisterPluginHttpRouteParams = Parameters<typeof registerPluginHttpRoute>[0];
export { registerPluginHttpRoute };
export type RegisterWebhookPluginRouteOptions = Omit<
RegisterPluginHttpRouteParams,
"path" | "fallbackPath"

View File

@ -1,4 +1,4 @@
// Public Z.ai helpers for provider plugins that need endpoint detection.
// Private Z.ai helpers for bundled provider plugins that need endpoint detection.
export {
detectZaiEndpoint,

View File

@ -1,4 +1,4 @@
// Narrow plugin-sdk surface for the bundled zalo plugin.
// Private helper surface for the bundled zalo plugin.
// Keep this list additive and scoped to symbols used under extensions/zalo.
export { jsonResult, readStringParam } from "../agents/tools/common.js";

View File

@ -1,4 +1,4 @@
// Narrow plugin-sdk surface for the bundled zalouser plugin.
// Private helper surface for the bundled zalouser plugin.
// Keep this list additive and scoped to symbols used under extensions/zalouser.
import { createOptionalChannelSetupSurface } from "./channel-setup.js";

View File

@ -23,8 +23,8 @@ vi.mock("@mariozechner/pi-ai/oauth", async () => {
};
});
vi.mock("../../plugin-sdk/qwen-portal-auth.js", async () => {
const actual = await vi.importActual<object>("../../plugin-sdk/qwen-portal-auth.js");
vi.mock("../../../extensions/qwen-portal-auth/refresh.js", async () => {
const actual = await vi.importActual<object>("../../../extensions/qwen-portal-auth/refresh.js");
return {
...actual,
refreshQwenPortalCredentials: refreshQwenPortalCredentialsMock,

View File

@ -1,140 +0,0 @@
import { describe, expect, it, vi, afterEach } from "vitest";
import { refreshQwenPortalCredentials } from "./qwen-portal-oauth.js";
const originalFetch = globalThis.fetch;
afterEach(() => {
vi.unstubAllGlobals();
globalThis.fetch = originalFetch;
});
describe("refreshQwenPortalCredentials", () => {
const expiredCredentials = () => ({
access: "old-access",
refresh: "old-refresh",
expires: Date.now() - 1000,
});
const runRefresh = async () => await refreshQwenPortalCredentials(expiredCredentials());
const stubFetchResponse = (response: unknown) => {
const fetchSpy = vi.fn().mockResolvedValue(response);
vi.stubGlobal("fetch", fetchSpy);
return fetchSpy;
};
it("refreshes tokens with a new access token", async () => {
const fetchSpy = stubFetchResponse({
ok: true,
status: 200,
json: async () => ({
access_token: "new-access",
refresh_token: "new-refresh",
expires_in: 3600,
}),
});
const result = await runRefresh();
expect(fetchSpy).toHaveBeenCalledWith(
"https://chat.qwen.ai/api/v1/oauth2/token",
expect.objectContaining({
method: "POST",
}),
);
expect(result.access).toBe("new-access");
expect(result.refresh).toBe("new-refresh");
expect(result.expires).toBeGreaterThan(Date.now());
});
it("keeps refresh token when refresh response omits it", async () => {
stubFetchResponse({
ok: true,
status: 200,
json: async () => ({
access_token: "new-access",
expires_in: 1800,
}),
});
const result = await runRefresh();
expect(result.refresh).toBe("old-refresh");
});
it("keeps refresh token when response sends an empty refresh token", async () => {
stubFetchResponse({
ok: true,
status: 200,
json: async () => ({
access_token: "new-access",
refresh_token: "",
expires_in: 1800,
}),
});
const result = await runRefresh();
expect(result.refresh).toBe("old-refresh");
});
it("errors when refresh response has invalid expires_in", async () => {
stubFetchResponse({
ok: true,
status: 200,
json: async () => ({
access_token: "new-access",
refresh_token: "new-refresh",
expires_in: 0,
}),
});
await expect(runRefresh()).rejects.toThrow(
"Qwen OAuth refresh response missing or invalid expires_in",
);
});
it("errors when refresh token is invalid", async () => {
stubFetchResponse({
ok: false,
status: 400,
text: async () => "invalid_grant",
});
await expect(runRefresh()).rejects.toThrow("Qwen OAuth refresh token expired or invalid");
});
it("errors when refresh token is missing before any request", async () => {
await expect(
refreshQwenPortalCredentials({
access: "old-access",
refresh: " ",
expires: Date.now() - 1000,
}),
).rejects.toThrow("Qwen OAuth refresh token missing");
});
it("errors when refresh response omits access token", async () => {
stubFetchResponse({
ok: true,
status: 200,
json: async () => ({
refresh_token: "new-refresh",
expires_in: 1800,
}),
});
await expect(runRefresh()).rejects.toThrow("Qwen OAuth refresh response missing access token");
});
it("errors with server payload text for non-400 status", async () => {
stubFetchResponse({
ok: false,
status: 500,
statusText: "Server Error",
text: async () => "gateway down",
});
await expect(runRefresh()).rejects.toThrow("Qwen OAuth refresh failed: gateway down");
});
});