@supyagent/sdk 0.1.12 → 0.1.14

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/index.cjs CHANGED
@@ -68,6 +68,22 @@ function createExecutor(metadata, baseUrl, apiKey) {
68
68
  delete remainingArgs[paramName];
69
69
  }
70
70
  }
71
+ if ("id" in remainingArgs && resolvedPath.includes("{")) {
72
+ const unresolved = resolvedPath.match(/\{(\w+)\}/g);
73
+ if (unresolved) {
74
+ for (const param of unresolved) {
75
+ const paramName = param.slice(1, -1);
76
+ if (paramName.toLowerCase().endsWith("id")) {
77
+ resolvedPath = resolvedPath.replace(
78
+ param,
79
+ encodeURIComponent(String(remainingArgs.id))
80
+ );
81
+ delete remainingArgs.id;
82
+ break;
83
+ }
84
+ }
85
+ }
86
+ }
71
87
  }
72
88
  let url = `${baseUrl}${resolvedPath}`;
73
89
  const fetchOptions = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/core/cache.ts","../src/core/tool-converter.ts","../src/core/http-executor.ts","../src/core/client.ts"],"sourcesContent":["export { supyagent } from \"./core/client.js\";\nexport type {\n SupyagentOptions,\n SupyagentClient,\n ToolFilterOptions,\n ToolMetadata,\n OpenAITool,\n ToolsResponse,\n} from \"./core/types.js\";\n","interface CacheEntry<T> {\n data: T;\n expiresAt: number;\n}\n\nexport class TTLCache<T> {\n private store = new Map<string, CacheEntry<T>>();\n\n get(key: string): T | undefined {\n const entry = this.store.get(key);\n if (!entry) return undefined;\n if (Date.now() > entry.expiresAt) {\n this.store.delete(key);\n return undefined;\n }\n return entry.data;\n }\n\n set(key: string, data: T, ttlSeconds: number): void {\n this.store.set(key, {\n data,\n expiresAt: Date.now() + ttlSeconds * 1000,\n });\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n","import { tool, jsonSchema } from \"ai\";\nimport type { OpenAITool } from \"./types.js\";\nimport { createExecutor } from \"./http-executor.js\";\n\n/**\n * Converts an array of OpenAI-format tools from the supyagent API\n * into a Record of AI SDK tool() instances ready for streamText/generateText.\n */\nexport function convertTools(\n tools: OpenAITool[],\n baseUrl: string,\n apiKey: string\n): Record<string, import(\"ai\").Tool> {\n const result: Record<string, import(\"ai\").Tool> = {};\n\n for (const t of tools) {\n const { name, description, parameters } = t.function;\n const metadata = t.metadata;\n\n result[name] = tool({\n description,\n parameters: jsonSchema(parameters as Parameters<typeof jsonSchema>[0]),\n execute: createExecutor(metadata, baseUrl, apiKey),\n });\n }\n\n return result;\n}\n\n/**\n * Filter tools by provider, service, or tool name.\n */\nexport function filterTools(\n tools: OpenAITool[],\n options: { only?: string[]; except?: string[] }\n): OpenAITool[] {\n let filtered = tools;\n\n if (options.only && options.only.length > 0) {\n const onlySet = new Set(options.only.map((s) => s.toLowerCase()));\n filtered = filtered.filter((t) => {\n const name = t.function.name.toLowerCase();\n const provider = t.metadata.provider.toLowerCase();\n const service = t.metadata.service.toLowerCase();\n return onlySet.has(name) || onlySet.has(provider) || onlySet.has(service);\n });\n }\n\n if (options.except && options.except.length > 0) {\n const exceptSet = new Set(options.except.map((s) => s.toLowerCase()));\n filtered = filtered.filter((t) => {\n const name = t.function.name.toLowerCase();\n const provider = t.metadata.provider.toLowerCase();\n const service = t.metadata.service.toLowerCase();\n return (\n !exceptSet.has(name) &&\n !exceptSet.has(provider) &&\n !exceptSet.has(service)\n );\n });\n }\n\n return filtered;\n}\n","import type { ToolMetadata } from \"./types.js\";\n\n/**\n * Creates an execute function for a tool that calls the supyagent API.\n *\n * Handles:\n * - Path parameter substitution (e.g., {messageId} in /api/v1/gmail/messages/{messageId})\n * - GET/DELETE: remaining args → query string\n * - POST/PUT/PATCH: merge bodyDefaults + remaining args → JSON body\n */\nexport function createExecutor(\n metadata: ToolMetadata,\n baseUrl: string,\n apiKey: string\n): (args: Record<string, unknown>) => Promise<unknown> {\n return async (args: Record<string, unknown>) => {\n const { method, path, bodyDefaults } = metadata;\n\n // Extract path parameters and substitute into the path\n const remainingArgs = { ...args };\n let resolvedPath = path;\n\n const pathParams = path.match(/\\{(\\w+)\\}/g);\n if (pathParams) {\n for (const param of pathParams) {\n const paramName = param.slice(1, -1);\n if (paramName in remainingArgs) {\n resolvedPath = resolvedPath.replace(\n param,\n encodeURIComponent(String(remainingArgs[paramName]))\n );\n delete remainingArgs[paramName];\n }\n }\n }\n\n let url = `${baseUrl}${resolvedPath}`;\n\n const fetchOptions: RequestInit = {\n method,\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n };\n\n if (method === \"GET\" || method === \"DELETE\") {\n // Remaining args go as query parameters\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(remainingArgs)) {\n if (value !== undefined && value !== null) {\n params.set(key, String(value));\n }\n }\n const qs = params.toString();\n if (qs) url += `?${qs}`;\n } else {\n // POST/PUT/PATCH: merge bodyDefaults with remaining args\n const body = { ...bodyDefaults, ...remainingArgs };\n fetchOptions.body = JSON.stringify(body);\n }\n\n const response = await fetch(url, fetchOptions);\n const data = await response.json();\n return data;\n };\n}\n","import type {\n SupyagentOptions,\n SupyagentClient,\n ToolFilterOptions,\n ToolsResponse,\n} from \"./types.js\";\nimport { TTLCache } from \"./cache.js\";\nimport { convertTools, filterTools } from \"./tool-converter.js\";\n\nconst DEFAULT_BASE_URL = \"https://app.supyagent.com\";\nconst CACHE_KEY = \"tools\";\n\n/**\n * Create a supyagent client for fetching and converting tools.\n *\n * @example\n * ```ts\n * import { supyagent } from '@supyagent/sdk';\n *\n * const client = supyagent({ apiKey: process.env.SUPYAGENT_API_KEY! });\n * const tools = await client.tools({ cache: 300 });\n * ```\n */\nexport function supyagent(options: SupyagentOptions): SupyagentClient {\n const { apiKey, baseUrl = DEFAULT_BASE_URL } = options;\n const cache = new TTLCache<ToolsResponse>();\n\n return {\n async tools(filterOptions?: ToolFilterOptions) {\n const cacheTTL = resolveCacheTTL(filterOptions?.cache);\n\n // Check cache\n let response = cacheTTL > 0 ? cache.get(CACHE_KEY) : undefined;\n\n if (!response) {\n // Fetch from API\n const res = await fetch(`${baseUrl}/api/v1/tools`, {\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!res.ok) {\n const error = await res.text();\n throw new Error(\n `Supyagent API error (${res.status}): ${error}`\n );\n }\n\n const json = await res.json();\n // API wraps response in { ok, data: { tools, base_url, total } }\n response = (json.data ?? json) as ToolsResponse;\n\n // Store in cache if TTL > 0\n if (cacheTTL > 0) {\n cache.set(CACHE_KEY, response, cacheTTL);\n }\n }\n\n // Use API-reported base_url for tool execution\n const toolBaseUrl = response.base_url || baseUrl;\n\n // Guard against non-array tools (e.g., API returns empty or unexpected shape)\n const toolsArray = Array.isArray(response.tools) ? response.tools : [];\n\n // Filter\n const filtered = filterTools(toolsArray, {\n only: filterOptions?.only,\n except: filterOptions?.except,\n });\n\n // Convert to AI SDK tools\n return convertTools(filtered, toolBaseUrl, apiKey);\n },\n };\n}\n\nfunction resolveCacheTTL(cache?: boolean | number): number {\n if (cache === true) return 60;\n if (typeof cache === \"number\") return cache;\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACKO,IAAM,WAAN,MAAkB;AAAA,EACf,QAAQ,oBAAI,IAA2B;AAAA,EAE/C,IAAI,KAA4B;AAC9B,UAAM,QAAQ,KAAK,MAAM,IAAI,GAAG;AAChC,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI,KAAK,IAAI,IAAI,MAAM,WAAW;AAChC,WAAK,MAAM,OAAO,GAAG;AACrB,aAAO;AAAA,IACT;AACA,WAAO,MAAM;AAAA,EACf;AAAA,EAEA,IAAI,KAAa,MAAS,YAA0B;AAClD,SAAK,MAAM,IAAI,KAAK;AAAA,MAClB;AAAA,MACA,WAAW,KAAK,IAAI,IAAI,aAAa;AAAA,IACvC,CAAC;AAAA,EACH;AAAA,EAEA,QAAc;AACZ,SAAK,MAAM,MAAM;AAAA,EACnB;AACF;;;AC5BA,gBAAiC;;;ACU1B,SAAS,eACd,UACA,SACA,QACqD;AACrD,SAAO,OAAO,SAAkC;AAC9C,UAAM,EAAE,QAAQ,MAAM,aAAa,IAAI;AAGvC,UAAM,gBAAgB,EAAE,GAAG,KAAK;AAChC,QAAI,eAAe;AAEnB,UAAM,aAAa,KAAK,MAAM,YAAY;AAC1C,QAAI,YAAY;AACd,iBAAW,SAAS,YAAY;AAC9B,cAAM,YAAY,MAAM,MAAM,GAAG,EAAE;AACnC,YAAI,aAAa,eAAe;AAC9B,yBAAe,aAAa;AAAA,YAC1B;AAAA,YACA,mBAAmB,OAAO,cAAc,SAAS,CAAC,CAAC;AAAA,UACrD;AACA,iBAAO,cAAc,SAAS;AAAA,QAChC;AAAA,MACF;AAAA,IACF;AAEA,QAAI,MAAM,GAAG,OAAO,GAAG,YAAY;AAEnC,UAAM,eAA4B;AAAA,MAChC;AAAA,MACA,SAAS;AAAA,QACP,eAAe,UAAU,MAAM;AAAA,QAC/B,gBAAgB;AAAA,MAClB;AAAA,IACF;AAEA,QAAI,WAAW,SAAS,WAAW,UAAU;AAE3C,YAAM,SAAS,IAAI,gBAAgB;AACnC,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,aAAa,GAAG;AACxD,YAAI,UAAU,UAAa,UAAU,MAAM;AACzC,iBAAO,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,QAC/B;AAAA,MACF;AACA,YAAM,KAAK,OAAO,SAAS;AAC3B,UAAI,GAAI,QAAO,IAAI,EAAE;AAAA,IACvB,OAAO;AAEL,YAAM,OAAO,EAAE,GAAG,cAAc,GAAG,cAAc;AACjD,mBAAa,OAAO,KAAK,UAAU,IAAI;AAAA,IACzC;AAEA,UAAM,WAAW,MAAM,MAAM,KAAK,YAAY;AAC9C,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,WAAO;AAAA,EACT;AACF;;;AD1DO,SAAS,aACd,OACA,SACA,QACmC;AACnC,QAAM,SAA4C,CAAC;AAEnD,aAAW,KAAK,OAAO;AACrB,UAAM,EAAE,MAAM,aAAa,WAAW,IAAI,EAAE;AAC5C,UAAM,WAAW,EAAE;AAEnB,WAAO,IAAI,QAAI,gBAAK;AAAA,MAClB;AAAA,MACA,gBAAY,sBAAW,UAA8C;AAAA,MACrE,SAAS,eAAe,UAAU,SAAS,MAAM;AAAA,IACnD,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAKO,SAAS,YACd,OACA,SACc;AACd,MAAI,WAAW;AAEf,MAAI,QAAQ,QAAQ,QAAQ,KAAK,SAAS,GAAG;AAC3C,UAAM,UAAU,IAAI,IAAI,QAAQ,KAAK,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;AAChE,eAAW,SAAS,OAAO,CAAC,MAAM;AAChC,YAAM,OAAO,EAAE,SAAS,KAAK,YAAY;AACzC,YAAM,WAAW,EAAE,SAAS,SAAS,YAAY;AACjD,YAAM,UAAU,EAAE,SAAS,QAAQ,YAAY;AAC/C,aAAO,QAAQ,IAAI,IAAI,KAAK,QAAQ,IAAI,QAAQ,KAAK,QAAQ,IAAI,OAAO;AAAA,IAC1E,CAAC;AAAA,EACH;AAEA,MAAI,QAAQ,UAAU,QAAQ,OAAO,SAAS,GAAG;AAC/C,UAAM,YAAY,IAAI,IAAI,QAAQ,OAAO,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;AACpE,eAAW,SAAS,OAAO,CAAC,MAAM;AAChC,YAAM,OAAO,EAAE,SAAS,KAAK,YAAY;AACzC,YAAM,WAAW,EAAE,SAAS,SAAS,YAAY;AACjD,YAAM,UAAU,EAAE,SAAS,QAAQ,YAAY;AAC/C,aACE,CAAC,UAAU,IAAI,IAAI,KACnB,CAAC,UAAU,IAAI,QAAQ,KACvB,CAAC,UAAU,IAAI,OAAO;AAAA,IAE1B,CAAC;AAAA,EACH;AAEA,SAAO;AACT;;;AEtDA,IAAM,mBAAmB;AACzB,IAAM,YAAY;AAaX,SAAS,UAAU,SAA4C;AACpE,QAAM,EAAE,QAAQ,UAAU,iBAAiB,IAAI;AAC/C,QAAM,QAAQ,IAAI,SAAwB;AAE1C,SAAO;AAAA,IACL,MAAM,MAAM,eAAmC;AAC7C,YAAM,WAAW,gBAAgB,eAAe,KAAK;AAGrD,UAAI,WAAW,WAAW,IAAI,MAAM,IAAI,SAAS,IAAI;AAErD,UAAI,CAAC,UAAU;AAEb,cAAM,MAAM,MAAM,MAAM,GAAG,OAAO,iBAAiB;AAAA,UACjD,SAAS;AAAA,YACP,eAAe,UAAU,MAAM;AAAA,YAC/B,gBAAgB;AAAA,UAClB;AAAA,QACF,CAAC;AAED,YAAI,CAAC,IAAI,IAAI;AACX,gBAAM,QAAQ,MAAM,IAAI,KAAK;AAC7B,gBAAM,IAAI;AAAA,YACR,wBAAwB,IAAI,MAAM,MAAM,KAAK;AAAA,UAC/C;AAAA,QACF;AAEA,cAAM,OAAO,MAAM,IAAI,KAAK;AAE5B,mBAAY,KAAK,QAAQ;AAGzB,YAAI,WAAW,GAAG;AAChB,gBAAM,IAAI,WAAW,UAAU,QAAQ;AAAA,QACzC;AAAA,MACF;AAGA,YAAM,cAAc,SAAS,YAAY;AAGzC,YAAM,aAAa,MAAM,QAAQ,SAAS,KAAK,IAAI,SAAS,QAAQ,CAAC;AAGrE,YAAM,WAAW,YAAY,YAAY;AAAA,QACvC,MAAM,eAAe;AAAA,QACrB,QAAQ,eAAe;AAAA,MACzB,CAAC;AAGD,aAAO,aAAa,UAAU,aAAa,MAAM;AAAA,IACnD;AAAA,EACF;AACF;AAEA,SAAS,gBAAgB,OAAkC;AACzD,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/core/cache.ts","../src/core/tool-converter.ts","../src/core/http-executor.ts","../src/core/client.ts"],"sourcesContent":["export { supyagent } from \"./core/client.js\";\nexport type {\n SupyagentOptions,\n SupyagentClient,\n ToolFilterOptions,\n ToolMetadata,\n OpenAITool,\n ToolsResponse,\n} from \"./core/types.js\";\n","interface CacheEntry<T> {\n data: T;\n expiresAt: number;\n}\n\nexport class TTLCache<T> {\n private store = new Map<string, CacheEntry<T>>();\n\n get(key: string): T | undefined {\n const entry = this.store.get(key);\n if (!entry) return undefined;\n if (Date.now() > entry.expiresAt) {\n this.store.delete(key);\n return undefined;\n }\n return entry.data;\n }\n\n set(key: string, data: T, ttlSeconds: number): void {\n this.store.set(key, {\n data,\n expiresAt: Date.now() + ttlSeconds * 1000,\n });\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n","import { tool, jsonSchema } from \"ai\";\nimport type { OpenAITool } from \"./types.js\";\nimport { createExecutor } from \"./http-executor.js\";\n\n/**\n * Converts an array of OpenAI-format tools from the supyagent API\n * into a Record of AI SDK tool() instances ready for streamText/generateText.\n */\nexport function convertTools(\n tools: OpenAITool[],\n baseUrl: string,\n apiKey: string\n): Record<string, import(\"ai\").Tool> {\n const result: Record<string, import(\"ai\").Tool> = {};\n\n for (const t of tools) {\n const { name, description, parameters } = t.function;\n const metadata = t.metadata;\n\n result[name] = tool({\n description,\n parameters: jsonSchema(parameters as Parameters<typeof jsonSchema>[0]),\n execute: createExecutor(metadata, baseUrl, apiKey),\n });\n }\n\n return result;\n}\n\n/**\n * Filter tools by provider, service, or tool name.\n */\nexport function filterTools(\n tools: OpenAITool[],\n options: { only?: string[]; except?: string[] }\n): OpenAITool[] {\n let filtered = tools;\n\n if (options.only && options.only.length > 0) {\n const onlySet = new Set(options.only.map((s) => s.toLowerCase()));\n filtered = filtered.filter((t) => {\n const name = t.function.name.toLowerCase();\n const provider = t.metadata.provider.toLowerCase();\n const service = t.metadata.service.toLowerCase();\n return onlySet.has(name) || onlySet.has(provider) || onlySet.has(service);\n });\n }\n\n if (options.except && options.except.length > 0) {\n const exceptSet = new Set(options.except.map((s) => s.toLowerCase()));\n filtered = filtered.filter((t) => {\n const name = t.function.name.toLowerCase();\n const provider = t.metadata.provider.toLowerCase();\n const service = t.metadata.service.toLowerCase();\n return (\n !exceptSet.has(name) &&\n !exceptSet.has(provider) &&\n !exceptSet.has(service)\n );\n });\n }\n\n return filtered;\n}\n","import type { ToolMetadata } from \"./types.js\";\n\n/**\n * Creates an execute function for a tool that calls the supyagent API.\n *\n * Handles:\n * - Path parameter substitution (e.g., {messageId} in /api/v1/gmail/messages/{messageId})\n * - GET/DELETE: remaining args → query string\n * - POST/PUT/PATCH: merge bodyDefaults + remaining args → JSON body\n */\nexport function createExecutor(\n metadata: ToolMetadata,\n baseUrl: string,\n apiKey: string\n): (args: Record<string, unknown>) => Promise<unknown> {\n return async (args: Record<string, unknown>) => {\n const { method, path, bodyDefaults } = metadata;\n\n // Extract path parameters and substitute into the path\n const remainingArgs = { ...args };\n let resolvedPath = path;\n\n const pathParams = path.match(/\\{(\\w+)\\}/g);\n if (pathParams) {\n for (const param of pathParams) {\n const paramName = param.slice(1, -1);\n if (paramName in remainingArgs) {\n resolvedPath = resolvedPath.replace(\n param,\n encodeURIComponent(String(remainingArgs[paramName]))\n );\n delete remainingArgs[paramName];\n }\n }\n\n // Fallback: LLMs often send { id } instead of { messageId }, { eventId }, etc.\n // because list responses return objects with an `id` field.\n // For any unresolved {fooId} param, try `id` from the args (once only).\n if (\"id\" in remainingArgs && resolvedPath.includes(\"{\")) {\n const unresolved = resolvedPath.match(/\\{(\\w+)\\}/g);\n if (unresolved) {\n for (const param of unresolved) {\n const paramName = param.slice(1, -1);\n if (paramName.toLowerCase().endsWith(\"id\")) {\n resolvedPath = resolvedPath.replace(\n param,\n encodeURIComponent(String(remainingArgs.id))\n );\n delete remainingArgs.id;\n break; // consume `id` only once\n }\n }\n }\n }\n }\n\n let url = `${baseUrl}${resolvedPath}`;\n\n const fetchOptions: RequestInit = {\n method,\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n };\n\n if (method === \"GET\" || method === \"DELETE\") {\n // Remaining args go as query parameters\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(remainingArgs)) {\n if (value !== undefined && value !== null) {\n params.set(key, String(value));\n }\n }\n const qs = params.toString();\n if (qs) url += `?${qs}`;\n } else {\n // POST/PUT/PATCH: merge bodyDefaults with remaining args\n const body = { ...bodyDefaults, ...remainingArgs };\n fetchOptions.body = JSON.stringify(body);\n }\n\n const response = await fetch(url, fetchOptions);\n const data = await response.json();\n return data;\n };\n}\n","import type {\n SupyagentOptions,\n SupyagentClient,\n ToolFilterOptions,\n ToolsResponse,\n} from \"./types.js\";\nimport { TTLCache } from \"./cache.js\";\nimport { convertTools, filterTools } from \"./tool-converter.js\";\n\nconst DEFAULT_BASE_URL = \"https://app.supyagent.com\";\nconst CACHE_KEY = \"tools\";\n\n/**\n * Create a supyagent client for fetching and converting tools.\n *\n * @example\n * ```ts\n * import { supyagent } from '@supyagent/sdk';\n *\n * const client = supyagent({ apiKey: process.env.SUPYAGENT_API_KEY! });\n * const tools = await client.tools({ cache: 300 });\n * ```\n */\nexport function supyagent(options: SupyagentOptions): SupyagentClient {\n const { apiKey, baseUrl = DEFAULT_BASE_URL } = options;\n const cache = new TTLCache<ToolsResponse>();\n\n return {\n async tools(filterOptions?: ToolFilterOptions) {\n const cacheTTL = resolveCacheTTL(filterOptions?.cache);\n\n // Check cache\n let response = cacheTTL > 0 ? cache.get(CACHE_KEY) : undefined;\n\n if (!response) {\n // Fetch from API\n const res = await fetch(`${baseUrl}/api/v1/tools`, {\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!res.ok) {\n const error = await res.text();\n throw new Error(\n `Supyagent API error (${res.status}): ${error}`\n );\n }\n\n const json = await res.json();\n // API wraps response in { ok, data: { tools, base_url, total } }\n response = (json.data ?? json) as ToolsResponse;\n\n // Store in cache if TTL > 0\n if (cacheTTL > 0) {\n cache.set(CACHE_KEY, response, cacheTTL);\n }\n }\n\n // Use API-reported base_url for tool execution\n const toolBaseUrl = response.base_url || baseUrl;\n\n // Guard against non-array tools (e.g., API returns empty or unexpected shape)\n const toolsArray = Array.isArray(response.tools) ? response.tools : [];\n\n // Filter\n const filtered = filterTools(toolsArray, {\n only: filterOptions?.only,\n except: filterOptions?.except,\n });\n\n // Convert to AI SDK tools\n return convertTools(filtered, toolBaseUrl, apiKey);\n },\n };\n}\n\nfunction resolveCacheTTL(cache?: boolean | number): number {\n if (cache === true) return 60;\n if (typeof cache === \"number\") return cache;\n return 0;\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -42,6 +42,22 @@ function createExecutor(metadata, baseUrl, apiKey) {
42
42
  delete remainingArgs[paramName];
43
43
  }
44
44
  }
45
+ if ("id" in remainingArgs && resolvedPath.includes("{")) {
46
+ const unresolved = resolvedPath.match(/\{(\w+)\}/g);
47
+ if (unresolved) {
48
+ for (const param of unresolved) {
49
+ const paramName = param.slice(1, -1);
50
+ if (paramName.toLowerCase().endsWith("id")) {
51
+ resolvedPath = resolvedPath.replace(
52
+ param,
53
+ encodeURIComponent(String(remainingArgs.id))
54
+ );
55
+ delete remainingArgs.id;
56
+ break;
57
+ }
58
+ }
59
+ }
60
+ }
45
61
  }
46
62
  let url = `${baseUrl}${resolvedPath}`;
47
63
  const fetchOptions = {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/cache.ts","../src/core/tool-converter.ts","../src/core/http-executor.ts","../src/core/client.ts"],"sourcesContent":["interface CacheEntry<T> {\n data: T;\n expiresAt: number;\n}\n\nexport class TTLCache<T> {\n private store = new Map<string, CacheEntry<T>>();\n\n get(key: string): T | undefined {\n const entry = this.store.get(key);\n if (!entry) return undefined;\n if (Date.now() > entry.expiresAt) {\n this.store.delete(key);\n return undefined;\n }\n return entry.data;\n }\n\n set(key: string, data: T, ttlSeconds: number): void {\n this.store.set(key, {\n data,\n expiresAt: Date.now() + ttlSeconds * 1000,\n });\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n","import { tool, jsonSchema } from \"ai\";\nimport type { OpenAITool } from \"./types.js\";\nimport { createExecutor } from \"./http-executor.js\";\n\n/**\n * Converts an array of OpenAI-format tools from the supyagent API\n * into a Record of AI SDK tool() instances ready for streamText/generateText.\n */\nexport function convertTools(\n tools: OpenAITool[],\n baseUrl: string,\n apiKey: string\n): Record<string, import(\"ai\").Tool> {\n const result: Record<string, import(\"ai\").Tool> = {};\n\n for (const t of tools) {\n const { name, description, parameters } = t.function;\n const metadata = t.metadata;\n\n result[name] = tool({\n description,\n parameters: jsonSchema(parameters as Parameters<typeof jsonSchema>[0]),\n execute: createExecutor(metadata, baseUrl, apiKey),\n });\n }\n\n return result;\n}\n\n/**\n * Filter tools by provider, service, or tool name.\n */\nexport function filterTools(\n tools: OpenAITool[],\n options: { only?: string[]; except?: string[] }\n): OpenAITool[] {\n let filtered = tools;\n\n if (options.only && options.only.length > 0) {\n const onlySet = new Set(options.only.map((s) => s.toLowerCase()));\n filtered = filtered.filter((t) => {\n const name = t.function.name.toLowerCase();\n const provider = t.metadata.provider.toLowerCase();\n const service = t.metadata.service.toLowerCase();\n return onlySet.has(name) || onlySet.has(provider) || onlySet.has(service);\n });\n }\n\n if (options.except && options.except.length > 0) {\n const exceptSet = new Set(options.except.map((s) => s.toLowerCase()));\n filtered = filtered.filter((t) => {\n const name = t.function.name.toLowerCase();\n const provider = t.metadata.provider.toLowerCase();\n const service = t.metadata.service.toLowerCase();\n return (\n !exceptSet.has(name) &&\n !exceptSet.has(provider) &&\n !exceptSet.has(service)\n );\n });\n }\n\n return filtered;\n}\n","import type { ToolMetadata } from \"./types.js\";\n\n/**\n * Creates an execute function for a tool that calls the supyagent API.\n *\n * Handles:\n * - Path parameter substitution (e.g., {messageId} in /api/v1/gmail/messages/{messageId})\n * - GET/DELETE: remaining args → query string\n * - POST/PUT/PATCH: merge bodyDefaults + remaining args → JSON body\n */\nexport function createExecutor(\n metadata: ToolMetadata,\n baseUrl: string,\n apiKey: string\n): (args: Record<string, unknown>) => Promise<unknown> {\n return async (args: Record<string, unknown>) => {\n const { method, path, bodyDefaults } = metadata;\n\n // Extract path parameters and substitute into the path\n const remainingArgs = { ...args };\n let resolvedPath = path;\n\n const pathParams = path.match(/\\{(\\w+)\\}/g);\n if (pathParams) {\n for (const param of pathParams) {\n const paramName = param.slice(1, -1);\n if (paramName in remainingArgs) {\n resolvedPath = resolvedPath.replace(\n param,\n encodeURIComponent(String(remainingArgs[paramName]))\n );\n delete remainingArgs[paramName];\n }\n }\n }\n\n let url = `${baseUrl}${resolvedPath}`;\n\n const fetchOptions: RequestInit = {\n method,\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n };\n\n if (method === \"GET\" || method === \"DELETE\") {\n // Remaining args go as query parameters\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(remainingArgs)) {\n if (value !== undefined && value !== null) {\n params.set(key, String(value));\n }\n }\n const qs = params.toString();\n if (qs) url += `?${qs}`;\n } else {\n // POST/PUT/PATCH: merge bodyDefaults with remaining args\n const body = { ...bodyDefaults, ...remainingArgs };\n fetchOptions.body = JSON.stringify(body);\n }\n\n const response = await fetch(url, fetchOptions);\n const data = await response.json();\n return data;\n };\n}\n","import type {\n SupyagentOptions,\n SupyagentClient,\n ToolFilterOptions,\n ToolsResponse,\n} from \"./types.js\";\nimport { TTLCache } from \"./cache.js\";\nimport { convertTools, filterTools } from \"./tool-converter.js\";\n\nconst DEFAULT_BASE_URL = \"https://app.supyagent.com\";\nconst CACHE_KEY = \"tools\";\n\n/**\n * Create a supyagent client for fetching and converting tools.\n *\n * @example\n * ```ts\n * import { supyagent } from '@supyagent/sdk';\n *\n * const client = supyagent({ apiKey: process.env.SUPYAGENT_API_KEY! });\n * const tools = await client.tools({ cache: 300 });\n * ```\n */\nexport function supyagent(options: SupyagentOptions): SupyagentClient {\n const { apiKey, baseUrl = DEFAULT_BASE_URL } = options;\n const cache = new TTLCache<ToolsResponse>();\n\n return {\n async tools(filterOptions?: ToolFilterOptions) {\n const cacheTTL = resolveCacheTTL(filterOptions?.cache);\n\n // Check cache\n let response = cacheTTL > 0 ? cache.get(CACHE_KEY) : undefined;\n\n if (!response) {\n // Fetch from API\n const res = await fetch(`${baseUrl}/api/v1/tools`, {\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!res.ok) {\n const error = await res.text();\n throw new Error(\n `Supyagent API error (${res.status}): ${error}`\n );\n }\n\n const json = await res.json();\n // API wraps response in { ok, data: { tools, base_url, total } }\n response = (json.data ?? json) as ToolsResponse;\n\n // Store in cache if TTL > 0\n if (cacheTTL > 0) {\n cache.set(CACHE_KEY, response, cacheTTL);\n }\n }\n\n // Use API-reported base_url for tool execution\n const toolBaseUrl = response.base_url || baseUrl;\n\n // Guard against non-array tools (e.g., API returns empty or unexpected shape)\n const toolsArray = Array.isArray(response.tools) ? response.tools : [];\n\n // Filter\n const filtered = filterTools(toolsArray, {\n only: filterOptions?.only,\n except: filterOptions?.except,\n });\n\n // Convert to AI SDK tools\n return convertTools(filtered, toolBaseUrl, apiKey);\n },\n };\n}\n\nfunction resolveCacheTTL(cache?: boolean | number): number {\n if (cache === true) return 60;\n if (typeof cache === \"number\") return cache;\n return 0;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/core/cache.ts","../src/core/tool-converter.ts","../src/core/http-executor.ts","../src/core/client.ts"],"sourcesContent":["interface CacheEntry<T> {\n data: T;\n expiresAt: number;\n}\n\nexport class TTLCache<T> {\n private store = new Map<string, CacheEntry<T>>();\n\n get(key: string): T | undefined {\n const entry = this.store.get(key);\n if (!entry) return undefined;\n if (Date.now() > entry.expiresAt) {\n this.store.delete(key);\n return undefined;\n }\n return entry.data;\n }\n\n set(key: string, data: T, ttlSeconds: number): void {\n this.store.set(key, {\n data,\n expiresAt: Date.now() + ttlSeconds * 1000,\n });\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n","import { tool, jsonSchema } from \"ai\";\nimport type { OpenAITool } from \"./types.js\";\nimport { createExecutor } from \"./http-executor.js\";\n\n/**\n * Converts an array of OpenAI-format tools from the supyagent API\n * into a Record of AI SDK tool() instances ready for streamText/generateText.\n */\nexport function convertTools(\n tools: OpenAITool[],\n baseUrl: string,\n apiKey: string\n): Record<string, import(\"ai\").Tool> {\n const result: Record<string, import(\"ai\").Tool> = {};\n\n for (const t of tools) {\n const { name, description, parameters } = t.function;\n const metadata = t.metadata;\n\n result[name] = tool({\n description,\n parameters: jsonSchema(parameters as Parameters<typeof jsonSchema>[0]),\n execute: createExecutor(metadata, baseUrl, apiKey),\n });\n }\n\n return result;\n}\n\n/**\n * Filter tools by provider, service, or tool name.\n */\nexport function filterTools(\n tools: OpenAITool[],\n options: { only?: string[]; except?: string[] }\n): OpenAITool[] {\n let filtered = tools;\n\n if (options.only && options.only.length > 0) {\n const onlySet = new Set(options.only.map((s) => s.toLowerCase()));\n filtered = filtered.filter((t) => {\n const name = t.function.name.toLowerCase();\n const provider = t.metadata.provider.toLowerCase();\n const service = t.metadata.service.toLowerCase();\n return onlySet.has(name) || onlySet.has(provider) || onlySet.has(service);\n });\n }\n\n if (options.except && options.except.length > 0) {\n const exceptSet = new Set(options.except.map((s) => s.toLowerCase()));\n filtered = filtered.filter((t) => {\n const name = t.function.name.toLowerCase();\n const provider = t.metadata.provider.toLowerCase();\n const service = t.metadata.service.toLowerCase();\n return (\n !exceptSet.has(name) &&\n !exceptSet.has(provider) &&\n !exceptSet.has(service)\n );\n });\n }\n\n return filtered;\n}\n","import type { ToolMetadata } from \"./types.js\";\n\n/**\n * Creates an execute function for a tool that calls the supyagent API.\n *\n * Handles:\n * - Path parameter substitution (e.g., {messageId} in /api/v1/gmail/messages/{messageId})\n * - GET/DELETE: remaining args → query string\n * - POST/PUT/PATCH: merge bodyDefaults + remaining args → JSON body\n */\nexport function createExecutor(\n metadata: ToolMetadata,\n baseUrl: string,\n apiKey: string\n): (args: Record<string, unknown>) => Promise<unknown> {\n return async (args: Record<string, unknown>) => {\n const { method, path, bodyDefaults } = metadata;\n\n // Extract path parameters and substitute into the path\n const remainingArgs = { ...args };\n let resolvedPath = path;\n\n const pathParams = path.match(/\\{(\\w+)\\}/g);\n if (pathParams) {\n for (const param of pathParams) {\n const paramName = param.slice(1, -1);\n if (paramName in remainingArgs) {\n resolvedPath = resolvedPath.replace(\n param,\n encodeURIComponent(String(remainingArgs[paramName]))\n );\n delete remainingArgs[paramName];\n }\n }\n\n // Fallback: LLMs often send { id } instead of { messageId }, { eventId }, etc.\n // because list responses return objects with an `id` field.\n // For any unresolved {fooId} param, try `id` from the args (once only).\n if (\"id\" in remainingArgs && resolvedPath.includes(\"{\")) {\n const unresolved = resolvedPath.match(/\\{(\\w+)\\}/g);\n if (unresolved) {\n for (const param of unresolved) {\n const paramName = param.slice(1, -1);\n if (paramName.toLowerCase().endsWith(\"id\")) {\n resolvedPath = resolvedPath.replace(\n param,\n encodeURIComponent(String(remainingArgs.id))\n );\n delete remainingArgs.id;\n break; // consume `id` only once\n }\n }\n }\n }\n }\n\n let url = `${baseUrl}${resolvedPath}`;\n\n const fetchOptions: RequestInit = {\n method,\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n };\n\n if (method === \"GET\" || method === \"DELETE\") {\n // Remaining args go as query parameters\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(remainingArgs)) {\n if (value !== undefined && value !== null) {\n params.set(key, String(value));\n }\n }\n const qs = params.toString();\n if (qs) url += `?${qs}`;\n } else {\n // POST/PUT/PATCH: merge bodyDefaults with remaining args\n const body = { ...bodyDefaults, ...remainingArgs };\n fetchOptions.body = JSON.stringify(body);\n }\n\n const response = await fetch(url, fetchOptions);\n const data = await response.json();\n return data;\n };\n}\n","import type {\n SupyagentOptions,\n SupyagentClient,\n ToolFilterOptions,\n ToolsResponse,\n} from \"./types.js\";\nimport { TTLCache } from \"./cache.js\";\nimport { convertTools, filterTools } from \"./tool-converter.js\";\n\nconst DEFAULT_BASE_URL = \"https://app.supyagent.com\";\nconst CACHE_KEY = \"tools\";\n\n/**\n * Create a supyagent client for fetching and converting tools.\n *\n * @example\n * ```ts\n * import { supyagent } from '@supyagent/sdk';\n *\n * const client = supyagent({ apiKey: process.env.SUPYAGENT_API_KEY! });\n * const tools = await client.tools({ cache: 300 });\n * ```\n */\nexport function supyagent(options: SupyagentOptions): SupyagentClient {\n const { apiKey, baseUrl = DEFAULT_BASE_URL } = options;\n const cache = new TTLCache<ToolsResponse>();\n\n return {\n async tools(filterOptions?: ToolFilterOptions) {\n const cacheTTL = resolveCacheTTL(filterOptions?.cache);\n\n // Check cache\n let response = cacheTTL > 0 ? cache.get(CACHE_KEY) : undefined;\n\n if (!response) {\n // Fetch from API\n const res = await fetch(`${baseUrl}/api/v1/tools`, {\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!res.ok) {\n const error = await res.text();\n throw new Error(\n `Supyagent API error (${res.status}): ${error}`\n );\n }\n\n const json = await res.json();\n // API wraps response in { ok, data: { tools, base_url, total } }\n response = (json.data ?? json) as ToolsResponse;\n\n // Store in cache if TTL > 0\n if (cacheTTL > 0) {\n cache.set(CACHE_KEY, response, cacheTTL);\n }\n }\n\n // Use API-reported base_url for tool execution\n const toolBaseUrl = response.base_url || baseUrl;\n\n // Guard against non-array tools (e.g., API returns empty or unexpected shape)\n const toolsArray = Array.isArray(response.tools) ? response.tools : [];\n\n // Filter\n const filtered = filterTools(toolsArray, {\n only: filterOptions?.only,\n except: filterOptions?.except,\n });\n\n // Convert to AI SDK tools\n return convertTools(filtered, toolBaseUrl, apiKey);\n },\n };\n}\n\nfunction resolveCacheTTL(cache?: boolean | number): number {\n if (cache === true) return 60;\n if (typeof cache === \"number\") return cache;\n return 0;\n}\n"],"mappings":"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","names":[]}
package/dist/prisma.cjs CHANGED
@@ -35,23 +35,24 @@ function createPrismaAdapter(prisma) {
35
35
  create: { id: chatId, title },
36
36
  update: { title }
37
37
  });
38
- const existing = await prisma.message.findMany({
39
- where: { chatId },
40
- select: { id: true }
41
- });
42
- const existingIds = new Set(existing.map((m) => m.id));
43
- const newMessages = messages.filter((msg) => !existingIds.has(msg.id));
44
- if (newMessages.length > 0) {
45
- await prisma.message.createMany({
46
- data: newMessages.map((msg) => ({
47
- id: msg.id,
48
- chatId,
49
- role: msg.role,
50
- parts: JSON.stringify(msg.parts),
51
- metadata: msg.metadata ? JSON.stringify(msg.metadata) : null
52
- }))
53
- });
54
- }
38
+ await prisma.$transaction(
39
+ messages.map(
40
+ (msg) => prisma.message.upsert({
41
+ where: { id: msg.id },
42
+ create: {
43
+ id: msg.id,
44
+ chatId,
45
+ role: msg.role,
46
+ parts: JSON.stringify(msg.parts),
47
+ metadata: msg.metadata ? JSON.stringify(msg.metadata) : null
48
+ },
49
+ update: {
50
+ parts: JSON.stringify(msg.parts),
51
+ metadata: msg.metadata ? JSON.stringify(msg.metadata) : null
52
+ }
53
+ })
54
+ )
55
+ );
55
56
  },
56
57
  async loadChat(chatId) {
57
58
  const messages = await prisma.message.findMany({
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/prisma.ts","../src/persistence/prisma-adapter.ts"],"sourcesContent":["export { createPrismaAdapter } from \"./persistence/prisma-adapter.js\";\nexport type { ChatAdapter, ChatSummary, UIMessageLike } from \"./persistence/types.js\";\n","import type { ChatAdapter, ChatSummary, UIMessageLike } from \"./types.js\";\n\n/**\n * Accepts any PrismaClient instance.\n * We use `any` here because Prisma generates unique client types per schema,\n * and a structural interface can't satisfy Prisma's branded enum types\n * (e.g. SortOrder). The implementation only calls standard Prisma methods.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype PrismaLike = any;\n\n/**\n * Create a ChatAdapter backed by Prisma.\n *\n * @example\n * ```ts\n * import { createPrismaAdapter } from '@supyagent/sdk/prisma';\n * import { prisma } from '@/lib/prisma';\n *\n * const adapter = createPrismaAdapter(prisma);\n * ```\n */\nexport function createPrismaAdapter(prisma: PrismaLike): ChatAdapter {\n return {\n async saveChat(chatId: string, messages: UIMessageLike[]) {\n // Extract title from first user message\n const firstUserMsg = messages.find((m) => m.role === \"user\");\n const title = extractTitle(firstUserMsg);\n\n // Upsert the chat record\n await prisma.chat.upsert({\n where: { id: chatId },\n create: { id: chatId, title },\n update: { title },\n });\n\n // Insert only messages not already persisted (idempotent)\n const existing = await prisma.message.findMany({\n where: { chatId },\n select: { id: true },\n });\n const existingIds = new Set(existing.map((m: { id: string }) => m.id));\n const newMessages = messages.filter((msg) => !existingIds.has(msg.id));\n\n if (newMessages.length > 0) {\n await prisma.message.createMany({\n data: newMessages.map((msg) => ({\n id: msg.id,\n chatId,\n role: msg.role,\n parts: JSON.stringify(msg.parts),\n metadata: msg.metadata ? JSON.stringify(msg.metadata) : null,\n })),\n });\n }\n },\n\n async loadChat(chatId: string) {\n const messages = await prisma.message.findMany({\n where: { chatId },\n orderBy: { createdAt: \"asc\" },\n });\n\n return messages.map((msg: { id: string; role: string; parts: string; metadata: string | null }) => ({\n id: msg.id,\n role: msg.role,\n parts: JSON.parse(msg.parts),\n metadata: msg.metadata ? JSON.parse(msg.metadata) : undefined,\n }));\n },\n\n async listChats() {\n return prisma.chat.findMany({\n orderBy: { updatedAt: \"desc\" },\n select: {\n id: true,\n title: true,\n createdAt: true,\n updatedAt: true,\n },\n });\n },\n\n async deleteChat(chatId: string) {\n await prisma.chat.delete({ where: { id: chatId } });\n },\n };\n}\n\nfunction extractTitle(message?: UIMessageLike): string {\n if (!message) return \"New Chat\";\n const textPart = message.parts.find((p) => p.type === \"text\");\n if (textPart && typeof textPart.text === \"string\") {\n return textPart.text.slice(0, 100);\n }\n return \"New Chat\";\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACsBO,SAAS,oBAAoB,QAAiC;AACnE,SAAO;AAAA,IACL,MAAM,SAAS,QAAgB,UAA2B;AAExD,YAAM,eAAe,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AAC3D,YAAM,QAAQ,aAAa,YAAY;AAGvC,YAAM,OAAO,KAAK,OAAO;AAAA,QACvB,OAAO,EAAE,IAAI,OAAO;AAAA,QACpB,QAAQ,EAAE,IAAI,QAAQ,MAAM;AAAA,QAC5B,QAAQ,EAAE,MAAM;AAAA,MAClB,CAAC;AAGD,YAAM,WAAW,MAAM,OAAO,QAAQ,SAAS;AAAA,QAC7C,OAAO,EAAE,OAAO;AAAA,QAChB,QAAQ,EAAE,IAAI,KAAK;AAAA,MACrB,CAAC;AACD,YAAM,cAAc,IAAI,IAAI,SAAS,IAAI,CAAC,MAAsB,EAAE,EAAE,CAAC;AACrE,YAAM,cAAc,SAAS,OAAO,CAAC,QAAQ,CAAC,YAAY,IAAI,IAAI,EAAE,CAAC;AAErE,UAAI,YAAY,SAAS,GAAG;AAC1B,cAAM,OAAO,QAAQ,WAAW;AAAA,UAC9B,MAAM,YAAY,IAAI,CAAC,SAAS;AAAA,YAC9B,IAAI,IAAI;AAAA,YACR;AAAA,YACA,MAAM,IAAI;AAAA,YACV,OAAO,KAAK,UAAU,IAAI,KAAK;AAAA,YAC/B,UAAU,IAAI,WAAW,KAAK,UAAU,IAAI,QAAQ,IAAI;AAAA,UAC1D,EAAE;AAAA,QACJ,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IAEA,MAAM,SAAS,QAAgB;AAC7B,YAAM,WAAW,MAAM,OAAO,QAAQ,SAAS;AAAA,QAC7C,OAAO,EAAE,OAAO;AAAA,QAChB,SAAS,EAAE,WAAW,MAAM;AAAA,MAC9B,CAAC;AAED,aAAO,SAAS,IAAI,CAAC,SAA+E;AAAA,QAClG,IAAI,IAAI;AAAA,QACR,MAAM,IAAI;AAAA,QACV,OAAO,KAAK,MAAM,IAAI,KAAK;AAAA,QAC3B,UAAU,IAAI,WAAW,KAAK,MAAM,IAAI,QAAQ,IAAI;AAAA,MACtD,EAAE;AAAA,IACJ;AAAA,IAEA,MAAM,YAAY;AAChB,aAAO,OAAO,KAAK,SAAS;AAAA,QAC1B,SAAS,EAAE,WAAW,OAAO;AAAA,QAC7B,QAAQ;AAAA,UACN,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,WAAW;AAAA,UACX,WAAW;AAAA,QACb;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,WAAW,QAAgB;AAC/B,YAAM,OAAO,KAAK,OAAO,EAAE,OAAO,EAAE,IAAI,OAAO,EAAE,CAAC;AAAA,IACpD;AAAA,EACF;AACF;AAEA,SAAS,aAAa,SAAiC;AACrD,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,WAAW,QAAQ,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AAC5D,MAAI,YAAY,OAAO,SAAS,SAAS,UAAU;AACjD,WAAO,SAAS,KAAK,MAAM,GAAG,GAAG;AAAA,EACnC;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/prisma.ts","../src/persistence/prisma-adapter.ts"],"sourcesContent":["export { createPrismaAdapter } from \"./persistence/prisma-adapter.js\";\nexport type { ChatAdapter, ChatSummary, UIMessageLike } from \"./persistence/types.js\";\n","import type { ChatAdapter, ChatSummary, UIMessageLike } from \"./types.js\";\n\n/**\n * Accepts any PrismaClient instance.\n * We use `any` here because Prisma generates unique client types per schema,\n * and a structural interface can't satisfy Prisma's branded enum types\n * (e.g. SortOrder). The implementation only calls standard Prisma methods.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype PrismaLike = any;\n\n/**\n * Create a ChatAdapter backed by Prisma.\n *\n * @example\n * ```ts\n * import { createPrismaAdapter } from '@supyagent/sdk/prisma';\n * import { prisma } from '@/lib/prisma';\n *\n * const adapter = createPrismaAdapter(prisma);\n * ```\n */\nexport function createPrismaAdapter(prisma: PrismaLike): ChatAdapter {\n return {\n async saveChat(chatId: string, messages: UIMessageLike[]) {\n // Extract title from first user message\n const firstUserMsg = messages.find((m) => m.role === \"user\");\n const title = extractTitle(firstUserMsg);\n\n // Upsert the chat record\n await prisma.chat.upsert({\n where: { id: chatId },\n create: { id: chatId, title },\n update: { title },\n });\n\n // Upsert each message works with SQLite and handles race conditions\n await prisma.$transaction(\n messages.map((msg) =>\n prisma.message.upsert({\n where: { id: msg.id },\n create: {\n id: msg.id,\n chatId,\n role: msg.role,\n parts: JSON.stringify(msg.parts),\n metadata: msg.metadata ? JSON.stringify(msg.metadata) : null,\n },\n update: {\n parts: JSON.stringify(msg.parts),\n metadata: msg.metadata ? JSON.stringify(msg.metadata) : null,\n },\n })\n )\n );\n },\n\n async loadChat(chatId: string) {\n const messages = await prisma.message.findMany({\n where: { chatId },\n orderBy: { createdAt: \"asc\" },\n });\n\n return messages.map((msg: { id: string; role: string; parts: string; metadata: string | null }) => ({\n id: msg.id,\n role: msg.role,\n parts: JSON.parse(msg.parts),\n metadata: msg.metadata ? JSON.parse(msg.metadata) : undefined,\n }));\n },\n\n async listChats() {\n return prisma.chat.findMany({\n orderBy: { updatedAt: \"desc\" },\n select: {\n id: true,\n title: true,\n createdAt: true,\n updatedAt: true,\n },\n });\n },\n\n async deleteChat(chatId: string) {\n await prisma.chat.delete({ where: { id: chatId } });\n },\n };\n}\n\nfunction extractTitle(message?: UIMessageLike): string {\n if (!message) return \"New Chat\";\n const textPart = message.parts.find((p) => p.type === \"text\");\n if (textPart && typeof textPart.text === \"string\") {\n return textPart.text.slice(0, 100);\n }\n return \"New Chat\";\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACsBO,SAAS,oBAAoB,QAAiC;AACnE,SAAO;AAAA,IACL,MAAM,SAAS,QAAgB,UAA2B;AAExD,YAAM,eAAe,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AAC3D,YAAM,QAAQ,aAAa,YAAY;AAGvC,YAAM,OAAO,KAAK,OAAO;AAAA,QACvB,OAAO,EAAE,IAAI,OAAO;AAAA,QACpB,QAAQ,EAAE,IAAI,QAAQ,MAAM;AAAA,QAC5B,QAAQ,EAAE,MAAM;AAAA,MAClB,CAAC;AAGD,YAAM,OAAO;AAAA,QACX,SAAS;AAAA,UAAI,CAAC,QACZ,OAAO,QAAQ,OAAO;AAAA,YACpB,OAAO,EAAE,IAAI,IAAI,GAAG;AAAA,YACpB,QAAQ;AAAA,cACN,IAAI,IAAI;AAAA,cACR;AAAA,cACA,MAAM,IAAI;AAAA,cACV,OAAO,KAAK,UAAU,IAAI,KAAK;AAAA,cAC/B,UAAU,IAAI,WAAW,KAAK,UAAU,IAAI,QAAQ,IAAI;AAAA,YAC1D;AAAA,YACA,QAAQ;AAAA,cACN,OAAO,KAAK,UAAU,IAAI,KAAK;AAAA,cAC/B,UAAU,IAAI,WAAW,KAAK,UAAU,IAAI,QAAQ,IAAI;AAAA,YAC1D;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,IAEA,MAAM,SAAS,QAAgB;AAC7B,YAAM,WAAW,MAAM,OAAO,QAAQ,SAAS;AAAA,QAC7C,OAAO,EAAE,OAAO;AAAA,QAChB,SAAS,EAAE,WAAW,MAAM;AAAA,MAC9B,CAAC;AAED,aAAO,SAAS,IAAI,CAAC,SAA+E;AAAA,QAClG,IAAI,IAAI;AAAA,QACR,MAAM,IAAI;AAAA,QACV,OAAO,KAAK,MAAM,IAAI,KAAK;AAAA,QAC3B,UAAU,IAAI,WAAW,KAAK,MAAM,IAAI,QAAQ,IAAI;AAAA,MACtD,EAAE;AAAA,IACJ;AAAA,IAEA,MAAM,YAAY;AAChB,aAAO,OAAO,KAAK,SAAS;AAAA,QAC1B,SAAS,EAAE,WAAW,OAAO;AAAA,QAC7B,QAAQ;AAAA,UACN,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,WAAW;AAAA,UACX,WAAW;AAAA,QACb;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,WAAW,QAAgB;AAC/B,YAAM,OAAO,KAAK,OAAO,EAAE,OAAO,EAAE,IAAI,OAAO,EAAE,CAAC;AAAA,IACpD;AAAA,EACF;AACF;AAEA,SAAS,aAAa,SAAiC;AACrD,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,WAAW,QAAQ,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AAC5D,MAAI,YAAY,OAAO,SAAS,SAAS,UAAU;AACjD,WAAO,SAAS,KAAK,MAAM,GAAG,GAAG;AAAA,EACnC;AACA,SAAO;AACT;","names":[]}
package/dist/prisma.js CHANGED
@@ -9,23 +9,24 @@ function createPrismaAdapter(prisma) {
9
9
  create: { id: chatId, title },
10
10
  update: { title }
11
11
  });
12
- const existing = await prisma.message.findMany({
13
- where: { chatId },
14
- select: { id: true }
15
- });
16
- const existingIds = new Set(existing.map((m) => m.id));
17
- const newMessages = messages.filter((msg) => !existingIds.has(msg.id));
18
- if (newMessages.length > 0) {
19
- await prisma.message.createMany({
20
- data: newMessages.map((msg) => ({
21
- id: msg.id,
22
- chatId,
23
- role: msg.role,
24
- parts: JSON.stringify(msg.parts),
25
- metadata: msg.metadata ? JSON.stringify(msg.metadata) : null
26
- }))
27
- });
28
- }
12
+ await prisma.$transaction(
13
+ messages.map(
14
+ (msg) => prisma.message.upsert({
15
+ where: { id: msg.id },
16
+ create: {
17
+ id: msg.id,
18
+ chatId,
19
+ role: msg.role,
20
+ parts: JSON.stringify(msg.parts),
21
+ metadata: msg.metadata ? JSON.stringify(msg.metadata) : null
22
+ },
23
+ update: {
24
+ parts: JSON.stringify(msg.parts),
25
+ metadata: msg.metadata ? JSON.stringify(msg.metadata) : null
26
+ }
27
+ })
28
+ )
29
+ );
29
30
  },
30
31
  async loadChat(chatId) {
31
32
  const messages = await prisma.message.findMany({
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/persistence/prisma-adapter.ts"],"sourcesContent":["import type { ChatAdapter, ChatSummary, UIMessageLike } from \"./types.js\";\n\n/**\n * Accepts any PrismaClient instance.\n * We use `any` here because Prisma generates unique client types per schema,\n * and a structural interface can't satisfy Prisma's branded enum types\n * (e.g. SortOrder). The implementation only calls standard Prisma methods.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype PrismaLike = any;\n\n/**\n * Create a ChatAdapter backed by Prisma.\n *\n * @example\n * ```ts\n * import { createPrismaAdapter } from '@supyagent/sdk/prisma';\n * import { prisma } from '@/lib/prisma';\n *\n * const adapter = createPrismaAdapter(prisma);\n * ```\n */\nexport function createPrismaAdapter(prisma: PrismaLike): ChatAdapter {\n return {\n async saveChat(chatId: string, messages: UIMessageLike[]) {\n // Extract title from first user message\n const firstUserMsg = messages.find((m) => m.role === \"user\");\n const title = extractTitle(firstUserMsg);\n\n // Upsert the chat record\n await prisma.chat.upsert({\n where: { id: chatId },\n create: { id: chatId, title },\n update: { title },\n });\n\n // Insert only messages not already persisted (idempotent)\n const existing = await prisma.message.findMany({\n where: { chatId },\n select: { id: true },\n });\n const existingIds = new Set(existing.map((m: { id: string }) => m.id));\n const newMessages = messages.filter((msg) => !existingIds.has(msg.id));\n\n if (newMessages.length > 0) {\n await prisma.message.createMany({\n data: newMessages.map((msg) => ({\n id: msg.id,\n chatId,\n role: msg.role,\n parts: JSON.stringify(msg.parts),\n metadata: msg.metadata ? JSON.stringify(msg.metadata) : null,\n })),\n });\n }\n },\n\n async loadChat(chatId: string) {\n const messages = await prisma.message.findMany({\n where: { chatId },\n orderBy: { createdAt: \"asc\" },\n });\n\n return messages.map((msg: { id: string; role: string; parts: string; metadata: string | null }) => ({\n id: msg.id,\n role: msg.role,\n parts: JSON.parse(msg.parts),\n metadata: msg.metadata ? JSON.parse(msg.metadata) : undefined,\n }));\n },\n\n async listChats() {\n return prisma.chat.findMany({\n orderBy: { updatedAt: \"desc\" },\n select: {\n id: true,\n title: true,\n createdAt: true,\n updatedAt: true,\n },\n });\n },\n\n async deleteChat(chatId: string) {\n await prisma.chat.delete({ where: { id: chatId } });\n },\n };\n}\n\nfunction extractTitle(message?: UIMessageLike): string {\n if (!message) return \"New Chat\";\n const textPart = message.parts.find((p) => p.type === \"text\");\n if (textPart && typeof textPart.text === \"string\") {\n return textPart.text.slice(0, 100);\n }\n return \"New Chat\";\n}\n"],"mappings":";AAsBO,SAAS,oBAAoB,QAAiC;AACnE,SAAO;AAAA,IACL,MAAM,SAAS,QAAgB,UAA2B;AAExD,YAAM,eAAe,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AAC3D,YAAM,QAAQ,aAAa,YAAY;AAGvC,YAAM,OAAO,KAAK,OAAO;AAAA,QACvB,OAAO,EAAE,IAAI,OAAO;AAAA,QACpB,QAAQ,EAAE,IAAI,QAAQ,MAAM;AAAA,QAC5B,QAAQ,EAAE,MAAM;AAAA,MAClB,CAAC;AAGD,YAAM,WAAW,MAAM,OAAO,QAAQ,SAAS;AAAA,QAC7C,OAAO,EAAE,OAAO;AAAA,QAChB,QAAQ,EAAE,IAAI,KAAK;AAAA,MACrB,CAAC;AACD,YAAM,cAAc,IAAI,IAAI,SAAS,IAAI,CAAC,MAAsB,EAAE,EAAE,CAAC;AACrE,YAAM,cAAc,SAAS,OAAO,CAAC,QAAQ,CAAC,YAAY,IAAI,IAAI,EAAE,CAAC;AAErE,UAAI,YAAY,SAAS,GAAG;AAC1B,cAAM,OAAO,QAAQ,WAAW;AAAA,UAC9B,MAAM,YAAY,IAAI,CAAC,SAAS;AAAA,YAC9B,IAAI,IAAI;AAAA,YACR;AAAA,YACA,MAAM,IAAI;AAAA,YACV,OAAO,KAAK,UAAU,IAAI,KAAK;AAAA,YAC/B,UAAU,IAAI,WAAW,KAAK,UAAU,IAAI,QAAQ,IAAI;AAAA,UAC1D,EAAE;AAAA,QACJ,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IAEA,MAAM,SAAS,QAAgB;AAC7B,YAAM,WAAW,MAAM,OAAO,QAAQ,SAAS;AAAA,QAC7C,OAAO,EAAE,OAAO;AAAA,QAChB,SAAS,EAAE,WAAW,MAAM;AAAA,MAC9B,CAAC;AAED,aAAO,SAAS,IAAI,CAAC,SAA+E;AAAA,QAClG,IAAI,IAAI;AAAA,QACR,MAAM,IAAI;AAAA,QACV,OAAO,KAAK,MAAM,IAAI,KAAK;AAAA,QAC3B,UAAU,IAAI,WAAW,KAAK,MAAM,IAAI,QAAQ,IAAI;AAAA,MACtD,EAAE;AAAA,IACJ;AAAA,IAEA,MAAM,YAAY;AAChB,aAAO,OAAO,KAAK,SAAS;AAAA,QAC1B,SAAS,EAAE,WAAW,OAAO;AAAA,QAC7B,QAAQ;AAAA,UACN,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,WAAW;AAAA,UACX,WAAW;AAAA,QACb;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,WAAW,QAAgB;AAC/B,YAAM,OAAO,KAAK,OAAO,EAAE,OAAO,EAAE,IAAI,OAAO,EAAE,CAAC;AAAA,IACpD;AAAA,EACF;AACF;AAEA,SAAS,aAAa,SAAiC;AACrD,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,WAAW,QAAQ,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AAC5D,MAAI,YAAY,OAAO,SAAS,SAAS,UAAU;AACjD,WAAO,SAAS,KAAK,MAAM,GAAG,GAAG;AAAA,EACnC;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/persistence/prisma-adapter.ts"],"sourcesContent":["import type { ChatAdapter, ChatSummary, UIMessageLike } from \"./types.js\";\n\n/**\n * Accepts any PrismaClient instance.\n * We use `any` here because Prisma generates unique client types per schema,\n * and a structural interface can't satisfy Prisma's branded enum types\n * (e.g. SortOrder). The implementation only calls standard Prisma methods.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype PrismaLike = any;\n\n/**\n * Create a ChatAdapter backed by Prisma.\n *\n * @example\n * ```ts\n * import { createPrismaAdapter } from '@supyagent/sdk/prisma';\n * import { prisma } from '@/lib/prisma';\n *\n * const adapter = createPrismaAdapter(prisma);\n * ```\n */\nexport function createPrismaAdapter(prisma: PrismaLike): ChatAdapter {\n return {\n async saveChat(chatId: string, messages: UIMessageLike[]) {\n // Extract title from first user message\n const firstUserMsg = messages.find((m) => m.role === \"user\");\n const title = extractTitle(firstUserMsg);\n\n // Upsert the chat record\n await prisma.chat.upsert({\n where: { id: chatId },\n create: { id: chatId, title },\n update: { title },\n });\n\n // Upsert each message works with SQLite and handles race conditions\n await prisma.$transaction(\n messages.map((msg) =>\n prisma.message.upsert({\n where: { id: msg.id },\n create: {\n id: msg.id,\n chatId,\n role: msg.role,\n parts: JSON.stringify(msg.parts),\n metadata: msg.metadata ? JSON.stringify(msg.metadata) : null,\n },\n update: {\n parts: JSON.stringify(msg.parts),\n metadata: msg.metadata ? JSON.stringify(msg.metadata) : null,\n },\n })\n )\n );\n },\n\n async loadChat(chatId: string) {\n const messages = await prisma.message.findMany({\n where: { chatId },\n orderBy: { createdAt: \"asc\" },\n });\n\n return messages.map((msg: { id: string; role: string; parts: string; metadata: string | null }) => ({\n id: msg.id,\n role: msg.role,\n parts: JSON.parse(msg.parts),\n metadata: msg.metadata ? JSON.parse(msg.metadata) : undefined,\n }));\n },\n\n async listChats() {\n return prisma.chat.findMany({\n orderBy: { updatedAt: \"desc\" },\n select: {\n id: true,\n title: true,\n createdAt: true,\n updatedAt: true,\n },\n });\n },\n\n async deleteChat(chatId: string) {\n await prisma.chat.delete({ where: { id: chatId } });\n },\n };\n}\n\nfunction extractTitle(message?: UIMessageLike): string {\n if (!message) return \"New Chat\";\n const textPart = message.parts.find((p) => p.type === \"text\");\n if (textPart && typeof textPart.text === \"string\") {\n return textPart.text.slice(0, 100);\n }\n return \"New Chat\";\n}\n"],"mappings":";AAsBO,SAAS,oBAAoB,QAAiC;AACnE,SAAO;AAAA,IACL,MAAM,SAAS,QAAgB,UAA2B;AAExD,YAAM,eAAe,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AAC3D,YAAM,QAAQ,aAAa,YAAY;AAGvC,YAAM,OAAO,KAAK,OAAO;AAAA,QACvB,OAAO,EAAE,IAAI,OAAO;AAAA,QACpB,QAAQ,EAAE,IAAI,QAAQ,MAAM;AAAA,QAC5B,QAAQ,EAAE,MAAM;AAAA,MAClB,CAAC;AAGD,YAAM,OAAO;AAAA,QACX,SAAS;AAAA,UAAI,CAAC,QACZ,OAAO,QAAQ,OAAO;AAAA,YACpB,OAAO,EAAE,IAAI,IAAI,GAAG;AAAA,YACpB,QAAQ;AAAA,cACN,IAAI,IAAI;AAAA,cACR;AAAA,cACA,MAAM,IAAI;AAAA,cACV,OAAO,KAAK,UAAU,IAAI,KAAK;AAAA,cAC/B,UAAU,IAAI,WAAW,KAAK,UAAU,IAAI,QAAQ,IAAI;AAAA,YAC1D;AAAA,YACA,QAAQ;AAAA,cACN,OAAO,KAAK,UAAU,IAAI,KAAK;AAAA,cAC/B,UAAU,IAAI,WAAW,KAAK,UAAU,IAAI,QAAQ,IAAI;AAAA,YAC1D;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,IAEA,MAAM,SAAS,QAAgB;AAC7B,YAAM,WAAW,MAAM,OAAO,QAAQ,SAAS;AAAA,QAC7C,OAAO,EAAE,OAAO;AAAA,QAChB,SAAS,EAAE,WAAW,MAAM;AAAA,MAC9B,CAAC;AAED,aAAO,SAAS,IAAI,CAAC,SAA+E;AAAA,QAClG,IAAI,IAAI;AAAA,QACR,MAAM,IAAI;AAAA,QACV,OAAO,KAAK,MAAM,IAAI,KAAK;AAAA,QAC3B,UAAU,IAAI,WAAW,KAAK,MAAM,IAAI,QAAQ,IAAI;AAAA,MACtD,EAAE;AAAA,IACJ;AAAA,IAEA,MAAM,YAAY;AAChB,aAAO,OAAO,KAAK,SAAS;AAAA,QAC1B,SAAS,EAAE,WAAW,OAAO;AAAA,QAC7B,QAAQ;AAAA,UACN,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,WAAW;AAAA,UACX,WAAW;AAAA,QACb;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,WAAW,QAAgB;AAC/B,YAAM,OAAO,KAAK,OAAO,EAAE,OAAO,EAAE,IAAI,OAAO,EAAE,CAAC;AAAA,IACpD;AAAA,EACF;AACF;AAEA,SAAS,aAAa,SAAiC;AACrD,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,WAAW,QAAQ,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AAC5D,MAAI,YAAY,OAAO,SAAS,SAAS,UAAU;AACjD,WAAO,SAAS,KAAK,MAAM,GAAG,GAAG;AAAA,EACnC;AACA,SAAO;AACT;","names":[]}
package/dist/react.cjs CHANGED
@@ -414,9 +414,15 @@ function getSlidesSummary(data) {
414
414
  return { text: "Slides result" };
415
415
  }
416
416
  function getHubspotSummary(data, toolName) {
417
- const isCompany = toolName.toLowerCase().includes("compan");
418
- const n = countItems(data, "results");
419
- if (n > 0) return { text: isCompany ? `Listed ${n} companies` : `Listed ${n} contacts`, badge: countBadge(n) };
417
+ const tl = toolName.toLowerCase();
418
+ const isDeal = tl.includes("deal");
419
+ const isCompany = tl.includes("compan");
420
+ const n = countItems(data, "results", "contacts", "companies", "deals");
421
+ if (n > 0) {
422
+ const label = isDeal ? "deals" : isCompany ? "companies" : "contacts";
423
+ return { text: `Listed ${n} ${label}`, badge: countBadge(n) };
424
+ }
425
+ if (isDeal) return { text: "Deal result" };
420
426
  return { text: isCompany ? "Company result" : "Contact result" };
421
427
  }
422
428
  function getLinearSummary(data, toolName) {
@@ -1340,9 +1346,50 @@ function isHubspotContact(data) {
1340
1346
  }
1341
1347
  function isHubspotCompany(data) {
1342
1348
  if (typeof data !== "object" || data === null) return false;
1343
- const props = data.properties;
1349
+ const d = data;
1350
+ if ("domain" in d && !("email" in d) && !("amount" in d)) return true;
1351
+ const props = d.properties;
1344
1352
  return props && ("name" in props || "domain" in props);
1345
1353
  }
1354
+ function isHubspotDeal(data) {
1355
+ if (typeof data !== "object" || data === null) return false;
1356
+ const d = data;
1357
+ return ("amount" in d || "stage" in d || "closeDate" in d) && !("email" in d);
1358
+ }
1359
+ function formatCurrency(value) {
1360
+ try {
1361
+ return new Intl.NumberFormat(void 0, {
1362
+ style: "currency",
1363
+ currency: "USD",
1364
+ minimumFractionDigits: 0,
1365
+ maximumFractionDigits: 0
1366
+ }).format(value);
1367
+ } catch {
1368
+ return `$${value.toLocaleString()}`;
1369
+ }
1370
+ }
1371
+ function formatDate(dateStr) {
1372
+ try {
1373
+ return new Date(dateStr).toLocaleDateString(void 0, {
1374
+ month: "short",
1375
+ day: "numeric",
1376
+ year: "numeric"
1377
+ });
1378
+ } catch {
1379
+ return dateStr;
1380
+ }
1381
+ }
1382
+ function getStageBadge(stage) {
1383
+ if (!stage) return null;
1384
+ const s = stage.toLowerCase();
1385
+ if (s === "closedwon" || s === "closed won")
1386
+ return { label: "Won", className: "text-green-500 bg-green-500/10" };
1387
+ if (s === "closedlost" || s === "closed lost")
1388
+ return { label: "Lost", className: "text-red-400 bg-red-400/10" };
1389
+ if (/^\d+$/.test(stage))
1390
+ return { label: "In Progress", className: "text-blue-400 bg-blue-400/10" };
1391
+ return { label: stage, className: "text-muted-foreground bg-muted" };
1392
+ }
1346
1393
  function ContactCard({ contact }) {
1347
1394
  const p = contact.properties || {};
1348
1395
  const first = contact.firstName || p.firstname;
@@ -1373,59 +1420,140 @@ function ContactCard({ contact }) {
1373
1420
  }
1374
1421
  function CompanyCard({ company }) {
1375
1422
  const p = company.properties || {};
1423
+ const name = company.name || p.name;
1424
+ const domain = company.domain || p.domain;
1425
+ const industry = company.industry || p.industry;
1426
+ const phone = p.phone;
1427
+ const employeeCount = company.employeeCount ?? (p.numberofemployees ? Number(p.numberofemployees) : null);
1376
1428
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rounded-lg border border-border bg-card p-3 space-y-1.5", children: [
1377
1429
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-start gap-2", children: [
1378
1430
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react12.Building2, { className: "h-4 w-4 text-muted-foreground mt-0.5 shrink-0" }),
1379
1431
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "min-w-0 flex-1", children: [
1380
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm font-medium text-foreground", children: p.name || "Unknown company" }),
1381
- p.industry && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-xs text-muted-foreground", children: p.industry })
1432
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm font-medium text-foreground", children: name || "Unknown company" }),
1433
+ industry && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-xs text-muted-foreground", children: industry })
1382
1434
  ] })
1383
1435
  ] }),
1384
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-wrap gap-x-4 gap-y-1 pl-6", children: [
1385
- p.domain && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-xs text-muted-foreground", children: p.domain }),
1386
- p.phone && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
1436
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-wrap gap-x-4 gap-y-1 pl-6 text-xs text-muted-foreground", children: [
1437
+ domain && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "flex items-center gap-1", children: [
1438
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react12.Globe, { className: "h-3 w-3" }),
1439
+ domain
1440
+ ] }),
1441
+ phone && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "flex items-center gap-1", children: [
1387
1442
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react12.Phone, { className: "h-3 w-3" }),
1388
- p.phone
1443
+ phone
1444
+ ] }),
1445
+ employeeCount != null && employeeCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "flex items-center gap-1", children: [
1446
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react12.Users, { className: "h-3 w-3" }),
1447
+ employeeCount.toLocaleString(),
1448
+ " employees"
1449
+ ] })
1450
+ ] })
1451
+ ] });
1452
+ }
1453
+ function DealCard({ deal }) {
1454
+ const stageBadge = getStageBadge(deal.stage);
1455
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rounded-lg border border-border bg-card p-3 space-y-1.5", children: [
1456
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-start gap-2", children: [
1457
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react12.Briefcase, { className: "h-4 w-4 text-muted-foreground mt-0.5 shrink-0" }),
1458
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "min-w-0 flex-1", children: [
1459
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm font-medium text-foreground", children: deal.name || "Untitled deal" }),
1460
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2 mt-0.5 flex-wrap", children: [
1461
+ deal.amount != null && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "flex items-center gap-1 text-xs font-medium text-foreground", children: [
1462
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react12.DollarSign, { className: "h-3 w-3" }),
1463
+ formatCurrency(deal.amount)
1464
+ ] }),
1465
+ stageBadge && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1466
+ "span",
1467
+ {
1468
+ className: `rounded-full px-2 py-0.5 text-xs ${stageBadge.className}`,
1469
+ children: stageBadge.label
1470
+ }
1471
+ )
1472
+ ] })
1389
1473
  ] })
1474
+ ] }),
1475
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-wrap gap-x-4 gap-y-1 pl-6 text-xs text-muted-foreground", children: [
1476
+ deal.closeDate && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "flex items-center gap-1", children: [
1477
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react12.Calendar, { className: "h-3 w-3" }),
1478
+ "Close: ",
1479
+ formatDate(deal.closeDate)
1480
+ ] }),
1481
+ deal.pipeline && deal.pipeline !== "default" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { children: deal.pipeline })
1390
1482
  ] })
1391
1483
  ] });
1392
1484
  }
1485
+ function ListWrapper({
1486
+ children,
1487
+ hasMore
1488
+ }) {
1489
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "space-y-2", children: [
1490
+ children,
1491
+ hasMore && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-xs text-muted-foreground text-center pt-1", children: "More results available" })
1492
+ ] });
1493
+ }
1494
+ function hasPaging(data) {
1495
+ if (typeof data !== "object" || data === null) return false;
1496
+ const paging = data.paging;
1497
+ return paging && typeof paging === "object" && paging.next;
1498
+ }
1393
1499
  function HubspotFormatter({ data }) {
1394
1500
  if (isHubspotContact(data)) {
1395
1501
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ContactCard, { contact: data });
1396
1502
  }
1503
+ if (isHubspotDeal(data)) {
1504
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DealCard, { deal: data });
1505
+ }
1397
1506
  if (isHubspotCompany(data)) {
1398
1507
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(CompanyCard, { company: data });
1399
1508
  }
1400
1509
  if (Array.isArray(data)) {
1510
+ const deals = data.filter(isHubspotDeal);
1511
+ if (deals.length > 0) {
1512
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ListWrapper, { children: deals.map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DealCard, { deal: d }, d.id || i)) });
1513
+ }
1401
1514
  const contacts = data.filter(isHubspotContact);
1402
1515
  if (contacts.length > 0) {
1403
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "space-y-2", children: contacts.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ContactCard, { contact: c }, c.id || i)) });
1516
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ListWrapper, { children: contacts.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ContactCard, { contact: c }, c.id || i)) });
1404
1517
  }
1405
1518
  const companies = data.filter(isHubspotCompany);
1406
1519
  if (companies.length > 0) {
1407
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "space-y-2", children: companies.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(CompanyCard, { company: c }, c.id || i)) });
1520
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ListWrapper, { children: companies.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(CompanyCard, { company: c }, c.id || i)) });
1408
1521
  }
1409
1522
  }
1410
- if (typeof data === "object" && data !== null && "contacts" in data) {
1411
- const contacts = data.contacts;
1412
- if (Array.isArray(contacts)) {
1413
- const valid = contacts.filter(isHubspotContact);
1523
+ if (typeof data === "object" && data !== null) {
1524
+ const d = data;
1525
+ const more = hasPaging(data);
1526
+ if ("contacts" in d && Array.isArray(d.contacts)) {
1527
+ const valid = d.contacts.filter(isHubspotContact);
1414
1528
  if (valid.length > 0) {
1415
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "space-y-2", children: valid.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ContactCard, { contact: c }, c.id || i)) });
1529
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ListWrapper, { hasMore: more, children: valid.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ContactCard, { contact: c }, c.id || i)) });
1416
1530
  }
1417
1531
  }
1418
- }
1419
- if (typeof data === "object" && data !== null && "results" in data) {
1420
- const results = data.results;
1421
- if (Array.isArray(results)) {
1532
+ if ("companies" in d && Array.isArray(d.companies)) {
1533
+ const valid = d.companies.filter(isHubspotCompany);
1534
+ if (valid.length > 0) {
1535
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ListWrapper, { hasMore: more, children: valid.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(CompanyCard, { company: c }, c.id || i)) });
1536
+ }
1537
+ }
1538
+ if ("deals" in d && Array.isArray(d.deals)) {
1539
+ const valid = d.deals.filter(isHubspotDeal);
1540
+ if (valid.length > 0) {
1541
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ListWrapper, { hasMore: more, children: valid.map((deal, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DealCard, { deal }, deal.id || i)) });
1542
+ }
1543
+ }
1544
+ if ("results" in d && Array.isArray(d.results)) {
1545
+ const results = d.results;
1546
+ const deals = results.filter(isHubspotDeal);
1547
+ if (deals.length > 0) {
1548
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ListWrapper, { hasMore: more, children: deals.map((deal, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DealCard, { deal }, deal.id || i)) });
1549
+ }
1422
1550
  const contacts = results.filter(isHubspotContact);
1423
1551
  if (contacts.length > 0) {
1424
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "space-y-2", children: contacts.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ContactCard, { contact: c }, c.id || i)) });
1552
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ListWrapper, { hasMore: more, children: contacts.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ContactCard, { contact: c }, c.id || i)) });
1425
1553
  }
1426
1554
  const companies = results.filter(isHubspotCompany);
1427
1555
  if (companies.length > 0) {
1428
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "space-y-2", children: companies.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(CompanyCard, { company: c }, c.id || i)) });
1556
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ListWrapper, { hasMore: more, children: companies.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(CompanyCard, { company: c }, c.id || i)) });
1429
1557
  }
1430
1558
  }
1431
1559
  }
@@ -1548,7 +1676,7 @@ var import_jsx_runtime14 = require("react/jsx-runtime");
1548
1676
  function isPipedriveDeal(data) {
1549
1677
  return typeof data === "object" && data !== null && ("title" in data || "value" in data || "status" in data) && !("subject" in data);
1550
1678
  }
1551
- function formatCurrency(value, currency) {
1679
+ function formatCurrency2(value, currency) {
1552
1680
  try {
1553
1681
  return new Intl.NumberFormat(void 0, {
1554
1682
  style: "currency",
@@ -1574,7 +1702,7 @@ function getStatusBadge(status) {
1574
1702
  return status ? { label: status, className: "text-muted-foreground bg-muted" } : null;
1575
1703
  }
1576
1704
  }
1577
- function DealCard({ deal }) {
1705
+ function DealCard2({ deal }) {
1578
1706
  const statusBadge = getStatusBadge(deal.status);
1579
1707
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "rounded-lg border border-border bg-card p-3 space-y-1.5", children: [
1580
1708
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-start gap-2", children: [
@@ -1584,7 +1712,7 @@ function DealCard({ deal }) {
1584
1712
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-2 mt-0.5 flex-wrap", children: [
1585
1713
  deal.value !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "flex items-center gap-1 text-xs font-medium text-foreground", children: [
1586
1714
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react14.DollarSign, { className: "h-3 w-3" }),
1587
- formatCurrency(deal.value, deal.currency)
1715
+ formatCurrency2(deal.value, deal.currency)
1588
1716
  ] }),
1589
1717
  statusBadge && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: `rounded-full px-2 py-0.5 text-xs ${statusBadge.className}`, children: statusBadge.label })
1590
1718
  ] })
@@ -1599,12 +1727,12 @@ function DealCard({ deal }) {
1599
1727
  }
1600
1728
  function PipedriveFormatter({ data }) {
1601
1729
  if (isPipedriveDeal(data)) {
1602
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DealCard, { deal: data });
1730
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DealCard2, { deal: data });
1603
1731
  }
1604
1732
  if (Array.isArray(data)) {
1605
1733
  const deals = data.filter(isPipedriveDeal);
1606
1734
  if (deals.length > 0) {
1607
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "space-y-2", children: deals.map((deal, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DealCard, { deal }, deal.id || i)) });
1735
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "space-y-2", children: deals.map((deal, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DealCard2, { deal }, deal.id || i)) });
1608
1736
  }
1609
1737
  }
1610
1738
  if (typeof data === "object" && data !== null && "deals" in data) {
@@ -1612,14 +1740,14 @@ function PipedriveFormatter({ data }) {
1612
1740
  if (Array.isArray(items)) {
1613
1741
  const deals = items.filter(isPipedriveDeal);
1614
1742
  if (deals.length > 0) {
1615
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "space-y-2", children: deals.map((deal, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DealCard, { deal }, deal.id || i)) });
1743
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "space-y-2", children: deals.map((deal, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DealCard2, { deal }, deal.id || i)) });
1616
1744
  }
1617
1745
  }
1618
1746
  }
1619
1747
  if (typeof data === "object" && data !== null && "deal" in data) {
1620
1748
  const item = data.deal;
1621
1749
  if (isPipedriveDeal(item)) {
1622
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DealCard, { deal: item });
1750
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DealCard2, { deal: item });
1623
1751
  }
1624
1752
  }
1625
1753
  if (typeof data === "object" && data !== null && "data" in data) {
@@ -1627,11 +1755,11 @@ function PipedriveFormatter({ data }) {
1627
1755
  if (Array.isArray(items)) {
1628
1756
  const deals = items.filter(isPipedriveDeal);
1629
1757
  if (deals.length > 0) {
1630
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "space-y-2", children: deals.map((deal, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DealCard, { deal }, deal.id || i)) });
1758
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "space-y-2", children: deals.map((deal, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DealCard2, { deal }, deal.id || i)) });
1631
1759
  }
1632
1760
  }
1633
1761
  if (isPipedriveDeal(items)) {
1634
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DealCard, { deal: items });
1762
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DealCard2, { deal: items });
1635
1763
  }
1636
1764
  }
1637
1765
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("pre", { className: "rounded-lg border border-border bg-background p-3 text-xs text-foreground overflow-x-auto max-h-96 overflow-y-auto", children: JSON.stringify(data, null, 2) });