mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
* feat(custom-blocks): log and bill child runs in the publisher's workspace * fix(custom-blocks): sanitize every boundary failure and classify it for consumers * fix(custom-blocks): surface a cancelled child as cancelled, not a generic failure * fix(custom-blocks): share one large-value id list so nested blocks propagate * fix(custom-blocks): add a durable cancel backstop to the child bridge * fix(tools): carry Sim's own status through the tool-response boundary * fix(custom-blocks): stop forwarding the publisher's personal quota to consumers * fix(custom-blocks): correlate agent-tool runs to the real invoking execution * fix(custom-blocks): plumb the invoking execution id and abort signal to agent tools * fix(agent): forward the execution id through the provider payload * fix(executor): stop adopting an upstream target's HTTP status as our own * fix(custom-blocks): drain child log finalization when the parent is cancelled * fix(custom-blocks): require curated outputs instead of exposing the whole result * fix(custom-blocks): track the whole child run, not just its finalization
145 lines
4.7 KiB
TypeScript
145 lines
4.7 KiB
TypeScript
/**
|
|
* Normalizes an unknown caught value into an Error instance.
|
|
* Replaces the common `e instanceof Error ? e : new Error(String(e))` pattern in catch clauses.
|
|
*/
|
|
export function toError(value: unknown): Error {
|
|
if (value instanceof Error) return value
|
|
if (typeof value === 'string') return new Error(value)
|
|
return new Error(String(value))
|
|
}
|
|
|
|
/**
|
|
* Extracts a string message from an unknown caught value.
|
|
* Use instead of `e instanceof Error ? e.message : 'fallback'` in catch clauses.
|
|
*
|
|
* - Error instance → `error.message`
|
|
* - Non-empty string → the string itself (handles `throw 'msg'` patterns)
|
|
* - Otherwise → `fallback` if provided, or `String(value)`
|
|
*/
|
|
export function getErrorMessage(value: unknown, fallback?: string): string {
|
|
if (value instanceof Error) return value.message
|
|
if (typeof value === 'string' && value.length > 0) return value
|
|
return fallback ?? String(value)
|
|
}
|
|
|
|
/**
|
|
* Returns PostgreSQL error code (e.g. `23505` for unique_violation) when present on a thrown value.
|
|
* Normalizes common Drizzle / `postgres` driver shapes and walks `cause` chains.
|
|
*/
|
|
export function getPostgresErrorCode(error: unknown): string | undefined {
|
|
return readPgErrorField(error, 'code')
|
|
}
|
|
|
|
/**
|
|
* Returns the name of the PostgreSQL constraint that triggered the error (e.g. the unique index
|
|
* name on a `23505`), when present on a thrown value. Mirrors the field populated by the
|
|
* `postgres` / `pg` drivers, walking `cause` chains the same way as `getPostgresErrorCode`.
|
|
*/
|
|
export function getPostgresConstraintName(error: unknown): string | undefined {
|
|
return readPgErrorField(error, 'constraint_name') ?? readPgErrorField(error, 'constraint')
|
|
}
|
|
|
|
export interface DescribedError {
|
|
name: string
|
|
message: string
|
|
code?: string
|
|
errno?: string
|
|
syscall?: string
|
|
/** `"Name: message"` per link in the `.cause` chain, outermost first. Present only when the chain has more than one link. */
|
|
causeChain?: string[]
|
|
}
|
|
|
|
/**
|
|
* Always-on diagnostic view of an error and its `.cause` chain.
|
|
*
|
|
* Reports the fields of the DEEPEST `.cause` link, because a wrapped driver
|
|
* error (e.g. Drizzle's `"Failed query: ..."` wrapping an `ECONNRESET`) carries
|
|
* the real reason there, not on the outer wrapper. Always returns a populated
|
|
* object — including for non-`Error` throws and unclassified errors like
|
|
* `AbortError`. Cycle-safe and depth-bounded.
|
|
*
|
|
* Loggers do not serialize the non-enumerable `Error.prototype.cause`, so pass
|
|
* the result as an explicit structured field rather than the raw error.
|
|
*/
|
|
export function describeError(error: unknown): DescribedError {
|
|
const chain: Error[] = []
|
|
const seen = new Set<unknown>()
|
|
let current: unknown = error
|
|
while (current instanceof Error && !seen.has(current) && chain.length < 10) {
|
|
seen.add(current)
|
|
chain.push(current)
|
|
current = current.cause
|
|
}
|
|
|
|
if (chain.length === 0) {
|
|
const normalized = toError(error)
|
|
return { name: normalized.name, message: normalized.message }
|
|
}
|
|
|
|
const deepest = chain[chain.length - 1] as Error & Record<string, unknown>
|
|
const asString = (value: unknown): string | undefined =>
|
|
typeof value === 'string' ? value : undefined
|
|
const code = asString(deepest.code)
|
|
const errno = asString(deepest.errno)
|
|
const syscall = asString(deepest.syscall)
|
|
|
|
return {
|
|
name: deepest.name,
|
|
message: deepest.message,
|
|
...(code ? { code } : {}),
|
|
...(errno ? { errno } : {}),
|
|
...(syscall ? { syscall } : {}),
|
|
...(chain.length > 1 ? { causeChain: chain.map((e) => `${e.name}: ${e.message}`) } : {}),
|
|
}
|
|
}
|
|
|
|
/**
|
|
* First link in the `.cause` chain (including `error` itself) matching
|
|
* `predicate`. Lets a caller recover a specific wrapped error class instead of
|
|
* re-parsing a formatted message. Cycle-safe and depth-bounded, mirroring
|
|
* {@link describeError}'s walk.
|
|
*/
|
|
export function findCause<T>(
|
|
error: unknown,
|
|
predicate: (value: unknown) => value is T
|
|
): T | undefined {
|
|
const seen = new Set<unknown>()
|
|
let current: unknown = error
|
|
|
|
while (current instanceof Error && !seen.has(current) && seen.size < 10) {
|
|
seen.add(current)
|
|
if (predicate(current)) return current
|
|
current = current.cause
|
|
}
|
|
|
|
return undefined
|
|
}
|
|
|
|
function readPgErrorField(error: unknown, field: string): string | undefined {
|
|
const seen = new Set<unknown>()
|
|
let current: unknown = error
|
|
|
|
while (current !== undefined && current !== null) {
|
|
if (seen.has(current)) {
|
|
break
|
|
}
|
|
seen.add(current)
|
|
|
|
if (typeof current === 'object') {
|
|
const value = (current as Record<string, unknown>)[field]
|
|
if (typeof value === 'string') {
|
|
return value
|
|
}
|
|
}
|
|
|
|
if (current instanceof Error && current.cause !== undefined) {
|
|
current = current.cause
|
|
continue
|
|
}
|
|
|
|
break
|
|
}
|
|
|
|
return undefined
|
|
}
|