veryfront 0.1.217 → 0.1.219

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.
Files changed (66) hide show
  1. package/esm/cli/commands/init/config-generator.d.ts +13 -1
  2. package/esm/cli/commands/init/config-generator.d.ts.map +1 -1
  3. package/esm/cli/commands/init/config-generator.js +15 -1
  4. package/esm/cli/commands/init/init-command.d.ts.map +1 -1
  5. package/esm/cli/commands/init/init-command.js +9 -2
  6. package/esm/deno.d.ts +1 -3
  7. package/esm/deno.js +6 -5
  8. package/esm/src/agent/ag-ui-detached-start.d.ts +84 -0
  9. package/esm/src/agent/ag-ui-detached-start.d.ts.map +1 -0
  10. package/esm/src/agent/ag-ui-detached-start.js +273 -0
  11. package/esm/src/agent/index.d.ts +1 -0
  12. package/esm/src/agent/index.d.ts.map +1 -1
  13. package/esm/src/agent/index.js +1 -0
  14. package/esm/src/extensions/interfaces/auth-provider.d.ts +30 -3
  15. package/esm/src/extensions/interfaces/auth-provider.d.ts.map +1 -1
  16. package/esm/src/extensions/interfaces/index.d.ts +2 -1
  17. package/esm/src/extensions/interfaces/index.d.ts.map +1 -1
  18. package/esm/src/extensions/interfaces/token-cache-store.d.ts +56 -0
  19. package/esm/src/extensions/interfaces/token-cache-store.d.ts.map +1 -0
  20. package/esm/src/extensions/interfaces/token-cache-store.js +12 -0
  21. package/esm/src/extensions/recommendations.d.ts.map +1 -1
  22. package/esm/src/extensions/recommendations.js +1 -0
  23. package/esm/src/integrations/_data.js +1 -1
  24. package/esm/src/integrations/schema.d.ts +1 -0
  25. package/esm/src/integrations/schema.d.ts.map +1 -1
  26. package/esm/src/integrations/schema.js +8 -0
  27. package/esm/src/proxy/cache/index.d.ts +1 -1
  28. package/esm/src/proxy/cache/index.d.ts.map +1 -1
  29. package/esm/src/proxy/cache/index.js +25 -15
  30. package/esm/src/proxy/cache/tracing-cache.d.ts +31 -0
  31. package/esm/src/proxy/cache/tracing-cache.d.ts.map +1 -0
  32. package/esm/src/proxy/cache/tracing-cache.js +44 -0
  33. package/esm/src/proxy/cache/types.d.ts +1 -1
  34. package/esm/src/proxy/cache/types.js +1 -1
  35. package/esm/src/proxy/handler.d.ts +7 -0
  36. package/esm/src/proxy/handler.d.ts.map +1 -1
  37. package/esm/src/proxy/handler.js +50 -29
  38. package/esm/src/server/runtime-handler/request-tracker.d.ts.map +1 -1
  39. package/esm/src/server/runtime-handler/request-tracker.js +5 -6
  40. package/esm/src/server/runtime-handler/request-utils.d.ts.map +1 -1
  41. package/esm/src/server/runtime-handler/request-utils.js +1 -0
  42. package/esm/src/utils/version-constant.d.ts +1 -1
  43. package/esm/src/utils/version-constant.js +1 -1
  44. package/package.json +66 -35
  45. package/src/cli/commands/init/config-generator.ts +33 -0
  46. package/src/cli/commands/init/init-command.ts +9 -2
  47. package/src/deno.js +6 -5
  48. package/src/src/agent/ag-ui-detached-start.ts +397 -0
  49. package/src/src/agent/index.ts +8 -0
  50. package/src/src/extensions/interfaces/auth-provider.ts +35 -3
  51. package/src/src/extensions/interfaces/index.ts +10 -1
  52. package/src/src/extensions/interfaces/token-cache-store.ts +58 -0
  53. package/src/src/extensions/recommendations.ts +1 -0
  54. package/src/src/integrations/_data.ts +1 -1
  55. package/src/src/integrations/schema.ts +8 -0
  56. package/src/src/proxy/cache/index.ts +27 -15
  57. package/src/src/proxy/cache/tracing-cache.ts +77 -0
  58. package/src/src/proxy/cache/types.ts +1 -1
  59. package/src/src/proxy/handler.ts +57 -31
  60. package/src/src/server/runtime-handler/request-tracker.ts +5 -7
  61. package/src/src/server/runtime-handler/request-utils.ts +1 -0
  62. package/src/src/utils/version-constant.ts +1 -1
  63. package/esm/src/proxy/cache/redis-cache.d.ts +0 -25
  64. package/esm/src/proxy/cache/redis-cache.d.ts.map +0 -1
  65. package/esm/src/proxy/cache/redis-cache.js +0 -219
  66. package/src/src/proxy/cache/redis-cache.ts +0 -255
@@ -0,0 +1,397 @@
1
+ import { z } from "zod";
2
+ import { INVALID_ARGUMENT } from "../errors/index.js";
3
+ import { SKILL_TOOL_IDS } from "../skill/types.js";
4
+ import { type Tool, toolRegistry } from "../tool/index.js";
5
+ import { streamDataStreamEvents } from "./data-stream.js";
6
+ import { type AgUiInjectedTool, type AgUiRequest, AgUiRequestSchema } from "./ag-ui-handler.js";
7
+ import {
8
+ AgentRuntime,
9
+ RunAlreadyExistsError,
10
+ type RunResumeSessionManager,
11
+ } from "./runtime/index.js";
12
+ import type { Agent, Message } from "./types.js";
13
+
14
+ const AGENT_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
15
+ const AG_UI_DETACHED_RUN_ID_SCHEMA = z.string().min(1).max(128).regex(AGENT_ID_PATTERN);
16
+ const MAX_TEXT_PART_LENGTH = 10_000;
17
+
18
+ type AgUiResumeValue = {
19
+ result: unknown;
20
+ isError: boolean;
21
+ };
22
+
23
+ type AgUiContextValue =
24
+ | Record<string, unknown>
25
+ | ((request: Request) => Record<string, unknown> | Promise<Record<string, unknown>>);
26
+
27
+ type AgUiRuntimePart = Record<string, unknown> & { type: string };
28
+
29
+ function isRecord(value: unknown): value is Record<string, unknown> {
30
+ return typeof value === "object" && value !== null && !Array.isArray(value);
31
+ }
32
+
33
+ function normalizeToolArgs(part: Record<string, unknown>): Record<string, unknown> {
34
+ if (isRecord(part.args)) return part.args;
35
+ if (isRecord(part.input)) return part.input;
36
+ return {};
37
+ }
38
+
39
+ function normalizeMessagePart(part: Record<string, unknown>): Message["parts"][number] | null {
40
+ if (
41
+ part.type === "text" && typeof part.text === "string" &&
42
+ part.text.length <= MAX_TEXT_PART_LENGTH
43
+ ) {
44
+ return { type: "text", text: part.text };
45
+ }
46
+
47
+ if (part.type === "tool_call" && typeof part.id === "string" && typeof part.name === "string") {
48
+ return {
49
+ type: "tool-call",
50
+ toolCallId: part.id,
51
+ toolName: part.name,
52
+ args: normalizeToolArgs(part),
53
+ };
54
+ }
55
+
56
+ if (
57
+ part.type === "tool-call" &&
58
+ typeof part.toolCallId === "string" &&
59
+ typeof part.toolName === "string"
60
+ ) {
61
+ return {
62
+ type: "tool-call",
63
+ toolCallId: part.toolCallId,
64
+ toolName: part.toolName,
65
+ args: normalizeToolArgs(part),
66
+ };
67
+ }
68
+
69
+ if (
70
+ typeof part.type === "string" &&
71
+ part.type.startsWith("tool-") &&
72
+ part.type !== "tool-result" &&
73
+ typeof part.toolCallId === "string" &&
74
+ typeof part.toolName === "string"
75
+ ) {
76
+ return {
77
+ type: part.type,
78
+ toolCallId: part.toolCallId,
79
+ toolName: part.toolName,
80
+ args: normalizeToolArgs(part),
81
+ };
82
+ }
83
+
84
+ if (part.type === "tool_result" && typeof part.tool_call_id === "string") {
85
+ return {
86
+ type: "tool-result",
87
+ toolCallId: part.tool_call_id,
88
+ toolName: typeof part.tool_name === "string" ? part.tool_name : "unknown",
89
+ result: "output" in part ? part.output : undefined,
90
+ };
91
+ }
92
+
93
+ if (part.type === "tool-result" && typeof part.toolCallId === "string") {
94
+ return {
95
+ type: "tool-result",
96
+ toolCallId: part.toolCallId,
97
+ toolName: typeof part.toolName === "string" ? part.toolName : "unknown",
98
+ result: "result" in part ? part.result : undefined,
99
+ };
100
+ }
101
+
102
+ return null;
103
+ }
104
+
105
+ function normalizeMessages(messages: AgUiRequest["messages"]): Message[] {
106
+ return messages.map((message) => ({
107
+ id: message.id,
108
+ role: message.role,
109
+ parts: message.parts
110
+ .map((part) => normalizeMessagePart(part))
111
+ .filter((part): part is Message["parts"][number] => part !== null),
112
+ ...(message.createdAt ? { timestamp: Date.parse(message.createdAt) || undefined } : {}),
113
+ ...(message.metadata ? { metadata: message.metadata } : {}),
114
+ }));
115
+ }
116
+
117
+ function isRequest(obj: unknown): obj is Request {
118
+ return (
119
+ typeof obj === "object" &&
120
+ obj !== null &&
121
+ "json" in obj &&
122
+ typeof obj.json === "function" &&
123
+ "url" in obj &&
124
+ typeof obj.url === "string" &&
125
+ "method" in obj &&
126
+ typeof obj.method === "string"
127
+ );
128
+ }
129
+
130
+ function extractRequest(requestOrCtx: unknown): Request {
131
+ if (isRequest(requestOrCtx)) return requestOrCtx;
132
+
133
+ if (typeof requestOrCtx === "object" && requestOrCtx !== null && "request" in requestOrCtx) {
134
+ const candidate = (requestOrCtx as Record<string, unknown>).request;
135
+ if (isRequest(candidate)) return candidate;
136
+ }
137
+
138
+ throw INVALID_ARGUMENT.create({
139
+ detail: "Invalid handler argument: expected Request or APIContext",
140
+ });
141
+ }
142
+
143
+ function buildStreamContext(
144
+ request: AgUiDetachedStartRequest,
145
+ baseContext: Record<string, unknown>,
146
+ threadId: string,
147
+ runId: string,
148
+ ): Record<string, unknown> {
149
+ return {
150
+ ...baseContext,
151
+ threadId,
152
+ runId,
153
+ agUi: {
154
+ context: request.context,
155
+ forwardedProps: request.forwardedProps,
156
+ },
157
+ };
158
+ }
159
+
160
+ function createInjectedAgUiTool(
161
+ runId: string,
162
+ tool: AgUiInjectedTool,
163
+ sessionManager: RunResumeSessionManager<AgUiResumeValue>,
164
+ ): Tool {
165
+ return {
166
+ id: tool.name,
167
+ type: "function",
168
+ description: tool.description ?? tool.name,
169
+ inputSchema: z.record(z.string(), z.unknown()),
170
+ inputSchemaJson: (tool.parameters ??
171
+ { type: "object", properties: {}, additionalProperties: true }) as Tool["inputSchemaJson"],
172
+ execute: async (_input, context) => {
173
+ const toolCallId = typeof context?.toolCallId === "string" ? context.toolCallId : null;
174
+ if (!toolCallId) {
175
+ throw new Error(`Missing toolCallId for injected tool "${tool.name}"`);
176
+ }
177
+
178
+ sessionManager.prepareForSignal(runId, toolCallId);
179
+ const submitted = await sessionManager.waitForSignal(runId, toolCallId);
180
+ if (submitted.isError) {
181
+ throw new Error(
182
+ typeof submitted.result === "string"
183
+ ? submitted.result
184
+ : JSON.stringify(submitted.result),
185
+ );
186
+ }
187
+ return submitted.result;
188
+ },
189
+ };
190
+ }
191
+
192
+ function buildMergedTools(
193
+ agent: Agent,
194
+ request: AgUiDetachedStartRequest,
195
+ sessionManager: RunResumeSessionManager<AgUiResumeValue>,
196
+ ): Agent["config"]["tools"] {
197
+ const injectedTools = Object.fromEntries(
198
+ request.tools.map((tool) => [
199
+ tool.name,
200
+ createInjectedAgUiTool(request.runId, tool, sessionManager),
201
+ ]),
202
+ );
203
+
204
+ if (!agent.config.tools) {
205
+ return Object.keys(injectedTools).length > 0 ? injectedTools : undefined;
206
+ }
207
+
208
+ if (agent.config.tools === true) {
209
+ const merged: Record<string, Tool | boolean> = {};
210
+ for (const [toolId] of toolRegistry.getAll()) {
211
+ if (!agent.config.skills && SKILL_TOOL_IDS.has(toolId)) {
212
+ continue;
213
+ }
214
+ merged[toolId] = true;
215
+ }
216
+ return { ...merged, ...injectedTools };
217
+ }
218
+
219
+ return { ...agent.config.tools, ...injectedTools };
220
+ }
221
+
222
+ async function resolveContextValue(
223
+ value: AgUiContextValue | undefined,
224
+ request: Request,
225
+ ): Promise<Record<string, unknown>> {
226
+ if (typeof value === "function") {
227
+ return await value(request);
228
+ }
229
+
230
+ return value ?? {};
231
+ }
232
+
233
+ function scheduleDetachedTask(requestOrCtx: unknown, task: Promise<void>): void {
234
+ if (
235
+ typeof requestOrCtx === "object" &&
236
+ requestOrCtx !== null &&
237
+ "waitUntil" in requestOrCtx &&
238
+ typeof (requestOrCtx as Record<string, unknown>).waitUntil === "function"
239
+ ) {
240
+ ((requestOrCtx as { waitUntil: (promise: Promise<void>) => void }).waitUntil)(task);
241
+ return;
242
+ }
243
+
244
+ void task;
245
+ }
246
+
247
+ async function drainRuntimeStream(
248
+ stream: ReadableStream<Uint8Array>,
249
+ ): Promise<void> {
250
+ for await (const _event of streamDataStreamEvents(stream) as AsyncIterable<AgUiRuntimePart>) {
251
+ continue;
252
+ }
253
+ }
254
+
255
+ export const AgUiDetachedStartRequestSchema = AgUiRequestSchema.extend({
256
+ threadId: z.string().uuid(),
257
+ runId: AG_UI_DETACHED_RUN_ID_SCHEMA,
258
+ });
259
+
260
+ export const AgUiDetachedStartAcceptedSchema = z.object({
261
+ accepted: z.literal(true),
262
+ duplicate: z.boolean(),
263
+ runId: AG_UI_DETACHED_RUN_ID_SCHEMA,
264
+ threadId: z.string().uuid(),
265
+ });
266
+
267
+ export type AgUiDetachedStartRequest = z.infer<typeof AgUiDetachedStartRequestSchema>;
268
+ export type AgUiDetachedStartAccepted = z.infer<typeof AgUiDetachedStartAcceptedSchema>;
269
+
270
+ export interface AgUiDetachedStartHandlerOptions {
271
+ agent: Agent;
272
+ sessionManager: RunResumeSessionManager<AgUiResumeValue>;
273
+ context?: AgUiContextValue;
274
+ onAccepted?: (input: {
275
+ request: AgUiDetachedStartRequest;
276
+ runId: string;
277
+ threadId: string;
278
+ }) => Promise<void> | void;
279
+ onDuplicate?: (input: {
280
+ request: AgUiDetachedStartRequest;
281
+ runId: string;
282
+ threadId: string;
283
+ }) => Promise<void> | void;
284
+ onFinish?: (input: { runId: string; threadId: string }) => Promise<void> | void;
285
+ onError?: (input: { runId: string; threadId: string; error: unknown }) => Promise<void> | void;
286
+ }
287
+
288
+ export function createAgUiDetachedStartHandler(
289
+ options: AgUiDetachedStartHandlerOptions,
290
+ ): (requestOrCtx: unknown) => Promise<Response> {
291
+ return async function POST(requestOrCtx: unknown): Promise<Response> {
292
+ const request = extractRequest(requestOrCtx);
293
+
294
+ try {
295
+ const parsed = AgUiDetachedStartRequestSchema.parse(await request.json());
296
+ const context = await resolveContextValue(options.context, request);
297
+
298
+ try {
299
+ const abortSignal = options.sessionManager.startRun({
300
+ runId: parsed.runId,
301
+ threadId: parsed.threadId,
302
+ });
303
+
304
+ const runtime = new AgentRuntime(options.agent.id, {
305
+ ...options.agent.config,
306
+ tools: buildMergedTools(options.agent, parsed, options.sessionManager),
307
+ });
308
+
309
+ const runtimeStream = await runtime.stream(
310
+ normalizeMessages(parsed.messages),
311
+ buildStreamContext(parsed, context, parsed.threadId, parsed.runId),
312
+ undefined,
313
+ parsed.model,
314
+ parsed.maxOutputTokens,
315
+ abortSignal,
316
+ );
317
+
318
+ await options.onAccepted?.({
319
+ request: parsed,
320
+ runId: parsed.runId,
321
+ threadId: parsed.threadId,
322
+ });
323
+
324
+ const detachedTask = (async () => {
325
+ try {
326
+ await drainRuntimeStream(runtimeStream);
327
+ options.sessionManager.completeRun(parsed.runId);
328
+ await options.onFinish?.({
329
+ runId: parsed.runId,
330
+ threadId: parsed.threadId,
331
+ });
332
+ } catch (error) {
333
+ options.sessionManager.failRun(parsed.runId);
334
+ await options.onError?.({
335
+ runId: parsed.runId,
336
+ threadId: parsed.threadId,
337
+ error,
338
+ });
339
+ }
340
+ })().catch(() => undefined);
341
+
342
+ scheduleDetachedTask(requestOrCtx, detachedTask);
343
+
344
+ return Response.json(
345
+ {
346
+ accepted: true,
347
+ duplicate: false,
348
+ runId: parsed.runId,
349
+ threadId: parsed.threadId,
350
+ } satisfies AgUiDetachedStartAccepted,
351
+ { status: 202 },
352
+ );
353
+ } catch (error) {
354
+ if (error instanceof RunAlreadyExistsError) {
355
+ await options.onDuplicate?.({
356
+ request: parsed,
357
+ runId: parsed.runId,
358
+ threadId: parsed.threadId,
359
+ });
360
+
361
+ return Response.json(
362
+ {
363
+ accepted: true,
364
+ duplicate: true,
365
+ runId: parsed.runId,
366
+ threadId: parsed.threadId,
367
+ } satisfies AgUiDetachedStartAccepted,
368
+ { status: 202 },
369
+ );
370
+ }
371
+
372
+ options.sessionManager.failRun(parsed.runId);
373
+ throw error;
374
+ }
375
+ } catch (error) {
376
+ if (error instanceof z.ZodError) {
377
+ return Response.json(
378
+ {
379
+ error: "Invalid AG-UI detached start request",
380
+ details: error.issues.map((issue) => ({
381
+ path: issue.path,
382
+ message: issue.message,
383
+ })),
384
+ },
385
+ { status: 400 },
386
+ );
387
+ }
388
+
389
+ return Response.json(
390
+ {
391
+ error: error instanceof Error ? error.message : "Internal detached start failed",
392
+ },
393
+ { status: 500 },
394
+ );
395
+ }
396
+ };
397
+ }
@@ -173,6 +173,14 @@ export {
173
173
  getProviderNativeToolNames,
174
174
  type ProviderNativeToolInventoryOptions,
175
175
  } from "./provider-native-tool-inventory.js";
176
+ export {
177
+ type AgUiDetachedStartAccepted,
178
+ AgUiDetachedStartAcceptedSchema,
179
+ type AgUiDetachedStartHandlerOptions,
180
+ type AgUiDetachedStartRequest,
181
+ AgUiDetachedStartRequestSchema,
182
+ createAgUiDetachedStartHandler,
183
+ } from "./ag-ui-detached-start.js";
176
184
  export {
177
185
  type AgUiCancelHandlerOptions,
178
186
  type AgUiResumeHandlerOptions,
@@ -36,17 +36,49 @@ export interface VerifyOptions {
36
36
  [key: string]: unknown;
37
37
  }
38
38
 
39
+ /**
40
+ * The parsed, unverified header of a JWT.
41
+ *
42
+ * Returned by {@link AuthProvider.decode}. `alg` is the signing algorithm
43
+ * advertised by the token (e.g. `"HS256"`, `"RS256"`); additional fields
44
+ * such as `kid` or `typ` may be present.
45
+ */
46
+ export interface TokenHeader {
47
+ /** Signing algorithm advertised by the token header. */
48
+ alg?: string;
49
+ /** Additional header fields. */
50
+ [key: string]: unknown;
51
+ }
52
+
39
53
  /**
40
54
  * AuthProvider contract interface.
41
55
  *
42
56
  * Implementations sign, verify, and decode authentication tokens
43
- * (e.g. JWTs) for request authentication.
57
+ * (e.g. JWTs) for request authentication, and verify third-party tokens
58
+ * against a remote JWKS.
44
59
  */
45
60
  export interface AuthProvider {
46
61
  /** Sign a payload into a token string. */
47
62
  sign(payload: TokenPayload, options?: SignOptions): Promise<string>;
48
63
  /** Verify a token and return its decoded payload. Throws on invalid tokens. */
49
64
  verify(token: string, options?: VerifyOptions): Promise<TokenPayload>;
50
- /** Decode a token without verifying its signature. */
51
- decode(token: string): TokenPayload | undefined;
65
+ /**
66
+ * Verify a token against a remote JSON Web Key Set.
67
+ *
68
+ * Fetches (and caches) the JWKS at `jwksUrl`, then verifies the token's
69
+ * signature and claims. Throws on invalid tokens, unreachable JWKS, or
70
+ * `kid`/algorithm mismatch.
71
+ */
72
+ verifyWithJwks(
73
+ token: string,
74
+ jwksUrl: string,
75
+ options?: VerifyOptions,
76
+ ): Promise<TokenPayload>;
77
+ /**
78
+ * Decode a token's protected header without verifying its signature.
79
+ *
80
+ * Returns `undefined` on malformed input. Useful for inspecting `alg`
81
+ * before choosing a verification strategy.
82
+ */
83
+ decode(token: string): TokenHeader | undefined;
52
84
  }
@@ -23,6 +23,9 @@ export type {
23
23
  // Cache store
24
24
  export type { CacheStore } from "./cache-store.js";
25
25
 
26
+ // Token cache store (proxy-grade cache with scan + stats)
27
+ export type { TokenCacheEntry, TokenCacheStats, TokenCacheStore } from "./token-cache-store.js";
28
+
26
29
  // CSS processor
27
30
  export type { CSSProcessOptions, CSSProcessor, CSSProcessResult } from "./css-processor.js";
28
31
 
@@ -37,7 +40,13 @@ export type {
37
40
  export type { DatabaseClient, QueryResult } from "./database-client.js";
38
41
 
39
42
  // Auth provider
40
- export type { AuthProvider, SignOptions, TokenPayload, VerifyOptions } from "./auth-provider.js";
43
+ export type {
44
+ AuthProvider,
45
+ SignOptions,
46
+ TokenHeader,
47
+ TokenPayload,
48
+ VerifyOptions,
49
+ } from "./auth-provider.js";
41
50
 
42
51
  // Tracing exporter
43
52
  export type { SpanData, TracingExporter } from "./tracing-exporter.js";
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Contract interface for OAuth-token-style cache stores used by the proxy.
3
+ *
4
+ * This contract is richer than the generic `CacheStore` — it adds scan-by-prefix,
5
+ * bulk read, and usage statistics primitives that the proxy's token cache needs.
6
+ * Simpler key-value consumers should use `CacheStore` instead.
7
+ *
8
+ * Default implementation: `@veryfront/ext-redis`.
9
+ *
10
+ * @module extensions/interfaces/token-cache-store
11
+ */
12
+
13
+ /**
14
+ * A cache entry stored by `TokenCacheStore`.
15
+ *
16
+ * The proxy persists OAuth tokens keyed by request metadata; this entry shape
17
+ * mirrors what the proxy has historically stored.
18
+ */
19
+ export interface TokenCacheEntry {
20
+ token: string;
21
+ /** Unix timestamp in milliseconds. */
22
+ expiresAt: number;
23
+ scope: "preview" | "production";
24
+ projectSlug?: string;
25
+ }
26
+
27
+ /**
28
+ * Aggregate usage statistics for a `TokenCacheStore`.
29
+ */
30
+ export interface TokenCacheStats {
31
+ hits: number;
32
+ misses: number;
33
+ size: number;
34
+ type: "memory" | "redis";
35
+ }
36
+
37
+ /**
38
+ * TokenCacheStore contract interface.
39
+ *
40
+ * Implementations provide TTL-aware token caching, plus scan and stats
41
+ * primitives that generic key-value caches do not require.
42
+ */
43
+ export interface TokenCacheStore {
44
+ /** Retrieve a cached entry by key. Returns `null` on miss or expiry. */
45
+ get(key: string): Promise<TokenCacheEntry | null>;
46
+ /** Store an entry. TTL is derived from `entry.expiresAt`. */
47
+ set(key: string, entry: TokenCacheEntry): Promise<void>;
48
+ /** Delete a cached entry. No-op if the key does not exist. */
49
+ delete(key: string): Promise<void>;
50
+ /** Remove every entry owned by this store. */
51
+ clear(): Promise<void>;
52
+ /** Check whether a non-expired entry exists for the given key. */
53
+ has(key: string): Promise<boolean>;
54
+ /** Return current hit/miss/size statistics. */
55
+ stats(): Promise<TokenCacheStats>;
56
+ /** Close connections and release resources. */
57
+ close(): Promise<void>;
58
+ }
@@ -7,6 +7,7 @@
7
7
  const recommendations = new Map<string, string>([
8
8
  ["Bundler", "@veryfront/ext-esbuild"],
9
9
  ["CacheStore", "@veryfront/ext-redis"],
10
+ ["TokenCacheStore", "@veryfront/ext-redis"],
10
11
  ["CSSProcessor", "@veryfront/ext-tailwind"],
11
12
  ["ContentTransformer", "@veryfront/ext-mdx"],
12
13
  ["DatabaseClient", "@veryfront/ext-postgres"],
@@ -27,7 +27,7 @@ export const connectors: IntegrationConfig[] = [
27
27
  {"name":"mailchimp","displayName":"Mailchimp","icon":"mailchimp.svg","description":"Manage email campaigns, lists, and subscribers in Mailchimp","auth":{"type":"oauth2","provider":"mailchimp","authorizationUrl":"https://login.mailchimp.com/oauth2/authorize","tokenUrl":"https://login.mailchimp.com/oauth2/token","scopes":[],"requiredApis":[{"name":"Mailchimp API","enableUrl":"https://admin.mailchimp.com/account/oauth2/"}]},"envVars":[{"name":"MAILCHIMP_CLIENT_ID","description":"Mailchimp OAuth Client ID","required":true,"sensitive":false,"docsUrl":"https://mailchimp.com/developer/marketing/guides/access-user-data-oauth-2/"},{"name":"MAILCHIMP_CLIENT_SECRET","description":"Mailchimp OAuth Client Secret","required":true,"sensitive":true,"docsUrl":"https://mailchimp.com/developer/marketing/guides/access-user-data-oauth-2/"}],"tools":[{"id":"list-campaigns","name":"List Campaigns","description":"List all email campaigns in Mailchimp","requiresWrite":false},{"id":"get-campaign","name":"Get Campaign","description":"Get details of a specific campaign","requiresWrite":false},{"id":"list-lists","name":"List Audience Lists","description":"List all audience lists (mailing lists) in Mailchimp","requiresWrite":false},{"id":"get-list","name":"Get Audience List","description":"Get details of a specific audience list","requiresWrite":false},{"id":"list-members","name":"List Members","description":"List subscribers/members in an audience list","requiresWrite":false}],"prompts":[{"id":"campaign-stats","title":"Show campaign stats","prompt":"Show me the performance statistics for my recent email campaigns in Mailchimp.","category":"marketing","icon":"chart"},{"id":"list-subscribers","title":"List subscribers","prompt":"Show me the subscribers in my main email list with their subscription status.","category":"marketing","icon":"users"}],"suggestedWith":["slack","notion","hubspot"]},
28
28
  {"name":"mixpanel","displayName":"Mixpanel","icon":"mixpanel.svg","description":"Track events, analyze funnels, and understand user behavior with Mixpanel analytics","auth":{"type":"api-key","requiredApis":[{"name":"Mixpanel API","enableUrl":"https://mixpanel.com/settings/project"}],"keyName":"MIXPANEL_PROJECT_TOKEN"},"envVars":[{"name":"MIXPANEL_PROJECT_TOKEN","description":"Mixpanel Project Token for event tracking","required":true,"sensitive":true,"docsUrl":"https://docs.mixpanel.com/docs/tracking-methods/id-management/authentication"},{"name":"MIXPANEL_API_SECRET","description":"Mixpanel API Secret for data export and query operations","required":true,"sensitive":true,"docsUrl":"https://developer.mixpanel.com/reference/authentication"},{"name":"MIXPANEL_PROJECT_ID","description":"Mixpanel Project ID (found in project settings)","required":true,"sensitive":false,"docsUrl":"https://docs.mixpanel.com/docs/admin/organizations-projects/manage-projects"}],"tools":[{"id":"track-event","name":"Track Event","description":"Track a custom event in Mixpanel with properties","requiresWrite":true},{"id":"query-events","name":"Query Events","description":"Query and export event data from Mixpanel","requiresWrite":false},{"id":"get-funnel","name":"Get Funnel","description":"Retrieve funnel analysis data to understand conversion rates","requiresWrite":false},{"id":"get-retention","name":"Get Retention","description":"Analyze user retention cohorts over time","requiresWrite":false},{"id":"list-cohorts","name":"List Cohorts","description":"List all user cohorts defined in your Mixpanel project","requiresWrite":false}],"prompts":[{"id":"event-analysis","title":"Event analysis","prompt":"Show me the most important events tracked in my Mixpanel project over the last 7 days and their trends.","category":"analytics","icon":"chart"},{"id":"funnel-performance","title":"Funnel performance","prompt":"Analyze my key conversion funnels and identify where users are dropping off.","category":"analytics","icon":"funnel"},{"id":"retention-insights","title":"Retention insights","prompt":"Give me insights about user retention and cohort behavior over the past month.","category":"analytics","icon":"users"}],"suggestedWith":["slack","analytics","monitoring"]},
29
29
  {"name":"monday","displayName":"Monday.com","icon":"monday.svg","description":"Manage projects, tasks, and workflows in Monday.com","auth":{"type":"oauth2","provider":"monday","authorizationUrl":"https://auth.monday.com/oauth2/authorize","tokenUrl":"https://auth.monday.com/oauth2/token","scopes":["me:read","boards:read","boards:write"],"requiredApis":[{"name":"Monday.com Developers","enableUrl":"https://monday.com/developers/apps"}]},"envVars":[{"name":"MONDAY_CLIENT_ID","description":"Monday.com OAuth Client ID","required":true,"sensitive":false,"docsUrl":"https://developer.monday.com/apps/docs/oauth"},{"name":"MONDAY_CLIENT_SECRET","description":"Monday.com OAuth Client Secret","required":true,"sensitive":true,"docsUrl":"https://developer.monday.com/apps/docs/oauth"}],"tools":[{"id":"list-boards","name":"List Boards","description":"List all boards in the workspace","requiresWrite":false},{"id":"list-items","name":"List Items","description":"List items in a board","requiresWrite":false},{"id":"get-item","name":"Get Item","description":"Get details of a specific item","requiresWrite":false},{"id":"create-item","name":"Create Item","description":"Create a new item in a board","requiresWrite":true},{"id":"update-item","name":"Update Item","description":"Update an existing item","requiresWrite":true}],"prompts":[{"id":"my-items","title":"Show my items","prompt":"List all items assigned to me in Monday.com with their status and due dates.","category":"productivity","icon":"list"},{"id":"create-item","title":"Create an item","prompt":"Create a new item with a name, status, and assign it to someone.","category":"productivity","icon":"plus"}],"suggestedWith":["slack","notion","asana"]},
30
- {"name":"neon","displayName":"Neon","icon":"neon.svg","description":"Manage Neon Postgres projects, branches, and execute database queries","auth":{"type":"api-key","requiredApis":[{"name":"Neon Management API","enableUrl":"https://console.neon.tech/app/settings/api-keys"}],"tokenName":"API Key","docsUrl":"https://neon.tech/docs/manage/api-keys"},"envVars":[{"name":"NEON_API_KEY","description":"Neon API Key for Management API access","required":true,"sensitive":true,"docsUrl":"https://neon.tech/docs/manage/api-keys"},{"name":"DATABASE_URL","description":"PostgreSQL connection string for database queries","required":true,"sensitive":true,"docsUrl":"https://neon.tech/docs/connect/connect-from-any-app"}],"tools":[{"id":"list-projects","name":"List Projects","description":"List all Neon projects in your account","requiresWrite":false},{"id":"list-branches","name":"List Branches","description":"List all branches for a specific project","requiresWrite":false},{"id":"query-database","name":"Query Database","description":"Execute SQL queries against the connected database","requiresWrite":false},{"id":"list-tables","name":"List Tables","description":"List all tables in the connected database","requiresWrite":false},{"id":"describe-table","name":"Describe Table","description":"Get detailed schema information for a specific table","requiresWrite":false}],"prompts":[{"id":"check-db-status","title":"Check database status","prompt":"Show me the status of my Neon projects and their branches.","category":"database","icon":"database"},{"id":"explore-schema","title":"Explore database schema","prompt":"List all tables in my database and show me the schema for the main tables.","category":"database","icon":"table"},{"id":"query-data","title":"Query database","prompt":"Help me query my database to find specific data.","category":"database","icon":"search"}],"suggestedWith":["stripe","clerk","vercel"]},
30
+ {"name":"neon","displayName":"Neon","icon":"neon.svg","description":"Manage Neon Postgres projects, branches, and execute database queries","auth":{"type":"api-key","requiredApis":[{"name":"Neon Management API","enableUrl":"https://console.neon.tech/app/settings/api-keys"}],"tokenName":"API Key","docsUrl":"https://neon.tech/docs/manage/api-keys"},"envVars":[{"name":"NEON_API_KEY","description":"Neon API Key for Management API access","required":true,"sensitive":true,"docsUrl":"https://neon.tech/docs/manage/api-keys"},{"name":"DATABASE_URL","description":"PostgreSQL connection string for database queries","required":true,"sensitive":true,"docsUrl":"https://neon.tech/docs/connect/connect-from-any-app"}],"npmDependencies":{"pg":"^8.13.1"},"tools":[{"id":"list-projects","name":"List Projects","description":"List all Neon projects in your account","requiresWrite":false},{"id":"list-branches","name":"List Branches","description":"List all branches for a specific project","requiresWrite":false},{"id":"query-database","name":"Query Database","description":"Execute SQL queries against the connected database","requiresWrite":false},{"id":"list-tables","name":"List Tables","description":"List all tables in the connected database","requiresWrite":false},{"id":"describe-table","name":"Describe Table","description":"Get detailed schema information for a specific table","requiresWrite":false}],"prompts":[{"id":"check-db-status","title":"Check database status","prompt":"Show me the status of my Neon projects and their branches.","category":"database","icon":"database"},{"id":"explore-schema","title":"Explore database schema","prompt":"List all tables in my database and show me the schema for the main tables.","category":"database","icon":"table"},{"id":"query-data","title":"Query database","prompt":"Help me query my database to find specific data.","category":"database","icon":"search"}],"suggestedWith":["stripe","clerk","vercel"]},
31
31
  {"name":"notion","displayName":"Notion","icon":"notion.svg","description":"Search, read, and create pages in Notion workspaces","auth":{"type":"oauth2","provider":"notion","authorizationUrl":"https://api.notion.com/v1/oauth/authorize","tokenUrl":"https://api.notion.com/v1/oauth/token","scopes":[],"tokenAuthMethod":"basic","requiredApis":[{"name":"Notion Integration","enableUrl":"https://www.notion.so/my-integrations"}]},"envVars":[{"name":"NOTION_CLIENT_ID","description":"Notion OAuth Client ID (from your integration)","required":true,"sensitive":false,"docsUrl":"https://www.notion.so/my-integrations"},{"name":"NOTION_CLIENT_SECRET","description":"Notion OAuth Client Secret","required":true,"sensitive":true,"docsUrl":"https://www.notion.so/my-integrations"}],"tools":[{"id":"search-notion","name":"Search Notion","description":"Search pages and databases in the workspace","requiresWrite":false},{"id":"read-page","name":"Read Page","description":"Read the content of a Notion page","requiresWrite":false},{"id":"create-page","name":"Create Page","description":"Create a new page in a database or as a subpage","requiresWrite":true},{"id":"query-database","name":"Query Database","description":"Query a Notion database with filters and sorts","requiresWrite":false}],"prompts":[{"id":"search-docs","title":"Search my docs","prompt":"Search my Notion workspace for relevant documentation or notes about a topic.","category":"productivity","icon":"search"},{"id":"summarize-page","title":"Summarize a page","prompt":"Read and summarize a specific Notion page. Extract the key points and action items.","category":"productivity","icon":"document"},{"id":"create-meeting-notes","title":"Create meeting notes","prompt":"Create a new meeting notes page with today's date, attendees, agenda, and action items sections.","category":"productivity","icon":"plus"}],"suggestedWith":["gmail","slack","calendar"]},
32
32
  {"name":"onedrive","displayName":"OneDrive","icon":"onedrive.svg","description":"Access and manage files in Microsoft OneDrive","auth":{"type":"oauth2","provider":"microsoft","authorizationUrl":"https://login.microsoftonline.com/common/oauth2/v2.0/authorize","tokenUrl":"https://login.microsoftonline.com/common/oauth2/v2.0/token","scopes":["Files.Read","Files.ReadWrite","Files.Read.All","Files.ReadWrite.All","offline_access"],"tokenAuthMethod":"body","requiredApis":[{"name":"Microsoft Graph API","enableUrl":"https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade"}]},"envVars":[{"name":"MICROSOFT_CLIENT_ID","description":"Microsoft Azure App Client ID (shared with Outlook/Teams/SharePoint)","required":true,"sensitive":false,"docsUrl":"https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade"},{"name":"MICROSOFT_CLIENT_SECRET","description":"Microsoft Azure App Client Secret","required":true,"sensitive":true,"docsUrl":"https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade"}],"tools":[{"id":"list-files","name":"List Files","description":"List files and folders in a OneDrive folder","requiresWrite":false},{"id":"search-files","name":"Search Files","description":"Search for files and folders in OneDrive by name or content","requiresWrite":false},{"id":"upload-file","name":"Upload File","description":"Upload or update a file in OneDrive","requiresWrite":true},{"id":"download-file","name":"Download File","description":"Download file content from OneDrive","requiresWrite":false}],"prompts":[{"id":"search-documents","title":"Search documents","prompt":"Search for documents in OneDrive and summarize their content.","category":"productivity","icon":"search"},{"id":"list-recent-files","title":"List recent files","prompt":"Show me the most recently modified files in my OneDrive.","category":"productivity","icon":"document"},{"id":"organize-files","title":"Organize files","prompt":"Help me organize and manage files in my OneDrive storage.","category":"productivity","icon":"folder"},{"id":"backup-file","title":"Backup a file","prompt":"Upload and backup a file to my OneDrive storage.","category":"productivity","icon":"upload"}],"suggestedWith":["outlook","teams","sharepoint"]},
33
33
  {"name":"outlook","displayName":"Microsoft Outlook","icon":"outlook.svg","description":"Read, send, and manage Outlook emails","auth":{"type":"oauth2","provider":"microsoft","authorizationUrl":"https://login.microsoftonline.com/common/oauth2/v2.0/authorize","tokenUrl":"https://login.microsoftonline.com/common/oauth2/v2.0/token","scopes":["Mail.Read","Mail.Send","Mail.ReadWrite","offline_access"],"tokenAuthMethod":"body","requiredApis":[{"name":"Microsoft Graph API","enableUrl":"https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade"}]},"envVars":[{"name":"MICROSOFT_CLIENT_ID","description":"Microsoft Azure App Client ID (Application ID)","required":true,"sensitive":false,"docsUrl":"https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade"},{"name":"MICROSOFT_CLIENT_SECRET","description":"Microsoft Azure App Client Secret","required":true,"sensitive":true,"docsUrl":"https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade"}],"tools":[{"id":"list-emails","name":"List Emails","description":"List recent emails from inbox or a specific folder","requiresWrite":false},{"id":"get-email","name":"Get Email","description":"Get detailed information about a specific email","requiresWrite":false},{"id":"send-email","name":"Send Email","description":"Send a new email message","requiresWrite":true},{"id":"search-emails","name":"Search Emails","description":"Search emails by query, subject, sender, or date","requiresWrite":false},{"id":"list-folders","name":"List Folders","description":"List all mail folders in the mailbox","requiresWrite":false}],"prompts":[{"id":"check-emails","title":"Check my emails","prompt":"List my recent unread emails and summarize the most important ones.","category":"productivity","icon":"mail"},{"id":"search-emails","title":"Search my emails","prompt":"Search my emails for specific topics, senders, or date ranges.","category":"productivity","icon":"search"},{"id":"draft-email","title":"Draft an email","prompt":"Help me draft a professional email with proper formatting and tone.","category":"productivity","icon":"compose"}],"suggestedWith":["teams","calendar","gmail"]},
@@ -151,6 +151,14 @@ export const IntegrationConfigSchema = z.object({
151
151
  description: z.string(),
152
152
  auth: OAuthConfigSchema,
153
153
  envVars: z.array(EnvVarSchema).optional(),
154
+ /**
155
+ * Optional map of npm packages to semver ranges. When this integration is
156
+ * selected during `veryfront init`, these deps are merged into the
157
+ * generated project's `package.json#dependencies`. Use this for templates
158
+ * that import packages beyond the init scaffold's defaults (react,
159
+ * react-dom, veryfront, zod).
160
+ */
161
+ npmDependencies: z.record(z.string(), z.string()).optional(),
154
162
  tools: z.array(IntegrationToolSchema),
155
163
  prompts: z.array(IntegrationPromptSchema).optional(),
156
164
  suggestedWith: z.array(z.string()).optional(),