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 (#6023)
* 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
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { describeError, getPostgresErrorCode, toError } from './errors.js'
|
||||
import { describeError, findCause, getPostgresErrorCode, toError } from './errors.js'
|
||||
|
||||
describe('toError', () => {
|
||||
it('returns the same Error when given an Error', () => {
|
||||
@@ -127,3 +127,31 @@ describe('describeError', () => {
|
||||
expect(described?.causeChain?.length).toBeLessThanOrEqual(10)
|
||||
})
|
||||
})
|
||||
|
||||
describe('findCause', () => {
|
||||
class Marker extends Error {}
|
||||
const isMarker = (value: unknown): value is Marker => value instanceof Marker
|
||||
|
||||
it('returns the error itself when it matches', () => {
|
||||
const marker = new Marker('hit')
|
||||
expect(findCause(marker, isMarker)).toBe(marker)
|
||||
})
|
||||
|
||||
it('finds a match further down the cause chain', () => {
|
||||
const marker = new Marker('deep')
|
||||
const wrapped = new Error('outer', { cause: new Error('middle', { cause: marker }) })
|
||||
expect(findCause(wrapped, isMarker)).toBe(marker)
|
||||
})
|
||||
|
||||
it('returns undefined when nothing matches', () => {
|
||||
expect(findCause(new Error('plain'), isMarker)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('survives a cyclic chain', () => {
|
||||
const a = new Error('a')
|
||||
const b = new Error('b')
|
||||
Object.assign(a, { cause: b })
|
||||
Object.assign(b, { cause: a })
|
||||
expect(findCause(a, isMarker)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -93,6 +93,28 @@ export function describeError(error: unknown): DescribedError {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
Reference in New Issue
Block a user