diff --git a/package.json b/package.json index 17c786017..7069185c9 100644 --- a/package.json +++ b/package.json @@ -126,6 +126,7 @@ "dotenv": "^17.2.3", "express": "^5.2.1", "file-type": "^21.3.0", + "googleapis": "^171.0.0", "grammy": "^1.39.3", "hono": "4.11.7", "jiti": "^2.6.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22985b90b..361f99e3c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -105,6 +105,9 @@ importers: file-type: specifier: ^21.3.0 version: 21.3.0 + googleapis: + specifier: ^171.0.0 + version: 171.0.0 grammy: specifier: ^1.39.3 version: 1.39.3 @@ -3764,6 +3767,14 @@ packages: resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} engines: {node: '>=14'} + googleapis-common@8.0.1: + resolution: {integrity: sha512-eCzNACUXPb1PW5l0ULTzMHaL/ltPRADoPgjBlT8jWsTbxkCp6siv+qKJ/1ldaybCthGwsYFYallF7u9AkU4L+A==} + engines: {node: '>=18.0.0'} + + googleapis@171.0.0: + resolution: {integrity: sha512-z+wpYZ9wfO/v58b/7fM0JqwDR6dx6yE3UdQZ9vWrzmzNVHFd85Uud8QewFfm8ht/3Hx2ISLbCs8RfnbrqR8X4A==} + engines: {node: '>=18'} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -5191,6 +5202,7 @@ packages: tar@7.5.7: resolution: {integrity: sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==} engines: {node: '>=18'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} @@ -5379,6 +5391,9 @@ packages: url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + url-template@2.0.8: + resolution: {integrity: sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -9347,6 +9362,23 @@ snapshots: google-logging-utils@1.1.3: {} + googleapis-common@8.0.1: + dependencies: + extend: 3.0.2 + gaxios: 7.1.3 + google-auth-library: 10.5.0 + qs: 6.14.1 + url-template: 2.0.8 + transitivePeerDependencies: + - supports-color + + googleapis@171.0.0: + dependencies: + google-auth-library: 10.5.0 + googleapis-common: 8.0.1 + transitivePeerDependencies: + - supports-color + gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -11145,6 +11177,8 @@ snapshots: querystringify: 2.2.0 requires-port: 1.0.0 + url-template@2.0.8: {} + util-deprecate@1.0.2: {} utils-merge@1.0.1: {} diff --git a/src/agents/model-selection.test.ts b/src/agents/model-selection.test.ts index 532936b8c..839e1efeb 100644 --- a/src/agents/model-selection.test.ts +++ b/src/agents/model-selection.test.ts @@ -101,6 +101,31 @@ describe("model-selection", () => { }); expect(resolved?.ref).toEqual({ provider: "openai", model: "gpt-4" }); }); + + it("resolves heartbeat alias glm to llamacpp/qwen3-coder:30b", () => { + const index = buildModelAliasIndex({ + cfg: { + agents: { + defaults: { + models: { + "llamacpp/qwen3-coder:30b": { alias: "glm" }, + }, + }, + }, + } as OpenClawConfig, + defaultProvider: "bifrost", + }); + const resolved = resolveModelRefFromString({ + raw: "glm", + defaultProvider: "bifrost", + aliasIndex: index, + }); + expect(resolved?.ref).toEqual({ + provider: "llamacpp", + model: "qwen3-coder:30b", + }); + expect(resolved?.alias).toBe("glm"); + }); }); describe("resolveConfiguredModelRef", () => { diff --git a/src/channels/dock.ts b/src/channels/dock.ts index e30a10b3c..39840e315 100644 --- a/src/channels/dock.ts +++ b/src/channels/dock.ts @@ -21,6 +21,7 @@ import { resolveTelegramAccount } from "../telegram/accounts.js"; import { normalizeE164 } from "../utils.js"; import { resolveWhatsAppAccount } from "../web/accounts.js"; import { normalizeWhatsAppTarget } from "../whatsapp/normalize.js"; +import { resolveGmailAccount } from "../gmail/accounts.js"; import { resolveDiscordGroupRequireMention, resolveDiscordGroupToolPolicy, @@ -371,6 +372,27 @@ const DOCKS: Record = { }, }, }, + gmail: { + id: "gmail", + capabilities: { + chatTypes: ["direct", "thread"], + media: false, + }, + outbound: { textChunkLimit: 10000 }, + config: { + resolveAllowFrom: ({ cfg, accountId }) => + (resolveGmailAccount({ cfg, accountId }).allowFrom ?? []).map((entry) => String(entry)), + formatAllowFrom: ({ allowFrom }) => + allowFrom.map((entry) => String(entry).trim().toLowerCase()).filter(Boolean), + }, + threading: { + buildToolContext: ({ context, hasRepliedRef }) => ({ + currentChannelId: context.To?.trim() || undefined, + currentThreadTs: context.ReplyToId, + hasRepliedRef, + }), + }, + }, }; function buildDockFromPlugin(plugin: ChannelPlugin): ChannelDock { diff --git a/src/channels/registry.ts b/src/channels/registry.ts index 701516a0c..0615d969b 100644 --- a/src/channels/registry.ts +++ b/src/channels/registry.ts @@ -12,6 +12,7 @@ export const CHAT_CHANNEL_ORDER = [ "slack", "signal", "imessage", + "gmail", ] as const; export type ChatChannelId = (typeof CHAT_CHANNEL_ORDER)[number]; @@ -98,6 +99,16 @@ const CHAT_CHANNEL_META: Record = { blurb: "this is still a work in progress.", systemImage: "message.fill", }, + gmail: { + id: "gmail", + label: "Gmail", + selectionLabel: "Gmail (API)", + detailLabel: "Gmail", + docsPath: "/channels/gmail", + docsLabel: "gmail", + blurb: "connect via Gmail API with OAuth 2.0 authentication.", + systemImage: "envelope", + }, }; export const CHAT_CHANNEL_ALIASES: Record = {