diff --git a/.changeset/friendly-photons-wave.md b/.changeset/friendly-photons-wave.md new file mode 100644 index 000000000..a649b994a --- /dev/null +++ b/.changeset/friendly-photons-wave.md @@ -0,0 +1,5 @@ +--- +"eve": patch +--- + +Add `photonIMessageChannel`, a first-class Photon iMessage channel with lazy credentials, Vercel OIDC webhook verification, and automatic eve session routing. diff --git a/docs/channels/meta.json b/docs/channels/meta.json index c0bcc03d2..c8119fbce 100644 --- a/docs/channels/meta.json +++ b/docs/channels/meta.json @@ -4,6 +4,7 @@ "overview", "eve", "slack", + "photon", "discord", "teams", "telegram", diff --git a/docs/channels/photon.mdx b/docs/channels/photon.mdx new file mode 100644 index 000000000..04645a156 --- /dev/null +++ b/docs/channels/photon.mdx @@ -0,0 +1,43 @@ +--- +title: Photon +description: Connect an eve agent to iMessage through Photon. +--- + +Use `photonIMessageChannel` to receive and reply to iMessages through a Photon project. +With Vercel Connect, the channel resolves credentials lazily when the adapter +first initializes: + +```ts title="agent/channels/photon.ts" +import { connectPhotonCredentials } from "@vercel/connect/eve"; +import { photonIMessageChannel } from "eve/channels/photon"; + +export default photonIMessageChannel({ + credentials: connectPhotonCredentials("photon/my-agent"), +}); +``` + +The default webhook route is `/eve/v1/photon`. The channel verifies forwarded +webhooks with same-project Vercel OIDC by default, marks accepted messages as +read, and continues the same eve session for every message in an iMessage +conversation. A new accepted message cooperatively cancels an active turn and +steers its replacement turn. + +Customize inbound dispatch with `onMessage`. Return `null` to ignore a message: + +```ts +export default photonIMessageChannel({ + credentials: connectPhotonCredentials("photon/my-agent"), + onMessage(_ctx, message) { + if (message.author.isBot) return null; + return { + auth: null, + context: [`The sender is ${message.author.fullName}.`], + }; + }, +}); +``` + +Set `route` to override the webhook path or `webhookVerifier` to use a different +trusted-forwarder verifier. For direct Photon webhooks, pass `webhookSecret` or +set `IMESSAGE_WEBHOOK_SECRET`; the signing secret takes precedence over the +default OIDC verifier. diff --git a/packages/eve/package.json b/packages/eve/package.json index 731aa6b1f..747936b86 100644 --- a/packages/eve/package.json +++ b/packages/eve/package.json @@ -236,6 +236,11 @@ "import": "./dist/src/public/channels/chat-sdk/index.js", "default": "./dist/src/public/channels/chat-sdk/index.js" }, + "./channels/photon": { + "types": "./dist/src/public/channels/photon/index.d.ts", + "import": "./dist/src/public/channels/photon/index.js", + "default": "./dist/src/public/channels/photon/index.js" + }, "./channels/github": { "types": "./dist/src/public/channels/github/index.d.ts", "import": "./dist/src/public/channels/github/index.js", @@ -323,6 +328,7 @@ "@opentelemetry/context-async-hooks": "catalog:", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/sdk-trace-base": "catalog:", + "@photon-ai/chat-adapter-imessage": "3.2.0", "@standard-schema/spec": "1.1.0", "@sveltejs/kit": "^2.0.0", "@types/json-schema": "7.0.15", diff --git a/packages/eve/scripts/vendor-compiled/@photon-ai/chat-adapter-imessage.mjs b/packages/eve/scripts/vendor-compiled/@photon-ai/chat-adapter-imessage.mjs new file mode 100644 index 000000000..ea70cda0d --- /dev/null +++ b/packages/eve/scripts/vendor-compiled/@photon-ai/chat-adapter-imessage.mjs @@ -0,0 +1,28 @@ +import { createDeclarationCopier } from "../_shared.mjs"; + +export default { + packageName: "@photon-ai/chat-adapter-imessage", + compiledPath: "@photon-ai/chat-adapter-imessage", + copyDeclarations: createDeclarationCopier({ + rewrites: { + chat: { kind: "vendored", compiledPath: "chat" }, + "@chat-adapter/shared": { + kind: "stub", + stubBaseName: "_chat-adapter-shared", + build: () => "export {};\n", + }, + "@spectrum-ts/core": { + kind: "stub", + stubBaseName: "_spectrum-core", + build: () => + "export type AppUrl = unknown;\nexport type ContentBuilder = unknown;\nexport type SpectrumInstance = unknown;\n", + }, + "@spectrum-ts/imessage": { + kind: "stub", + stubBaseName: "_spectrum-imessage", + build: () => + "export type CustomizedMiniAppInput = unknown;\nexport type IMessageMessageEffect = string;\n", + }, + }, + }), +}; diff --git a/packages/eve/scripts/vendor-compiled/index.mjs b/packages/eve/scripts/vendor-compiled/index.mjs index 2b74bfce7..90a35ac9b 100644 --- a/packages/eve/scripts/vendor-compiled/index.mjs +++ b/packages/eve/scripts/vendor-compiled/index.mjs @@ -14,6 +14,7 @@ import providerUtils from "./@ai-sdk/provider-utils.mjs"; import chatAdapterSlack from "./@chat-adapter/slack.mjs"; import chatAdapterStateMemory from "./@chat-adapter/state-memory.mjs"; import chatAdapterTwilio from "./@chat-adapter/twilio.mjs"; +import photonChatAdapterIMessage from "./@photon-ai/chat-adapter-imessage.mjs"; import opentelemetryApi from "./@opentelemetry/api.mjs"; import opentelemetryOtlpTransformer from "./@opentelemetry/otlp-transformer.mjs"; @@ -69,6 +70,7 @@ export const MODULES = [ opentelemetryApi, opentelemetryOtlpTransformer, otel, + photonChatAdapterIMessage, picocolors, provider, providerUtils, diff --git a/packages/eve/src/public/channels/photon/inboundContent.test.ts b/packages/eve/src/public/channels/photon/inboundContent.test.ts new file mode 100644 index 000000000..c031c075e --- /dev/null +++ b/packages/eve/src/public/channels/photon/inboundContent.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from "vitest"; + +import { Message } from "#compiled/chat/index.js"; +import { photonInboundContent } from "#public/channels/photon/inboundContent.js"; + +function message(text: string, attachments: Message["attachments"] = []): Message { + return new Message({ + attachments, + author: { isBot: false, isMe: false, userId: "user", userName: "user" }, + id: "message-id", + raw: {}, + text, + threadId: "thread-id", + }); +} + +describe("photonInboundContent", () => { + it("returns plain text", () => { + expect(photonInboundContent(message("hello"))).toBe("hello"); + }); + + it("drops blank messages", () => { + expect(photonInboundContent(message(" \n"))).toBeUndefined(); + }); + + it("drops attachment-only messages when Photon provides no attachment URL", () => { + expect( + photonInboundContent( + message("", [ + { + mimeType: "image/jpeg", + name: "photo.jpg", + size: 10, + type: "image", + }, + ]), + ), + ).toBeUndefined(); + }); +}); diff --git a/packages/eve/src/public/channels/photon/inboundContent.ts b/packages/eve/src/public/channels/photon/inboundContent.ts new file mode 100644 index 000000000..6f5dec1c6 --- /dev/null +++ b/packages/eve/src/public/channels/photon/inboundContent.ts @@ -0,0 +1,13 @@ +import type { UserContent } from "ai"; + +import type { Message } from "#compiled/chat/index.js"; +import { messageToUserContent } from "#public/channels/chat-sdk/index.js"; + +/** Returns model-visible Photon content, or `undefined` for non-message events. */ +export function photonInboundContent(message: Message): string | UserContent | undefined { + const content = messageToUserContent(message); + if (typeof content === "string") { + return content.trim().length > 0 ? content : undefined; + } + return content.length > 0 ? content : undefined; +} diff --git a/packages/eve/src/public/channels/photon/index.ts b/packages/eve/src/public/channels/photon/index.ts new file mode 100644 index 000000000..249b38e0c --- /dev/null +++ b/packages/eve/src/public/channels/photon/index.ts @@ -0,0 +1,9 @@ +export { + photonIMessageChannel, + type PhotonIMessageChannel, + type PhotonIMessageChannelConfig, + type PhotonIMessageChannelCredentials, + type PhotonInboundMessageContext, + type PhotonInboundResult, + type PhotonInboundResultOrPromise, +} from "#public/channels/photon/photonIMessageChannel.js"; diff --git a/packages/eve/src/public/channels/photon/photonIMessageChannel.test.ts b/packages/eve/src/public/channels/photon/photonIMessageChannel.test.ts new file mode 100644 index 000000000..e52be32b9 --- /dev/null +++ b/packages/eve/src/public/channels/photon/photonIMessageChannel.test.ts @@ -0,0 +1,105 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const { directMessage, newMessage, send } = vi.hoisted(() => ({ + directMessage: vi.fn(), + newMessage: vi.fn(), + send: vi.fn(), +})); + +vi.mock("#public/channels/chat-sdk/index.js", () => ({ + chatSdkChannel: () => ({ + bot: { + getAdapter: () => ({ markRead: vi.fn() }), + onDirectMessage: directMessage, + onNewMessage: newMessage, + }, + channel: { routes: [] }, + send, + }), + messageToUserContent: (message: Message) => message.text, +})); +vi.mock("#compiled/@chat-adapter/state-memory/index.js", () => ({ + createMemoryState: vi.fn(), +})); +vi.mock("#compiled/@photon-ai/chat-adapter-imessage/index.js", () => ({ + createiMessageAdapter: vi.fn(), +})); +vi.mock("#public/channels/auth.js", () => ({ vercelOidc: vi.fn() })); + +import { photonIMessageChannel } from "#public/channels/photon/photonIMessageChannel.js"; +import { Message } from "#compiled/chat/index.js"; + +describe("photonIMessageChannel", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("cancels the active Eve turn before steering a direct message into its thread", async () => { + photonIMessageChannel({ + credentials: async () => ({ projectId: "project-id", projectSecret: "project-secret" }), + }); + const handler = directMessage.mock.calls[0]?.[0]; + if (handler === undefined) throw new Error("Expected an inbound direct-message handler."); + const thread = { id: "thread-id" }; + const message = new Message({ + author: { isBot: false, isMe: false, userId: "user", userName: "user" }, + id: "message-id", + raw: {}, + text: "Steer this response", + threadId: thread.id, + }); + + await handler(thread, message); + + expect(send).toHaveBeenCalledWith( + { context: [], message: "Steer this response" }, + { auth: null, thread, turnPolicy: "experimental-steer" }, + ); + }); + + it("drops blank inbound messages without cancelling or sending", async () => { + photonIMessageChannel({ + credentials: async () => ({ projectId: "project-id", projectSecret: "project-secret" }), + }); + const handler = directMessage.mock.calls[0]?.[0]; + if (handler === undefined) throw new Error("Expected an inbound direct-message handler."); + const thread = { id: "thread-id" }; + const message = new Message({ + author: { isBot: false, isMe: false, userId: "user", userName: "user" }, + id: "message-id", + raw: {}, + text: " \n", + threadId: thread.id, + }); + + await handler(thread, message); + + expect(send).not.toHaveBeenCalled(); + }); + + it("routes group messages without a Chat SDK subscription", async () => { + photonIMessageChannel({ + credentials: async () => ({ projectId: "project-id", projectSecret: "project-secret" }), + }); + const [pattern, handler] = newMessage.mock.calls[0] ?? []; + if (!(pattern instanceof RegExp) || handler === undefined) { + throw new Error("Expected an inbound group-message handler."); + } + const thread = { id: "group-thread-id" }; + const message = new Message({ + author: { isBot: false, isMe: false, userId: "user", userName: "user" }, + id: "message-id", + raw: {}, + text: "Hello group", + threadId: thread.id, + }); + + expect(pattern.test(message.text)).toBe(true); + await handler(thread, message); + + expect(send).toHaveBeenCalledWith( + { context: [], message: "Hello group" }, + { auth: null, thread, turnPolicy: "experimental-steer" }, + ); + }); +}); diff --git a/packages/eve/src/public/channels/photon/photonIMessageChannel.ts b/packages/eve/src/public/channels/photon/photonIMessageChannel.ts new file mode 100644 index 000000000..f3f93c3c8 --- /dev/null +++ b/packages/eve/src/public/channels/photon/photonIMessageChannel.ts @@ -0,0 +1,139 @@ +import type { SessionAuthContext } from "#channel/types.js"; +import { vercelOidc } from "#public/channels/auth.js"; +import { + chatSdkChannel, + type ChatSdkChannel, + type ChatSdkChannelBridge, + type ChatSdkChannelEvents, +} from "#public/channels/chat-sdk/index.js"; +import type { Message, Thread } from "#compiled/chat/index.js"; +import { createMemoryState } from "#compiled/@chat-adapter/state-memory/index.js"; +import { + createiMessageAdapter, + type iMessageAdapter, + type iMessageCredentialProvider, + type iMessageWebhookVerifier, +} from "#compiled/@photon-ai/chat-adapter-imessage/index.js"; +import { photonInboundContent } from "#public/channels/photon/inboundContent.js"; + +/** Photon project credentials used by {@link photonIMessageChannel}. */ +export type PhotonIMessageChannelCredentials = iMessageCredentialProvider; + +/** Context passed to {@link PhotonIMessageChannelConfig.onMessage}. */ +export interface PhotonInboundMessageContext { + /** Low-level Chat SDK thread for iMessage-specific operations. */ + readonly thread: Thread; +} + +/** Result of {@link PhotonIMessageChannelConfig.onMessage}. Return `null` to drop the message. */ +export type PhotonInboundResult = { + readonly auth: SessionAuthContext | null; + readonly context?: readonly string[]; +} | null; + +/** Sync or async {@link PhotonInboundResult}. */ +export type PhotonInboundResultOrPromise = PhotonInboundResult | Promise; + +/** Configuration for {@link photonIMessageChannel}. */ +export interface PhotonIMessageChannelConfig { + /** Lazy Photon project credentials, such as `connectPhotonCredentials(...)`. */ + readonly credentials: PhotonIMessageChannelCredentials; + /** Per-event overrides for the underlying Chat SDK channel. */ + readonly events?: ChatSdkChannelEvents<{ imessage: iMessageAdapter }>; + /** Inbound message policy. Defaults to dispatching every message with no user auth. */ + readonly onMessage?: ( + ctx: PhotonInboundMessageContext, + message: Message, + ) => PhotonInboundResultOrPromise; + /** Override the default webhook route (`/eve/v1/photon`). */ + readonly route?: string; + /** Display name used by the Chat SDK runtime. Defaults to `"eve"`. */ + readonly userName?: string; + /** Photon webhook signing secret. Falls back to `IMESSAGE_WEBHOOK_SECRET`. */ + readonly webhookSecret?: string; + /** Trusted webhook verifier. Takes precedence over `webhookSecret`. */ + readonly webhookVerifier?: iMessageWebhookVerifier; +} + +/** First-class eve channel backed by Photon iMessage. */ +export interface PhotonIMessageChannel extends ChatSdkChannel {} + +/** + * Creates an eve channel for Photon-powered iMessage. + * + * @example + * ```ts + * import { connectPhotonCredentials } from "@vercel/connect/eve"; + * import { photonIMessageChannel } from "eve/channels/photon"; + * + * export default photonIMessageChannel({ + * credentials: connectPhotonCredentials("photon/my-agent"), + * }); + * ``` + */ +export function photonIMessageChannel(config: PhotonIMessageChannelConfig): PhotonIMessageChannel { + const webhookSecret = config.webhookSecret ?? process.env.IMESSAGE_WEBHOOK_SECRET; + const imessage = createiMessageAdapter({ + credentials: config.credentials, + ...(config.webhookVerifier + ? { webhookVerifier: config.webhookVerifier } + : webhookSecret + ? { webhookSecret } + : { webhookVerifier: vercelOidc() }), + }); + const bridge = chatSdkChannel({ + adapters: { imessage }, + concurrency: "concurrent", + events: config.events, + routes: { imessage: config.route ?? "/eve/v1/photon" }, + state: createMemoryState(), + streaming: false, + userName: config.userName ?? "eve", + }); + const onMessage = config.onMessage ?? defaultOnMessage; + + bridge.bot.onDirectMessage(async (thread: Thread, message: Message) => { + await dispatchMessage(bridge, onMessage, thread, message); + }); + bridge.bot.onNewMessage(/[\s\S]*/, async (thread: Thread, message: Message) => { + await dispatchMessage(bridge, onMessage, thread, message); + }); + + return bridge.channel; +} + +async function defaultOnMessage(): Promise { + return { auth: null }; +} + +async function dispatchMessage( + bridge: ChatSdkChannelBridge<{ imessage: iMessageAdapter }>, + onMessage: NonNullable, + thread: Thread, + message: Message, +): Promise { + const result = await onMessage({ thread }, message); + if (result === null) return; + await markReadBestEffort(bridge.bot.getAdapter("imessage"), thread, message); + const content = photonInboundContent(message); + if (content === undefined) return; + await bridge.send( + { + context: [...(result.context ?? [])], + message: content, + }, + { auth: result.auth, thread, turnPolicy: "experimental-steer" }, + ); +} + +async function markReadBestEffort( + adapter: iMessageAdapter, + thread: Thread, + message: Message, +): Promise { + try { + await adapter.markRead(thread.id, message.id); + } catch { + // A read receipt should never prevent the user's message from reaching eve. + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 736b8879b..d5dfacd61 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1258,6 +1258,9 @@ importers: '@opentelemetry/sdk-trace-base': specifier: 'catalog:' version: 2.6.1(@opentelemetry/api@1.9.1) + '@photon-ai/chat-adapter-imessage': + specifier: 3.2.0 + version: 3.2.0(ai@7.0.38(zod@4.4.3))(chat@4.34.0(ai@7.0.38(zod@4.4.3))(supports-color@10.2.2)(zod@4.4.3))(supports-color@10.2.2)(typescript@7.0.2)(zod@4.4.3) '@standard-schema/spec': specifier: 1.1.0 version: 1.1.0 @@ -1374,7 +1377,7 @@ importers: version: 7.8.4 shadcn: specifier: 4.16.0 - version: 4.16.0(supports-color@10.2.2)(typescript@6.0.3) + version: 4.16.0(supports-color@10.2.2)(typescript@7.0.2) svelte: specifier: ^5.0.0 version: 5.56.1(@typescript-eslint/types@8.59.4) @@ -18424,6 +18427,20 @@ snapshots: - typescript - zod + '@photon-ai/chat-adapter-imessage@3.2.0(ai@7.0.38(zod@4.4.3))(chat@4.34.0(ai@7.0.38(zod@4.4.3))(supports-color@10.2.2)(zod@4.4.3))(supports-color@10.2.2)(typescript@7.0.2)(zod@4.4.3)': + dependencies: + '@chat-adapter/shared': 4.34.0(ai@7.0.38(zod@4.4.3))(supports-color@10.2.2)(zod@4.4.3) + '@spectrum-ts/core': 10.0.0(supports-color@10.2.2)(typescript@7.0.2) + '@spectrum-ts/imessage': 10.0.0(@spectrum-ts/core@10.0.0(supports-color@10.2.2)(typescript@7.0.2))(supports-color@10.2.2)(typescript@7.0.2) + chat: 4.34.0(ai@7.0.38(zod@4.4.3))(supports-color@10.2.2)(zod@4.4.3) + mime-types: 3.0.2 + transitivePeerDependencies: + - ai + - ffmpeg-static + - supports-color + - typescript + - zod + '@photon-ai/otel@3.3.0(supports-color@10.2.2)(typescript@6.0.3)': dependencies: '@opentelemetry/api': 1.9.1 @@ -18445,6 +18462,27 @@ snapshots: transitivePeerDependencies: - supports-color + '@photon-ai/otel@3.3.0(supports-color@10.2.2)(typescript@7.0.2)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.218.0 + '@opentelemetry/context-async-hooks': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 + typescript: 7.0.2 + optionalDependencies: + '@opentelemetry/instrumentation': 0.219.0(@opentelemetry/api@1.9.1)(supports-color@10.2.2) + '@opentelemetry/instrumentation-undici': 0.29.0(@opentelemetry/api@1.9.1)(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + '@photon-ai/proto@0.2.4': dependencies: '@bufbuild/protobuf': 2.13.0 @@ -19832,6 +19870,20 @@ snapshots: transitivePeerDependencies: - supports-color + '@spectrum-ts/core@10.0.0(supports-color@10.2.2)(typescript@7.0.2)': + dependencies: + '@photon-ai/otel': 3.3.0(supports-color@10.2.2)(typescript@7.0.2) + '@photon-ai/proto': 0.2.4 + '@repeaterjs/repeater': 3.1.0 + marked: 18.0.7 + mime-types: 3.0.2 + open-graph-scraper: 6.12.0 + typescript: 7.0.2 + vcf: 2.1.2 + zod: 4.4.3 + transitivePeerDependencies: + - supports-color + '@spectrum-ts/imessage@10.0.0(@spectrum-ts/core@10.0.0(supports-color@10.2.2)(typescript@6.0.3))(supports-color@10.2.2)(typescript@6.0.3)': dependencies: '@photon-ai/advanced-imessage': 1.0.0 @@ -19844,6 +19896,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@spectrum-ts/imessage@10.0.0(@spectrum-ts/core@10.0.0(supports-color@10.2.2)(typescript@7.0.2))(supports-color@10.2.2)(typescript@7.0.2)': + dependencies: + '@photon-ai/advanced-imessage': 1.0.0 + '@photon-ai/otel': 3.3.0(supports-color@10.2.2)(typescript@7.0.2) + '@spectrum-ts/core': 10.0.0(supports-color@10.2.2)(typescript@7.0.2) + lru-cache: 11.5.2 + marked: 18.0.7 + typescript: 7.0.2 + zod: 4.4.3 + transitivePeerDependencies: + - supports-color + '@speed-highlight/core@1.2.17': {} '@stablelib/base64@1.0.1': {} @@ -22583,6 +22647,15 @@ snapshots: optionalDependencies: typescript: 6.0.3 + cosmiconfig@9.0.2(typescript@7.0.2): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + parse-json: 5.2.0 + optionalDependencies: + typescript: 7.0.2 + crc-32@1.2.2: {} crc32-stream@6.0.0: @@ -29273,6 +29346,46 @@ snapshots: - supports-color - typescript + shadcn@4.16.0(supports-color@10.2.2)(typescript@7.0.2): + dependencies: + '@babel/core': 7.29.0(supports-color@10.2.2) + '@babel/parser': 7.29.7 + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2) + '@babel/preset-typescript': 7.29.7(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2) + '@dotenvx/dotenvx': 1.75.1 + '@modelcontextprotocol/sdk': 1.29.0(supports-color@10.2.2)(zod@3.25.76) + '@types/validate-npm-package-name': 4.0.2 + browserslist: 4.28.6 + commander: 14.0.3 + cosmiconfig: 9.0.2(typescript@7.0.2) + dedent: 1.7.2 + deepmerge: 4.3.1 + diff: 8.0.4 + execa: 9.6.1 + fast-glob: 3.3.3 + fs-extra: 11.3.6 + fuzzysort: 3.1.0 + kleur: 4.1.5 + open: 11.0.0 + ora: 8.2.0 + postcss: 8.5.15 + postcss-selector-parser: 7.1.4 + prompts: 2.4.2 + recast: 0.23.12 + stringify-object: 5.0.0 + tailwind-merge: 3.6.0 + ts-morph: 26.0.0 + tsconfig-paths: 4.2.0 + undici: 7.28.0 + validate-npm-package-name: 7.0.2 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + transitivePeerDependencies: + - '@cfworker/json-schema' + - babel-plugin-macros + - supports-color + - typescript + sharp@0.34.5: dependencies: '@img/colour': 1.1.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 42ad44eb8..094e16b51 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -79,6 +79,7 @@ minimumReleaseAgeExclude: - chat - chat-adapter-sendblue - "@ai-sdk/*" + - "@photon-ai/chat-adapter-imessage" - "@rolldown/*" - "@typescript/typescript-*" - "@turbo/*"