mirror of
https://github.com/simstudioai/sim.git
synced 2026-11-02 22:52:17 +08:00
* refactor(executor): collapse subflow node-ID logic behind a codec Extract the ~20 scattered subflow node-ID parsing/building helpers and their regex patterns into a single SubflowNodeIdCodec. All patterns now live in one place; subflow-utils and execution state delegate to it. Pure refactor — byte-identical output, ordering, and error modes. * refactor(providers): extract StreamingExecution assembly into a factory The near-identical streaming-response assembly (timing segments, cost, token counts, onComplete wiring, success/logs/metadata envelope) that was copy-pasted across ~16 providers now goes through createStreamingExecution. Each provider injects only its stream iterable and delta extractors. Gemini is intentionally left as-is (its assembly is structurally divergent). Pure refactor — identical StreamingExecution shape, cost, tokens, timing, and callback order per provider. * refactor(providers): dedupe tool-schema wrapping into adapters Replace the identical inline tool-schema literals repeated across ~16 providers with shared adaptOpenAIChatToolSchema / adaptAnthropicToolSchema helpers. Content building reverts to calling the attachments builders directly (the per-provider content seam was pure pass-through indirection). Pure refactor — byte-identical tool-schema and content output. * chore(skills): point add-model provider edits at shared response/tool-schema helpers When editing provider code, reuse createStreamingExecution and the tool-schema-adapter helpers instead of hand-rolling. * refactor(providers): drop placeholder stream double-cast in streaming factory Build the output object before creating the stream so the factory returns a fully-typed StreamingExecution without 'undefined as unknown as ReadableStream'. Keeps the strict API-validation boundary ratchet green (double-cast count back to baseline). No behavior change: the same output reference is mutated on drain.
176 lines
7.5 KiB
TypeScript
176 lines
7.5 KiB
TypeScript
/**
|
|
* @vitest-environment node
|
|
*/
|
|
import { describe, expect, it } from 'vitest'
|
|
import { SubflowNodeIdCodec } from '@/executor/utils/subflow-node-id-codec'
|
|
|
|
describe('SubflowNodeIdCodec', () => {
|
|
describe('branch subscripts', () => {
|
|
it('builds and round-trips branch node IDs', () => {
|
|
const id = SubflowNodeIdCodec.buildBranchNodeId('block-1', 2)
|
|
expect(id).toBe('block-1₍2₎')
|
|
expect(SubflowNodeIdCodec.isBranchNodeId(id)).toBe(true)
|
|
expect(SubflowNodeIdCodec.extractBaseBlockId(id)).toBe('block-1')
|
|
expect(SubflowNodeIdCodec.extractBranchIndex(id)).toBe(2)
|
|
})
|
|
|
|
it('returns null index and false predicate for non-branch IDs', () => {
|
|
expect(SubflowNodeIdCodec.isBranchNodeId('block-1')).toBe(false)
|
|
expect(SubflowNodeIdCodec.extractBranchIndex('block-1')).toBeNull()
|
|
expect(SubflowNodeIdCodec.extractBaseBlockId('block-1')).toBe('block-1')
|
|
})
|
|
|
|
it('only strips a trailing branch subscript', () => {
|
|
expect(SubflowNodeIdCodec.extractBaseBlockId('a₍1₎b')).toBe('a₍1₎b')
|
|
expect(SubflowNodeIdCodec.extractBaseBlockId('a₍1₎b₍2₎')).toBe('a₍1₎b')
|
|
})
|
|
})
|
|
|
|
describe('loop sentinels', () => {
|
|
it('builds and parses loop sentinel IDs', () => {
|
|
const start = SubflowNodeIdCodec.buildLoopSentinelStartId('loop-1')
|
|
const end = SubflowNodeIdCodec.buildLoopSentinelEndId('loop-1')
|
|
expect(start).toBe('loop-loop-1-sentinel-start')
|
|
expect(end).toBe('loop-loop-1-sentinel-end')
|
|
expect(SubflowNodeIdCodec.isLoopSentinelNodeId(start)).toBe(true)
|
|
expect(SubflowNodeIdCodec.isLoopSentinelNodeId(end)).toBe(true)
|
|
expect(SubflowNodeIdCodec.extractLoopIdFromSentinel(start)).toBe('loop-1')
|
|
expect(SubflowNodeIdCodec.extractLoopIdFromSentinel(end)).toBe('loop-1')
|
|
})
|
|
|
|
it('returns null when not a loop sentinel', () => {
|
|
expect(SubflowNodeIdCodec.isLoopSentinelNodeId('block-1')).toBe(false)
|
|
expect(SubflowNodeIdCodec.extractLoopIdFromSentinel('block-1')).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('parallel sentinels', () => {
|
|
it('builds and parses parallel sentinel IDs', () => {
|
|
const start = SubflowNodeIdCodec.buildParallelSentinelStartId('p-1')
|
|
const end = SubflowNodeIdCodec.buildParallelSentinelEndId('p-1')
|
|
expect(start).toBe('parallel-p-1-sentinel-start')
|
|
expect(end).toBe('parallel-p-1-sentinel-end')
|
|
expect(SubflowNodeIdCodec.isParallelSentinelNodeId(start)).toBe(true)
|
|
expect(SubflowNodeIdCodec.isParallelSentinelNodeId(end)).toBe(true)
|
|
expect(SubflowNodeIdCodec.extractParallelIdFromSentinel(start)).toBe('p-1')
|
|
expect(SubflowNodeIdCodec.extractParallelIdFromSentinel(end)).toBe('p-1')
|
|
})
|
|
|
|
it('returns null when not a parallel sentinel', () => {
|
|
expect(SubflowNodeIdCodec.isParallelSentinelNodeId('block-1')).toBe(false)
|
|
expect(SubflowNodeIdCodec.extractParallelIdFromSentinel('block-1')).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('outer-branch clone scoping', () => {
|
|
it('builds and extracts outer branch index', () => {
|
|
const id = SubflowNodeIdCodec.buildOuterBranchScopedId('loop-1', 3)
|
|
expect(id).toBe('loop-1__obranch-3')
|
|
expect(SubflowNodeIdCodec.extractOuterBranchIndex(id)).toBe(3)
|
|
})
|
|
|
|
it('extracts the innermost outer branch index for nested clones', () => {
|
|
const id = 'loop-1__obranch-2__obranch-5'
|
|
expect(SubflowNodeIdCodec.extractOuterBranchIndex(id)).toBe(2)
|
|
expect(SubflowNodeIdCodec.extractInnermostOuterBranchIndex(id)).toBe(5)
|
|
})
|
|
|
|
it('returns undefined when no outer branch suffix is present', () => {
|
|
expect(SubflowNodeIdCodec.extractOuterBranchIndex('loop-1')).toBeUndefined()
|
|
expect(SubflowNodeIdCodec.extractInnermostOuterBranchIndex('loop-1')).toBeUndefined()
|
|
})
|
|
|
|
it('strips outer-branch and clone-digest suffixes', () => {
|
|
expect(SubflowNodeIdCodec.stripOuterBranchSuffix('loop-1__obranch-2')).toBe('loop-1')
|
|
expect(SubflowNodeIdCodec.stripOuterBranchSuffix('loop-1__cloneABCDEF__obranch-2')).toBe(
|
|
'loop-1'
|
|
)
|
|
})
|
|
|
|
it('strips all clone suffixes and branch subscripts to the base block ID', () => {
|
|
expect(SubflowNodeIdCodec.stripCloneSuffixes('block-1__obranch-2₍3₎')).toBe('block-1')
|
|
expect(SubflowNodeIdCodec.stripCloneSuffixes('block-1__clone0a1f__obranch-2₍0₎')).toBe(
|
|
'block-1'
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('normalizeNodeId', () => {
|
|
it('normalizes branch, loop sentinel, and parallel sentinel IDs', () => {
|
|
expect(SubflowNodeIdCodec.normalizeNodeId('block-1₍2₎')).toBe('block-1')
|
|
expect(SubflowNodeIdCodec.normalizeNodeId('loop-loop-1-sentinel-start')).toBe('loop-1')
|
|
expect(SubflowNodeIdCodec.normalizeNodeId('parallel-p-1-sentinel-end')).toBe('p-1')
|
|
expect(SubflowNodeIdCodec.normalizeNodeId('block-1')).toBe('block-1')
|
|
})
|
|
})
|
|
|
|
describe('loop digest lookup helpers', () => {
|
|
it('strips branch subscripts and loop digests for lookup keys', () => {
|
|
expect(SubflowNodeIdCodec.normalizeLookupId('block-1₍2₎_loop3')).toBe('block-1')
|
|
expect(SubflowNodeIdCodec.normalizeLookupId('block-1')).toBe('block-1')
|
|
})
|
|
|
|
it('extracts the leading branch suffix and loop digest segments', () => {
|
|
expect(SubflowNodeIdCodec.extractBranchSuffix('block-1₍2₎_loop3')).toBe('₍2₎')
|
|
expect(SubflowNodeIdCodec.extractBranchSuffix('block-1')).toBe('')
|
|
expect(SubflowNodeIdCodec.extractLoopSuffix('block-1₍2₎_loop3')).toBe('_loop3')
|
|
expect(SubflowNodeIdCodec.extractLoopSuffix('block-1')).toBe('')
|
|
})
|
|
})
|
|
|
|
describe('findEffectiveContainerId', () => {
|
|
it('returns the original ID for branch 0 / missing scope', () => {
|
|
const map = new Map<string, unknown>([['loop-1', {}]])
|
|
expect(SubflowNodeIdCodec.findEffectiveContainerId('loop-1', 'block-1', map)).toBe('loop-1')
|
|
})
|
|
|
|
it('prefers the mapped cloned scope when present', () => {
|
|
const map = new Map<string, unknown>([
|
|
['loop-1', {}],
|
|
['loop-1__obranch-2', {}],
|
|
])
|
|
expect(SubflowNodeIdCodec.findEffectiveContainerId('loop-1', 'block-1', map, 2)).toBe(
|
|
'loop-1__obranch-2'
|
|
)
|
|
})
|
|
|
|
it('resolves the cloned scope from the current node ID suffix', () => {
|
|
const map = new Map<string, unknown>([
|
|
['loop-1', {}],
|
|
['loop-1__obranch-3', {}],
|
|
])
|
|
expect(SubflowNodeIdCodec.findEffectiveContainerId('loop-1', 'block-1__obranch-3', map)).toBe(
|
|
'loop-1__obranch-3'
|
|
)
|
|
})
|
|
|
|
it('prefers __clone scopes when the current node carries a clone marker', () => {
|
|
const map = new Map<string, unknown>([
|
|
['loop-1__obranch-2', {}],
|
|
['loop-1__cloneabc__obranch-2', {}],
|
|
])
|
|
expect(
|
|
SubflowNodeIdCodec.findEffectiveContainerId('loop-1', 'block-1__cloneabc__obranch-2', map)
|
|
).toBe('loop-1__cloneabc__obranch-2')
|
|
})
|
|
})
|
|
|
|
describe('round-trip parse ∘ build', () => {
|
|
it('builds then parses branch IDs symmetrically', () => {
|
|
for (const index of [0, 1, 7, 20]) {
|
|
const id = SubflowNodeIdCodec.buildBranchNodeId('base-id', index)
|
|
expect(SubflowNodeIdCodec.extractBranchIndex(id)).toBe(index)
|
|
expect(SubflowNodeIdCodec.extractBaseBlockId(id)).toBe('base-id')
|
|
}
|
|
})
|
|
|
|
it('builds then parses outer-branch scoped IDs symmetrically', () => {
|
|
for (const index of [1, 4, 19]) {
|
|
const id = SubflowNodeIdCodec.buildOuterBranchScopedId('base-id', index)
|
|
expect(SubflowNodeIdCodec.extractOuterBranchIndex(id)).toBe(index)
|
|
expect(SubflowNodeIdCodec.stripOuterBranchSuffix(id)).toBe('base-id')
|
|
}
|
|
})
|
|
})
|
|
})
|