@joeybuilt/plexo-sdk 1.2.0 → 1.3.0
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.
- package/dist/connect/index.d.ts +59 -1
- package/dist/connect/index.js +10 -0
- package/dist/connect/index.js.map +1 -1
- package/package.json +1 -1
package/dist/connect/index.d.ts
CHANGED
|
@@ -100,6 +100,60 @@ interface AiCompleteOptions {
|
|
|
100
100
|
maxTokens?: number;
|
|
101
101
|
taskType?: string;
|
|
102
102
|
}
|
|
103
|
+
interface RunCustomTool {
|
|
104
|
+
/** Tool name surfaced to the LLM. Must be `[a-zA-Z0-9_]{1,64}`. */
|
|
105
|
+
name: string;
|
|
106
|
+
/** Plain-English purpose. Influences when the LLM chooses this tool. */
|
|
107
|
+
description: string;
|
|
108
|
+
/**
|
|
109
|
+
* JSON Schema describing tool input. Plexo validates the LLM's call against this
|
|
110
|
+
* before dispatching; failures are surfaced as tool errors back to the LLM.
|
|
111
|
+
*/
|
|
112
|
+
inputSchema: Record<string, unknown>;
|
|
113
|
+
/**
|
|
114
|
+
* Plexo POSTs `{ runId, toolName, input }` here when the LLM calls this tool.
|
|
115
|
+
* Caller's handler must respond with `{ output: <string|json> }` within 30s.
|
|
116
|
+
* Signed with per-run JWT (HS256, claims: workspaceId/runId/allowedTools).
|
|
117
|
+
*/
|
|
118
|
+
callbackUrl: string;
|
|
119
|
+
}
|
|
120
|
+
interface RunCustomOptions {
|
|
121
|
+
/** Free-form system prompt; replaces any built-in agent prompt for this run. */
|
|
122
|
+
systemPrompt: string;
|
|
123
|
+
/** Caller-provided tools. Plexo dispatches via HTTP callback per `RunCustomTool.callbackUrl`. */
|
|
124
|
+
tools: RunCustomTool[];
|
|
125
|
+
/** Initial user-turn input to the agent loop. */
|
|
126
|
+
input: string;
|
|
127
|
+
/** Optional model override. Defaults to workspace primary model. */
|
|
128
|
+
model?: string;
|
|
129
|
+
/** Hard cap on agent loop iterations. Default 12. */
|
|
130
|
+
maxSteps?: number;
|
|
131
|
+
/**
|
|
132
|
+
* EP2: when true, Plexo registers a `read_memory` tool alongside caller tools.
|
|
133
|
+
* Body shape: `{ query: string, limit?: number, tags?: string[] }` → `MemoryEntry[]`.
|
|
134
|
+
*/
|
|
135
|
+
enableMemoryTool?: boolean;
|
|
136
|
+
}
|
|
137
|
+
interface RunCustomStep {
|
|
138
|
+
/** Tool name invoked, if any. Absent on final assistant text turn. */
|
|
139
|
+
tool?: string;
|
|
140
|
+
/** Input that was passed to the tool. */
|
|
141
|
+
input?: Record<string, unknown>;
|
|
142
|
+
/** Tool output (string or JSON, as returned by callback). */
|
|
143
|
+
output?: unknown;
|
|
144
|
+
/** Free-form error if the tool call failed. */
|
|
145
|
+
error?: string;
|
|
146
|
+
}
|
|
147
|
+
interface RunCustomResult {
|
|
148
|
+
/** Server-issued run identifier. Stable across the agent loop; tied to JWT claims. */
|
|
149
|
+
runId: string;
|
|
150
|
+
/** Final assistant message after the loop terminated. */
|
|
151
|
+
output: string;
|
|
152
|
+
/** Ordered transcript of tool dispatches + outputs. Empty if LLM responded directly. */
|
|
153
|
+
steps: RunCustomStep[];
|
|
154
|
+
/** True if `maxSteps` cap was hit before the LLM declared done. */
|
|
155
|
+
truncated: boolean;
|
|
156
|
+
}
|
|
103
157
|
interface ChatOptions {
|
|
104
158
|
message: string;
|
|
105
159
|
sessionId?: string;
|
|
@@ -303,6 +357,10 @@ interface TestConnectionResult {
|
|
|
303
357
|
|
|
304
358
|
declare class PlexoClient {
|
|
305
359
|
#private;
|
|
360
|
+
/** Agent-loop operations. EP1: caller-provided systemPrompt + tools via HTTP callback. */
|
|
361
|
+
readonly agents: {
|
|
362
|
+
runCustom: (workspaceId: string, opts: RunCustomOptions) => Promise<RunCustomResult>;
|
|
363
|
+
};
|
|
306
364
|
constructor(opts: PlexoClientOptions);
|
|
307
365
|
get appId(): string;
|
|
308
366
|
get isConfigured(): boolean;
|
|
@@ -440,4 +498,4 @@ declare function verifyInboundSignature(body: string, signature: string | null,
|
|
|
440
498
|
*/
|
|
441
499
|
declare function createPlexoClient(opts: PlexoClientOptions): PlexoClient;
|
|
442
500
|
|
|
443
|
-
export { type AiCompleteOptions, type AiMessage, type AppChannelConfig, type AppConnectorConfig, type AppExtension, type AppProfile, type AppToolConfig, type ChatOptions, type ChatReply, type DataQuery, type DataResponse, type DispatchContext, type DispatchOptions, type DispatchResult, type GmessagesLastMessage, type GmessagesListThreadsOptions, type GmessagesSendOptions, type GmessagesSendResult, type GmessagesThread, type GmessagesThreadParticipant, type InboundEvent, type InboundEventType, type InboundHandlers, type InboundVerifyResult, type InstallConnectionOptions, type MemoryEntry, type MemorySearchResult, type OcrResult, PlexoApiError, PlexoAuthError, PlexoClient, type PlexoClientOptions, type PlexoConnection, type PlexoConversation, PlexoNotConfiguredError, PlexoRateLimitedError, type PlexoTask, type PlexoToken, type PlexoTokenWithConnection, PlexoUnreachableError, type PublishEventOptions, type ResilienceOptions, type StoreMemoryOptions, type TestConnectionResult, createPlexoClient, verifyInboundSignature };
|
|
501
|
+
export { type AiCompleteOptions, type AiMessage, type AppChannelConfig, type AppConnectorConfig, type AppExtension, type AppProfile, type AppToolConfig, type ChatOptions, type ChatReply, type DataQuery, type DataResponse, type DispatchContext, type DispatchOptions, type DispatchResult, type GmessagesLastMessage, type GmessagesListThreadsOptions, type GmessagesSendOptions, type GmessagesSendResult, type GmessagesThread, type GmessagesThreadParticipant, type InboundEvent, type InboundEventType, type InboundHandlers, type InboundVerifyResult, type InstallConnectionOptions, type MemoryEntry, type MemorySearchResult, type OcrResult, PlexoApiError, PlexoAuthError, PlexoClient, type PlexoClientOptions, type PlexoConnection, type PlexoConversation, PlexoNotConfiguredError, PlexoRateLimitedError, type PlexoTask, type PlexoToken, type PlexoTokenWithConnection, PlexoUnreachableError, type PublishEventOptions, type ResilienceOptions, type RunCustomOptions, type RunCustomResult, type RunCustomStep, type RunCustomTool, type StoreMemoryOptions, type TestConnectionResult, createPlexoClient, verifyInboundSignature };
|
package/dist/connect/index.js
CHANGED
|
@@ -163,9 +163,19 @@ async function register(opts, profile) {
|
|
|
163
163
|
var PlexoClient = class {
|
|
164
164
|
#opts;
|
|
165
165
|
#base;
|
|
166
|
+
/** Agent-loop operations. EP1: caller-provided systemPrompt + tools via HTTP callback. */
|
|
167
|
+
agents;
|
|
166
168
|
constructor(opts) {
|
|
167
169
|
this.#opts = opts;
|
|
168
170
|
this.#base = opts.plexoUrl.replace(/\/$/, "");
|
|
171
|
+
this.agents = {
|
|
172
|
+
runCustom: (workspaceId, runOpts) => this.#post(
|
|
173
|
+
"/api/v1/agents/run-custom",
|
|
174
|
+
{ workspaceId, ...runOpts },
|
|
175
|
+
{},
|
|
176
|
+
12e4
|
|
177
|
+
)
|
|
178
|
+
};
|
|
169
179
|
}
|
|
170
180
|
get appId() {
|
|
171
181
|
return this.#opts.appId;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/connect/errors.ts","../../src/connect/inbound.ts","../../src/connect/registration.ts","../../src/connect/client.ts","../../src/connect/index.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-only\n// Copyright (C) 2026 Joeybuilt LLC\n\nexport class PlexoNotConfiguredError extends Error {\n override readonly name = 'PlexoNotConfiguredError'\n constructor(missing: string) {\n super(`PlexoClient not configured: ${missing} is required`)\n }\n}\n\nexport class PlexoUnreachableError extends Error {\n override readonly name = 'PlexoUnreachableError'\n readonly url: string\n constructor(url: string, cause?: unknown) {\n super(`Plexo Core unreachable at ${url}`)\n this.url = url\n if (cause) this.cause = cause\n }\n}\n\nexport class PlexoApiError extends Error {\n override readonly name: string = 'PlexoApiError'\n readonly status: number\n readonly path: string\n constructor(status: number, path: string, detail?: string) {\n super(`Plexo API error ${status} on ${path}${detail ? `: ${detail}` : ''}`)\n this.status = status\n this.path = path\n }\n}\n\nexport class PlexoAuthError extends PlexoApiError {\n readonly name = 'PlexoAuthError' as const\n constructor(path: string) {\n super(401, path, 'invalid or missing service key')\n }\n}\n\nexport class PlexoRateLimitedError extends PlexoApiError {\n readonly name = 'PlexoRateLimitedError' as const\n readonly retryAfterMs: number\n constructor(path: string, retryAfterSec?: number) {\n super(429, path, 'rate limited')\n this.retryAfterMs = (retryAfterSec ?? 60) * 1000\n }\n}\n","// SPDX-License-Identifier: AGPL-3.0-only\n// Copyright (C) 2026 Joeybuilt LLC\n\n/**\n * Inbound request handling — verifies and dispatches requests Plexo Core\n * sends back to the app (events push + data/tool queries).\n *\n * Framework-agnostic: `handleInbound` receives a plain Request and returns\n * a plain Response so it can be wrapped in any framework's route handler.\n *\n * Next.js:\n * export const POST = (req: Request) => plexo.inbound.handle(req)\n *\n * Express:\n * app.post('/api/plexo/events', async (req, res) => {\n * const raw = JSON.stringify(req.body)\n * const r = new Request(req.url, { method:'POST', body: raw, headers: req.headers })\n * const resp = await plexo.inbound.handle(r)\n * res.status(resp.status).json(await resp.json())\n * })\n */\n\nimport { createHmac, timingSafeEqual } from 'node:crypto'\nimport type {\n DataQuery,\n DataResponse,\n InboundEvent,\n InboundHandlers,\n InboundVerifyResult,\n} from './types.js'\n\nconst SKEW_MS = 5 * 60 * 1000\n\nexport function verifyInboundSignature(\n body: string,\n signature: string | null,\n timestamp: string | null,\n serviceKey: string,\n): InboundVerifyResult {\n if (!signature || !timestamp) {\n return { ok: false, error: 'missing X-Plexo-Signature or X-Plexo-Timestamp' }\n }\n const ts = Date.parse(timestamp)\n if (Number.isNaN(ts) || Math.abs(Date.now() - ts) > SKEW_MS) {\n return { ok: false, error: 'timestamp skew exceeds 5 minutes' }\n }\n const expected = 'sha256=' + createHmac('sha256', serviceKey).update(body).digest('hex')\n const sigBuf = Buffer.from(signature)\n const expBuf = Buffer.from(expected)\n if (sigBuf.length !== expBuf.length || !timingSafeEqual(sigBuf, expBuf)) {\n return { ok: false, error: 'signature mismatch' }\n }\n return { ok: true }\n}\n\nexport function createInboundRouter(serviceKey: string, handlers: InboundHandlers) {\n return {\n async handle(req: Request): Promise<Response> {\n const body = await req.text()\n const sig = req.headers.get('x-plexo-signature')\n const ts = req.headers.get('x-plexo-timestamp')\n\n const check = verifyInboundSignature(body, sig, ts, serviceKey)\n if (!check.ok) {\n return new Response(\n JSON.stringify({ error: check.error }),\n { status: 401, headers: { 'Content-Type': 'application/json' } },\n )\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(body)\n } catch {\n return new Response(\n JSON.stringify({ error: 'invalid JSON' }),\n { status: 400, headers: { 'Content-Type': 'application/json' } },\n )\n }\n\n const msg = parsed as Record<string, unknown>\n const kind = msg['kind'] as string | undefined\n\n try {\n if (kind === 'event' && handlers.onEvent) {\n await handlers.onEvent(msg['event'] as InboundEvent)\n return new Response(null, { status: 204 })\n }\n\n if (kind === 'data_query' && handlers.onDataQuery) {\n const query = msg['query'] as DataQuery\n const result = await handlers.onDataQuery(query)\n const response: DataResponse = { requestId: query.requestId, result }\n return new Response(\n JSON.stringify(response),\n { status: 200, headers: { 'Content-Type': 'application/json' } },\n )\n }\n\n return new Response(null, { status: 204 })\n } catch (err) {\n const response: DataResponse = {\n requestId: (msg['query'] as DataQuery | undefined)?.requestId ?? '',\n error: err instanceof Error ? err.message : String(err),\n }\n return new Response(\n JSON.stringify(response),\n { status: 500, headers: { 'Content-Type': 'application/json' } },\n )\n }\n },\n }\n}\n","// SPDX-License-Identifier: AGPL-3.0-only\n// Copyright (C) 2026 Joeybuilt LLC\n\nimport type { AppProfile, PlexoClientOptions } from './types.js'\n\nfunction buildHeaders(opts: PlexoClientOptions): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${opts.serviceKey}`,\n 'X-App-Id': opts.appId,\n }\n}\n\nasync function post(\n opts: PlexoClientOptions,\n path: string,\n body: unknown,\n): Promise<boolean> {\n const fetchImpl = opts.fetchImpl ?? fetch\n try {\n const res = await fetchImpl(\n `${opts.plexoUrl.replace(/\\/$/, '')}${path}`,\n {\n method: 'POST',\n headers: buildHeaders(opts),\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(8_000),\n },\n )\n if (!res.ok) {\n const detail = await res.text().catch(() => `HTTP ${res.status}`)\n console.error(`[plexo/connect] registration rejected (${res.status}): ${detail}`)\n return false\n }\n return true\n } catch (err) {\n console.warn(`[plexo/connect] registration attempt failed: ${(err as Error).message}`)\n return false\n }\n}\n\nexport async function register(\n opts: PlexoClientOptions,\n profile: AppProfile,\n): Promise<void> {\n const backoff = opts.resilience?.registrationBackoffMs ?? [5_000, 15_000, 45_000]\n\n if (await post(opts, '/api/v1/profiles/register', profile)) {\n console.info(`[plexo/connect] registered appId=${opts.appId}`)\n return\n }\n\n for (const delay of backoff) {\n console.info(`[plexo/connect] retrying registration in ${delay / 1000}s`)\n await new Promise<void>((r) => setTimeout(r, delay))\n if (await post(opts, '/api/v1/profiles/register', profile)) {\n console.info(`[plexo/connect] registered appId=${opts.appId}`)\n return\n }\n }\n\n console.warn('[plexo/connect] registration retries exhausted — running in standalone mode')\n}\n","// SPDX-License-Identifier: AGPL-3.0-only\n// Copyright (C) 2026 Joeybuilt LLC\n\nimport { PlexoApiError, PlexoAuthError, PlexoRateLimitedError, PlexoUnreachableError } from './errors.js'\nimport { createInboundRouter } from './inbound.js'\nimport { register } from './registration.js'\nimport type {\n AddEpisodeOptions,\n AddEpisodeResult,\n AiCompleteOptions,\n AppProfile,\n ChatOptions,\n ChatReply,\n DispatchContext,\n DispatchOptions,\n DispatchResult,\n FactSearchResult,\n GmessagesListThreadsOptions,\n GmessagesSendOptions,\n GmessagesSendResult,\n GmessagesThread,\n InboundHandlers,\n InstallConnectionOptions,\n MemorySearchResult,\n OcrResult,\n PlexoClientOptions,\n PlexoConnection,\n PlexoConversation,\n PlexoTask,\n PlexoToken,\n PlexoTokenWithConnection,\n PublishEventOptions,\n StoreMemoryOptions,\n TestConnectionResult,\n} from './types.js'\n\nexport class PlexoClient {\n readonly #opts: PlexoClientOptions\n readonly #base: string\n\n constructor(opts: PlexoClientOptions) {\n this.#opts = opts\n this.#base = opts.plexoUrl.replace(/\\/$/, '')\n }\n\n get appId(): string { return this.#opts.appId }\n\n get isConfigured(): boolean {\n return !!(this.#opts.plexoUrl && this.#opts.serviceKey)\n }\n\n // -----------------------------------------------------------------------\n // Registration\n // -----------------------------------------------------------------------\n\n async register(): Promise<void> {\n if (!this.isConfigured) return\n\n let domain = ''\n try { domain = new URL(this.#opts.plexoUrl).host } catch {}\n\n const profile: AppProfile = {\n appId: this.#opts.appId,\n schemaNamespace: this.#opts.schemaNamespace ?? this.#opts.appId,\n displayName: this.#opts.displayName ?? this.#opts.appId,\n domain,\n extensions: this.#opts.extensions ?? [],\n eventContracts: this.#opts.eventContracts ?? [],\n }\n await register(this.#opts, profile)\n }\n\n // -----------------------------------------------------------------------\n // Workspace\n // -----------------------------------------------------------------------\n\n async ensureWorkspace(userId: string, email?: string): Promise<string> {\n const data = await this.#post<{ workspaceId: string }>(\n '/api/v1/auth/workspace/ensure',\n { userId, email, name: this.#opts.displayName ?? this.#opts.appId },\n { userId },\n )\n return data.workspaceId\n }\n\n /**\n * Idempotent get-or-create + auto-install of this app's connection.\n * Used when an app needs Plexo to know about a user without going\n * through the workspace-ensure → install dance separately.\n */\n async autoAttachUser(userId: string, email?: string): Promise<{ workspaceId: string }> {\n return await this.#post<{ workspaceId: string }>(\n '/api/v1/auth/profiles/auto-attach-user',\n { userId, email, name: this.#opts.displayName ?? this.#opts.appId },\n { userId },\n )\n }\n\n // -----------------------------------------------------------------------\n // Connections\n // -----------------------------------------------------------------------\n\n async getInstalledConnections(workspaceId: string): Promise<PlexoConnection[]> {\n try {\n const data = await this.#get<{ items?: PlexoConnection[] }>(\n `/api/v1/connections/installed?workspaceId=${encodeURIComponent(workspaceId)}`,\n )\n return data.items ?? []\n } catch {\n return []\n }\n }\n\n async getToken(workspaceId: string, registryId: string): Promise<PlexoToken | null> {\n try {\n return await this.#get<PlexoToken>(\n `/api/v1/connections/token?workspaceId=${encodeURIComponent(workspaceId)}®istryId=${encodeURIComponent(registryId)}`,\n )\n } catch (err) {\n if (err instanceof PlexoApiError && err.status === 404) return null\n throw err\n }\n }\n\n async disconnect(workspaceId: string, connectionId: string): Promise<void> {\n await this.#delete(\n `/api/v1/connections/installed/${encodeURIComponent(connectionId)}?workspaceId=${encodeURIComponent(workspaceId)}`,\n )\n }\n\n /**\n * Idempotent install. Returns true if already present or newly installed,\n * false on error. Empty `credentials` are valid for app-owned bridges.\n */\n async installConnection(opts: InstallConnectionOptions): Promise<boolean> {\n try {\n const existing = await this.getInstalledConnections(opts.workspaceId)\n if (existing.some((c) => c.registryId === opts.registryId)) return true\n await this.#post('/api/v1/connections/install', opts)\n return true\n } catch {\n return false\n }\n }\n\n /**\n * Multi-token fetch for a registry (e.g. multi-account Google Workspace).\n * Falls back to per-connection getToken() if the bulk endpoint 404s.\n */\n async getTokens(workspaceId: string, registryId: string): Promise<PlexoTokenWithConnection[]> {\n try {\n const data = await this.#get<PlexoTokenWithConnection[]>(\n `/api/v1/connections/tokens?workspaceId=${encodeURIComponent(workspaceId)}®istryId=${encodeURIComponent(registryId)}`,\n )\n return data\n } catch (err) {\n if (!(err instanceof PlexoApiError) || err.status !== 404) throw err\n }\n const conns = await this.getInstalledConnections(workspaceId)\n const matches = conns.filter((c) => c.registryId === registryId && c.status === 'active')\n const out: PlexoTokenWithConnection[] = []\n for (const c of matches) {\n const t = await this.getToken(workspaceId, c.registryId)\n if (t?.access_token) out.push({ ...t, connectionId: c.id })\n }\n return out\n }\n\n async getTasks(\n workspaceId: string,\n opts: { status?: string; limit?: number } = {},\n ): Promise<PlexoTask[]> {\n const params = new URLSearchParams({\n workspaceId,\n limit: String(opts.limit ?? 10),\n })\n if (opts.status) params.set('status', opts.status)\n try {\n const data = await this.#get<{ items?: PlexoTask[] }>(`/api/v1/tasks?${params}`)\n return data.items ?? []\n } catch {\n return []\n }\n }\n\n oauthPopupUrl(provider: string, workspaceId: string): string {\n return `${this.#base}/api/oauth/${encodeURIComponent(provider)}/start?workspaceId=${encodeURIComponent(workspaceId)}`\n }\n\n // -----------------------------------------------------------------------\n // AI\n // -----------------------------------------------------------------------\n\n async aiComplete(workspaceId: string, opts: AiCompleteOptions): Promise<string> {\n const data = await this.#post<{ text?: string }>(\n '/api/v1/ai/complete',\n { workspaceId, ...opts },\n {},\n 30_000,\n )\n return data.text ?? ''\n }\n\n async chatMessage(\n workspaceId: string,\n userId: string,\n opts: ChatOptions,\n ): Promise<ChatReply> {\n const data = await this.#post<Partial<ChatReply>>(\n '/api/v1/chat/message',\n { workspaceId, forceConversation: true, ...opts },\n { userId },\n 30_000,\n )\n return {\n reply: data.reply ?? '',\n conversationId: data.conversationId,\n sessionId: data.sessionId,\n taskId: data.taskId,\n }\n }\n\n // -----------------------------------------------------------------------\n // Channel dispatch\n // -----------------------------------------------------------------------\n\n async dispatch(\n opts: DispatchOptions,\n ctx: DispatchContext = {},\n ): Promise<DispatchResult> {\n const data = await this.#post<DispatchResult>(\n '/api/v1/channel/dispatch',\n opts,\n ctx,\n )\n return { messageId: data.messageId, deliveryStatus: data.deliveryStatus }\n }\n\n // -----------------------------------------------------------------------\n // Events (node-events stream)\n // -----------------------------------------------------------------------\n\n /** Best-effort emit. Swallows errors — events are non-critical by design. */\n async publishEvent(opts: PublishEventOptions): Promise<{ eventId?: string } | null> {\n try {\n return await this.#post<{ eventId?: string }>('/api/v1/events', opts)\n } catch {\n return null\n }\n }\n\n // -----------------------------------------------------------------------\n // Conversations\n // -----------------------------------------------------------------------\n\n async getConversations(\n workspaceId: string,\n limit = 20,\n ): Promise<PlexoConversation[]> {\n try {\n const data = await this.#get<{ conversations?: PlexoConversation[] }>(\n `/api/v1/conversations?workspaceId=${encodeURIComponent(workspaceId)}&limit=${limit}`,\n )\n return data.conversations ?? []\n } catch {\n return []\n }\n }\n\n // -----------------------------------------------------------------------\n // Memory\n // -----------------------------------------------------------------------\n\n /** Best-effort store. Returns null on any failure. */\n async storeMemory(\n workspaceId: string,\n opts: StoreMemoryOptions,\n ): Promise<{ id: string } | null> {\n try {\n return await this.#post<{ id: string }>(\n '/api/v1/memory/entries',\n { workspaceId, ...opts },\n )\n } catch {\n return null\n }\n }\n\n /** Returns [] on any failure. */\n async searchMemory(\n workspaceId: string,\n query: string,\n limit = 20,\n ): Promise<MemorySearchResult[]> {\n try {\n const params = new URLSearchParams({\n workspaceId,\n q: query,\n limit: String(limit),\n })\n const data = await this.#get<{ results?: MemorySearchResult[] }>(\n `/api/v1/memory/search?${params}`,\n )\n return data.results ?? []\n } catch {\n return []\n }\n }\n\n // -----------------------------------------------------------------------\n // Graph (SDK 1.1.0 — Plexo Graphiti integration)\n // -----------------------------------------------------------------------\n //\n // Two methods land in 1.1.0; `searchNodes` and `getCommunity` are\n // deferred to 1.2.0. graphiti-core 0.29 has no `search_nodes` method\n // and no community accessor (only `build_communities` as a builder),\n // so cleanly typed wrappers wait until upstream surface lands or the\n // sidecar grows custom Cypher/Kuzu queries.\n\n /** Add a knowledge-graph episode. Returns null on any failure. */\n async addEpisode(\n workspaceId: string,\n opts: AddEpisodeOptions,\n ): Promise<AddEpisodeResult | null> {\n try {\n return await this.#post<AddEpisodeResult>('/api/v1/graph/episodes', { workspaceId, ...opts })\n } catch {\n return null\n }\n }\n\n /** Hybrid-search facts in the workspace's knowledge graph. Returns [] on any failure. */\n async searchFacts(\n workspaceId: string,\n query: string,\n limit = 10,\n ): Promise<FactSearchResult[]> {\n try {\n const params = new URLSearchParams({ workspaceId, q: query, limit: String(limit) })\n const data = await this.#get<{ results?: Array<{ uuid: string | null; fact: string | null; source_node_uuid: string | null; target_node_uuid: string | null; valid_at: string | null; invalid_at: string | null; created_at: string | null }> }>(\n `/api/v1/graph/facts/search?${params}`,\n )\n return (data.results ?? []).map((edge) => ({\n uuid: edge.uuid,\n fact: edge.fact,\n sourceNodeUuid: edge.source_node_uuid,\n targetNodeUuid: edge.target_node_uuid,\n validAt: edge.valid_at,\n invalidAt: edge.invalid_at,\n createdAt: edge.created_at,\n }))\n } catch {\n return []\n }\n }\n\n // -----------------------------------------------------------------------\n // Tools (SDK 1.2.0 — synchronous tool-invoke surface)\n // -----------------------------------------------------------------------\n //\n // Surface: `client.tools.gmessages.send({...})` and\n // `client.tools.gmessages.listThreads({...})`. Distinct from\n // `dispatch()` (which is the async channel-delivery primitive) — these\n // calls block until the sidecar has accepted (or rejected) the request\n // and surface dispatch errors directly so Levio's chat loop can react.\n //\n // Errors are NOT swallowed here (unlike addEpisode/searchFacts): a Levio\n // user pressing \"send\" expects to see the failure. Network errors bubble\n // as PlexoUnreachableError; HTTP-level errors bubble as PlexoApiError\n // with the structured `error.code` from the route in the message body.\n\n get tools(): {\n gmessages: {\n send: (opts: GmessagesSendOptions) => Promise<GmessagesSendResult>\n listThreads: (opts: GmessagesListThreadsOptions) => Promise<GmessagesThread[]>\n }\n } {\n return {\n gmessages: {\n send: (opts) => this.#gmessagesSend(opts),\n listThreads: (opts) => this.#gmessagesListThreads(opts),\n },\n }\n }\n\n async #gmessagesSend(opts: GmessagesSendOptions): Promise<GmessagesSendResult> {\n const body: Record<string, unknown> = {\n workspaceId: opts.workspaceId,\n text: opts.text,\n }\n if (opts.threadId) body.threadId = opts.threadId\n if (opts.phoneE164) body.phoneE164 = opts.phoneE164\n const data = await this.#post<{ messageId?: string; deliveryStatus?: string }>(\n '/api/v1/tools/gmessages/send',\n body,\n { workspaceId: opts.workspaceId },\n )\n return {\n messageId: data.messageId ?? '',\n deliveryStatus: data.deliveryStatus ?? 'accepted',\n }\n }\n\n async #gmessagesListThreads(opts: GmessagesListThreadsOptions): Promise<GmessagesThread[]> {\n const params = new URLSearchParams({ workspaceId: opts.workspaceId })\n if (opts.phoneE164) params.set('phoneE164', opts.phoneE164)\n if (typeof opts.limit === 'number') params.set('limit', String(opts.limit))\n const data = await this.#get<{ threads?: GmessagesThread[] }>(\n `/api/v1/tools/gmessages/threads?${params}`,\n )\n return data.threads ?? []\n }\n\n // -----------------------------------------------------------------------\n // Vision\n // -----------------------------------------------------------------------\n\n /**\n * OCR an image via Plexo's vision endpoint. The image must be reachable\n * from Plexo (a signed CDN URL works). Returns null on any failure so\n * callers can mark the asset as `failed` and retry later.\n */\n async visionOcr(workspaceId: string, imageUrl: string): Promise<OcrResult | null> {\n try {\n const data = await this.#post<Partial<OcrResult>>(\n '/api/v1/vision/ocr',\n { workspaceId, imageUrl },\n {},\n 60_000,\n )\n return {\n text: data.text ?? '',\n confidence: typeof data.confidence === 'number' ? data.confidence : 0,\n model: data.model ?? 'unknown',\n }\n } catch {\n return null\n }\n }\n\n // -----------------------------------------------------------------------\n // Inbound\n // -----------------------------------------------------------------------\n\n /** Returns a framework-agnostic handler for Plexo → app push requests. */\n inbound(handlers: InboundHandlers) {\n return createInboundRouter(this.#opts.serviceKey, handlers)\n }\n\n // -----------------------------------------------------------------------\n // Utilities\n // -----------------------------------------------------------------------\n\n async testConnection(): Promise<TestConnectionResult> {\n const start = Date.now()\n try {\n const fetchImpl = this.#opts.fetchImpl ?? fetch\n const res = await fetchImpl(`${this.#base}/api/health`, {\n signal: AbortSignal.timeout(5_000),\n })\n const latencyMs = Date.now() - start\n if (!res.ok) {\n return { ok: false, latencyMs, error: `HTTP ${res.status}` }\n }\n const body = await res.json().catch(() => ({})) as Record<string, unknown>\n return { ok: true, latencyMs, plexoVersion: body['version'] as string | undefined }\n } catch (err) {\n return {\n ok: false,\n latencyMs: Date.now() - start,\n error: err instanceof Error ? err.message : String(err),\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // HTTP helpers\n // -----------------------------------------------------------------------\n\n #headers(extra: {\n userId?: string\n tenantId?: string\n workspaceId?: string\n traceId?: string\n } = {}): Record<string, string> {\n const h: Record<string, string> = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.#opts.serviceKey}`,\n 'X-App-Id': this.#opts.appId,\n 'X-Service-Key-Version': this.#opts.serviceKeyVersion ?? 'v1',\n }\n if (extra.userId) h['X-User-Id'] = extra.userId\n if (extra.tenantId) h['X-Tenant-Id'] = extra.tenantId\n if (extra.workspaceId) h['X-Workspace-Id'] = extra.workspaceId\n if (extra.traceId) h['X-Trace-Id'] = extra.traceId\n return h\n }\n\n async #get<T>(path: string): Promise<T> {\n const fetchImpl = this.#opts.fetchImpl ?? fetch\n const timeout = this.#opts.resilience?.timeoutMs ?? 15_000\n let res: Response\n try {\n res = await fetchImpl(`${this.#base}${path}`, {\n headers: this.#headers(),\n signal: AbortSignal.timeout(timeout),\n })\n } catch (err) {\n throw new PlexoUnreachableError(this.#base, err)\n }\n return this.#parse<T>(res, path)\n }\n\n async #post<T>(\n path: string,\n body: unknown,\n extra: {\n userId?: string\n tenantId?: string\n workspaceId?: string\n traceId?: string\n } = {},\n timeoutMs?: number,\n ): Promise<T> {\n const fetchImpl = this.#opts.fetchImpl ?? fetch\n const timeout = timeoutMs ?? this.#opts.resilience?.timeoutMs ?? 15_000\n let res: Response\n try {\n res = await fetchImpl(`${this.#base}${path}`, {\n method: 'POST',\n headers: this.#headers(extra),\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(timeout),\n })\n } catch (err) {\n throw new PlexoUnreachableError(this.#base, err)\n }\n return this.#parse<T>(res, path)\n }\n\n async #delete(path: string): Promise<void> {\n const fetchImpl = this.#opts.fetchImpl ?? fetch\n const timeout = this.#opts.resilience?.timeoutMs ?? 15_000\n let res: Response\n try {\n res = await fetchImpl(`${this.#base}${path}`, {\n method: 'DELETE',\n headers: this.#headers(),\n signal: AbortSignal.timeout(timeout),\n })\n } catch (err) {\n throw new PlexoUnreachableError(this.#base, err)\n }\n if (!res.ok && res.status !== 404) {\n await this.#parse<void>(res, path)\n }\n }\n\n async #parse<T>(res: Response, path: string): Promise<T> {\n if (res.status === 401) throw new PlexoAuthError(path)\n if (res.status === 429) {\n const retry = Number(res.headers.get('retry-after'))\n throw new PlexoRateLimitedError(path, Number.isNaN(retry) ? undefined : retry)\n }\n if (!res.ok) {\n const detail = await res.text().catch(() => '')\n throw new PlexoApiError(res.status, path, detail.slice(0, 300))\n }\n if (res.status === 204) return undefined as T\n return res.json() as Promise<T>\n }\n}\n","// SPDX-License-Identifier: AGPL-3.0-only\n// Copyright (C) 2026 Joeybuilt LLC\n\n/**\n * @joeybuilt/plexo-sdk — connect module\n *\n * Universal client for connecting any app to Plexo Core.\n *\n * Quick start:\n *\n * import { createPlexoClient } from '@joeybuilt/plexo-sdk/connect'\n *\n * export const plexo = createPlexoClient({\n * appId: process.env.APP_ID ?? 'my-app',\n * plexoUrl: process.env.PLEXO_URL ?? '',\n * serviceKey: process.env.SERVICE_KEY ?? '',\n * extensions: [\n * { id: 'my-app.todos.list', type: 'tool', name: 'List Todos',\n * config: { description: 'List todos for the current user' } },\n * ],\n * eventContracts: ['my-app.todo.created'],\n * })\n *\n * Register at boot (Next.js instrumentation.ts, Express server start, etc.):\n * await plexo.register()\n *\n * Per-user workspace (call once on first login):\n * const workspaceId = await plexo.ensureWorkspace(userId, email)\n *\n * AI / chat:\n * const text = await plexo.aiComplete(workspaceId, { messages: [...] })\n * const reply = await plexo.chatMessage(workspaceId, userId, { message })\n *\n * Inbound events + data queries (mount at POST /api/plexo/events):\n * export const POST = (req: Request) =>\n * plexo.inbound({ onEvent, onDataQuery }).handle(req)\n */\n\nexport { PlexoClient } from './client.js'\nexport {\n PlexoApiError,\n PlexoAuthError,\n PlexoNotConfiguredError,\n PlexoRateLimitedError,\n PlexoUnreachableError,\n} from './errors.js'\nexport { verifyInboundSignature } from './inbound.js'\nexport type {\n AiCompleteOptions,\n AiMessage,\n AppChannelConfig,\n AppConnectorConfig,\n AppExtension,\n AppProfile,\n AppToolConfig,\n ChatOptions,\n ChatReply,\n DataQuery,\n DataResponse,\n DispatchContext,\n DispatchOptions,\n DispatchResult,\n GmessagesLastMessage,\n GmessagesListThreadsOptions,\n GmessagesSendOptions,\n GmessagesSendResult,\n GmessagesThread,\n GmessagesThreadParticipant,\n InboundEvent,\n InboundEventType,\n InboundHandlers,\n InboundVerifyResult,\n InstallConnectionOptions,\n MemoryEntry,\n MemorySearchResult,\n OcrResult,\n PlexoClientOptions,\n PlexoConnection,\n PlexoConversation,\n PlexoTask,\n PlexoToken,\n PlexoTokenWithConnection,\n PublishEventOptions,\n ResilienceOptions,\n StoreMemoryOptions,\n TestConnectionResult,\n} from './types.js'\n\nimport { PlexoClient } from './client.js'\nimport type { PlexoClientOptions } from './types.js'\n\n/**\n * Create a configured Plexo client.\n * Call `await client.register()` once at app boot to announce this app to\n * Plexo Core and make its extensions discoverable.\n */\nexport function createPlexoClient(opts: PlexoClientOptions): PlexoClient {\n return new PlexoClient(opts)\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/connect/errors.ts","../../src/connect/inbound.ts","../../src/connect/registration.ts","../../src/connect/client.ts","../../src/connect/index.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-only\n// Copyright (C) 2026 Joeybuilt LLC\n\nexport class PlexoNotConfiguredError extends Error {\n override readonly name = 'PlexoNotConfiguredError'\n constructor(missing: string) {\n super(`PlexoClient not configured: ${missing} is required`)\n }\n}\n\nexport class PlexoUnreachableError extends Error {\n override readonly name = 'PlexoUnreachableError'\n readonly url: string\n constructor(url: string, cause?: unknown) {\n super(`Plexo Core unreachable at ${url}`)\n this.url = url\n if (cause) this.cause = cause\n }\n}\n\nexport class PlexoApiError extends Error {\n override readonly name: string = 'PlexoApiError'\n readonly status: number\n readonly path: string\n constructor(status: number, path: string, detail?: string) {\n super(`Plexo API error ${status} on ${path}${detail ? `: ${detail}` : ''}`)\n this.status = status\n this.path = path\n }\n}\n\nexport class PlexoAuthError extends PlexoApiError {\n readonly name = 'PlexoAuthError' as const\n constructor(path: string) {\n super(401, path, 'invalid or missing service key')\n }\n}\n\nexport class PlexoRateLimitedError extends PlexoApiError {\n readonly name = 'PlexoRateLimitedError' as const\n readonly retryAfterMs: number\n constructor(path: string, retryAfterSec?: number) {\n super(429, path, 'rate limited')\n this.retryAfterMs = (retryAfterSec ?? 60) * 1000\n }\n}\n","// SPDX-License-Identifier: AGPL-3.0-only\n// Copyright (C) 2026 Joeybuilt LLC\n\n/**\n * Inbound request handling — verifies and dispatches requests Plexo Core\n * sends back to the app (events push + data/tool queries).\n *\n * Framework-agnostic: `handleInbound` receives a plain Request and returns\n * a plain Response so it can be wrapped in any framework's route handler.\n *\n * Next.js:\n * export const POST = (req: Request) => plexo.inbound.handle(req)\n *\n * Express:\n * app.post('/api/plexo/events', async (req, res) => {\n * const raw = JSON.stringify(req.body)\n * const r = new Request(req.url, { method:'POST', body: raw, headers: req.headers })\n * const resp = await plexo.inbound.handle(r)\n * res.status(resp.status).json(await resp.json())\n * })\n */\n\nimport { createHmac, timingSafeEqual } from 'node:crypto'\nimport type {\n DataQuery,\n DataResponse,\n InboundEvent,\n InboundHandlers,\n InboundVerifyResult,\n} from './types.js'\n\nconst SKEW_MS = 5 * 60 * 1000\n\nexport function verifyInboundSignature(\n body: string,\n signature: string | null,\n timestamp: string | null,\n serviceKey: string,\n): InboundVerifyResult {\n if (!signature || !timestamp) {\n return { ok: false, error: 'missing X-Plexo-Signature or X-Plexo-Timestamp' }\n }\n const ts = Date.parse(timestamp)\n if (Number.isNaN(ts) || Math.abs(Date.now() - ts) > SKEW_MS) {\n return { ok: false, error: 'timestamp skew exceeds 5 minutes' }\n }\n const expected = 'sha256=' + createHmac('sha256', serviceKey).update(body).digest('hex')\n const sigBuf = Buffer.from(signature)\n const expBuf = Buffer.from(expected)\n if (sigBuf.length !== expBuf.length || !timingSafeEqual(sigBuf, expBuf)) {\n return { ok: false, error: 'signature mismatch' }\n }\n return { ok: true }\n}\n\nexport function createInboundRouter(serviceKey: string, handlers: InboundHandlers) {\n return {\n async handle(req: Request): Promise<Response> {\n const body = await req.text()\n const sig = req.headers.get('x-plexo-signature')\n const ts = req.headers.get('x-plexo-timestamp')\n\n const check = verifyInboundSignature(body, sig, ts, serviceKey)\n if (!check.ok) {\n return new Response(\n JSON.stringify({ error: check.error }),\n { status: 401, headers: { 'Content-Type': 'application/json' } },\n )\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(body)\n } catch {\n return new Response(\n JSON.stringify({ error: 'invalid JSON' }),\n { status: 400, headers: { 'Content-Type': 'application/json' } },\n )\n }\n\n const msg = parsed as Record<string, unknown>\n const kind = msg['kind'] as string | undefined\n\n try {\n if (kind === 'event' && handlers.onEvent) {\n await handlers.onEvent(msg['event'] as InboundEvent)\n return new Response(null, { status: 204 })\n }\n\n if (kind === 'data_query' && handlers.onDataQuery) {\n const query = msg['query'] as DataQuery\n const result = await handlers.onDataQuery(query)\n const response: DataResponse = { requestId: query.requestId, result }\n return new Response(\n JSON.stringify(response),\n { status: 200, headers: { 'Content-Type': 'application/json' } },\n )\n }\n\n return new Response(null, { status: 204 })\n } catch (err) {\n const response: DataResponse = {\n requestId: (msg['query'] as DataQuery | undefined)?.requestId ?? '',\n error: err instanceof Error ? err.message : String(err),\n }\n return new Response(\n JSON.stringify(response),\n { status: 500, headers: { 'Content-Type': 'application/json' } },\n )\n }\n },\n }\n}\n","// SPDX-License-Identifier: AGPL-3.0-only\n// Copyright (C) 2026 Joeybuilt LLC\n\nimport type { AppProfile, PlexoClientOptions } from './types.js'\n\nfunction buildHeaders(opts: PlexoClientOptions): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${opts.serviceKey}`,\n 'X-App-Id': opts.appId,\n }\n}\n\nasync function post(\n opts: PlexoClientOptions,\n path: string,\n body: unknown,\n): Promise<boolean> {\n const fetchImpl = opts.fetchImpl ?? fetch\n try {\n const res = await fetchImpl(\n `${opts.plexoUrl.replace(/\\/$/, '')}${path}`,\n {\n method: 'POST',\n headers: buildHeaders(opts),\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(8_000),\n },\n )\n if (!res.ok) {\n const detail = await res.text().catch(() => `HTTP ${res.status}`)\n console.error(`[plexo/connect] registration rejected (${res.status}): ${detail}`)\n return false\n }\n return true\n } catch (err) {\n console.warn(`[plexo/connect] registration attempt failed: ${(err as Error).message}`)\n return false\n }\n}\n\nexport async function register(\n opts: PlexoClientOptions,\n profile: AppProfile,\n): Promise<void> {\n const backoff = opts.resilience?.registrationBackoffMs ?? [5_000, 15_000, 45_000]\n\n if (await post(opts, '/api/v1/profiles/register', profile)) {\n console.info(`[plexo/connect] registered appId=${opts.appId}`)\n return\n }\n\n for (const delay of backoff) {\n console.info(`[plexo/connect] retrying registration in ${delay / 1000}s`)\n await new Promise<void>((r) => setTimeout(r, delay))\n if (await post(opts, '/api/v1/profiles/register', profile)) {\n console.info(`[plexo/connect] registered appId=${opts.appId}`)\n return\n }\n }\n\n console.warn('[plexo/connect] registration retries exhausted — running in standalone mode')\n}\n","// SPDX-License-Identifier: AGPL-3.0-only\n// Copyright (C) 2026 Joeybuilt LLC\n\nimport { PlexoApiError, PlexoAuthError, PlexoRateLimitedError, PlexoUnreachableError } from './errors.js'\nimport { createInboundRouter } from './inbound.js'\nimport { register } from './registration.js'\nimport type {\n AddEpisodeOptions,\n AddEpisodeResult,\n AiCompleteOptions,\n AppProfile,\n ChatOptions,\n ChatReply,\n DispatchContext,\n DispatchOptions,\n DispatchResult,\n FactSearchResult,\n GmessagesListThreadsOptions,\n GmessagesSendOptions,\n GmessagesSendResult,\n GmessagesThread,\n InboundHandlers,\n InstallConnectionOptions,\n MemorySearchResult,\n OcrResult,\n PlexoClientOptions,\n PlexoConnection,\n PlexoConversation,\n PlexoTask,\n PlexoToken,\n PlexoTokenWithConnection,\n PublishEventOptions,\n RunCustomOptions,\n RunCustomResult,\n StoreMemoryOptions,\n TestConnectionResult,\n} from './types.js'\n\nexport class PlexoClient {\n readonly #opts: PlexoClientOptions\n readonly #base: string\n\n /** Agent-loop operations. EP1: caller-provided systemPrompt + tools via HTTP callback. */\n readonly agents: {\n runCustom: (workspaceId: string, opts: RunCustomOptions) => Promise<RunCustomResult>\n }\n\n constructor(opts: PlexoClientOptions) {\n this.#opts = opts\n this.#base = opts.plexoUrl.replace(/\\/$/, '')\n this.agents = {\n runCustom: (workspaceId, runOpts) =>\n this.#post<RunCustomResult>(\n '/api/v1/agents/run-custom',\n { workspaceId, ...runOpts },\n {},\n 120_000,\n ),\n }\n }\n\n get appId(): string { return this.#opts.appId }\n\n get isConfigured(): boolean {\n return !!(this.#opts.plexoUrl && this.#opts.serviceKey)\n }\n\n // -----------------------------------------------------------------------\n // Registration\n // -----------------------------------------------------------------------\n\n async register(): Promise<void> {\n if (!this.isConfigured) return\n\n let domain = ''\n try { domain = new URL(this.#opts.plexoUrl).host } catch {}\n\n const profile: AppProfile = {\n appId: this.#opts.appId,\n schemaNamespace: this.#opts.schemaNamespace ?? this.#opts.appId,\n displayName: this.#opts.displayName ?? this.#opts.appId,\n domain,\n extensions: this.#opts.extensions ?? [],\n eventContracts: this.#opts.eventContracts ?? [],\n }\n await register(this.#opts, profile)\n }\n\n // -----------------------------------------------------------------------\n // Workspace\n // -----------------------------------------------------------------------\n\n async ensureWorkspace(userId: string, email?: string): Promise<string> {\n const data = await this.#post<{ workspaceId: string }>(\n '/api/v1/auth/workspace/ensure',\n { userId, email, name: this.#opts.displayName ?? this.#opts.appId },\n { userId },\n )\n return data.workspaceId\n }\n\n /**\n * Idempotent get-or-create + auto-install of this app's connection.\n * Used when an app needs Plexo to know about a user without going\n * through the workspace-ensure → install dance separately.\n */\n async autoAttachUser(userId: string, email?: string): Promise<{ workspaceId: string }> {\n return await this.#post<{ workspaceId: string }>(\n '/api/v1/auth/profiles/auto-attach-user',\n { userId, email, name: this.#opts.displayName ?? this.#opts.appId },\n { userId },\n )\n }\n\n // -----------------------------------------------------------------------\n // Connections\n // -----------------------------------------------------------------------\n\n async getInstalledConnections(workspaceId: string): Promise<PlexoConnection[]> {\n try {\n const data = await this.#get<{ items?: PlexoConnection[] }>(\n `/api/v1/connections/installed?workspaceId=${encodeURIComponent(workspaceId)}`,\n )\n return data.items ?? []\n } catch {\n return []\n }\n }\n\n async getToken(workspaceId: string, registryId: string): Promise<PlexoToken | null> {\n try {\n return await this.#get<PlexoToken>(\n `/api/v1/connections/token?workspaceId=${encodeURIComponent(workspaceId)}®istryId=${encodeURIComponent(registryId)}`,\n )\n } catch (err) {\n if (err instanceof PlexoApiError && err.status === 404) return null\n throw err\n }\n }\n\n async disconnect(workspaceId: string, connectionId: string): Promise<void> {\n await this.#delete(\n `/api/v1/connections/installed/${encodeURIComponent(connectionId)}?workspaceId=${encodeURIComponent(workspaceId)}`,\n )\n }\n\n /**\n * Idempotent install. Returns true if already present or newly installed,\n * false on error. Empty `credentials` are valid for app-owned bridges.\n */\n async installConnection(opts: InstallConnectionOptions): Promise<boolean> {\n try {\n const existing = await this.getInstalledConnections(opts.workspaceId)\n if (existing.some((c) => c.registryId === opts.registryId)) return true\n await this.#post('/api/v1/connections/install', opts)\n return true\n } catch {\n return false\n }\n }\n\n /**\n * Multi-token fetch for a registry (e.g. multi-account Google Workspace).\n * Falls back to per-connection getToken() if the bulk endpoint 404s.\n */\n async getTokens(workspaceId: string, registryId: string): Promise<PlexoTokenWithConnection[]> {\n try {\n const data = await this.#get<PlexoTokenWithConnection[]>(\n `/api/v1/connections/tokens?workspaceId=${encodeURIComponent(workspaceId)}®istryId=${encodeURIComponent(registryId)}`,\n )\n return data\n } catch (err) {\n if (!(err instanceof PlexoApiError) || err.status !== 404) throw err\n }\n const conns = await this.getInstalledConnections(workspaceId)\n const matches = conns.filter((c) => c.registryId === registryId && c.status === 'active')\n const out: PlexoTokenWithConnection[] = []\n for (const c of matches) {\n const t = await this.getToken(workspaceId, c.registryId)\n if (t?.access_token) out.push({ ...t, connectionId: c.id })\n }\n return out\n }\n\n async getTasks(\n workspaceId: string,\n opts: { status?: string; limit?: number } = {},\n ): Promise<PlexoTask[]> {\n const params = new URLSearchParams({\n workspaceId,\n limit: String(opts.limit ?? 10),\n })\n if (opts.status) params.set('status', opts.status)\n try {\n const data = await this.#get<{ items?: PlexoTask[] }>(`/api/v1/tasks?${params}`)\n return data.items ?? []\n } catch {\n return []\n }\n }\n\n oauthPopupUrl(provider: string, workspaceId: string): string {\n return `${this.#base}/api/oauth/${encodeURIComponent(provider)}/start?workspaceId=${encodeURIComponent(workspaceId)}`\n }\n\n // -----------------------------------------------------------------------\n // AI\n // -----------------------------------------------------------------------\n\n async aiComplete(workspaceId: string, opts: AiCompleteOptions): Promise<string> {\n const data = await this.#post<{ text?: string }>(\n '/api/v1/ai/complete',\n { workspaceId, ...opts },\n {},\n 30_000,\n )\n return data.text ?? ''\n }\n\n async chatMessage(\n workspaceId: string,\n userId: string,\n opts: ChatOptions,\n ): Promise<ChatReply> {\n const data = await this.#post<Partial<ChatReply>>(\n '/api/v1/chat/message',\n { workspaceId, forceConversation: true, ...opts },\n { userId },\n 30_000,\n )\n return {\n reply: data.reply ?? '',\n conversationId: data.conversationId,\n sessionId: data.sessionId,\n taskId: data.taskId,\n }\n }\n\n // -----------------------------------------------------------------------\n // Channel dispatch\n // -----------------------------------------------------------------------\n\n async dispatch(\n opts: DispatchOptions,\n ctx: DispatchContext = {},\n ): Promise<DispatchResult> {\n const data = await this.#post<DispatchResult>(\n '/api/v1/channel/dispatch',\n opts,\n ctx,\n )\n return { messageId: data.messageId, deliveryStatus: data.deliveryStatus }\n }\n\n // -----------------------------------------------------------------------\n // Events (node-events stream)\n // -----------------------------------------------------------------------\n\n /** Best-effort emit. Swallows errors — events are non-critical by design. */\n async publishEvent(opts: PublishEventOptions): Promise<{ eventId?: string } | null> {\n try {\n return await this.#post<{ eventId?: string }>('/api/v1/events', opts)\n } catch {\n return null\n }\n }\n\n // -----------------------------------------------------------------------\n // Conversations\n // -----------------------------------------------------------------------\n\n async getConversations(\n workspaceId: string,\n limit = 20,\n ): Promise<PlexoConversation[]> {\n try {\n const data = await this.#get<{ conversations?: PlexoConversation[] }>(\n `/api/v1/conversations?workspaceId=${encodeURIComponent(workspaceId)}&limit=${limit}`,\n )\n return data.conversations ?? []\n } catch {\n return []\n }\n }\n\n // -----------------------------------------------------------------------\n // Memory\n // -----------------------------------------------------------------------\n\n /** Best-effort store. Returns null on any failure. */\n async storeMemory(\n workspaceId: string,\n opts: StoreMemoryOptions,\n ): Promise<{ id: string } | null> {\n try {\n return await this.#post<{ id: string }>(\n '/api/v1/memory/entries',\n { workspaceId, ...opts },\n )\n } catch {\n return null\n }\n }\n\n /** Returns [] on any failure. */\n async searchMemory(\n workspaceId: string,\n query: string,\n limit = 20,\n ): Promise<MemorySearchResult[]> {\n try {\n const params = new URLSearchParams({\n workspaceId,\n q: query,\n limit: String(limit),\n })\n const data = await this.#get<{ results?: MemorySearchResult[] }>(\n `/api/v1/memory/search?${params}`,\n )\n return data.results ?? []\n } catch {\n return []\n }\n }\n\n // -----------------------------------------------------------------------\n // Graph (SDK 1.1.0 — Plexo Graphiti integration)\n // -----------------------------------------------------------------------\n //\n // Two methods land in 1.1.0; `searchNodes` and `getCommunity` are\n // deferred to 1.2.0. graphiti-core 0.29 has no `search_nodes` method\n // and no community accessor (only `build_communities` as a builder),\n // so cleanly typed wrappers wait until upstream surface lands or the\n // sidecar grows custom Cypher/Kuzu queries.\n\n /** Add a knowledge-graph episode. Returns null on any failure. */\n async addEpisode(\n workspaceId: string,\n opts: AddEpisodeOptions,\n ): Promise<AddEpisodeResult | null> {\n try {\n return await this.#post<AddEpisodeResult>('/api/v1/graph/episodes', { workspaceId, ...opts })\n } catch {\n return null\n }\n }\n\n /** Hybrid-search facts in the workspace's knowledge graph. Returns [] on any failure. */\n async searchFacts(\n workspaceId: string,\n query: string,\n limit = 10,\n ): Promise<FactSearchResult[]> {\n try {\n const params = new URLSearchParams({ workspaceId, q: query, limit: String(limit) })\n const data = await this.#get<{ results?: Array<{ uuid: string | null; fact: string | null; source_node_uuid: string | null; target_node_uuid: string | null; valid_at: string | null; invalid_at: string | null; created_at: string | null }> }>(\n `/api/v1/graph/facts/search?${params}`,\n )\n return (data.results ?? []).map((edge) => ({\n uuid: edge.uuid,\n fact: edge.fact,\n sourceNodeUuid: edge.source_node_uuid,\n targetNodeUuid: edge.target_node_uuid,\n validAt: edge.valid_at,\n invalidAt: edge.invalid_at,\n createdAt: edge.created_at,\n }))\n } catch {\n return []\n }\n }\n\n // -----------------------------------------------------------------------\n // Tools (SDK 1.2.0 — synchronous tool-invoke surface)\n // -----------------------------------------------------------------------\n //\n // Surface: `client.tools.gmessages.send({...})` and\n // `client.tools.gmessages.listThreads({...})`. Distinct from\n // `dispatch()` (which is the async channel-delivery primitive) — these\n // calls block until the sidecar has accepted (or rejected) the request\n // and surface dispatch errors directly so Levio's chat loop can react.\n //\n // Errors are NOT swallowed here (unlike addEpisode/searchFacts): a Levio\n // user pressing \"send\" expects to see the failure. Network errors bubble\n // as PlexoUnreachableError; HTTP-level errors bubble as PlexoApiError\n // with the structured `error.code` from the route in the message body.\n\n get tools(): {\n gmessages: {\n send: (opts: GmessagesSendOptions) => Promise<GmessagesSendResult>\n listThreads: (opts: GmessagesListThreadsOptions) => Promise<GmessagesThread[]>\n }\n } {\n return {\n gmessages: {\n send: (opts) => this.#gmessagesSend(opts),\n listThreads: (opts) => this.#gmessagesListThreads(opts),\n },\n }\n }\n\n async #gmessagesSend(opts: GmessagesSendOptions): Promise<GmessagesSendResult> {\n const body: Record<string, unknown> = {\n workspaceId: opts.workspaceId,\n text: opts.text,\n }\n if (opts.threadId) body.threadId = opts.threadId\n if (opts.phoneE164) body.phoneE164 = opts.phoneE164\n const data = await this.#post<{ messageId?: string; deliveryStatus?: string }>(\n '/api/v1/tools/gmessages/send',\n body,\n { workspaceId: opts.workspaceId },\n )\n return {\n messageId: data.messageId ?? '',\n deliveryStatus: data.deliveryStatus ?? 'accepted',\n }\n }\n\n async #gmessagesListThreads(opts: GmessagesListThreadsOptions): Promise<GmessagesThread[]> {\n const params = new URLSearchParams({ workspaceId: opts.workspaceId })\n if (opts.phoneE164) params.set('phoneE164', opts.phoneE164)\n if (typeof opts.limit === 'number') params.set('limit', String(opts.limit))\n const data = await this.#get<{ threads?: GmessagesThread[] }>(\n `/api/v1/tools/gmessages/threads?${params}`,\n )\n return data.threads ?? []\n }\n\n // -----------------------------------------------------------------------\n // Vision\n // -----------------------------------------------------------------------\n\n /**\n * OCR an image via Plexo's vision endpoint. The image must be reachable\n * from Plexo (a signed CDN URL works). Returns null on any failure so\n * callers can mark the asset as `failed` and retry later.\n */\n async visionOcr(workspaceId: string, imageUrl: string): Promise<OcrResult | null> {\n try {\n const data = await this.#post<Partial<OcrResult>>(\n '/api/v1/vision/ocr',\n { workspaceId, imageUrl },\n {},\n 60_000,\n )\n return {\n text: data.text ?? '',\n confidence: typeof data.confidence === 'number' ? data.confidence : 0,\n model: data.model ?? 'unknown',\n }\n } catch {\n return null\n }\n }\n\n // -----------------------------------------------------------------------\n // Inbound\n // -----------------------------------------------------------------------\n\n /** Returns a framework-agnostic handler for Plexo → app push requests. */\n inbound(handlers: InboundHandlers) {\n return createInboundRouter(this.#opts.serviceKey, handlers)\n }\n\n // -----------------------------------------------------------------------\n // Utilities\n // -----------------------------------------------------------------------\n\n async testConnection(): Promise<TestConnectionResult> {\n const start = Date.now()\n try {\n const fetchImpl = this.#opts.fetchImpl ?? fetch\n const res = await fetchImpl(`${this.#base}/api/health`, {\n signal: AbortSignal.timeout(5_000),\n })\n const latencyMs = Date.now() - start\n if (!res.ok) {\n return { ok: false, latencyMs, error: `HTTP ${res.status}` }\n }\n const body = await res.json().catch(() => ({})) as Record<string, unknown>\n return { ok: true, latencyMs, plexoVersion: body['version'] as string | undefined }\n } catch (err) {\n return {\n ok: false,\n latencyMs: Date.now() - start,\n error: err instanceof Error ? err.message : String(err),\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // HTTP helpers\n // -----------------------------------------------------------------------\n\n #headers(extra: {\n userId?: string\n tenantId?: string\n workspaceId?: string\n traceId?: string\n } = {}): Record<string, string> {\n const h: Record<string, string> = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.#opts.serviceKey}`,\n 'X-App-Id': this.#opts.appId,\n 'X-Service-Key-Version': this.#opts.serviceKeyVersion ?? 'v1',\n }\n if (extra.userId) h['X-User-Id'] = extra.userId\n if (extra.tenantId) h['X-Tenant-Id'] = extra.tenantId\n if (extra.workspaceId) h['X-Workspace-Id'] = extra.workspaceId\n if (extra.traceId) h['X-Trace-Id'] = extra.traceId\n return h\n }\n\n async #get<T>(path: string): Promise<T> {\n const fetchImpl = this.#opts.fetchImpl ?? fetch\n const timeout = this.#opts.resilience?.timeoutMs ?? 15_000\n let res: Response\n try {\n res = await fetchImpl(`${this.#base}${path}`, {\n headers: this.#headers(),\n signal: AbortSignal.timeout(timeout),\n })\n } catch (err) {\n throw new PlexoUnreachableError(this.#base, err)\n }\n return this.#parse<T>(res, path)\n }\n\n async #post<T>(\n path: string,\n body: unknown,\n extra: {\n userId?: string\n tenantId?: string\n workspaceId?: string\n traceId?: string\n } = {},\n timeoutMs?: number,\n ): Promise<T> {\n const fetchImpl = this.#opts.fetchImpl ?? fetch\n const timeout = timeoutMs ?? this.#opts.resilience?.timeoutMs ?? 15_000\n let res: Response\n try {\n res = await fetchImpl(`${this.#base}${path}`, {\n method: 'POST',\n headers: this.#headers(extra),\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(timeout),\n })\n } catch (err) {\n throw new PlexoUnreachableError(this.#base, err)\n }\n return this.#parse<T>(res, path)\n }\n\n async #delete(path: string): Promise<void> {\n const fetchImpl = this.#opts.fetchImpl ?? fetch\n const timeout = this.#opts.resilience?.timeoutMs ?? 15_000\n let res: Response\n try {\n res = await fetchImpl(`${this.#base}${path}`, {\n method: 'DELETE',\n headers: this.#headers(),\n signal: AbortSignal.timeout(timeout),\n })\n } catch (err) {\n throw new PlexoUnreachableError(this.#base, err)\n }\n if (!res.ok && res.status !== 404) {\n await this.#parse<void>(res, path)\n }\n }\n\n async #parse<T>(res: Response, path: string): Promise<T> {\n if (res.status === 401) throw new PlexoAuthError(path)\n if (res.status === 429) {\n const retry = Number(res.headers.get('retry-after'))\n throw new PlexoRateLimitedError(path, Number.isNaN(retry) ? undefined : retry)\n }\n if (!res.ok) {\n const detail = await res.text().catch(() => '')\n throw new PlexoApiError(res.status, path, detail.slice(0, 300))\n }\n if (res.status === 204) return undefined as T\n return res.json() as Promise<T>\n }\n}\n","// SPDX-License-Identifier: AGPL-3.0-only\n// Copyright (C) 2026 Joeybuilt LLC\n\n/**\n * @joeybuilt/plexo-sdk — connect module\n *\n * Universal client for connecting any app to Plexo Core.\n *\n * Quick start:\n *\n * import { createPlexoClient } from '@joeybuilt/plexo-sdk/connect'\n *\n * export const plexo = createPlexoClient({\n * appId: process.env.APP_ID ?? 'my-app',\n * plexoUrl: process.env.PLEXO_URL ?? '',\n * serviceKey: process.env.SERVICE_KEY ?? '',\n * extensions: [\n * { id: 'my-app.todos.list', type: 'tool', name: 'List Todos',\n * config: { description: 'List todos for the current user' } },\n * ],\n * eventContracts: ['my-app.todo.created'],\n * })\n *\n * Register at boot (Next.js instrumentation.ts, Express server start, etc.):\n * await plexo.register()\n *\n * Per-user workspace (call once on first login):\n * const workspaceId = await plexo.ensureWorkspace(userId, email)\n *\n * AI / chat:\n * const text = await plexo.aiComplete(workspaceId, { messages: [...] })\n * const reply = await plexo.chatMessage(workspaceId, userId, { message })\n *\n * Inbound events + data queries (mount at POST /api/plexo/events):\n * export const POST = (req: Request) =>\n * plexo.inbound({ onEvent, onDataQuery }).handle(req)\n */\n\nexport { PlexoClient } from './client.js'\nexport {\n PlexoApiError,\n PlexoAuthError,\n PlexoNotConfiguredError,\n PlexoRateLimitedError,\n PlexoUnreachableError,\n} from './errors.js'\nexport { verifyInboundSignature } from './inbound.js'\nexport type {\n AiCompleteOptions,\n AiMessage,\n AppChannelConfig,\n AppConnectorConfig,\n AppExtension,\n AppProfile,\n AppToolConfig,\n ChatOptions,\n ChatReply,\n DataQuery,\n DataResponse,\n DispatchContext,\n DispatchOptions,\n DispatchResult,\n GmessagesLastMessage,\n GmessagesListThreadsOptions,\n GmessagesSendOptions,\n GmessagesSendResult,\n GmessagesThread,\n GmessagesThreadParticipant,\n InboundEvent,\n InboundEventType,\n InboundHandlers,\n InboundVerifyResult,\n InstallConnectionOptions,\n MemoryEntry,\n MemorySearchResult,\n OcrResult,\n PlexoClientOptions,\n PlexoConnection,\n PlexoConversation,\n PlexoTask,\n PlexoToken,\n PlexoTokenWithConnection,\n PublishEventOptions,\n ResilienceOptions,\n RunCustomOptions,\n RunCustomResult,\n RunCustomStep,\n RunCustomTool,\n StoreMemoryOptions,\n TestConnectionResult,\n} from './types.js'\n\nimport { PlexoClient } from './client.js'\nimport type { PlexoClientOptions } from './types.js'\n\n/**\n * Create a configured Plexo client.\n * Call `await client.register()` once at app boot to announce this app to\n * Plexo Core and make its extensions discoverable.\n */\nexport function createPlexoClient(opts: PlexoClientOptions): PlexoClient {\n return new PlexoClient(opts)\n}\n"],"mappings":"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","names":[]}
|