mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
v0.6.50: ppt/doc/pdf worker isolation, docs, chat, sidebar improvements
This commit is contained in:
@@ -30,5 +30,25 @@ const shortId = generateShortId()
|
||||
const tiny = generateShortId(8)
|
||||
```
|
||||
|
||||
## Common Utilities
|
||||
Use shared helpers from `@/lib/core/utils/helpers` instead of writing inline implementations:
|
||||
|
||||
- `sleep(ms)` — async delay. Never write `new Promise(resolve => setTimeout(resolve, ms))`
|
||||
- `toError(value)` — normalize unknown caught values to `Error`. Never write `e instanceof Error ? e : new Error(String(e))`
|
||||
- `toError(value).message` — get error message safely. Never write `e instanceof Error ? e.message : String(e)`
|
||||
|
||||
```typescript
|
||||
// ✗ Bad
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
const msg = error instanceof Error ? error.message : String(error)
|
||||
const err = error instanceof Error ? error : new Error(String(error))
|
||||
|
||||
// ✓ Good
|
||||
import { sleep, toError } from '@/lib/core/utils/helpers'
|
||||
await sleep(1000)
|
||||
const msg = toError(error).message
|
||||
const err = toError(error)
|
||||
```
|
||||
|
||||
## Package Manager
|
||||
Use `bun` and `bunx`, not `npm` and `npx`.
|
||||
|
||||
@@ -37,5 +37,25 @@ const shortId = generateShortId()
|
||||
const tiny = generateShortId(8)
|
||||
```
|
||||
|
||||
## Common Utilities
|
||||
Use shared helpers from `@/lib/core/utils/helpers` instead of writing inline implementations:
|
||||
|
||||
- `sleep(ms)` — async delay. Never write `new Promise(resolve => setTimeout(resolve, ms))`
|
||||
- `toError(value)` — normalize unknown caught values to `Error`. Never write `e instanceof Error ? e : new Error(String(e))`
|
||||
- `toError(value).message` — get error message safely. Never write `e instanceof Error ? e.message : String(e)`
|
||||
|
||||
```typescript
|
||||
// ✗ Bad
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
const msg = error instanceof Error ? error.message : String(error)
|
||||
const err = error instanceof Error ? error : new Error(String(error))
|
||||
|
||||
// ✓ Good
|
||||
import { sleep, toError } from '@/lib/core/utils/helpers'
|
||||
await sleep(1000)
|
||||
const msg = toError(error).message
|
||||
const err = toError(error)
|
||||
```
|
||||
|
||||
## Package Manager
|
||||
Use `bun` and `bunx`, not `npm` and `npx`.
|
||||
|
||||
85
.cursor/rules/sim-sandbox.mdc
Normal file
85
.cursor/rules/sim-sandbox.mdc
Normal file
@@ -0,0 +1,85 @@
|
||||
---
|
||||
description: Isolated-vm sandbox worker security policy. Hard rules for anything that lives in the worker child process that runs user code.
|
||||
globs: ["apps/sim/lib/execution/isolated-vm-worker.cjs", "apps/sim/lib/execution/isolated-vm.ts", "apps/sim/lib/execution/sandbox/**", "apps/sim/sandbox-tasks/**"]
|
||||
---
|
||||
|
||||
# Sim Sandbox — Worker Security Policy
|
||||
|
||||
The isolated-vm worker child process at
|
||||
`apps/sim/lib/execution/isolated-vm-worker.cjs` runs untrusted user code inside
|
||||
V8 isolates. The process itself is a trust boundary. Everything in this rule is
|
||||
about what must **never** live in that process.
|
||||
|
||||
## Hard rules
|
||||
|
||||
1. **No app credentials in the worker process**. The worker must not hold, load,
|
||||
or receive via IPC: database URLs, Redis URLs, AWS keys, Stripe keys,
|
||||
session-signing keys, encryption keys, OAuth client secrets, internal API
|
||||
secrets, or any LLM / email / search provider API keys. If you catch yourself
|
||||
`require`'ing `@/lib/auth`, `@sim/db`, `@/lib/uploads/core/storage-service`,
|
||||
or anything that imports `env` directly inside the worker, stop and use a
|
||||
host-side broker instead.
|
||||
|
||||
2. **Host-side brokers own all credentialed work**. The worker can only access
|
||||
resources through `ivm.Reference` / `ivm.Callback` bridges back to the host
|
||||
process. Today the only broker is `workspaceFileBroker`
|
||||
(`apps/sim/lib/execution/sandbox/brokers/workspace-file.ts`); adding a new
|
||||
one requires co-reviewing this file.
|
||||
|
||||
3. **Host-side brokers must scope every resource access to a single tenant**.
|
||||
The `SandboxBrokerContext` always carries `workspaceId`. Any new broker that
|
||||
accesses storage, DB, or an external API must use `ctx.workspaceId` to scope
|
||||
the lookup — never accept a raw path, key, or URL from isolate code without
|
||||
validation.
|
||||
|
||||
4. **Nothing that runs in the isolate is trusted, even if we wrote it**. The
|
||||
task `bootstrap` and `finalize` strings in `apps/sim/sandbox-tasks/` execute
|
||||
inside the isolate. They must treat `globalThis` as adversarial — no pulling
|
||||
values from it that might have been mutated by user code. The hardening
|
||||
script in `executeTask` undefines dangerous globals before user code runs.
|
||||
|
||||
## Why
|
||||
|
||||
A V8 JIT bug (Chrome ships these roughly monthly) gives an attacker a native
|
||||
code primitive inside the process that owns whatever that process can reach.
|
||||
If the worker only holds `isolated-vm` + a single narrow workspace-file broker,
|
||||
a V8 escape leaks one tenant's files. If the worker holds a Stripe key or a DB
|
||||
connection, a V8 escape leaks the service.
|
||||
|
||||
The original `doc-worker.cjs` vulnerability (CVE-class, 225 production secrets
|
||||
leaked via `/proc/1/environ`) was the forcing function for this architecture.
|
||||
Keep the blast radius small.
|
||||
|
||||
## Checklist for changes to `isolated-vm-worker.cjs`
|
||||
|
||||
Before landing any change that adds a new `require(...)` or `process.send(...)`
|
||||
payload or `ivm.Reference` wrapper in the worker:
|
||||
|
||||
- [ ] Does it load a credential, key, connection string, or secret? If yes,
|
||||
move it host-side and expose as a broker.
|
||||
- [ ] Does it import from `@/lib/auth`, `@sim/db`, `@/lib/uploads/core/*`,
|
||||
`@/lib/core/config/env`, or any module that reads `process.env` of the
|
||||
main app? If yes, same — move host-side.
|
||||
- [ ] Does it expose a resource that's workspace-scoped without taking a
|
||||
`workspaceId`? If yes, re-scope.
|
||||
- [ ] Did you update the broker limits (`IVM_MAX_BROKER_ARGS_JSON_CHARS`,
|
||||
`IVM_MAX_BROKER_RESULT_JSON_CHARS`, `IVM_MAX_BROKERS_PER_EXECUTION`) if
|
||||
the new broker can emit large payloads or fire frequently?
|
||||
|
||||
## What the worker *may* hold
|
||||
|
||||
- `isolated-vm` module
|
||||
- Node built-ins: `node:fs` (only for reading the checked-in bundle `.cjs`
|
||||
files) and `node:path`
|
||||
- The three prebuilt library bundles under
|
||||
`apps/sim/lib/execution/sandbox/bundles/*.cjs`
|
||||
- IPC message handlers for `execute`, `cancel`, `fetchResponse`,
|
||||
`brokerResponse`
|
||||
|
||||
The worker deliberately has **no host-side logger**. All errors and
|
||||
diagnostics flow through IPC back to the host, which has `@sim/logger`. Do
|
||||
not add `createLogger` or console-based logging to the worker — it would
|
||||
require pulling the main app's config / env, which is exactly what this
|
||||
rule is preventing.
|
||||
|
||||
Anything else is suspect.
|
||||
@@ -8,6 +8,7 @@ You are a professional software engineer. All code must follow best practices: a
|
||||
- **Comments**: Use TSDoc for documentation. No `====` separators. No non-TSDoc comments
|
||||
- **Styling**: Never update global styles. Keep all styling local to components
|
||||
- **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@/lib/core/utils/uuid`
|
||||
- **Common Utilities**: Use shared helpers from `@/lib/core/utils/helpers` instead of inline implementations. `sleep(ms)` for delays, `toError(e)` to normalize caught values.
|
||||
- **Package Manager**: Use `bun` and `bunx`, not `npm` and `npx`
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useRef, useState } from 'react'
|
||||
import { cn, getAssetUrl } from '@/lib/utils'
|
||||
import { Lightbox } from './lightbox'
|
||||
|
||||
@@ -50,11 +50,14 @@ export function ActionImage({ src, alt, enableLightbox = true }: ActionImageProp
|
||||
}
|
||||
|
||||
export function ActionVideo({ src, alt, enableLightbox = true }: ActionVideoProps) {
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
const startTimeRef = useRef(0)
|
||||
const [isLightboxOpen, setIsLightboxOpen] = useState(false)
|
||||
const resolvedSrc = getAssetUrl(src)
|
||||
|
||||
const handleClick = () => {
|
||||
if (enableLightbox) {
|
||||
startTimeRef.current = videoRef.current?.currentTime ?? 0
|
||||
setIsLightboxOpen(true)
|
||||
}
|
||||
}
|
||||
@@ -62,6 +65,7 @@ export function ActionVideo({ src, alt, enableLightbox = true }: ActionVideoProp
|
||||
return (
|
||||
<>
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={resolvedSrc}
|
||||
autoPlay
|
||||
loop
|
||||
@@ -80,6 +84,7 @@ export function ActionVideo({ src, alt, enableLightbox = true }: ActionVideoProp
|
||||
src={src}
|
||||
alt={alt}
|
||||
type='video'
|
||||
startTime={startTimeRef.current}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
import { memo } from 'react'
|
||||
|
||||
const RX = '2.59574'
|
||||
|
||||
interface BlockRect {
|
||||
opacity: number
|
||||
width: string
|
||||
height: string
|
||||
fill: string
|
||||
x?: string
|
||||
y?: string
|
||||
transform?: string
|
||||
}
|
||||
|
||||
const RECTS = {
|
||||
topRight: [
|
||||
{ opacity: 1, x: '0', y: '0', width: '16.8626', height: '33.7252', fill: '#2ABBF8' },
|
||||
{ opacity: 0.6, x: '0', y: '0', width: '85.3433', height: '16.8626', fill: '#2ABBF8' },
|
||||
{ opacity: 1, x: '0', y: '0', width: '16.8626', height: '16.8626', fill: '#2ABBF8' },
|
||||
{ opacity: 0.6, x: '34.2403', y: '0', width: '34.2403', height: '33.7252', fill: '#2ABBF8' },
|
||||
{ opacity: 1, x: '34.2403', y: '0', width: '16.8626', height: '16.8626', fill: '#2ABBF8' },
|
||||
{
|
||||
opacity: 1,
|
||||
x: '51.6188',
|
||||
y: '16.8626',
|
||||
width: '16.8626',
|
||||
height: '16.8626',
|
||||
fill: '#2ABBF8',
|
||||
},
|
||||
{ opacity: 1, x: '68.4812', y: '0', width: '54.6502', height: '16.8626', fill: '#00F701' },
|
||||
{ opacity: 0.6, x: '106.268', y: '0', width: '34.2403', height: '33.7252', fill: '#00F701' },
|
||||
{ opacity: 0.6, x: '106.268', y: '0', width: '51.103', height: '16.8626', fill: '#00F701' },
|
||||
{
|
||||
opacity: 1,
|
||||
x: '123.6484',
|
||||
y: '16.8626',
|
||||
width: '16.8626',
|
||||
height: '16.8626',
|
||||
fill: '#00F701',
|
||||
},
|
||||
{ opacity: 0.6, x: '157.371', y: '0', width: '34.2403', height: '16.8626', fill: '#FFCC02' },
|
||||
{ opacity: 1, x: '157.371', y: '0', width: '16.8626', height: '16.8626', fill: '#FFCC02' },
|
||||
{ opacity: 0.6, x: '208.993', y: '0', width: '68.4805', height: '16.8626', fill: '#FA4EDF' },
|
||||
{ opacity: 0.6, x: '209.137', y: '0', width: '16.8626', height: '33.7252', fill: '#FA4EDF' },
|
||||
{ opacity: 0.6, x: '243.233', y: '0', width: '34.2403', height: '33.7252', fill: '#FA4EDF' },
|
||||
{ opacity: 1, x: '243.233', y: '0', width: '16.8626', height: '16.8626', fill: '#FA4EDF' },
|
||||
{ opacity: 0.6, x: '260.096', y: '0', width: '34.04', height: '16.8626', fill: '#FA4EDF' },
|
||||
{
|
||||
opacity: 1,
|
||||
x: '260.611',
|
||||
y: '16.8626',
|
||||
width: '16.8626',
|
||||
height: '16.8626',
|
||||
fill: '#FA4EDF',
|
||||
},
|
||||
],
|
||||
bottomLeft: [
|
||||
{ opacity: 1, x: '0', y: '0', width: '16.8626', height: '33.7252', fill: '#2ABBF8' },
|
||||
{ opacity: 0.6, x: '0', y: '0', width: '85.3433', height: '16.8626', fill: '#2ABBF8' },
|
||||
{ opacity: 1, x: '0', y: '0', width: '16.8626', height: '16.8626', fill: '#2ABBF8' },
|
||||
{ opacity: 0.6, x: '34.2403', y: '0', width: '34.2403', height: '33.7252', fill: '#2ABBF8' },
|
||||
{ opacity: 1, x: '34.2403', y: '0', width: '16.8626', height: '16.8626', fill: '#2ABBF8' },
|
||||
{
|
||||
opacity: 1,
|
||||
x: '51.6188',
|
||||
y: '16.8626',
|
||||
width: '16.8626',
|
||||
height: '16.8626',
|
||||
fill: '#2ABBF8',
|
||||
},
|
||||
{ opacity: 1, x: '68.4812', y: '0', width: '54.6502', height: '16.8626', fill: '#00F701' },
|
||||
{ opacity: 0.6, x: '106.268', y: '0', width: '34.2403', height: '33.7252', fill: '#00F701' },
|
||||
{ opacity: 0.6, x: '106.268', y: '0', width: '51.103', height: '16.8626', fill: '#00F701' },
|
||||
{
|
||||
opacity: 1,
|
||||
x: '123.6484',
|
||||
y: '16.8626',
|
||||
width: '16.8626',
|
||||
height: '16.8626',
|
||||
fill: '#00F701',
|
||||
},
|
||||
],
|
||||
bottomRight: [
|
||||
{
|
||||
opacity: 0.6,
|
||||
width: '16.8626',
|
||||
height: '33.726',
|
||||
fill: '#FA4EDF',
|
||||
transform: 'matrix(0 1 1 0 0 0)',
|
||||
},
|
||||
{
|
||||
opacity: 0.6,
|
||||
width: '34.241',
|
||||
height: '16.8626',
|
||||
fill: '#FA4EDF',
|
||||
transform: 'matrix(0 1 1 0 16.891 0)',
|
||||
},
|
||||
{
|
||||
opacity: 0.6,
|
||||
width: '16.8626',
|
||||
height: '68.482',
|
||||
fill: '#FA4EDF',
|
||||
transform: 'matrix(-1 0 0 1 33.739 16.888)',
|
||||
},
|
||||
{
|
||||
opacity: 0.6,
|
||||
width: '16.8626',
|
||||
height: '33.726',
|
||||
fill: '#FA4EDF',
|
||||
transform: 'matrix(0 1 1 0 0 33.776)',
|
||||
},
|
||||
{
|
||||
opacity: 1,
|
||||
width: '16.8626',
|
||||
height: '16.8626',
|
||||
fill: '#FA4EDF',
|
||||
transform: 'matrix(-1 0 0 1 33.739 34.272)',
|
||||
},
|
||||
{
|
||||
opacity: 0.6,
|
||||
width: '16.8626',
|
||||
height: '34.24',
|
||||
fill: '#2ABBF8',
|
||||
transform: 'matrix(-1 0 0 1 33.787 68)',
|
||||
},
|
||||
{
|
||||
opacity: 0.4,
|
||||
width: '16.8626',
|
||||
height: '16.8626',
|
||||
fill: '#1A8FCC',
|
||||
transform: 'matrix(-1 0 0 1 33.787 85)',
|
||||
},
|
||||
],
|
||||
} as const satisfies Record<string, readonly BlockRect[]>
|
||||
|
||||
const GLOBAL_OPACITY = 0.55
|
||||
|
||||
const BlockGroup = memo(function BlockGroup({
|
||||
width,
|
||||
height,
|
||||
viewBox,
|
||||
rects,
|
||||
}: {
|
||||
width: number
|
||||
height: number
|
||||
viewBox: string
|
||||
rects: readonly BlockRect[]
|
||||
}) {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox={viewBox}
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
className='h-auto w-full'
|
||||
style={{ opacity: GLOBAL_OPACITY }}
|
||||
>
|
||||
{rects.map((r, i) => (
|
||||
<rect
|
||||
key={i}
|
||||
x={r.x}
|
||||
y={r.y}
|
||||
width={r.width}
|
||||
height={r.height}
|
||||
rx={RX}
|
||||
fill={r.fill}
|
||||
transform={r.transform}
|
||||
opacity={r.opacity}
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
)
|
||||
})
|
||||
|
||||
export function AnimatedBlocks() {
|
||||
return (
|
||||
<div
|
||||
className='pointer-events-none fixed inset-0 z-0 hidden overflow-hidden lg:block'
|
||||
aria-hidden='true'
|
||||
>
|
||||
<div className='absolute top-[93px] right-0 w-[calc(140px+10.76vw)] max-w-[295px]'>
|
||||
<BlockGroup width={295} height={34} viewBox='0 0 295 34' rects={RECTS.topRight} />
|
||||
</div>
|
||||
|
||||
<div className='-left-24 absolute bottom-0 w-[calc(140px+10.76vw)] max-w-[295px] rotate-180'>
|
||||
<BlockGroup width={295} height={34} viewBox='0 0 295 34' rects={RECTS.bottomLeft} />
|
||||
</div>
|
||||
|
||||
<div className='-bottom-2 absolute right-0 w-[calc(16px+1.25vw)] max-w-[34px]'>
|
||||
<BlockGroup width={34} height={102} viewBox='0 0 34 102' rects={RECTS.bottomRight} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useState } from 'react'
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface FAQItem {
|
||||
question: string
|
||||
@@ -31,9 +32,10 @@ function FAQItemRow({
|
||||
className='flex w-full cursor-pointer items-center gap-3 px-4 py-2.5 text-left font-[470] text-[0.875rem] text-[rgba(0,0,0,0.8)] transition-colors hover:bg-[rgba(0,0,0,0.02)] dark:text-[rgba(255,255,255,0.85)] dark:hover:bg-[rgba(255,255,255,0.03)]'
|
||||
>
|
||||
<ChevronRight
|
||||
className={`h-3.5 w-3.5 shrink-0 text-[rgba(0,0,0,0.3)] transition-transform duration-200 dark:text-[rgba(255,255,255,0.3)] ${
|
||||
isOpen ? 'rotate-90' : ''
|
||||
}`}
|
||||
className={cn(
|
||||
'h-3.5 w-3.5 shrink-0 text-[rgba(0,0,0,0.3)] transition-transform duration-200 dark:text-[rgba(255,255,255,0.3)]',
|
||||
isOpen && 'rotate-90'
|
||||
)}
|
||||
/>
|
||||
{item.question}
|
||||
</button>
|
||||
@@ -81,11 +83,10 @@ export function FAQ({ items, title = 'Common Questions' }: FAQProps) {
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={
|
||||
index !== items.length - 1
|
||||
? 'border-[rgba(0,0,0,0.08)] border-b dark:border-[rgba(255,255,255,0.08)]'
|
||||
: ''
|
||||
}
|
||||
className={cn(
|
||||
index !== items.length - 1 &&
|
||||
'border-[rgba(0,0,0,0.08)] border-b dark:border-[rgba(255,255,255,0.08)]'
|
||||
)}
|
||||
>
|
||||
<FAQItemRow
|
||||
item={item}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Check } from 'lucide-react'
|
||||
import { useParams, usePathname, useRouter } from 'next/navigation'
|
||||
import {
|
||||
@@ -25,24 +24,9 @@ export function LanguageDropdown() {
|
||||
const params = useParams()
|
||||
const router = useRouter()
|
||||
|
||||
const [currentLang, setCurrentLang] = useState(() => {
|
||||
const langFromParams = params?.lang as string
|
||||
return langFromParams && Object.keys(languages).includes(langFromParams) ? langFromParams : 'en'
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const langFromParams = params?.lang as string
|
||||
|
||||
if (langFromParams && Object.keys(languages).includes(langFromParams)) {
|
||||
if (langFromParams !== currentLang) {
|
||||
setCurrentLang(langFromParams)
|
||||
}
|
||||
} else {
|
||||
if (currentLang !== 'en') {
|
||||
setCurrentLang('en')
|
||||
}
|
||||
}
|
||||
}, [params])
|
||||
const langFromParams = params?.lang as string
|
||||
const currentLang =
|
||||
langFromParams && Object.keys(languages).includes(langFromParams) ? langFromParams : 'en'
|
||||
|
||||
const handleLanguageChange = (locale: string) => {
|
||||
if (locale === currentLang) return
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useEffect, useLayoutEffect, useRef } from 'react'
|
||||
import { getAssetUrl } from '@/lib/utils'
|
||||
|
||||
interface LightboxProps {
|
||||
@@ -9,10 +9,12 @@ interface LightboxProps {
|
||||
src: string
|
||||
alt: string
|
||||
type: 'image' | 'video'
|
||||
startTime?: number
|
||||
}
|
||||
|
||||
export function Lightbox({ isOpen, onClose, src, alt, type }: LightboxProps) {
|
||||
export function Lightbox({ isOpen, onClose, src, alt, type, startTime }: LightboxProps) {
|
||||
const overlayRef = useRef<HTMLDivElement>(null)
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
@@ -40,6 +42,12 @@ export function Lightbox({ isOpen, onClose, src, alt, type }: LightboxProps) {
|
||||
}
|
||||
}, [isOpen, onClose])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (isOpen && type === 'video' && videoRef.current && startTime != null && startTime > 0) {
|
||||
videoRef.current.currentTime = startTime
|
||||
}
|
||||
}, [isOpen, startTime, type])
|
||||
|
||||
if (!isOpen) return null
|
||||
|
||||
return (
|
||||
@@ -61,6 +69,7 @@ export function Lightbox({ isOpen, onClose, src, alt, type }: LightboxProps) {
|
||||
/>
|
||||
) : (
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={getAssetUrl(src)}
|
||||
autoPlay
|
||||
loop
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { getAssetUrl } from '@/lib/utils'
|
||||
import { useRef, useState } from 'react'
|
||||
import { cn, getAssetUrl } from '@/lib/utils'
|
||||
import { Lightbox } from './lightbox'
|
||||
|
||||
interface VideoProps {
|
||||
@@ -12,6 +12,8 @@ interface VideoProps {
|
||||
muted?: boolean
|
||||
playsInline?: boolean
|
||||
enableLightbox?: boolean
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
||||
|
||||
export function Video({
|
||||
@@ -22,11 +24,16 @@ export function Video({
|
||||
muted = true,
|
||||
playsInline = true,
|
||||
enableLightbox = true,
|
||||
width,
|
||||
height,
|
||||
}: VideoProps) {
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
const startTimeRef = useRef(0)
|
||||
const [isLightboxOpen, setIsLightboxOpen] = useState(false)
|
||||
|
||||
const handleVideoClick = () => {
|
||||
if (enableLightbox) {
|
||||
startTimeRef.current = videoRef.current?.currentTime ?? 0
|
||||
setIsLightboxOpen(true)
|
||||
}
|
||||
}
|
||||
@@ -34,11 +41,17 @@ export function Video({
|
||||
return (
|
||||
<>
|
||||
<video
|
||||
ref={videoRef}
|
||||
autoPlay={autoPlay}
|
||||
loop={loop}
|
||||
muted={muted}
|
||||
playsInline={playsInline}
|
||||
className={`${className} ${enableLightbox ? 'cursor-pointer transition-opacity hover:opacity-95' : ''}`}
|
||||
width={width}
|
||||
height={height}
|
||||
className={cn(
|
||||
className,
|
||||
enableLightbox && 'cursor-pointer transition-opacity hover:opacity-95'
|
||||
)}
|
||||
src={getAssetUrl(src)}
|
||||
onClick={handleVideoClick}
|
||||
/>
|
||||
@@ -50,6 +63,7 @@ export function Video({
|
||||
src={src}
|
||||
alt={`Video: ${src}`}
|
||||
type='video'
|
||||
startTime={startTimeRef.current}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -5,6 +5,7 @@ description: Your per-workflow AI assistant for building and editing workflows.
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Image } from '@/components/ui/image'
|
||||
import { Video } from '@/components/ui/video'
|
||||
import { FAQ } from '@/components/ui/faq'
|
||||
|
||||
Copilot is the AI assistant built into every workflow editor. It is scoped to the workflow you have open — it reads the current structure, makes changes directly, and saves checkpoints so you can revert if needed.
|
||||
@@ -15,7 +16,7 @@ For workspace-wide tasks (managing multiple workflows, running research, working
|
||||
Copilot is a Sim-managed service. For self-hosted deployments, go to [sim.ai](https://sim.ai) → Settings → Copilot, generate a Copilot API key, then set `COPILOT_API_KEY` in your self-hosted environment.
|
||||
</Callout>
|
||||
|
||||
{/* TODO: Screenshot of the workflow editor with the Copilot panel open on the right side — showing a conversation with a workflow change applied. Ideally shows a message from the user, a response from Copilot, and the checkpoint icon visible on the message. */}
|
||||
<Video src="copilot/copilot.mp4" width={700} height={450} />
|
||||
|
||||
## What Copilot Can Do
|
||||
|
||||
|
||||
@@ -4,18 +4,17 @@ description: Upload, create, edit, and generate files — documents, presentatio
|
||||
---
|
||||
|
||||
import { Image } from '@/components/ui/image'
|
||||
import { Video } from '@/components/ui/video'
|
||||
import { FAQ } from '@/components/ui/faq'
|
||||
|
||||
Describe a document, presentation, image, or visualization and Mothership creates it — streaming the content live into the resource panel as it writes. Attach any file to your message and Mothership reads it, processes it, and saves it to your workspace.
|
||||
<Video src="mothership/files-pipeline-deals-summarizer.mp4" width={700} height={450} />
|
||||
|
||||
{/* TODO: Screenshot of Mothership with the File Write subagent active — file content streaming into the resource panel in split or preview mode. Shows the live streaming preview experience as a document is being written. */}
|
||||
Describe a document, presentation, image, or visualization and Mothership creates it — streaming the content live into the resource panel as it writes. Attach any file to your message and Mothership reads it, processes it, and saves it to your workspace.
|
||||
|
||||
## Uploading Files to the Workspace
|
||||
|
||||
Attach any file directly to your Mothership message — drag it into the input, paste it, or click the attachment icon. Mothership reads the file as context and saves it to your workspace.
|
||||
|
||||
{/* TODO: Screenshot of the Mothership input area showing a file attached — e.g., a PDF or image thumbnail visible in the input before sending. */}
|
||||
|
||||
Use this to:
|
||||
- Hand Mothership a document and ask it to process, summarize, or extract data from it
|
||||
- Upload a CSV and have it create a table from it
|
||||
@@ -48,6 +47,8 @@ Open a file using `@filename` or the **+** menu, then describe the change:
|
||||
|
||||
## Presentations
|
||||
|
||||
<Image src="/static/mothership/pptx-example.png" alt="Mothership resource panel showing a generated Mothership-Use-Cases.pptx file open with the title slide and first use case slide visible" width={900} height={500} />
|
||||
|
||||
Mothership can generate `.pptx` files:
|
||||
|
||||
- "Create a pitch deck for Q3 review — 8 slides covering growth, retention, and roadmap"
|
||||
@@ -58,8 +59,6 @@ Mothership can generate `.pptx` files:
|
||||
|
||||
The file is saved to your workspace and can be downloaded.
|
||||
|
||||
{/* TODO: Screenshot of the resource panel with a generated .pptx file open or a download prompt visible, showing the file name and confirming it was saved to the workspace. */}
|
||||
|
||||
## Images
|
||||
|
||||
Mothership can generate images using AI, and can use an existing image as a reference to guide the output:
|
||||
@@ -73,7 +72,7 @@ Mothership can generate images using AI, and can use an existing image as a refe
|
||||
- Attach an existing image to your message, then describe what you want: "Generate a new version of this banner with a blue color scheme instead of green"
|
||||
- "Create a variation of this diagram with the boxes rearranged horizontally [attach image]"
|
||||
|
||||
{/* TODO: Screenshot of the resource panel showing a generated image open as a file tab — ideally with the image rendered in the viewer panel. */}
|
||||
<Image src="/static/mothership/image-example.png" alt="Mothership resource panel showing a generated hero image of a Mothership-branded blimp flying over San Francisco at golden hour, alongside the chat response linking the file" width={900} height={500} />
|
||||
|
||||
Generated images are saved as workspace files.
|
||||
|
||||
@@ -85,7 +84,7 @@ Mothership can generate charts and data visualizations from data you describe or
|
||||
- "Create a line chart of token usage over the past 30 days from this data [paste data]"
|
||||
- "Generate a pie chart showing the distribution of lead sources from the leads table"
|
||||
|
||||
{/* TODO: Screenshot of a chart or visualization rendered in the resource panel as a file. */}
|
||||
<Image src="/static/mothership/chart-example.png" alt="Mothership resource panel showing a generated chart file with bar charts for backend 5xx errors and error rate over time" width={900} height={500} />
|
||||
|
||||
Visualizations are saved as files and rendered in the resource panel.
|
||||
|
||||
@@ -104,7 +103,7 @@ Results come back directly in the chat. Ask Mothership to save the output as a f
|
||||
|
||||
When a file opens in the resource panel, you can switch between three views:
|
||||
|
||||
{/* TODO: Screenshot of the file viewer in the resource panel showing the mode selector (editor/split/preview), ideally in split mode with a markdown file showing raw content on the left and rendered preview on the right. */}
|
||||
<Video src="mothership/toggle-file-view.mp4" width={700} height={450} />
|
||||
|
||||
| Mode | What it shows |
|
||||
|------|--------------|
|
||||
|
||||
@@ -4,11 +4,12 @@ description: Your AI command center. Build and manage your entire workspace in n
|
||||
---
|
||||
|
||||
import { Image } from '@/components/ui/image'
|
||||
import { Video } from '@/components/ui/video'
|
||||
import { FAQ } from '@/components/ui/faq'
|
||||
|
||||
Describe what you want and Mothership handles it. Build a workflow, run research, generate a presentation, query a table, schedule a recurring job, send a Slack message — Mothership knows your entire workspace and takes action directly.
|
||||
<Video src="mothership/create-workflow.mp4" width={700} height={450} />
|
||||
|
||||
{/* TODO: Screenshot or GIF of the full Mothership home page — chat pane on the left with a conversation in progress, resource panel on the right with a workflow or file tab open. Hero shot for the page. */}
|
||||
Describe what you want and Mothership handles it. Build a workflow, run research, generate a presentation, query a table, schedule a recurring job, send a Slack message — Mothership knows your entire workspace and takes action directly.
|
||||
|
||||
## What You Can Do
|
||||
|
||||
@@ -44,6 +45,8 @@ For complex tasks, Mothership delegates to specialized subagents automatically.
|
||||
|
||||
Bring any workspace object into the conversation via the **+** menu, `@`-mentions, or drag-and-drop from the sidebar. Mothership also opens resources automatically when it creates or modifies them.
|
||||
|
||||
<Video src="mothership/context-menu.mp4" width={700} height={450} />
|
||||
|
||||
{/* TODO: Screenshot of the resource panel with multiple tabs open — a workflow tab, a table tab, and a file tab — showing different resource types side by side. */}
|
||||
|
||||
| What to add | How it appears |
|
||||
@@ -59,6 +62,8 @@ Bring any workspace object into the conversation via the **+** menu, `@`-mention
|
||||
|
||||
Mothership has two panes. On the left: the chat thread, where your messages and Mothership's responses appear. On the right: the resource panel, where workflows, tables, files, and knowledge bases open as tabs. The panel is resizable; tabs are draggable and closeable.
|
||||
|
||||
<Video src="mothership/split-view.mp4" width={700} height={450} />
|
||||
|
||||
<FAQ items={[
|
||||
{ question: "How is Mothership different from Copilot?", answer: "Copilot is scoped to a single workflow — it helps you build and edit that workflow. Mothership has access to your entire workspace and can build workflows, manage data, run research, schedule jobs, take actions across integrations, and more." },
|
||||
{ question: "What model does Mothership use?", answer: "Mothership always uses Claude Opus 4.6. There is no model selector." },
|
||||
|
||||
@@ -4,11 +4,12 @@ description: Create, populate, and query knowledge bases from Mothership.
|
||||
---
|
||||
|
||||
import { Image } from '@/components/ui/image'
|
||||
import { Video } from '@/components/ui/video'
|
||||
import { FAQ } from '@/components/ui/faq'
|
||||
|
||||
Create a knowledge base, add documents to it, and query it in plain language — all through conversation. Knowledge bases you create in Mothership are immediately available to Agent blocks in any workflow.
|
||||
<Video src="mothership/kb.mp4" width={700} height={450} />
|
||||
|
||||
{/* TODO: Screenshot of Mothership with a knowledge base open in the resource panel — showing the knowledge base name, document list, and status of indexed documents. */}
|
||||
Create a knowledge base, add documents to it, and query it in plain language — all through conversation. Knowledge bases you create in Mothership are immediately available to Agent blocks in any workflow.
|
||||
|
||||
## Creating Knowledge Bases
|
||||
|
||||
|
||||
@@ -4,11 +4,12 @@ description: Ask Mothership to research anything — it searches, reads, and syn
|
||||
---
|
||||
|
||||
import { Image } from '@/components/ui/image'
|
||||
import { Video } from '@/components/ui/video'
|
||||
import { FAQ } from '@/components/ui/faq'
|
||||
|
||||
Ask Mothership to research anything and it figures out the best approach — searching the web, reading specific pages, crawling sites, looking up technical docs. Just describe what you want to know.
|
||||
<Video src="mothership/research-agent.mp4" width={700} height={450} />
|
||||
|
||||
{/* TODO: Screenshot of the Research subagent section in the Mothership chat — expanded, showing it working through a research task with the final report or answer appearing. Ideally with a file tab open in the resource panel showing the output. */}
|
||||
Ask Mothership to research anything and it figures out the best approach — searching the web, reading specific pages, crawling sites, looking up technical docs. Just describe what you want to know.
|
||||
|
||||
## Asking Questions
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ description: Create, query, and manage workspace tables from Mothership.
|
||||
import { Image } from '@/components/ui/image'
|
||||
import { FAQ } from '@/components/ui/faq'
|
||||
|
||||
Create a table from a description or a CSV, query it in plain language, add or update rows, and export the results — all through conversation. Tables open in the resource panel when created or referenced.
|
||||
<Image src="/static/mothership/table-example.png" alt="Mothership resource panel showing the pipeline_deals table with company, deal_owner, stage, and amount columns, alongside a chat summary of total pipeline value and breakdown by stage" width={900} height={500} />
|
||||
|
||||
{/* TODO: Screenshot of Mothership with a table open in the resource panel — ideally after a query or row operation, showing the table with data populated. */}
|
||||
Create a table from a description or a CSV, query it in plain language, add or update rows, and export the results — all through conversation. Tables open in the resource panel as soon as they're created or referenced.
|
||||
|
||||
## Creating Tables
|
||||
|
||||
|
||||
@@ -5,16 +5,17 @@ description: Schedule recurring jobs, take immediate actions, connect integratio
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Image } from '@/components/ui/image'
|
||||
import { Video } from '@/components/ui/video'
|
||||
import { FAQ } from '@/components/ui/faq'
|
||||
|
||||
<Video src="mothership/job-create.mp4" width={700} height={450} />
|
||||
|
||||
Mothership can act on your behalf right now — send a message, create an issue, call an API — or on a schedule, running a prompt automatically every hour, day, or week. It can also connect integrations, set environment variables, add MCP servers, and create custom tools.
|
||||
|
||||
## Scheduled Jobs
|
||||
|
||||
A scheduled job is a Mothership task that runs on a cron schedule. On each run, Mothership reads the current workspace state and executes the job's prompt as if you had just sent it.
|
||||
|
||||
{/* TODO: Screenshot of Mothership chat confirming a scheduled job was created — showing the job name, schedule, and what it will do. If there's a jobs list view in the sidebar, include that as a second screenshot here. */}
|
||||
|
||||
### Creating a Job
|
||||
|
||||
Describe the recurring task and how often it should run:
|
||||
|
||||
@@ -3,13 +3,13 @@ title: Workflows
|
||||
description: Create, edit, run, debug, deploy, and organize workflows from Mothership.
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Image } from '@/components/ui/image'
|
||||
import { Video } from '@/components/ui/video'
|
||||
import { FAQ } from '@/components/ui/faq'
|
||||
|
||||
Describe a workflow and Mothership builds it. Reference an existing one by name and it edits it. No canvas navigation required — every change appears in the resource panel in real time.
|
||||
<Video src="mothership/create-workflow.mp4" width={700} height={450} />
|
||||
|
||||
{/* TODO: Screenshot of Mothership chat on the left with the Build subagent section visible, and a workflow open in the resource panel on the right. Shows the split-pane experience of building via natural language. */}
|
||||
Describe a workflow and Mothership builds it. Reference an existing one by name and it edits it. No canvas navigation required — every change appears in the resource panel in real time.
|
||||
|
||||
## Creating Workflows
|
||||
|
||||
@@ -33,7 +33,7 @@ Open an existing workflow with `@workflow-name` or the **+** menu, then describe
|
||||
|
||||
## Running Workflows
|
||||
|
||||
{/* TODO: Screenshot or GIF of Mothership running a workflow — showing the chat streaming execution output on the left while the workflow canvas in the resource panel highlights blocks as they execute in real time. */}
|
||||
<Video src="mothership/run-workflow.mp4" width={700} height={450} />
|
||||
|
||||
Ask Mothership to run a workflow and it handles the execution:
|
||||
|
||||
@@ -110,10 +110,6 @@ Variables set this way are available via `<variable.VARIABLE_NAME>` syntax insid
|
||||
- "Delete the old_api_prototype workflow"
|
||||
- "Delete all workflows in the deprecated folder"
|
||||
|
||||
<Callout type="warn">
|
||||
Workflow deletion is permanent. Deployed versions are also removed. There is no recycle bin.
|
||||
</Callout>
|
||||
|
||||
<FAQ items={[
|
||||
{ question: "Can Mothership edit a workflow while it's deployed?", answer: "Yes. Editing a workflow does not affect the live deployment. The deployed version is a snapshot — you need to ask Mothership to redeploy to push changes to production." },
|
||||
{ question: "Can I run a workflow with specific inputs from Mothership?", answer: "Yes. Describe the inputs in your message and Mothership passes them to the workflow's start block." },
|
||||
|
||||
BIN
apps/docs/public/static/mothership/chart-example.png
Normal file
BIN
apps/docs/public/static/mothership/chart-example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 562 KiB |
BIN
apps/docs/public/static/mothership/image-example.png
Normal file
BIN
apps/docs/public/static/mothership/image-example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
BIN
apps/docs/public/static/mothership/pptx-example.png
Normal file
BIN
apps/docs/public/static/mothership/pptx-example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 726 KiB |
BIN
apps/docs/public/static/mothership/scheduled-task.png
Normal file
BIN
apps/docs/public/static/mothership/scheduled-task.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 268 KiB |
BIN
apps/docs/public/static/mothership/table-example.png
Normal file
BIN
apps/docs/public/static/mothership/table-example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 462 KiB |
@@ -12,6 +12,7 @@ import type {
|
||||
} from '@/lib/academy/types'
|
||||
import { validateExercise } from '@/lib/academy/validation'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { sleep } from '@/lib/core/utils/helpers'
|
||||
import { getEffectiveBlockOutputs } from '@/lib/workflows/blocks/block-outputs'
|
||||
import { getQueryClient } from '@/app/_shell/providers/get-query-client'
|
||||
import { GlobalCommandsProvider } from '@/app/workspace/[workspaceId]/providers/global-commands-provider'
|
||||
@@ -323,7 +324,7 @@ export function SandboxCanvasProvider({
|
||||
for (let i = 0; i < plan.length; i++) {
|
||||
const step = plan[i]
|
||||
setActiveBlocks(workflowId, new Set([step.blockId]))
|
||||
await new Promise((resolve) => setTimeout(resolve, step.delay))
|
||||
await sleep(step.delay)
|
||||
addConsole({
|
||||
workflowId,
|
||||
blockId: step.blockId,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { account, credential, credentialSetMember } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { and, desc, eq, inArray } from 'drizzle-orm'
|
||||
import { decryptSecret } from '@/lib/core/security/encryption'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { refreshOAuthToken } from '@/lib/oauth'
|
||||
import {
|
||||
getMicrosoftRefreshTokenExpiry,
|
||||
@@ -331,7 +332,7 @@ export async function getOAuthToken(userId: string, providerId: string): Promise
|
||||
return accessToken
|
||||
} catch (error) {
|
||||
logger.error(`Error refreshing token for user ${userId}, provider ${providerId}`, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
providerId,
|
||||
userId,
|
||||
@@ -460,7 +461,7 @@ export async function refreshAccessTokenIfNeeded(
|
||||
return refreshedToken.accessToken
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error refreshing token for credential`, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
providerId: credential.providerId,
|
||||
credentialId,
|
||||
@@ -664,7 +665,7 @@ export async function getCredentialsForCredentialSet(
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Failed to refresh token for user ${cred.userId}, provider ${providerId}`, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { and, eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { getBaseUrl } from '@/lib/core/utils/urls'
|
||||
import { isSameOrigin } from '@/lib/core/utils/validation'
|
||||
import { processCredentialDraft } from '@/lib/credentials/draft-processor'
|
||||
import { safeAccountInsert } from '@/app/api/auth/oauth/utils'
|
||||
|
||||
@@ -113,7 +114,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
const returnUrl = request.cookies.get('shopify_return_url')?.value
|
||||
|
||||
const redirectUrl = returnUrl || `${baseUrl}/workspace`
|
||||
const redirectUrl = returnUrl && isSameOrigin(returnUrl) ? returnUrl : `${baseUrl}/workspace`
|
||||
const finalUrl = new URL(redirectUrl)
|
||||
finalUrl.searchParams.set('shopify_connected', 'true')
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getSession } from '@/lib/auth'
|
||||
import { env } from '@/lib/core/config/env'
|
||||
import { getBaseUrl } from '@/lib/core/utils/urls'
|
||||
import { generateId } from '@/lib/core/utils/uuid'
|
||||
import { isSameOrigin } from '@/lib/core/utils/validation'
|
||||
import { getScopesForService } from '@/lib/oauth/utils'
|
||||
|
||||
const logger = createLogger('ShopifyAuthorize')
|
||||
@@ -192,7 +193,7 @@ export async function GET(request: NextRequest) {
|
||||
path: '/',
|
||||
})
|
||||
|
||||
if (returnUrl) {
|
||||
if (returnUrl && isSameOrigin(returnUrl)) {
|
||||
response.cookies.set('shopify_return_url', returnUrl, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
|
||||
@@ -3,6 +3,7 @@ import { headers } from 'next/headers'
|
||||
import { NextResponse } from 'next/server'
|
||||
import { auth } from '@/lib/auth'
|
||||
import { isAuthDisabled } from '@/lib/core/config/feature-flags'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
|
||||
const logger = createLogger('SocketTokenAPI')
|
||||
|
||||
@@ -36,7 +37,7 @@ export async function POST() {
|
||||
}
|
||||
|
||||
logger.error('Failed to generate socket token', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
return NextResponse.json({ error: 'Failed to generate token' }, { status: 500 })
|
||||
|
||||
@@ -147,6 +147,32 @@ export async function POST(request: NextRequest) {
|
||||
oidcConfig.userInfoEndpoint = userInfoEndpoint
|
||||
oidcConfig.jwksEndpoint = jwksEndpoint
|
||||
|
||||
const userProvidedEndpoints: Record<string, string | undefined> = {
|
||||
authorizationEndpoint,
|
||||
tokenEndpoint,
|
||||
userInfoEndpoint,
|
||||
jwksEndpoint,
|
||||
}
|
||||
|
||||
for (const [name, endpointUrl] of Object.entries(userProvidedEndpoints)) {
|
||||
if (endpointUrl) {
|
||||
const endpointValidation = await validateUrlWithDNS(endpointUrl, `OIDC ${name}`)
|
||||
if (!endpointValidation.isValid) {
|
||||
logger.warn('Explicitly provided OIDC endpoint failed SSRF validation', {
|
||||
endpoint: name,
|
||||
url: endpointUrl,
|
||||
error: endpointValidation.error,
|
||||
})
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: `OIDC ${name} failed security validation: ${endpointValidation.error}`,
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const needsDiscovery =
|
||||
!oidcConfig.authorizationEndpoint || !oidcConfig.tokenEndpoint || !oidcConfig.jwksEndpoint
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
hasUsableSubscriptionStatus,
|
||||
} from '@/lib/billing/subscriptions/utils'
|
||||
import { isBillingEnabled } from '@/lib/core/config/feature-flags'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { captureServerEvent } from '@/lib/posthog/server'
|
||||
|
||||
const logger = createLogger('SwitchPlan')
|
||||
@@ -185,7 +186,7 @@ export async function POST(request: NextRequest) {
|
||||
} catch (error) {
|
||||
logger.error('Failed to switch subscription', {
|
||||
userId: session?.user?.id,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
})
|
||||
return NextResponse.json(
|
||||
{ error: error instanceof Error ? error.message : 'Failed to switch plan' },
|
||||
|
||||
@@ -7,6 +7,7 @@ import { checkAndBillOverageThreshold } from '@/lib/billing/threshold-billing'
|
||||
import { checkInternalApiKey } from '@/lib/copilot/request/http'
|
||||
import { isBillingEnabled } from '@/lib/core/config/feature-flags'
|
||||
import { type AtomicClaimResult, billingIdempotency } from '@/lib/core/idempotency/service'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
|
||||
const logger = createLogger('BillingUpdateCostAPI')
|
||||
@@ -170,7 +171,7 @@ export async function POST(req: NextRequest) {
|
||||
const duration = Date.now() - startTime
|
||||
|
||||
logger.error(`[${requestId}] Cost update failed`, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
duration,
|
||||
})
|
||||
@@ -180,7 +181,7 @@ export async function POST(req: NextRequest) {
|
||||
.release(claim.normalizedKey, claim.storageMethod)
|
||||
.catch((releaseErr) => {
|
||||
logger.warn(`[${requestId}] Failed to release idempotency claim`, {
|
||||
error: releaseErr instanceof Error ? releaseErr.message : String(releaseErr),
|
||||
error: toError(releaseErr).message,
|
||||
normalizedKey: claim?.normalizedKey,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,6 +5,7 @@ import { SIM_AGENT_API_URL } from '@/lib/copilot/constants'
|
||||
import { authenticateCopilotRequestSessionOnly } from '@/lib/copilot/request/http'
|
||||
import { abortActiveStream, waitForPendingChatStream } from '@/lib/copilot/request/session'
|
||||
import { env } from '@/lib/core/config/env'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
|
||||
const logger = createLogger('CopilotChatAbortAPI')
|
||||
const GO_EXPLICIT_ABORT_TIMEOUT_MS = 3000
|
||||
@@ -20,7 +21,7 @@ export async function POST(request: Request) {
|
||||
|
||||
const body = await request.json().catch((err) => {
|
||||
logger.warn('Abort request body parse failed; continuing with empty object', {
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
error: toError(err).message,
|
||||
})
|
||||
return {}
|
||||
})
|
||||
@@ -35,7 +36,7 @@ export async function POST(request: Request) {
|
||||
const run = await getLatestRunForStream(streamId, authenticatedUserId).catch((err) => {
|
||||
logger.warn('getLatestRunForStream failed while resolving chatId for abort', {
|
||||
streamId,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
error: toError(err).message,
|
||||
})
|
||||
return null
|
||||
})
|
||||
@@ -70,7 +71,7 @@ export async function POST(request: Request) {
|
||||
} catch (err) {
|
||||
logger.warn('Explicit abort marker request failed; proceeding with local abort', {
|
||||
streamId,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
error: toError(err).message,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
import { readFilePreviewSessions } from '@/lib/copilot/request/session'
|
||||
import { readEvents } from '@/lib/copilot/request/session/buffer'
|
||||
import { toStreamBatchEvent } from '@/lib/copilot/request/session/types'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { authorizeWorkflowByWorkspacePermission } from '@/lib/workflows/utils'
|
||||
import { assertActiveWorkspaceAccess } from '@/lib/workspaces/permissions/utils'
|
||||
|
||||
@@ -82,7 +83,7 @@ export async function GET(req: NextRequest) {
|
||||
logger.warn('Failed to read preview sessions for copilot chat', {
|
||||
chatId,
|
||||
conversationId: chat.conversationId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
})
|
||||
return []
|
||||
}),
|
||||
@@ -90,7 +91,7 @@ export async function GET(req: NextRequest) {
|
||||
logger.warn('Failed to fetch latest run for copilot chat snapshot', {
|
||||
chatId,
|
||||
conversationId: chat.conversationId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
})
|
||||
return null
|
||||
}),
|
||||
@@ -110,7 +111,7 @@ export async function GET(req: NextRequest) {
|
||||
logger.warn('Failed to load copilot chat stream snapshot', {
|
||||
chatId,
|
||||
conversationId: chat.conversationId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
SSE_RESPONSE_HEADERS,
|
||||
} from '@/lib/copilot/request/session'
|
||||
import { toStreamBatchEvent } from '@/lib/copilot/request/session/types'
|
||||
import { sleep, toError } from '@/lib/core/utils/helpers'
|
||||
|
||||
export const maxDuration = 3600
|
||||
|
||||
@@ -97,7 +98,7 @@ export async function GET(request: NextRequest) {
|
||||
const run = await getLatestRunForStream(streamId, authenticatedUserId).catch((err) => {
|
||||
logger.warn('Failed to fetch latest run for stream', {
|
||||
streamId,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
error: toError(err).message,
|
||||
})
|
||||
return null
|
||||
})
|
||||
@@ -119,7 +120,7 @@ export async function GET(request: NextRequest) {
|
||||
readFilePreviewSessions(streamId).catch((error) => {
|
||||
logger.warn('Failed to read preview sessions for stream batch', {
|
||||
streamId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
})
|
||||
return []
|
||||
}),
|
||||
@@ -235,7 +236,7 @@ export async function GET(request: NextRequest) {
|
||||
(err) => {
|
||||
logger.warn('Failed to poll latest run for stream', {
|
||||
streamId,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
error: toError(err).message,
|
||||
})
|
||||
return null
|
||||
}
|
||||
@@ -273,7 +274,7 @@ export async function GET(request: NextRequest) {
|
||||
break
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS))
|
||||
await sleep(POLL_INTERVAL_MS)
|
||||
}
|
||||
if (!controllerClosed && Date.now() - startTime >= MAX_STREAM_MS) {
|
||||
emitTerminalIfMissing(MothershipStreamV1CompletionStatus.error, {
|
||||
@@ -286,7 +287,7 @@ export async function GET(request: NextRequest) {
|
||||
if (!controllerClosed && !request.signal.aborted) {
|
||||
logger.warn('Stream replay failed', {
|
||||
streamId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
})
|
||||
emitTerminalIfMissing(MothershipStreamV1CompletionStatus.error, {
|
||||
message: 'The stream replay failed before completion.',
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
createRequestTracker,
|
||||
createUnauthorizedResponse,
|
||||
} from '@/lib/copilot/request/http'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
|
||||
const logger = createLogger('CopilotConfirmAPI')
|
||||
|
||||
@@ -106,7 +107,7 @@ async function updateToolCallStatus(
|
||||
logger.error('Failed to update tool call status', {
|
||||
toolCallId,
|
||||
status,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
})
|
||||
return false
|
||||
}
|
||||
@@ -133,7 +134,7 @@ export async function POST(req: NextRequest) {
|
||||
const existing = await getAsyncToolCall(toolCallId).catch((err) => {
|
||||
logger.warn('Failed to fetch async tool call', {
|
||||
toolCallId,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
error: toError(err).message,
|
||||
})
|
||||
return null
|
||||
})
|
||||
@@ -145,7 +146,7 @@ export async function POST(req: NextRequest) {
|
||||
const run = await getRunSegment(existing.runId).catch((err) => {
|
||||
logger.warn('Failed to fetch run segment', {
|
||||
runId: existing.runId,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
error: toError(err).message,
|
||||
})
|
||||
return null
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { SIM_AGENT_API_URL } from '@/lib/copilot/constants'
|
||||
import { authenticateCopilotRequestSessionOnly } from '@/lib/copilot/request/http'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
|
||||
interface AvailableModel {
|
||||
id: string
|
||||
@@ -76,7 +77,7 @@ export async function GET(_req: NextRequest) {
|
||||
return NextResponse.json({ success: true, models })
|
||||
} catch (error) {
|
||||
logger.error('Error fetching available models', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
})
|
||||
return NextResponse.json(
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { verifyCronAuth } from '@/lib/auth/internal'
|
||||
import { JOB_RETENTION_HOURS, JOB_STATUS } from '@/lib/core/async-jobs'
|
||||
import { getMaxExecutionTimeout } from '@/lib/core/execution-limits'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
|
||||
const logger = createLogger('CleanupStaleExecutions')
|
||||
|
||||
@@ -73,7 +74,7 @@ export async function GET(request: NextRequest) {
|
||||
cleaned++
|
||||
} catch (error) {
|
||||
logger.error(`Failed to clean up execution ${execution.executionId}:`, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
})
|
||||
failed++
|
||||
}
|
||||
@@ -104,7 +105,7 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to clean up stale async jobs:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -131,7 +132,7 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to clean up stale pending jobs:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -158,7 +159,7 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to delete old async jobs:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -75,10 +75,12 @@ vi.mock('@/lib/uploads/utils/file-utils', () => ({
|
||||
|
||||
vi.mock('@/lib/uploads/setup.server', () => ({}))
|
||||
|
||||
vi.mock('@/lib/execution/doc-vm', () => ({
|
||||
generatePdfFromCode: vi.fn().mockResolvedValue(Buffer.from('%PDF-compiled')),
|
||||
generateDocxFromCode: vi.fn().mockResolvedValue(Buffer.from('PK\x03\x04compiled')),
|
||||
generatePptxFromCode: vi.fn().mockResolvedValue(Buffer.from('PK\x03\x04compiled')),
|
||||
vi.mock('@/lib/execution/sandbox/run-task', () => ({
|
||||
runSandboxTask: vi
|
||||
.fn()
|
||||
.mockImplementation(async (taskId: string) =>
|
||||
taskId === 'pdf-generate' ? Buffer.from('%PDF-compiled') : Buffer.from('PK\x03\x04compiled')
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/uploads/contexts/workspace/workspace-file-manager', () => ({
|
||||
|
||||
@@ -4,11 +4,7 @@ import { createLogger } from '@sim/logger'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { NextResponse } from 'next/server'
|
||||
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||
import {
|
||||
generateDocxFromCode,
|
||||
generatePdfFromCode,
|
||||
generatePptxFromCode,
|
||||
} from '@/lib/execution/doc-vm'
|
||||
import { runSandboxTask } from '@/lib/execution/sandbox/run-task'
|
||||
import { CopilotFiles, isUsingCloudStorage } from '@/lib/uploads'
|
||||
import type { StorageContext } from '@/lib/uploads/config'
|
||||
import { parseWorkspaceFileKey } from '@/lib/uploads/contexts/workspace/workspace-file-manager'
|
||||
@@ -22,6 +18,7 @@ import {
|
||||
findLocalFile,
|
||||
getContentType,
|
||||
} from '@/app/api/files/utils'
|
||||
import type { SandboxTaskId } from '@/sandbox-tasks/registry'
|
||||
|
||||
const logger = createLogger('FilesServeAPI')
|
||||
|
||||
@@ -30,24 +27,24 @@ const PDF_MAGIC = Buffer.from([0x25, 0x50, 0x44, 0x46, 0x2d]) // %PDF-
|
||||
|
||||
interface CompilableFormat {
|
||||
magic: Buffer
|
||||
compile: (code: string, workspaceId: string) => Promise<Buffer>
|
||||
taskId: SandboxTaskId
|
||||
contentType: string
|
||||
}
|
||||
|
||||
const COMPILABLE_FORMATS: Record<string, CompilableFormat> = {
|
||||
'.pptx': {
|
||||
magic: ZIP_MAGIC,
|
||||
compile: generatePptxFromCode,
|
||||
taskId: 'pptx-generate',
|
||||
contentType: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
},
|
||||
'.docx': {
|
||||
magic: ZIP_MAGIC,
|
||||
compile: generateDocxFromCode,
|
||||
taskId: 'docx-generate',
|
||||
contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
},
|
||||
'.pdf': {
|
||||
magic: PDF_MAGIC,
|
||||
compile: generatePdfFromCode,
|
||||
taskId: 'pdf-generate',
|
||||
contentType: 'application/pdf',
|
||||
},
|
||||
}
|
||||
@@ -65,8 +62,10 @@ function compiledCacheSet(key: string, buffer: Buffer): void {
|
||||
async function compileDocumentIfNeeded(
|
||||
buffer: Buffer,
|
||||
filename: string,
|
||||
workspaceId?: string,
|
||||
raw?: boolean
|
||||
workspaceId: string | undefined,
|
||||
raw: boolean,
|
||||
ownerKey: string | undefined,
|
||||
signal: AbortSignal | undefined
|
||||
): Promise<{ buffer: Buffer; contentType: string }> {
|
||||
if (raw) return { buffer, contentType: getContentType(filename) }
|
||||
|
||||
@@ -90,7 +89,11 @@ async function compileDocumentIfNeeded(
|
||||
return { buffer: cached, contentType: format.contentType }
|
||||
}
|
||||
|
||||
const compiled = await format.compile(code, workspaceId || '')
|
||||
const compiled = await runSandboxTask(
|
||||
format.taskId,
|
||||
{ code, workspaceId: workspaceId || '' },
|
||||
{ ownerKey, signal }
|
||||
)
|
||||
compiledCacheSet(cacheKey, compiled)
|
||||
return { buffer: compiled, contentType: format.contentType }
|
||||
}
|
||||
@@ -153,10 +156,10 @@ export async function GET(
|
||||
const userId = authResult.userId
|
||||
|
||||
if (isUsingCloudStorage()) {
|
||||
return await handleCloudProxy(cloudKey, userId, raw)
|
||||
return await handleCloudProxy(cloudKey, userId, raw, request.signal)
|
||||
}
|
||||
|
||||
return await handleLocalFile(cloudKey, userId, raw)
|
||||
return await handleLocalFile(cloudKey, userId, raw, request.signal)
|
||||
} catch (error) {
|
||||
logger.error('Error serving file:', error)
|
||||
|
||||
@@ -171,8 +174,10 @@ export async function GET(
|
||||
async function handleLocalFile(
|
||||
filename: string,
|
||||
userId: string,
|
||||
raw: boolean
|
||||
raw: boolean,
|
||||
signal: AbortSignal | undefined
|
||||
): Promise<NextResponse> {
|
||||
const ownerKey = `user:${userId}`
|
||||
try {
|
||||
const contextParam: StorageContext | undefined = inferContextFromKey(filename) as
|
||||
| StorageContext
|
||||
@@ -205,7 +210,9 @@ async function handleLocalFile(
|
||||
rawBuffer,
|
||||
displayName,
|
||||
workspaceId,
|
||||
raw
|
||||
raw,
|
||||
ownerKey,
|
||||
signal
|
||||
)
|
||||
|
||||
logger.info('Local file served', { userId, filename, size: fileBuffer.length })
|
||||
@@ -225,8 +232,10 @@ async function handleLocalFile(
|
||||
async function handleCloudProxy(
|
||||
cloudKey: string,
|
||||
userId: string,
|
||||
raw = false
|
||||
raw = false,
|
||||
signal: AbortSignal | undefined = undefined
|
||||
): Promise<NextResponse> {
|
||||
const ownerKey = `user:${userId}`
|
||||
try {
|
||||
const context = inferContextFromKey(cloudKey)
|
||||
logger.info(`Inferred context: ${context} from key pattern: ${cloudKey}`)
|
||||
@@ -262,7 +271,9 @@ async function handleCloudProxy(
|
||||
rawBuffer,
|
||||
displayName,
|
||||
workspaceId,
|
||||
raw
|
||||
raw,
|
||||
ownerKey,
|
||||
signal
|
||||
)
|
||||
|
||||
logger.info('Cloud file served', {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
||||
import { getJobQueue } from '@/lib/core/async-jobs'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { createErrorResponse } from '@/app/api/workflows/utils'
|
||||
|
||||
@@ -70,7 +71,7 @@ export async function GET(
|
||||
|
||||
return NextResponse.json(response)
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
const errorMessage = toError(error).message
|
||||
logger.error(`[${requestId}] Error fetching task status:`, error)
|
||||
|
||||
if (errorMessage?.includes('not found')) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import { prepareExecutionContext } from '@/lib/copilot/tools/handlers/context'
|
||||
import { DIRECT_TOOL_DEFS, SUBAGENT_TOOL_DEFS } from '@/lib/copilot/tools/mcp/definitions'
|
||||
import { env } from '@/lib/core/config/env'
|
||||
import { RateLimiter } from '@/lib/core/rate-limiter'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getBaseUrl } from '@/lib/core/utils/urls'
|
||||
import { generateId } from '@/lib/core/utils/uuid'
|
||||
import {
|
||||
@@ -231,7 +232,7 @@ class NextResponseCapture {
|
||||
try {
|
||||
handler()
|
||||
} catch (error) {
|
||||
this.triggerErrorHandlers(error instanceof Error ? error : new Error(String(error)))
|
||||
this.triggerErrorHandlers(toError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,7 +291,7 @@ class NextResponseCapture {
|
||||
try {
|
||||
this._controller.enqueue(normalized)
|
||||
} catch (error) {
|
||||
this.triggerErrorHandlers(error instanceof Error ? error : new Error(String(error)))
|
||||
this.triggerErrorHandlers(toError(error))
|
||||
}
|
||||
} else {
|
||||
this._pendingChunks.push(normalized)
|
||||
@@ -311,7 +312,7 @@ class NextResponseCapture {
|
||||
try {
|
||||
this._controller.close()
|
||||
} catch (error) {
|
||||
this.triggerErrorHandlers(error instanceof Error ? error : new Error(String(error)))
|
||||
this.triggerErrorHandlers(toError(error))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -659,7 +660,7 @@ async function handleDirectToolCall(
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||
text: `Tool execution failed: ${toError(error).message}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
@@ -740,7 +741,7 @@ async function handleBuildToolCall(
|
||||
logger.warn('Failed to generate workspace context for build tool call', {
|
||||
workflowId: resolved.workflowId,
|
||||
workspaceId: resolvedWorkspaceId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -789,7 +790,7 @@ async function handleBuildToolCall(
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Build failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||
text: `Build failed: ${toError(error).message}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
@@ -880,7 +881,7 @@ async function handleSubagentToolCall(
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Subagent call failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||
text: `Subagent call failed: ${toError(error).message}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { mcpServers, workflow, workflowBlocks } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { and, eq, isNull } from 'drizzle-orm'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { withMcpAuth } from '@/lib/mcp/middleware'
|
||||
import { mcpService } from '@/lib/mcp/service'
|
||||
import type { McpServerStatusConfig, McpTool, McpToolSchema } from '@/lib/mcp/types'
|
||||
@@ -249,11 +250,7 @@ export const POST = withMcpAuth<{ id: string }>('read')(
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error refreshing MCP server:`, error)
|
||||
return createMcpErrorResponse(
|
||||
error instanceof Error ? error : new Error('Failed to refresh MCP server'),
|
||||
'Failed to refresh MCP server',
|
||||
500
|
||||
)
|
||||
return createMcpErrorResponse(toError(error), 'Failed to refresh MCP server', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { and, eq, isNull } from 'drizzle-orm'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import {
|
||||
McpDnsResolutionError,
|
||||
McpDomainNotAllowedError,
|
||||
@@ -138,11 +139,7 @@ export const PATCH = withMcpAuth<{ id: string }>('write')(
|
||||
return createMcpSuccessResponse({ server: updatedServer })
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error updating MCP server:`, error)
|
||||
return createMcpErrorResponse(
|
||||
error instanceof Error ? error : new Error('Failed to update MCP server'),
|
||||
'Failed to update MCP server',
|
||||
500
|
||||
)
|
||||
return createMcpErrorResponse(toError(error), 'Failed to update MCP server', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { and, eq, isNull } from 'drizzle-orm'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { generateId } from '@/lib/core/utils/uuid'
|
||||
import {
|
||||
McpDnsResolutionError,
|
||||
@@ -44,11 +45,7 @@ export const GET = withMcpAuth('read')(
|
||||
return createMcpSuccessResponse({ servers })
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error listing MCP servers:`, error)
|
||||
return createMcpErrorResponse(
|
||||
error instanceof Error ? error : new Error('Failed to list MCP servers'),
|
||||
'Failed to list MCP servers',
|
||||
500
|
||||
)
|
||||
return createMcpErrorResponse(toError(error), 'Failed to list MCP servers', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -220,11 +217,7 @@ export const POST = withMcpAuth('write')(
|
||||
return createMcpSuccessResponse({ serverId }, 201)
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error registering MCP server:`, error)
|
||||
return createMcpErrorResponse(
|
||||
error instanceof Error ? error : new Error('Failed to register MCP server'),
|
||||
'Failed to register MCP server',
|
||||
500
|
||||
)
|
||||
return createMcpErrorResponse(toError(error), 'Failed to register MCP server', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -297,11 +290,7 @@ export const DELETE = withMcpAuth('admin')(
|
||||
return createMcpSuccessResponse({ message: `Server ${serverId} deleted successfully` })
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error deleting MCP server:`, error)
|
||||
return createMcpErrorResponse(
|
||||
error instanceof Error ? error : new Error('Failed to delete MCP server'),
|
||||
'Failed to delete MCP server',
|
||||
500
|
||||
)
|
||||
return createMcpErrorResponse(toError(error), 'Failed to delete MCP server', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { McpClient } from '@/lib/mcp/client'
|
||||
import {
|
||||
McpDnsResolutionError,
|
||||
@@ -220,11 +221,7 @@ export const POST = withMcpAuth('write')(
|
||||
return createMcpSuccessResponse(result, result.success ? 200 : 400)
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error testing MCP server connection:`, error)
|
||||
return createMcpErrorResponse(
|
||||
error instanceof Error ? error : new Error('Failed to test server connection'),
|
||||
'Failed to test server connection',
|
||||
500
|
||||
)
|
||||
return createMcpErrorResponse(toError(error), 'Failed to test server connection', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ import { workflow, workflowBlocks } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { withMcpAuth } from '@/lib/mcp/middleware'
|
||||
import type { McpToolSchema, StoredMcpTool } from '@/lib/mcp/types'
|
||||
import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils'
|
||||
@@ -70,11 +71,7 @@ export const GET = withMcpAuth('read')(
|
||||
return createMcpSuccessResponse({ tools: storedTools })
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error fetching stored MCP tools:`, error)
|
||||
return createMcpErrorResponse(
|
||||
error instanceof Error ? error : new Error('Failed to fetch stored MCP tools'),
|
||||
'Failed to fetch stored MCP tools',
|
||||
500
|
||||
)
|
||||
return createMcpErrorResponse(toError(error), 'Failed to fetch stored MCP tools', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { and, eq, isNull } from 'drizzle-orm'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware'
|
||||
import { mcpPubSub } from '@/lib/mcp/pubsub'
|
||||
import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils'
|
||||
@@ -63,11 +64,7 @@ export const GET = withMcpAuth<RouteParams>('read')(
|
||||
return createMcpSuccessResponse({ server, tools })
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error getting workflow MCP server:`, error)
|
||||
return createMcpErrorResponse(
|
||||
error instanceof Error ? error : new Error('Failed to get workflow MCP server'),
|
||||
'Failed to get workflow MCP server',
|
||||
500
|
||||
)
|
||||
return createMcpErrorResponse(toError(error), 'Failed to get workflow MCP server', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -146,11 +143,7 @@ export const PATCH = withMcpAuth<RouteParams>('write')(
|
||||
return createMcpSuccessResponse({ server: updatedServer })
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error updating workflow MCP server:`, error)
|
||||
return createMcpErrorResponse(
|
||||
error instanceof Error ? error : new Error('Failed to update workflow MCP server'),
|
||||
'Failed to update workflow MCP server',
|
||||
500
|
||||
)
|
||||
return createMcpErrorResponse(toError(error), 'Failed to update workflow MCP server', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -201,11 +194,7 @@ export const DELETE = withMcpAuth<RouteParams>('admin')(
|
||||
return createMcpSuccessResponse({ message: `Server ${serverId} deleted successfully` })
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error deleting workflow MCP server:`, error)
|
||||
return createMcpErrorResponse(
|
||||
error instanceof Error ? error : new Error('Failed to delete workflow MCP server'),
|
||||
'Failed to delete workflow MCP server',
|
||||
500
|
||||
)
|
||||
return createMcpErrorResponse(toError(error), 'Failed to delete workflow MCP server', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { and, eq, isNull } from 'drizzle-orm'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware'
|
||||
import { mcpPubSub } from '@/lib/mcp/pubsub'
|
||||
import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils'
|
||||
@@ -63,11 +64,7 @@ export const GET = withMcpAuth<RouteParams>('read')(
|
||||
return createMcpSuccessResponse({ tool })
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error getting tool:`, error)
|
||||
return createMcpErrorResponse(
|
||||
error instanceof Error ? error : new Error('Failed to get tool'),
|
||||
'Failed to get tool',
|
||||
500
|
||||
)
|
||||
return createMcpErrorResponse(toError(error), 'Failed to get tool', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -164,11 +161,7 @@ export const PATCH = withMcpAuth<RouteParams>('write')(
|
||||
return createMcpSuccessResponse({ tool: updatedTool })
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error updating tool:`, error)
|
||||
return createMcpErrorResponse(
|
||||
error instanceof Error ? error : new Error('Failed to update tool'),
|
||||
'Failed to update tool',
|
||||
500
|
||||
)
|
||||
return createMcpErrorResponse(toError(error), 'Failed to update tool', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -232,11 +225,7 @@ export const DELETE = withMcpAuth<RouteParams>('write')(
|
||||
return createMcpSuccessResponse({ message: `Tool ${toolId} deleted successfully` })
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error deleting tool:`, error)
|
||||
return createMcpErrorResponse(
|
||||
error instanceof Error ? error : new Error('Failed to delete tool'),
|
||||
'Failed to delete tool',
|
||||
500
|
||||
)
|
||||
return createMcpErrorResponse(toError(error), 'Failed to delete tool', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { and, eq, isNull } from 'drizzle-orm'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { generateId } from '@/lib/core/utils/uuid'
|
||||
import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware'
|
||||
import { mcpPubSub } from '@/lib/mcp/pubsub'
|
||||
@@ -72,11 +73,7 @@ export const GET = withMcpAuth<RouteParams>('read')(
|
||||
return createMcpSuccessResponse({ tools })
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error listing tools:`, error)
|
||||
return createMcpErrorResponse(
|
||||
error instanceof Error ? error : new Error('Failed to list tools'),
|
||||
'Failed to list tools',
|
||||
500
|
||||
)
|
||||
return createMcpErrorResponse(toError(error), 'Failed to list tools', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -237,11 +234,7 @@ export const POST = withMcpAuth<RouteParams>('write')(
|
||||
return createMcpSuccessResponse({ tool }, 201)
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error adding tool:`, error)
|
||||
return createMcpErrorResponse(
|
||||
error instanceof Error ? error : new Error('Failed to add tool'),
|
||||
'Failed to add tool',
|
||||
500
|
||||
)
|
||||
return createMcpErrorResponse(toError(error), 'Failed to add tool', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { and, eq, inArray, isNull, sql } from 'drizzle-orm'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { generateId } from '@/lib/core/utils/uuid'
|
||||
import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware'
|
||||
import { mcpPubSub } from '@/lib/mcp/pubsub'
|
||||
@@ -82,11 +83,7 @@ export const GET = withMcpAuth('read')(
|
||||
return createMcpSuccessResponse({ servers: serversWithToolNames })
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error listing workflow MCP servers:`, error)
|
||||
return createMcpErrorResponse(
|
||||
error instanceof Error ? error : new Error('Failed to list workflow MCP servers'),
|
||||
'Failed to list workflow MCP servers',
|
||||
500
|
||||
)
|
||||
return createMcpErrorResponse(toError(error), 'Failed to list workflow MCP servers', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -221,11 +218,7 @@ export const POST = withMcpAuth('write')(
|
||||
return createMcpSuccessResponse({ server, addedTools }, 201)
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error creating workflow MCP server:`, error)
|
||||
return createMcpErrorResponse(
|
||||
error instanceof Error ? error : new Error('Failed to create workflow MCP server'),
|
||||
'Failed to create workflow MCP server',
|
||||
500
|
||||
)
|
||||
return createMcpErrorResponse(toError(error), 'Failed to create workflow MCP server', 500)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@ import { readEvents } from '@/lib/copilot/request/session/buffer'
|
||||
import { readFilePreviewSessions } from '@/lib/copilot/request/session/file-preview-session'
|
||||
import { type StreamBatchEvent, toStreamBatchEvent } from '@/lib/copilot/request/session/types'
|
||||
import { taskPubSub } from '@/lib/copilot/tasks'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { captureServerEvent } from '@/lib/posthog/server'
|
||||
|
||||
const logger = createLogger('MothershipChatAPI')
|
||||
@@ -66,7 +67,7 @@ export async function GET(
|
||||
logger.warn('Failed to read preview sessions for mothership chat', {
|
||||
chatId,
|
||||
conversationId: chat.conversationId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
})
|
||||
return []
|
||||
}),
|
||||
@@ -75,7 +76,7 @@ export async function GET(
|
||||
logger.warn('Failed to fetch latest run for mothership chat snapshot', {
|
||||
chatId,
|
||||
conversationId: chat.conversationId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
})
|
||||
return null
|
||||
})
|
||||
@@ -90,7 +91,7 @@ export async function GET(
|
||||
logger.warn('Failed to read stream snapshot for mothership chat', {
|
||||
chatId,
|
||||
conversationId: chat.conversationId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { buildIntegrationToolSchemas } from '@/lib/copilot/chat/payload'
|
||||
import { generateWorkspaceContext } from '@/lib/copilot/chat/workspace-context'
|
||||
import { runHeadlessCopilotLifecycle } from '@/lib/copilot/request/lifecycle/headless'
|
||||
import { requestExplicitStreamAbort } from '@/lib/copilot/request/session/explicit-abort'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { generateId } from '@/lib/core/utils/uuid'
|
||||
import {
|
||||
assertActiveWorkspaceAccess,
|
||||
@@ -110,7 +111,7 @@ export async function POST(req: NextRequest) {
|
||||
chatId: effectiveChatId,
|
||||
}).catch((error) => {
|
||||
reqLogger.warn('Failed to send explicit abort for mothership execution', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils'
|
||||
import {
|
||||
@@ -112,7 +113,7 @@ export async function POST(request: NextRequest) {
|
||||
logger.error(`[${requestId}] Failed to resolve Vertex credential:`, {
|
||||
provider,
|
||||
model,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
hasVertexCredential: !!vertexCredential,
|
||||
})
|
||||
return NextResponse.json(
|
||||
@@ -258,17 +259,14 @@ export async function POST(request: NextRequest) {
|
||||
} catch (error) {
|
||||
const executionTime = Date.now() - startTime
|
||||
logger.error(`[${requestId}] Provider request failed:`, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
errorName: error instanceof Error ? error.name : 'Unknown',
|
||||
errorStack: error instanceof Error ? error.stack : undefined,
|
||||
executionTime,
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{ error: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
)
|
||||
return NextResponse.json({ error: toError(error).message }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { AuthType } from '@/lib/auth/hybrid'
|
||||
import { getJobQueue } from '@/lib/core/async-jobs'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { SSE_HEADERS } from '@/lib/core/utils/sse'
|
||||
import { getBaseUrl } from '@/lib/core/utils/urls'
|
||||
@@ -235,7 +236,7 @@ export async function POST(
|
||||
})
|
||||
} catch (dispatchError) {
|
||||
logger.error('Failed to dispatch async resume execution', {
|
||||
error: dispatchError instanceof Error ? dispatchError.message : String(dispatchError),
|
||||
error: toError(dispatchError).message,
|
||||
resumeExecutionId: enqueueResult.resumeExecutionId,
|
||||
})
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -4,6 +4,7 @@ import { and, eq, isNull, lt, lte, ne, not, or, sql } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { verifyCronAuth } from '@/lib/auth/internal'
|
||||
import { getJobQueue, shouldExecuteInline } from '@/lib/core/async-jobs'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { generateId } from '@/lib/core/utils/uuid'
|
||||
import {
|
||||
@@ -136,7 +137,7 @@ export async function GET(request: NextRequest) {
|
||||
const output = await executeScheduleJob(payload)
|
||||
await jobQueue.completeJob(jobId, output)
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
const errorMessage = toError(error).message
|
||||
logger.error(
|
||||
`[${requestId}] Schedule execution failed for workflow ${schedule.workflowId}`,
|
||||
{
|
||||
@@ -191,7 +192,7 @@ export async function GET(request: NextRequest) {
|
||||
await executeJobInline(payload)
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Job execution failed for ${job.id}`, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
})
|
||||
await releaseScheduleLock(
|
||||
job.id,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { generateId } from '@/lib/core/utils/uuid'
|
||||
import {
|
||||
@@ -163,7 +164,7 @@ export async function POST(request: NextRequest, { params }: RouteParams) {
|
||||
inserted += result.length
|
||||
}
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
const message = toError(err).message
|
||||
logger.warn(`[${requestId}] Append failed mid-import for table ${tableId}`, {
|
||||
inserted,
|
||||
total: coerced.length,
|
||||
@@ -238,7 +239,7 @@ export async function POST(request: NextRequest, { params }: RouteParams) {
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
const message = toError(err).message
|
||||
const isClientError =
|
||||
message.includes('row limit') ||
|
||||
message.includes('Schema validation') ||
|
||||
@@ -251,7 +252,7 @@ export async function POST(request: NextRequest, { params }: RouteParams) {
|
||||
throw err
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
const message = toError(error).message
|
||||
logger.error(`[${requestId}] CSV import into existing table failed:`, error)
|
||||
|
||||
const isClientError =
|
||||
|
||||
@@ -5,6 +5,7 @@ import { and, eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import type { RowData } from '@/lib/table'
|
||||
import { deleteRow, updateRow } from '@/lib/table'
|
||||
@@ -193,7 +194,7 @@ export async function PATCH(request: NextRequest, { params }: RowRouteParams) {
|
||||
)
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
const errorMessage = toError(error).message
|
||||
|
||||
if (errorMessage === 'Row not found') {
|
||||
return NextResponse.json({ error: errorMessage }, { status: 404 })
|
||||
@@ -260,7 +261,7 @@ export async function DELETE(request: NextRequest, { params }: RowRouteParams) {
|
||||
)
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
const errorMessage = toError(error).message
|
||||
|
||||
if (errorMessage === 'Row not found') {
|
||||
return NextResponse.json({ error: errorMessage }, { status: 404 })
|
||||
|
||||
@@ -5,6 +5,7 @@ import { and, eq, sql } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import type { Filter, RowData, Sort, TableSchema } from '@/lib/table'
|
||||
import {
|
||||
@@ -181,7 +182,7 @@ async function handleBatchInsert(
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
const errorMessage = toError(error).message
|
||||
|
||||
if (
|
||||
errorMessage.includes('row limit') ||
|
||||
@@ -289,7 +290,7 @@ export async function POST(request: NextRequest, { params }: TableRowsRouteParam
|
||||
)
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
const errorMessage = toError(error).message
|
||||
|
||||
if (
|
||||
errorMessage.includes('row limit') ||
|
||||
@@ -516,7 +517,7 @@ export async function PUT(request: NextRequest, { params }: TableRowsRouteParams
|
||||
)
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
const errorMessage = toError(error).message
|
||||
|
||||
if (
|
||||
errorMessage.includes('Row size exceeds') ||
|
||||
@@ -616,7 +617,7 @@ export async function DELETE(request: NextRequest, { params }: TableRowsRoutePar
|
||||
)
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
const errorMessage = toError(error).message
|
||||
|
||||
if (errorMessage.includes('Filter is required')) {
|
||||
return NextResponse.json({ error: errorMessage }, { status: 400 })
|
||||
@@ -685,7 +686,7 @@ export async function PATCH(request: NextRequest, { params }: TableRowsRoutePara
|
||||
)
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
const errorMessage = toError(error).message
|
||||
|
||||
if (
|
||||
errorMessage.includes('Row size exceeds') ||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import type { RowData } from '@/lib/table'
|
||||
import { upsertRow } from '@/lib/table'
|
||||
@@ -87,7 +88,7 @@ export async function POST(request: NextRequest, { params }: UpsertRouteParams)
|
||||
)
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
const errorMessage = toError(error).message
|
||||
|
||||
// Service layer throws descriptive errors for validation/capacity issues
|
||||
if (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { generateId } from '@/lib/core/utils/uuid'
|
||||
import {
|
||||
@@ -124,7 +125,7 @@ export async function POST(request: NextRequest) {
|
||||
throw insertError
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
const message = toError(error).message
|
||||
logger.error(`[${requestId}] CSV import failed:`, error)
|
||||
|
||||
const isClientError =
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalApiKey } from '@/lib/copilot/request/http'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { sanitizeForCopilot } from '@/lib/workflows/sanitization/json-sanitizer'
|
||||
|
||||
@@ -97,7 +98,7 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error sanitizing template ${template.id}`, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
})
|
||||
return null
|
||||
}
|
||||
@@ -112,7 +113,7 @@ export async function GET(request: NextRequest) {
|
||||
return NextResponse.json(response)
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error fetching approved sanitized templates`, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -5,6 +5,7 @@ import { z } from 'zod'
|
||||
import { createA2AClient, extractTextContent, isTerminalState } from '@/lib/a2a/utils'
|
||||
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateUrlWithDNS } from '@/lib/core/security/input-validation.server'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { generateId } from '@/lib/core/utils/uuid'
|
||||
|
||||
@@ -89,7 +90,7 @@ export async function POST(request: NextRequest) {
|
||||
parts.push(dataPart)
|
||||
} catch (parseError) {
|
||||
logger.warn(`[${requestId}] Failed to parse data as JSON, skipping DataPart`, {
|
||||
error: parseError instanceof Error ? parseError.message : String(parseError),
|
||||
error: toError(parseError).message,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
134
apps/sim/app/api/tools/agiloft/retrieve/route.ts
Normal file
134
apps/sim/app/api/tools/agiloft/retrieve/route.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateUrlWithDNS } from '@/lib/core/security/input-validation.server'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { agiloftLogin, agiloftLogout, buildRetrieveAttachmentUrl } from '@/tools/agiloft/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('AgiloftRetrieveAPI')
|
||||
|
||||
const AgiloftRetrieveSchema = z.object({
|
||||
instanceUrl: z.string().min(1, 'Instance URL is required'),
|
||||
knowledgeBase: z.string().min(1, 'Knowledge base is required'),
|
||||
login: z.string().min(1, 'Login is required'),
|
||||
password: z.string().min(1, 'Password is required'),
|
||||
table: z.string().min(1, 'Table is required'),
|
||||
recordId: z.string().min(1, 'Record ID is required'),
|
||||
fieldName: z.string().min(1, 'Field name is required'),
|
||||
position: z.string().min(1, 'Position is required'),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
try {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
|
||||
if (!authResult.success) {
|
||||
logger.warn(`[${requestId}] Unauthorized Agiloft retrieve attempt: ${authResult.error}`)
|
||||
return NextResponse.json(
|
||||
{ success: false, error: authResult.error || 'Authentication required' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const data = AgiloftRetrieveSchema.parse(body)
|
||||
|
||||
const urlValidation = await validateUrlWithDNS(data.instanceUrl, 'instanceUrl')
|
||||
if (!urlValidation.isValid) {
|
||||
logger.warn(`[${requestId}] SSRF attempt blocked for Agiloft instance URL`, {
|
||||
instanceUrl: data.instanceUrl,
|
||||
})
|
||||
return NextResponse.json(
|
||||
{ success: false, error: urlValidation.error || 'Invalid instance URL' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const token = await agiloftLogin(data)
|
||||
const base = data.instanceUrl.replace(/\/$/, '')
|
||||
|
||||
try {
|
||||
const url = buildRetrieveAttachmentUrl(base, data)
|
||||
|
||||
logger.info(`[${requestId}] Downloading attachment from Agiloft`, {
|
||||
recordId: data.recordId,
|
||||
fieldName: data.fieldName,
|
||||
position: data.position,
|
||||
})
|
||||
|
||||
const agiloftResponse = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
})
|
||||
|
||||
if (!agiloftResponse.ok) {
|
||||
const errorText = await agiloftResponse.text()
|
||||
logger.error(
|
||||
`[${requestId}] Agiloft retrieve error: ${agiloftResponse.status} - ${errorText}`
|
||||
)
|
||||
return NextResponse.json(
|
||||
{ success: false, error: `Agiloft error: ${agiloftResponse.status} - ${errorText}` },
|
||||
{ status: agiloftResponse.status }
|
||||
)
|
||||
}
|
||||
|
||||
const contentType = agiloftResponse.headers.get('content-type') || 'application/octet-stream'
|
||||
const contentDisposition = agiloftResponse.headers.get('content-disposition')
|
||||
let fileName = 'attachment'
|
||||
|
||||
if (contentDisposition) {
|
||||
const match = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)
|
||||
if (match?.[1]) {
|
||||
fileName = match[1].replace(/['"]/g, '')
|
||||
}
|
||||
}
|
||||
|
||||
const arrayBuffer = await agiloftResponse.arrayBuffer()
|
||||
const fileBuffer = Buffer.from(arrayBuffer)
|
||||
|
||||
logger.info(`[${requestId}] Attachment downloaded successfully`, {
|
||||
name: fileName,
|
||||
size: fileBuffer.length,
|
||||
mimeType: contentType,
|
||||
})
|
||||
|
||||
const base64Data = fileBuffer.toString('base64')
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
file: {
|
||||
name: fileName,
|
||||
mimeType: contentType,
|
||||
data: base64Data,
|
||||
size: fileBuffer.length,
|
||||
},
|
||||
},
|
||||
})
|
||||
} finally {
|
||||
await agiloftLogout(data.instanceUrl, data.knowledgeBase, token)
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Invalid request data', details: error.errors },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
logger.error(`[${requestId}] Error retrieving Agiloft attachment:`, error)
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error instanceof Error ? error.message : 'Internal server error' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateAlphanumericId } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@@ -115,7 +116,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error: any) {
|
||||
logger.error('Error creating Asana task:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateAlphanumericId } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@@ -114,7 +115,7 @@ export async function PUT(request: NextRequest) {
|
||||
})
|
||||
} catch (error: any) {
|
||||
logger.error('Error updating Asana task:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
type ResultField,
|
||||
} from '@aws-sdk/client-cloudwatch-logs'
|
||||
import { DEFAULT_EXECUTION_TIMEOUT_MS } from '@/lib/core/execution-limits'
|
||||
import { sleep } from '@/lib/core/utils/helpers'
|
||||
|
||||
interface AwsCredentials {
|
||||
region: string
|
||||
@@ -79,7 +80,7 @@ export async function pollQueryResults(
|
||||
throw new Error(`CloudWatch Log Insights query ${status.toLowerCase()}`)
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs))
|
||||
await sleep(pollIntervalMs)
|
||||
}
|
||||
|
||||
// Timeout -- fetch one last time for partial results
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
secureFetchWithPinnedIP,
|
||||
validateUrlWithDNS,
|
||||
} from '@/lib/core/security/input-validation.server'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
|
||||
const logger = createLogger('ImageProxyAPI')
|
||||
@@ -83,7 +84,7 @@ export async function GET(request: NextRequest) {
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
const errorMessage = toError(error).message
|
||||
logger.error(`[${requestId}] Image proxy error:`, { error: errorMessage })
|
||||
|
||||
return new NextResponse(`Failed to proxy image: ${errorMessage}`, {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage, toAdf } from '@/tools/jira/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
@@ -182,7 +183,7 @@ export async function PUT(request: NextRequest) {
|
||||
})
|
||||
} catch (error: any) {
|
||||
logger.error('Error updating Jira issue:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage, toAdf } from '@/tools/jira/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
@@ -225,7 +226,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error: any) {
|
||||
logger.error('Error creating Jira issue:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
validateJiraCloudId,
|
||||
validateJiraIssueKey,
|
||||
} from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -199,7 +200,7 @@ export async function POST(request: NextRequest) {
|
||||
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
|
||||
} catch (error) {
|
||||
logger.error('Error in approvals operation:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -112,7 +113,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error adding comment:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -105,7 +106,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error fetching comments:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -157,7 +158,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error with customers operation:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -96,7 +97,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error getting form answers:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -104,7 +105,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error attaching form:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -111,7 +112,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error copying forms:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -96,7 +97,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error deleting form:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -97,7 +98,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error externalising form:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -98,7 +99,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error getting form:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -97,7 +98,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error internalising form:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -96,7 +97,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error fetching issue forms:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -97,7 +98,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error reopening form:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -103,7 +104,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error saving form answers:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -98,7 +99,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error fetching form structure:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -97,7 +98,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error submitting form:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -96,7 +97,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error fetching form templates:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
validateEnum,
|
||||
validateJiraCloudId,
|
||||
} from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -169,7 +170,7 @@ export async function POST(request: NextRequest) {
|
||||
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
|
||||
} catch (error) {
|
||||
logger.error('Error in organization operation:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -91,7 +92,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error fetching organizations:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
validateJiraCloudId,
|
||||
validateJiraIssueKey,
|
||||
} from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -174,7 +175,7 @@ export async function POST(request: NextRequest) {
|
||||
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
|
||||
} catch (error) {
|
||||
logger.error('Error in participants operation:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -100,7 +101,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error fetching queues:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
validateJiraCloudId,
|
||||
validateJiraIssueKey,
|
||||
} from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -250,7 +251,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error with request operation:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
validateEnum,
|
||||
validateJiraCloudId,
|
||||
} from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -140,7 +141,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error fetching requests:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -108,7 +109,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error fetching request type fields:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -104,7 +105,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error fetching request types:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -82,7 +83,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error fetching service desks:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -92,7 +93,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error fetching SLA info:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
validateJiraCloudId,
|
||||
validateJiraIssueKey,
|
||||
} from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -116,7 +117,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error transitioning request:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
@@ -92,7 +93,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error fetching transitions:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: toError(error).message,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { NextResponse } from 'next/server'
|
||||
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
|
||||
import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
@@ -99,7 +100,7 @@ export async function POST(request: Request) {
|
||||
} catch (innerError) {
|
||||
logger.error('Error during API requests:', innerError)
|
||||
|
||||
const errorMessage = innerError instanceof Error ? innerError.message : String(innerError)
|
||||
const errorMessage = toError(innerError).message
|
||||
if (
|
||||
errorMessage.includes('auth') ||
|
||||
errorMessage.includes('token') ||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { NextResponse } from 'next/server'
|
||||
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
|
||||
import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation'
|
||||
import { toError } from '@/lib/core/utils/helpers'
|
||||
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
@@ -110,15 +111,13 @@ const getChatDisplayName = async (
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`Failed to get better name from messages for chat ${chatId}: ${error instanceof Error ? error.message : String(error)}`
|
||||
`Failed to get better name from messages for chat ${chatId}: ${toError(error).message}`
|
||||
)
|
||||
}
|
||||
|
||||
return `Chat ${chatId.split(':')[0] || chatId.substring(0, 8)}...`
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`Failed to get display name for chat ${chatId}: ${error instanceof Error ? error.message : String(error)}`
|
||||
)
|
||||
logger.warn(`Failed to get display name for chat ${chatId}: ${toError(error).message}`)
|
||||
return `Chat ${chatId.split(':')[0] || chatId.substring(0, 8)}...`
|
||||
}
|
||||
}
|
||||
@@ -200,7 +199,7 @@ export async function POST(request: Request) {
|
||||
} catch (innerError) {
|
||||
logger.error('Error during API requests:', innerError)
|
||||
|
||||
const errorMessage = innerError instanceof Error ? innerError.message : String(innerError)
|
||||
const errorMessage = toError(innerError).message
|
||||
if (
|
||||
errorMessage.includes('auth') ||
|
||||
errorMessage.includes('token') ||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user