11import { parse } from "acorn"
2- import { Cause , Effect , Exit , Fiber , Scope , Semaphore } from "effect"
2+ import { Cause , Deferred , Effect , Exit , Fiber , Scope , Semaphore } from "effect"
33import { DiagnosticCategory , ModuleKind , ScriptTarget , flattenDiagnosticMessageText , transpileModule } from "typescript"
44import {
55 copyIn ,
@@ -42,6 +42,7 @@ import {
4242 isRecord ,
4343 type MemberReference ,
4444 OptionalShortCircuit ,
45+ PromiseCapabilityFunction ,
4546 PromiseInstanceMethodReference ,
4647 PromiseMethodReference ,
4748 type PromiseMethodName ,
@@ -94,6 +95,7 @@ import {
9495 coerceToNumber ,
9596 coerceToString ,
9697 compoundOperators ,
98+ createAggregateErrorValue ,
9799 createErrorValue ,
98100 errorBrandName ,
99101 errorConstructors ,
@@ -226,11 +228,22 @@ const settleAfterTurn = <A, E, R>(body: Effect.Effect<A, E, R>): Effect.Effect<A
226228const selfResolutionError = ( node ?: AstNode ) : InterpreterRuntimeError =>
227229 new InterpreterRuntimeError ( "Chaining cycle detected: a promise cannot resolve with itself." , node ) . as ( "TypeError" )
228230
229- type ReactionHandler = CodeModeFunction | CoercionFunction | UriFunction
231+ // Short-circuit marker for Promise.any: the first fulfillment travels the error channel of
232+ // the flipped members so fail-fast Effect.all stops observing on it.
233+ class PromiseAnyFulfilled {
234+ constructor ( readonly value : unknown ) { }
235+ }
236+
237+ type ReactionHandler = CodeModeFunction | CoercionFunction | UriFunction | PromiseCapabilityFunction
230238
231239// Non-callables are ignored as in JS: `.then(undefined, f)` relies on the passthrough.
232240const reactionHandler = ( value : unknown , method : string , node : AstNode ) : ReactionHandler | undefined => {
233- if ( value instanceof CodeModeFunction || value instanceof CoercionFunction || value instanceof UriFunction ) {
241+ if (
242+ value instanceof CodeModeFunction ||
243+ value instanceof CoercionFunction ||
244+ value instanceof UriFunction ||
245+ value instanceof PromiseCapabilityFunction
246+ ) {
234247 return value
235248 }
236249 if ( typeofValue ( value ) === "function" ) {
@@ -250,6 +263,21 @@ const caughtErrorValue = (thrown: unknown): unknown => {
250263 return createErrorValue ( name , normalizeError ( thrown ) . message )
251264}
252265
266+ // `new Error("msg")` and the no-new call form share this; AggregateError alone takes
267+ // (errors, message?) with a required errors collection, as in JS.
268+ const constructErrorValue = ( name : string , args : Array < unknown > , node : AstNode ) : SafeObject => {
269+ if ( name !== "AggregateError" ) return createErrorValue ( name , args [ 0 ] === undefined ? "" : coerceToString ( args [ 0 ] ) )
270+ const errors = spreadItems ( args [ 0 ] )
271+ if ( errors === undefined ) {
272+ throw new InterpreterRuntimeError (
273+ "new AggregateError(...) expects an array of errors (e.g. new AggregateError(errors, message?))." ,
274+ node ,
275+ ) . as ( "TypeError" )
276+ }
277+ // Copy: spreadItems returns array input itself, and the error value must not alias caller data.
278+ return createAggregateErrorValue ( [ ...errors ] , args [ 1 ] === undefined ? "" : coerceToString ( args [ 1 ] ) )
279+ }
280+
253281const isRuntimeReference = ( value : unknown ) : boolean =>
254282 value instanceof CodeModeFunction ||
255283 value instanceof ToolReference ||
@@ -262,6 +290,7 @@ const isRuntimeReference = (value: unknown): boolean =>
262290 value instanceof SandboxPromise ||
263291 value instanceof CoercionFunction ||
264292 value instanceof UriFunction ||
293+ value instanceof PromiseCapabilityFunction ||
265294 value instanceof ErrorConstructorReference ||
266295 isSandboxValue ( value )
267296
@@ -305,6 +334,7 @@ const typeofValue = (value: unknown): string => {
305334 value instanceof PromiseMethodReference ||
306335 value instanceof PromiseInstanceMethodReference ||
307336 value instanceof PromiseNamespace ||
337+ value instanceof PromiseCapabilityFunction ||
308338 value instanceof ErrorConstructorReference
309339 )
310340 return "function"
@@ -1584,19 +1614,10 @@ class Interpreter<R> {
15841614 const argNodes = getArray ( node , "arguments" )
15851615 const self = this
15861616 if ( name === "Promise" ) {
1587- throw new InterpreterRuntimeError (
1588- "new Promise(...) is not supported in CodeMode; tool calls already return promises - call the tool and await the result." ,
1589- node ,
1590- "UnsupportedSyntax" ,
1591- [ supportedSyntaxMessage ] ,
1592- )
1617+ return Effect . flatMap ( this . evaluateCallArguments ( argNodes ) , ( args ) => self . constructPromise ( args [ 0 ] , node ) )
15931618 }
15941619 if ( errorConstructors . has ( name ) ) {
1595- return Effect . gen ( function * ( ) {
1596- const arg =
1597- argNodes . length > 0 ? yield * self . evaluateExpression ( asNode ( argNodes [ 0 ] , "arguments[0]" ) ) : undefined
1598- return createErrorValue ( name , arg === undefined ? "" : coerceToString ( arg ) )
1599- } )
1620+ return Effect . map ( this . evaluateCallArguments ( argNodes ) , ( args ) => constructErrorValue ( name , args , node ) )
16001621 }
16011622 if ( valueConstructors . has ( name ) ) {
16021623 return Effect . gen ( function * ( ) {
@@ -2066,7 +2087,11 @@ class Interpreter<R> {
20662087 }
20672088 // `Error("msg")` without `new` constructs an error exactly like `new Error("msg")`, as in JS.
20682089 if ( callable instanceof ErrorConstructorReference ) {
2069- return createErrorValue ( callable . name , args [ 0 ] === undefined ? "" : coerceToString ( args [ 0 ] ) )
2090+ return constructErrorValue ( callable . name , args , node )
2091+ }
2092+ if ( callable instanceof PromiseCapabilityFunction ) {
2093+ callable . settle ( args [ 0 ] )
2094+ return undefined
20702095 }
20712096 throw new InterpreterRuntimeError ( "Only tools are callable in CodeMode." , callee )
20722097 } )
@@ -2255,8 +2280,8 @@ class Interpreter<R> {
22552280 return this . createPromise ( Effect . fail ( new ProgramThrow ( args [ 0 ] ) ) )
22562281 }
22572282
2258- const items = spreadItems ( args [ 0 ] )
2259- if ( items === undefined ) {
2283+ const spread = spreadItems ( args [ 0 ] )
2284+ if ( spread === undefined ) {
22602285 return this . createPromise (
22612286 Effect . fail (
22622287 new InterpreterRuntimeError (
@@ -2266,6 +2291,8 @@ class Interpreter<R> {
22662291 ) ,
22672292 )
22682293 }
2294+ // Densify: JS combinator iteration reads sparse holes as undefined members; .map would skip them.
2295+ const items = Array . from ( spread )
22692296
22702297 // JS makes combinator members "handled" synchronously at the call - their rejections
22712298 // belong to the aggregate from this moment, even ones settling before it runs.
@@ -2331,6 +2358,29 @@ class Interpreter<R> {
23312358 // and is interrupted at normal completion (already observed) or by teardown.
23322359 return this . createPromise ( settleAfterTurn ( Effect . flatten ( Effect . raceAll ( observations ) ) ) )
23332360 }
2361+ case "any" : {
2362+ // De Morgan dual of Promise.all: members are flipped so the first fulfillment
2363+ // short-circuits fail-fast Effect.all, and all-rejected completes with the reasons
2364+ // in input order for the AggregateError.
2365+ const flipped = items . map ( ( item ) =>
2366+ item instanceof SandboxPromise
2367+ ? Effect . flatMap ( this . promises . await ( item ) , ( exit ) => {
2368+ if ( Exit . isSuccess ( exit ) ) return Effect . fail ( new PromiseAnyFulfilled ( exit . value ) )
2369+ if ( Cause . hasInterruptsOnly ( exit . cause ) ) return Effect . failCause ( exit . cause )
2370+ return Effect . succeed ( caughtErrorValue ( Cause . squash ( exit . cause ) ) )
2371+ } )
2372+ : Effect . fail ( new PromiseAnyFulfilled ( item ) ) ,
2373+ )
2374+ const body = Effect . all ( flipped , { concurrency : "unbounded" } ) . pipe (
2375+ Effect . flatMap ( ( reasons ) =>
2376+ Effect . fail ( new ProgramThrow ( createAggregateErrorValue ( reasons , "All promises were rejected" ) ) ) ,
2377+ ) ,
2378+ Effect . catch ( ( error ) =>
2379+ error instanceof PromiseAnyFulfilled ? Effect . succeed ( error . value ) : Effect . fail ( error ) ,
2380+ ) ,
2381+ )
2382+ return this . createPromise ( settleAfterTurn ( body ) )
2383+ }
23342384 }
23352385 }
23362386
@@ -2405,6 +2455,43 @@ class Interpreter<R> {
24052455 )
24062456 }
24072457
2458+ // new Promise(executor): the promise's fiber awaits a Deferred that resolve/reject settle
2459+ // exactly once. The executor runs synchronously; its throw rejects the promise unless it
2460+ // already settled (JS swallows post-settlement executor throws).
2461+ private constructPromise ( executor : unknown , node : AstNode ) : Effect . Effect < SandboxPromise , unknown , R > {
2462+ if ( ! ( executor instanceof CodeModeFunction ) ) {
2463+ throw new InterpreterRuntimeError (
2464+ "new Promise(...) expects an executor function (e.g. new Promise((resolve, reject) => { ... }))." ,
2465+ node ,
2466+ ) . as ( "TypeError" )
2467+ }
2468+ const self = this
2469+ return Effect . gen ( function * ( ) {
2470+ const deferred = Deferred . makeUnsafe < unknown , unknown > ( )
2471+ const box : { own ?: SandboxPromise } = { }
2472+ const promise = yield * self . createPromise (
2473+ Effect . flatMap ( Deferred . await ( deferred ) , ( value ) => {
2474+ if ( ! ( value instanceof SandboxPromise ) ) return Effect . succeed ( value )
2475+ if ( value === box . own ) return Effect . fail ( selfResolutionError ( node ) )
2476+ return self . settlePromise ( value )
2477+ } ) ,
2478+ )
2479+ box . own = promise
2480+ const resolve = new PromiseCapabilityFunction ( ( value ) => {
2481+ Deferred . doneUnsafe ( deferred , Exit . succeed ( value ) )
2482+ } )
2483+ const reject = new PromiseCapabilityFunction ( ( value ) => {
2484+ Deferred . doneUnsafe ( deferred , Exit . fail ( new ProgramThrow ( value ) ) )
2485+ } )
2486+ const executed = yield * Effect . exit ( self . invokeFunction ( executor , [ resolve , reject ] ) )
2487+ if ( ! Exit . isSuccess ( executed ) ) {
2488+ if ( Cause . hasInterruptsOnly ( executed . cause ) ) return yield * Effect . failCause ( executed . cause )
2489+ Deferred . doneUnsafe ( deferred , Exit . fail ( Cause . squash ( executed . cause ) ) )
2490+ }
2491+ return promise
2492+ } )
2493+ }
2494+
24082495 private invokeFunction ( fn : CodeModeFunction , args : Array < unknown > ) : Effect . Effect < unknown , unknown , R > {
24092496 const invocation = new Interpreter ( this . invokeTool , this . toolKeys , this . promises , this . logs , this . callPermits )
24102497 invocation . scopes = [ ...fn . capturedScopes , new Map < string , Binding > ( ) ]
@@ -2568,7 +2655,8 @@ class Interpreter<R> {
25682655 if (
25692656 ! ( callback instanceof CodeModeFunction ) &&
25702657 ! ( callback instanceof CoercionFunction ) &&
2571- ! ( callback instanceof UriFunction )
2658+ ! ( callback instanceof UriFunction ) &&
2659+ ! ( callback instanceof PromiseCapabilityFunction )
25722660 ) {
25732661 throw new InterpreterRuntimeError ( `${ name } expects a function callback.` , node )
25742662 }
@@ -2577,7 +2665,9 @@ class Interpreter<R> {
25772665 ? Effect . succeed ( invokeCoercion ( callback , callbackArgs , node ) )
25782666 : callback instanceof UriFunction
25792667 ? Effect . succeed ( invokeUriFunction ( callback , callbackArgs , node ) )
2580- : this . invokeFunction ( callback , callbackArgs )
2668+ : callback instanceof PromiseCapabilityFunction
2669+ ? Effect . sync ( ( ) => callback . settle ( callbackArgs [ 0 ] ) )
2670+ : this . invokeFunction ( callback , callbackArgs )
25812671 }
25822672
25832673 private invokeMapMethod (
@@ -3185,7 +3275,7 @@ class Interpreter<R> {
31853275 return new PromiseMethodReference ( key as PromiseMethodName )
31863276 }
31873277 throw new InterpreterRuntimeError (
3188- `Promise.${ String ( key ) } is not available in CodeMode. Available: Promise.all, Promise.allSettled, Promise.race, Promise.resolve, and Promise.reject; consume promises with await.` ,
3278+ `Promise.${ String ( key ) } is not available in CodeMode. Available: Promise.all, Promise.allSettled, Promise.race, Promise.any, Promise. resolve, and Promise.reject; consume promises with await.` ,
31893279 propertyNode ,
31903280 )
31913281 }
0 commit comments