effect 4.1.1 → 4.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ai-docs/package.json +2 -2
- package/dist/ByteSize.d.ts.map +1 -1
- package/dist/ByteSize.js +27 -14
- package/dist/ByteSize.js.map +1 -1
- package/dist/Iterable.d.ts.map +1 -1
- package/dist/Iterable.js +27 -24
- package/dist/Iterable.js.map +1 -1
- package/dist/JsonSchema.d.ts.map +1 -1
- package/dist/JsonSchema.js +27 -6
- package/dist/JsonSchema.js.map +1 -1
- package/dist/MutableRef.d.ts.map +1 -1
- package/dist/MutableRef.js +0 -7
- package/dist/MutableRef.js.map +1 -1
- package/dist/Ref.d.ts.map +1 -1
- package/dist/Ref.js +0 -8
- package/dist/Ref.js.map +1 -1
- package/dist/Schedule.d.ts.map +1 -1
- package/dist/Schedule.js +8 -3
- package/dist/Schedule.js.map +1 -1
- package/dist/Stream.d.ts.map +1 -1
- package/dist/Stream.js +6 -5
- package/dist/Stream.js.map +1 -1
- package/dist/ai/McpServer.d.ts.map +1 -1
- package/dist/ai/McpServer.js +61 -33
- package/dist/ai/McpServer.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2026_07_28.js +4 -3
- package/dist/ai/internal/mcpProtocol/v2026_07_28.js.map +1 -1
- package/dist/ai/internal/structured-output.js +11 -2
- package/dist/ai/internal/structured-output.js.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.js +18 -9
- package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
- package/dist/cluster/Entity.d.ts +26 -0
- package/dist/cluster/Entity.d.ts.map +1 -1
- package/dist/cluster/Entity.js.map +1 -1
- package/dist/cluster/MessageStorage.d.ts +6 -6
- package/dist/cluster/MessageStorage.d.ts.map +1 -1
- package/dist/cluster/MessageStorage.js +1 -3
- package/dist/cluster/MessageStorage.js.map +1 -1
- package/dist/cluster/Runners.d.ts.map +1 -1
- package/dist/cluster/Runners.js +13 -2
- package/dist/cluster/Runners.js.map +1 -1
- package/dist/cluster/Sharding.d.ts +13 -0
- package/dist/cluster/Sharding.d.ts.map +1 -1
- package/dist/cluster/Sharding.js.map +1 -1
- package/dist/cluster/internal/clusterAbandon.d.ts.map +1 -1
- package/dist/cluster/internal/clusterAbandon.js +6 -4
- package/dist/cluster/internal/clusterAbandon.js.map +1 -1
- package/dist/cluster/internal/entityManager.js +48 -4
- package/dist/cluster/internal/entityManager.js.map +1 -1
- package/dist/cluster/internal/transactionNotifications.d.ts +5 -0
- package/dist/cluster/internal/transactionNotifications.d.ts.map +1 -0
- package/dist/cluster/internal/transactionNotifications.js +5 -0
- package/dist/cluster/internal/transactionNotifications.js.map +1 -0
- package/dist/encoding/Json5.d.ts +44 -0
- package/dist/encoding/Json5.d.ts.map +1 -0
- package/dist/encoding/Json5.js +56 -0
- package/dist/encoding/Json5.js.map +1 -0
- package/dist/encoding/Jsonc.d.ts +47 -0
- package/dist/encoding/Jsonc.d.ts.map +1 -0
- package/dist/encoding/Jsonc.js +120 -0
- package/dist/encoding/Jsonc.js.map +1 -0
- package/dist/encoding/SchemaBinary.d.ts +94 -0
- package/dist/encoding/SchemaBinary.d.ts.map +1 -1
- package/dist/encoding/SchemaBinary.js +219 -6
- package/dist/encoding/SchemaBinary.js.map +1 -1
- package/dist/encoding/index.d.ts +10 -0
- package/dist/encoding/index.d.ts.map +1 -1
- package/dist/encoding/index.js +10 -0
- package/dist/encoding/index.js.map +1 -1
- package/dist/eventlog/EventLogServer.d.ts.map +1 -1
- package/dist/eventlog/EventLogServer.js +28 -12
- package/dist/eventlog/EventLogServer.js.map +1 -1
- package/dist/http/HttpClient.d.ts +15 -9
- package/dist/http/HttpClient.d.ts.map +1 -1
- package/dist/http/HttpClient.js +18 -11
- package/dist/http/HttpClient.js.map +1 -1
- package/dist/internal/dateTime.js +17 -3
- package/dist/internal/dateTime.js.map +1 -1
- package/dist/internal/jsonParser.d.ts +5 -0
- package/dist/internal/jsonParser.d.ts.map +1 -0
- package/dist/internal/jsonParser.js +280 -0
- package/dist/internal/jsonParser.js.map +1 -0
- package/dist/internal/persistence.js +8 -1
- package/dist/internal/persistence.js.map +1 -1
- package/dist/internal/sql.js +16 -0
- package/dist/internal/sql.js.map +1 -1
- package/dist/internal/version.js +1 -1
- package/dist/observability/OtlpExporter.d.ts +4 -0
- package/dist/observability/OtlpExporter.d.ts.map +1 -1
- package/dist/observability/OtlpExporter.js +40 -12
- package/dist/observability/OtlpExporter.js.map +1 -1
- package/dist/persistence/RateLimiter.d.ts +7 -0
- package/dist/persistence/RateLimiter.d.ts.map +1 -1
- package/dist/persistence/RateLimiter.js +13 -4
- package/dist/persistence/RateLimiter.js.map +1 -1
- package/dist/rpc/RpcServer.d.ts.map +1 -1
- package/dist/rpc/RpcServer.js +10 -1
- package/dist/rpc/RpcServer.js.map +1 -1
- package/dist/sql/SqlClient.d.ts +8 -0
- package/dist/sql/SqlClient.d.ts.map +1 -1
- package/dist/sql/SqlClient.js +36 -12
- package/dist/sql/SqlClient.js.map +1 -1
- package/dist/sql/Statement.d.ts +5 -0
- package/dist/sql/Statement.d.ts.map +1 -1
- package/dist/sql/Statement.js +8 -6
- package/dist/sql/Statement.js.map +1 -1
- package/package.json +4 -4
- package/src/ByteSize.ts +29 -14
- package/src/Iterable.ts +20 -14
- package/src/JsonSchema.ts +45 -7
- package/src/MutableRef.ts +1 -8
- package/src/Ref.ts +0 -8
- package/src/Schedule.ts +8 -6
- package/src/Stream.ts +6 -5
- package/src/ai/McpServer.ts +66 -43
- package/src/ai/internal/mcpProtocol/v2026_07_28.ts +4 -5
- package/src/ai/internal/structured-output.ts +10 -4
- package/src/cluster/ClusterWorkflowEngine.ts +29 -28
- package/src/cluster/Entity.ts +52 -0
- package/src/cluster/MessageStorage.ts +3 -6
- package/src/cluster/Runners.ts +16 -2
- package/src/cluster/Sharding.ts +13 -0
- package/src/cluster/internal/clusterAbandon.ts +7 -7
- package/src/cluster/internal/entityManager.ts +65 -5
- package/src/cluster/internal/transactionNotifications.ts +7 -0
- package/src/encoding/Json5.ts +59 -0
- package/src/encoding/Jsonc.ts +126 -0
- package/src/encoding/SchemaBinary.ts +228 -7
- package/src/encoding/index.ts +12 -0
- package/src/eventlog/EventLogServer.ts +36 -12
- package/src/http/HttpClient.ts +34 -22
- package/src/internal/dateTime.ts +26 -3
- package/src/internal/jsonParser.ts +307 -0
- package/src/internal/persistence.ts +9 -1
- package/src/internal/sql.ts +25 -0
- package/src/internal/version.ts +1 -1
- package/src/observability/OtlpExporter.ts +42 -18
- package/src/persistence/RateLimiter.ts +13 -4
- package/src/rpc/RpcServer.ts +12 -1
- package/src/sql/SqlClient.ts +48 -14
- package/src/sql/Statement.ts +8 -13
package/src/JsonSchema.ts
CHANGED
|
@@ -506,9 +506,23 @@ export function fromSchemaOpenApi3_0(schema: JsonSchema): Document<"draft-2020-1
|
|
|
506
506
|
* @since 4.0.0
|
|
507
507
|
*/
|
|
508
508
|
export function toDocumentDraft07(document: Document<"draft-2020-12">): Document<"draft-07"> {
|
|
509
|
+
return toDocumentDraft07WithDefinitionsPath(document)
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Converts to Draft-07 with definitions located at `definitionsPath`, such as
|
|
514
|
+
* `["components", "schemas"]` after {@link toMultiDocumentComponents}, so
|
|
515
|
+
* references into them stay in place. Defaults to `$defs` → `definitions`.
|
|
516
|
+
*
|
|
517
|
+
* @internal
|
|
518
|
+
*/
|
|
519
|
+
export function toDocumentDraft07WithDefinitionsPath(
|
|
520
|
+
document: Document<"draft-2020-12">,
|
|
521
|
+
definitionsPath?: ReadonlyArray<string>
|
|
522
|
+
): Document<"draft-07"> {
|
|
509
523
|
return {
|
|
510
524
|
dialect: "draft-07",
|
|
511
|
-
...convertDocument(document, draft07Adapter)
|
|
525
|
+
...convertDocument(document, draft07Adapter, undefined, definitionsPath)
|
|
512
526
|
}
|
|
513
527
|
}
|
|
514
528
|
|
|
@@ -620,6 +634,25 @@ export function toDocumentDraft04(document: Document<"draft-2020-12">): Document
|
|
|
620
634
|
* @since 4.0.0
|
|
621
635
|
*/
|
|
622
636
|
export function toMultiDocumentOpenApi3_1(multiDocument: MultiDocument<"draft-2020-12">): MultiDocument<"openapi-3.1"> {
|
|
637
|
+
return {
|
|
638
|
+
dialect: "openapi-3.1",
|
|
639
|
+
...toMultiDocumentComponents(multiDocument, {
|
|
640
|
+
target: "OpenAPI 3.1",
|
|
641
|
+
checkSchema(schema) {
|
|
642
|
+
rejectKeywordCollisions(schema, OPEN_API_31_TARGET_COLLISIONS, "OpenAPI 3.1", "Draft 2020-12")
|
|
643
|
+
}
|
|
644
|
+
})
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/** @internal */
|
|
649
|
+
export function toMultiDocumentComponents(
|
|
650
|
+
multiDocument: Pick<MultiDocument<"draft-2020-12">, "schemas" | "definitions">,
|
|
651
|
+
options: {
|
|
652
|
+
readonly target: string
|
|
653
|
+
readonly checkSchema?: ((schema: JsonSchema) => void) | undefined
|
|
654
|
+
}
|
|
655
|
+
): Pick<MultiDocument<"draft-2020-12">, "schemas" | "definitions"> {
|
|
623
656
|
const definitionKeys = Object.keys(multiDocument.definitions)
|
|
624
657
|
const keyMap = new Map<string, string>()
|
|
625
658
|
const usedKeys = new Set(definitionKeys.filter((key) => VALID_OPEN_API_COMPONENTS_SCHEMAS_KEY_REGEXP.test(key)))
|
|
@@ -649,7 +682,7 @@ export function toMultiDocumentOpenApi3_1(multiDocument: MultiDocument<"draft-20
|
|
|
649
682
|
const isRootResource = createsResource(schema.$id)
|
|
650
683
|
const localDefinitions = Predicate.isObject(schema.$defs) ? schema.$defs : undefined
|
|
651
684
|
return transformSchema(schema, (schema, inEmbeddedResource) => {
|
|
652
|
-
|
|
685
|
+
options.checkSchema?.(schema)
|
|
653
686
|
rewriteSchemaRef(schema, (reference, keyword) => {
|
|
654
687
|
const path = reference.startsWith("#") ? parseUriFragment(reference) : undefined
|
|
655
688
|
if (path === undefined || path[0] !== "$defs" || path.length < 2) return reference
|
|
@@ -661,7 +694,7 @@ export function toMultiDocumentOpenApi3_1(multiDocument: MultiDocument<"draft-20
|
|
|
661
694
|
Object.hasOwn(multiDocument.definitions, key) &&
|
|
662
695
|
(localDefinitions === undefined || !Object.hasOwn(localDefinitions, key))
|
|
663
696
|
) {
|
|
664
|
-
unsupported(keyword,
|
|
697
|
+
unsupported(keyword, options.target, "a schema resource cannot reference the shared definitions pool")
|
|
665
698
|
}
|
|
666
699
|
return reference
|
|
667
700
|
}
|
|
@@ -673,7 +706,7 @@ export function toMultiDocumentOpenApi3_1(multiDocument: MultiDocument<"draft-20
|
|
|
673
706
|
}
|
|
674
707
|
|
|
675
708
|
const schemas = multiDocument.schemas.map((schema) => rewrite(schema, true)) as unknown as MultiDocument<
|
|
676
|
-
"
|
|
709
|
+
"draft-2020-12"
|
|
677
710
|
>["schemas"]
|
|
678
711
|
const definitions: Definitions = {}
|
|
679
712
|
for (const key of definitionKeys) {
|
|
@@ -685,7 +718,6 @@ export function toMultiDocumentOpenApi3_1(multiDocument: MultiDocument<"draft-20
|
|
|
685
718
|
}
|
|
686
719
|
|
|
687
720
|
return {
|
|
688
|
-
dialect: "openapi-3.1",
|
|
689
721
|
schemas,
|
|
690
722
|
definitions
|
|
691
723
|
}
|
|
@@ -957,13 +989,19 @@ function convertSchema(root: JsonSchema, adapter: Adapter, options?: ConverterOp
|
|
|
957
989
|
function convertDocument(
|
|
958
990
|
document: Document<"draft-2020-12">,
|
|
959
991
|
adapter: Adapter,
|
|
960
|
-
options?: ConverterOptions
|
|
992
|
+
options?: ConverterOptions,
|
|
993
|
+
definitionsPath?: Path
|
|
961
994
|
): { readonly schema: JsonSchema; readonly definitions: Definitions } {
|
|
962
995
|
return runConverter(adapter, { ...options, trackIds: true }, (convert) => ({
|
|
963
996
|
schema: convert(document.schema) as JsonSchema,
|
|
964
997
|
definitions: mapObject(
|
|
965
998
|
document.definitions,
|
|
966
|
-
(definition, key) =>
|
|
999
|
+
(definition, key) =>
|
|
1000
|
+
convert(
|
|
1001
|
+
definition,
|
|
1002
|
+
[...(definitionsPath ?? ["$defs"]), key],
|
|
1003
|
+
[...(definitionsPath ?? ["definitions"]), key]
|
|
1004
|
+
) as JsonSchema
|
|
967
1005
|
) as Definitions
|
|
968
1006
|
}))
|
|
969
1007
|
}
|
package/src/MutableRef.ts
CHANGED
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import * as Equal from "./Equal.ts"
|
|
14
14
|
import * as Dual from "./Function.ts"
|
|
15
|
-
import * as Hash from "./Hash.ts"
|
|
16
15
|
import { type Inspectable, toJson } from "./Inspectable.ts"
|
|
17
16
|
import { PipeInspectableProto } from "./internal/core.ts"
|
|
18
17
|
import type { Pipeable } from "./Pipeable.ts"
|
|
@@ -75,15 +74,9 @@ export interface MutableRef<out T> extends Pipeable, Inspectable {
|
|
|
75
74
|
current: T
|
|
76
75
|
}
|
|
77
76
|
|
|
78
|
-
const MutableRefProto: Omit<MutableRef<unknown>, "current">
|
|
77
|
+
const MutableRefProto: Omit<MutableRef<unknown>, "current"> = {
|
|
79
78
|
[TypeId]: TypeId,
|
|
80
79
|
...PipeInspectableProto,
|
|
81
|
-
[Equal.symbol](that: unknown): boolean {
|
|
82
|
-
return this === that
|
|
83
|
-
},
|
|
84
|
-
[Hash.symbol](): number {
|
|
85
|
-
return Hash.random(this)
|
|
86
|
-
},
|
|
87
80
|
toJSON<A>(this: MutableRef<A>) {
|
|
88
81
|
return {
|
|
89
82
|
_id: "MutableRef",
|
package/src/Ref.ts
CHANGED
|
@@ -12,9 +12,7 @@
|
|
|
12
12
|
* @since 2.0.0
|
|
13
13
|
*/
|
|
14
14
|
import * as Effect from "./Effect.ts"
|
|
15
|
-
import * as Equal from "./Equal.ts"
|
|
16
15
|
import { dual, identity } from "./Function.ts"
|
|
17
|
-
import * as Hash from "./Hash.ts"
|
|
18
16
|
import { PipeInspectableProto } from "./internal/core.ts"
|
|
19
17
|
import * as MutableRef from "./MutableRef.ts"
|
|
20
18
|
import type * as Option from "./Option.ts"
|
|
@@ -112,12 +110,6 @@ const RefProto = {
|
|
|
112
110
|
_A: identity
|
|
113
111
|
},
|
|
114
112
|
...PipeInspectableProto,
|
|
115
|
-
[Equal.symbol](that: unknown): boolean {
|
|
116
|
-
return this === that
|
|
117
|
-
},
|
|
118
|
-
[Hash.symbol](): number {
|
|
119
|
-
return Hash.random(this)
|
|
120
|
-
},
|
|
121
113
|
toJSON(this: Ref<any>) {
|
|
122
114
|
return {
|
|
123
115
|
_id: "Ref",
|
package/src/Schedule.ts
CHANGED
|
@@ -889,7 +889,8 @@ export const cron: {
|
|
|
889
889
|
return Cause.done(Duration.zero)
|
|
890
890
|
}
|
|
891
891
|
return effect.sync(() => {
|
|
892
|
-
|
|
892
|
+
let next = Cron.next(cron, now).getTime()
|
|
893
|
+
if (Duration.isZero(Duration.millis(next - now))) next = Cron.next(cron, next).getTime()
|
|
893
894
|
const duration = Duration.millis(next - now)
|
|
894
895
|
return [duration, duration]
|
|
895
896
|
})
|
|
@@ -1157,7 +1158,7 @@ export const fixed = (interval: Duration.Input): Schedule<number> => {
|
|
|
1157
1158
|
}
|
|
1158
1159
|
const runningBehind = meta.now > (lastRun + window)
|
|
1159
1160
|
const boundary = window - ((meta.now - start) % window)
|
|
1160
|
-
const delay = runningBehind ? 0 : boundary
|
|
1161
|
+
const delay = runningBehind ? 0 : Duration.isZero(Duration.millis(boundary)) ? window : boundary
|
|
1161
1162
|
lastRun = runningBehind ? meta.now : meta.now + delay
|
|
1162
1163
|
return [meta.attempt - 1, Duration.millis(delay)] as const
|
|
1163
1164
|
})
|
|
@@ -1901,10 +1902,11 @@ export {
|
|
|
1901
1902
|
export const windowed = (interval: Duration.Input): Schedule<number> => {
|
|
1902
1903
|
const window = Duration.toMillis(Duration.fromInputUnsafe(interval))
|
|
1903
1904
|
return fromStepWithMetadata(effect.succeed((meta) =>
|
|
1904
|
-
effect.sync(() =>
|
|
1905
|
-
meta.attempt - 1,
|
|
1906
|
-
|
|
1907
|
-
|
|
1905
|
+
effect.sync(() => {
|
|
1906
|
+
if (window === 0) return [meta.attempt - 1, Duration.zero] as const
|
|
1907
|
+
const delay = Duration.millis(window - (meta.elapsed % window))
|
|
1908
|
+
return [meta.attempt - 1, Duration.isZero(delay) ? Duration.millis(window) : delay] as const
|
|
1909
|
+
})
|
|
1908
1910
|
))
|
|
1909
1911
|
}
|
|
1910
1912
|
|
package/src/Stream.ts
CHANGED
|
@@ -15245,12 +15245,13 @@ export const groupAdjacentBy: {
|
|
|
15245
15245
|
return loop
|
|
15246
15246
|
})
|
|
15247
15247
|
)
|
|
15248
|
-
let
|
|
15249
|
-
return
|
|
15250
|
-
|
|
15251
|
-
|
|
15248
|
+
let cause: Cause.Cause<E | Cause.Done> | undefined
|
|
15249
|
+
return Effect.catchCause(Effect.suspend(() => cause ? Effect.failCause(cause) : loop), (cause_) => {
|
|
15250
|
+
cause = cause_
|
|
15251
|
+
if (group === undefined) return Effect.failCause(cause_)
|
|
15252
|
+
const out = Arr.of([currentKey, group] as const)
|
|
15252
15253
|
group = undefined
|
|
15253
|
-
return
|
|
15254
|
+
return Effect.succeed(out)
|
|
15254
15255
|
})
|
|
15255
15256
|
})))
|
|
15256
15257
|
|
package/src/ai/McpServer.ts
CHANGED
|
@@ -737,23 +737,48 @@ class MiddlewareImpl {
|
|
|
737
737
|
}
|
|
738
738
|
}
|
|
739
739
|
|
|
740
|
-
// The innermost middleware comes first.
|
|
740
|
+
// The innermost middleware comes first. Each stack keeps its own order, and
|
|
741
|
+
// where that leaves the order open, stacks provided further outside, which come
|
|
742
|
+
// first in the context, run further outside. Stacks that order the same
|
|
743
|
+
// middleware both ways have no common order, so insertion order decides.
|
|
741
744
|
const getMiddleware = (context: Context.Context<never>): ReadonlyArray<AnyMiddlewareFn> => {
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
745
|
+
// The middleware directly inside each middleware, and how many of those
|
|
746
|
+
// directly outside it are still unplaced.
|
|
747
|
+
const inners = new Map<AnyMiddlewareFn, Array<AnyMiddlewareFn>>()
|
|
748
|
+
const unplacedOuters = new Map<AnyMiddlewareFn, number>()
|
|
749
|
+
for (const [key, stack] of context.mapUnsafe) {
|
|
750
|
+
if (!key.startsWith(middlewareKeyPrefix)) continue
|
|
751
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
752
|
+
const fn = stack[i]
|
|
753
|
+
if (!inners.has(fn)) {
|
|
754
|
+
inners.set(fn, [])
|
|
755
|
+
unplacedOuters.set(fn, 0)
|
|
756
|
+
}
|
|
757
|
+
const outer = stack[i + 1]
|
|
758
|
+
if (outer !== undefined && outer !== fn) {
|
|
759
|
+
inners.get(outer)!.push(fn)
|
|
760
|
+
unplacedOuters.set(fn, unplacedOuters.get(fn)! + 1)
|
|
761
|
+
}
|
|
748
762
|
}
|
|
749
763
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
764
|
+
// Repeatedly places the first middleware with nothing left to place outside it.
|
|
765
|
+
const outermostFirst: Array<AnyMiddlewareFn> = []
|
|
766
|
+
while (unplacedOuters.size > 0) {
|
|
767
|
+
let next = unplacedOuters.keys().next().value!
|
|
768
|
+
for (const [fn, count] of unplacedOuters) {
|
|
769
|
+
if (count === 0) {
|
|
770
|
+
next = fn
|
|
771
|
+
break
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
unplacedOuters.delete(next)
|
|
775
|
+
outermostFirst.push(next)
|
|
776
|
+
for (const fn of inners.get(next)!) {
|
|
777
|
+
const count = unplacedOuters.get(fn)
|
|
778
|
+
if (count !== undefined) unplacedOuters.set(fn, count - 1)
|
|
754
779
|
}
|
|
755
780
|
}
|
|
756
|
-
return
|
|
781
|
+
return outermostFirst.reverse()
|
|
757
782
|
}
|
|
758
783
|
|
|
759
784
|
const isMcpError = (error: unknown): error is McpErrorBase =>
|
|
@@ -2099,7 +2124,7 @@ const runWithRuntime = Effect.fnUntraced(function*(
|
|
|
2099
2124
|
const selectedProtocol = clientState?.protocol ?? runtime.protocols[0]
|
|
2100
2125
|
const profile = reverseKey?.profile ?? clientState?.profile ?? {
|
|
2101
2126
|
protocolVersion: selectedProtocol.protocolVersion,
|
|
2102
|
-
clientCapabilities: {},
|
|
2127
|
+
clientCapabilities: new McpSchema.ClientCapabilities({}),
|
|
2103
2128
|
clientInfo: { name: "unknown", version: "unknown" }
|
|
2104
2129
|
}
|
|
2105
2130
|
return RcMap.get(
|
|
@@ -3081,40 +3106,40 @@ export const registerToolkit: <Tools extends Record<string, Tool.Any>>(
|
|
|
3081
3106
|
const annotations = tool.annotations
|
|
3082
3107
|
const toolMeta = Context.getOrUndefined(annotations, Tool.Meta)
|
|
3083
3108
|
const isDeclaredFailure = Schema.is(tool.failureSchema)
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
new CallToolResult({ isError: true, content: toolResultContent(encoded) }))
|
|
3092
|
-
// `structuredContent` must be JSON and match the advertised `outputSchema`, which is derived from the JSON codec.
|
|
3093
|
-
// The Toolkit has already encoded the result, so only the JSON conversion of its encoded form runs here.
|
|
3094
|
-
const encodeStructured = Schema.encodeUnknownEffect(Schema.toCodecJson(Schema.toEncoded(tool.successSchema))) as (
|
|
3095
|
-
result: unknown
|
|
3096
|
-
) => Effect.Effect<unknown, Schema.SchemaError, Tool.HandlerServices<Tools[keyof Tools]>>
|
|
3097
|
-
const successResult = (result: unknown) =>
|
|
3098
|
-
encodeStructured(result).pipe(
|
|
3099
|
-
Effect.mapError((cause) =>
|
|
3109
|
+
// MCP content is JSON, so results and declared failures go through their JSON codec.
|
|
3110
|
+
const encodeJson = (schema: Schema.Constraint) => {
|
|
3111
|
+
const encode = Schema.encodeUnknownEffect(Schema.toCodecJson(schema)) as (
|
|
3112
|
+
value: unknown
|
|
3113
|
+
) => Effect.Effect<unknown, Schema.SchemaError, Tool.HandlerServices<Tools[keyof Tools]>>
|
|
3114
|
+
return (toolResult: unknown) =>
|
|
3115
|
+
Effect.mapError(encode(toolResult), (cause) =>
|
|
3100
3116
|
AiError.make({
|
|
3101
3117
|
module: "McpServer",
|
|
3102
3118
|
method: "registerToolkit",
|
|
3103
3119
|
reason: new AiError.ToolResultEncodingError({
|
|
3104
3120
|
toolName: tool.name,
|
|
3105
|
-
toolResult
|
|
3121
|
+
toolResult,
|
|
3106
3122
|
description: cause.message
|
|
3107
3123
|
})
|
|
3108
|
-
})
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3124
|
+
}))
|
|
3125
|
+
}
|
|
3126
|
+
const encodeFailure = encodeJson(tool.failureSchema)
|
|
3127
|
+
// Successes and "return"-mode failures arrive already encoded by the Toolkit; only their JSON conversion runs here.
|
|
3128
|
+
const encodeReturnedFailure = encodeJson(Schema.toEncoded(tool.failureSchema))
|
|
3129
|
+
const encodeStructured = encodeJson(Schema.toEncoded(tool.successSchema))
|
|
3130
|
+
const failureResult = (encoded: unknown) =>
|
|
3131
|
+
new CallToolResult({ isError: true, content: toolResultContent(encoded) })
|
|
3132
|
+
const declaredFailureResult = (error: unknown) =>
|
|
3133
|
+
error instanceof Error && error.message !== ""
|
|
3134
|
+
? Effect.succeed(toolErrorResult(error.message))
|
|
3135
|
+
: Effect.map(encodeFailure(error), failureResult)
|
|
3136
|
+
const successResult = (result: unknown) =>
|
|
3137
|
+
Effect.map(encodeStructured(result), (structuredContent) =>
|
|
3138
|
+
new CallToolResult({
|
|
3139
|
+
isError: false,
|
|
3140
|
+
structuredContent,
|
|
3141
|
+
content: toolResultContent(structuredContent)
|
|
3142
|
+
}))
|
|
3118
3143
|
const handleCause = (cause: Cause.Cause<unknown>) => {
|
|
3119
3144
|
const failure = Cause.findFail(cause)
|
|
3120
3145
|
if (Result.isSuccess(failure)) {
|
|
@@ -3183,9 +3208,7 @@ export const registerToolkit: <Tools extends Record<string, Tool.Any>>(
|
|
|
3183
3208
|
}
|
|
3184
3209
|
// Declared failures return their encoded payload; anything else is classified by origin.
|
|
3185
3210
|
return result.failureOrigin === "handler"
|
|
3186
|
-
? Effect.
|
|
3187
|
-
new CallToolResult({ isError: true, content: toolResultContent(result.encodedResult) })
|
|
3188
|
-
)
|
|
3211
|
+
? Effect.map(encodeReturnedFailure(result.encodedResult), failureResult)
|
|
3189
3212
|
: Effect.failCause(Cause.annotate(
|
|
3190
3213
|
Cause.fail(result.result),
|
|
3191
3214
|
Context.make(Toolkit.FailureOrigin, result.failureOrigin ?? "result")
|
|
@@ -203,13 +203,12 @@ const projectContent = Match.type<PublicMcpSchema.ContentBlock>().pipe(
|
|
|
203
203
|
Match.exhaustive
|
|
204
204
|
)
|
|
205
205
|
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
// Registrations may be plain objects, so encode with the wire struct.
|
|
207
|
+
const projectResource = (resource: PublicMcpSchema.Resource) => Schema.encodeEffect(McpSchema.Resource)(resource)
|
|
208
208
|
const projectResourceTemplate = (resourceTemplate: PublicMcpSchema.ResourceTemplate) =>
|
|
209
|
-
|
|
209
|
+
Schema.encodeEffect(McpSchema.ResourceTemplate)(resourceTemplate)
|
|
210
210
|
|
|
211
|
-
const projectPrompt = (prompt: PublicMcpSchema.Prompt) =>
|
|
212
|
-
McpProtocol.transcode(PublicMcpSchema.Prompt, McpSchema.Prompt, prompt)
|
|
211
|
+
const projectPrompt = (prompt: PublicMcpSchema.Prompt) => Schema.encodeEffect(McpSchema.Prompt)(prompt)
|
|
213
212
|
|
|
214
213
|
const projectTool = (tool: PublicMcpSchema.Tool) => McpProtocol.transcode(PublicMcpSchema.Tool, McpSchema.Tool, tool)
|
|
215
214
|
|
|
@@ -38,11 +38,17 @@ export function resolveReference($ref: string, definitions: JsonSchema.Definitio
|
|
|
38
38
|
export function resolveTopLevelReference(
|
|
39
39
|
document: JsonSchema.Document<"draft-2020-12">
|
|
40
40
|
): JsonSchema.Document<"draft-2020-12"> {
|
|
41
|
-
|
|
42
|
-
return
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
let schema = document.schema
|
|
42
|
+
if (typeof schema.$ref !== "string") return document
|
|
43
|
+
const visited = new Set<string>()
|
|
44
|
+
while (typeof schema.$ref === "string") {
|
|
45
|
+
if (visited.has(schema.$ref)) {
|
|
46
|
+
throw new Error(`Circular reference ${JSON.stringify(schema.$ref)}`)
|
|
47
|
+
}
|
|
48
|
+
visited.add(schema.$ref)
|
|
49
|
+
schema = resolveReference(schema.$ref, document.definitions)
|
|
45
50
|
}
|
|
51
|
+
return { ...document, schema }
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
/**
|
|
@@ -267,7 +267,11 @@ export const make = Effect.gen(function*() {
|
|
|
267
267
|
id: activityPrimaryKey(options.activity.name, options.attempt)
|
|
268
268
|
})
|
|
269
269
|
if (Option.isNone(requestId)) return
|
|
270
|
-
|
|
270
|
+
// Reset only the Suspended reply this runner saw. If another runner has
|
|
271
|
+
// stored a newer reply, storage skips the reset and the caller reads it.
|
|
272
|
+
const reply = yield* replyForRequestId(requestId.value)
|
|
273
|
+
if (Option.isNone(reply) || !isSuspendedReply(reply.value)) return
|
|
274
|
+
yield* sharding.reset(requestId.value, { expectedReplyId: Snowflake.Snowflake(reply.value.id) })
|
|
271
275
|
},
|
|
272
276
|
Effect.retry({
|
|
273
277
|
times: 3,
|
|
@@ -344,7 +348,7 @@ export const make = Effect.gen(function*() {
|
|
|
344
348
|
}
|
|
345
349
|
const reply = yield* replyForRequestId(requestId.value)
|
|
346
350
|
if (Option.isNone(reply)) return
|
|
347
|
-
yield* sharding.reset(requestId.value)
|
|
351
|
+
yield* sharding.reset(requestId.value, { expectedReplyId: Snowflake.Snowflake(reply.value.id) })
|
|
348
352
|
}, Effect.scoped)
|
|
349
353
|
|
|
350
354
|
// Subscribe to the run's next reply before reading storage, so a reply
|
|
@@ -865,7 +869,7 @@ export const make = Effect.gen(function*() {
|
|
|
865
869
|
}
|
|
866
870
|
const workflow = workflows.get(workflowName)
|
|
867
871
|
if (workflow) {
|
|
868
|
-
return yield*
|
|
872
|
+
return yield* retryPersistence(sendDiscard({
|
|
869
873
|
rpc: DeferredRpc,
|
|
870
874
|
address: entityAddressFor({
|
|
871
875
|
workflow,
|
|
@@ -879,7 +883,7 @@ export const make = Effect.gen(function*() {
|
|
|
879
883
|
}))
|
|
880
884
|
}
|
|
881
885
|
const client = yield* RcMap.get(clientsPartial, workflowName)
|
|
882
|
-
return yield*
|
|
886
|
+
return yield* retryPersistence(
|
|
883
887
|
client(executionId).deferred({
|
|
884
888
|
name: deferredName,
|
|
885
889
|
exit
|
|
@@ -890,27 +894,24 @@ export const make = Effect.gen(function*() {
|
|
|
890
894
|
),
|
|
891
895
|
|
|
892
896
|
scheduleClock(workflow, options) {
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
),
|
|
912
|
-
Effect.orDie
|
|
913
|
-
)
|
|
897
|
+
// Compute the deadline once, so a retried save keeps the original wake-up.
|
|
898
|
+
return Effect.flatMap(DateTime.now, (now) =>
|
|
899
|
+
retryPersistence(sendDiscard({
|
|
900
|
+
rpc: ClockRpc,
|
|
901
|
+
address: entityAddressFor({
|
|
902
|
+
workflow,
|
|
903
|
+
entityType: ClockEntity.type,
|
|
904
|
+
executionId: options.executionId
|
|
905
|
+
}),
|
|
906
|
+
payload: {
|
|
907
|
+
name: options.clock.name,
|
|
908
|
+
workflowName: workflow._tag,
|
|
909
|
+
wakeUp: DateTime.mapEpochMillis(
|
|
910
|
+
DateTime.addDuration(now, options.clock.duration),
|
|
911
|
+
Math.ceil
|
|
912
|
+
)
|
|
913
|
+
}
|
|
914
|
+
})))
|
|
914
915
|
}
|
|
915
916
|
})
|
|
916
917
|
|
|
@@ -922,9 +923,9 @@ const retryPolicy = Schedule.min([
|
|
|
922
923
|
Schedule.spaced("1 minute")
|
|
923
924
|
])
|
|
924
925
|
|
|
925
|
-
// A transient storage failure must not become the workflow result.
|
|
926
|
-
// are saved under a primary key, so a retry after
|
|
927
|
-
// deduplicated instead of
|
|
926
|
+
// A transient storage failure must not become the workflow result. Activity,
|
|
927
|
+
// clock and deferred requests are saved under a primary key, so a retry after
|
|
928
|
+
// a lost acknowledgement is deduplicated instead of stored twice.
|
|
928
929
|
const retryPersistence = <A, E extends { readonly _tag: string }, R>(effect: Effect.Effect<A, E, R>) =>
|
|
929
930
|
effect.pipe(
|
|
930
931
|
Effect.retry({ schedule: retryPolicy, while: (e) => e._tag === "PersistenceError" }),
|
package/src/cluster/Entity.ts
CHANGED
|
@@ -148,7 +148,20 @@ export interface Entity<
|
|
|
148
148
|
readonly concurrency?: number | "unbounded" | undefined
|
|
149
149
|
readonly mailboxCapacity?: number | "unbounded" | undefined
|
|
150
150
|
readonly mailboxMetrics?: boolean | undefined
|
|
151
|
+
/**
|
|
152
|
+
* When `true`, a handler defect fails only its request instead of
|
|
153
|
+
* restarting the entity and replaying its in-flight requests.
|
|
154
|
+
*/
|
|
151
155
|
readonly disableFatalDefects?: boolean | undefined
|
|
156
|
+
/**
|
|
157
|
+
* Schedule for restarting an entity after a defect and for retrying failed
|
|
158
|
+
* handler construction.
|
|
159
|
+
*
|
|
160
|
+
* Backoff is tracked per entity and resets once a request completes.
|
|
161
|
+
* When the policy ends, the default (500 ms growing by 1.5x, capped at 10
|
|
162
|
+
* seconds) continues, so restarts never stop; use `disableFatalDefects` to
|
|
163
|
+
* avoid them.
|
|
164
|
+
*/
|
|
152
165
|
readonly defectRetryPolicy?: Schedule.Schedule<any, unknown> | undefined
|
|
153
166
|
readonly spanAttributes?: Record<string, string> | undefined
|
|
154
167
|
}
|
|
@@ -193,7 +206,20 @@ export interface Entity<
|
|
|
193
206
|
readonly maxIdleTime?: Duration.Input | undefined
|
|
194
207
|
readonly mailboxCapacity?: number | "unbounded" | undefined
|
|
195
208
|
readonly mailboxMetrics?: boolean | undefined
|
|
209
|
+
/**
|
|
210
|
+
* When `true`, a handler defect fails only its request instead of
|
|
211
|
+
* restarting the entity and replaying its in-flight requests.
|
|
212
|
+
*/
|
|
196
213
|
readonly disableFatalDefects?: boolean | undefined
|
|
214
|
+
/**
|
|
215
|
+
* Schedule for restarting an entity after a defect and for retrying failed
|
|
216
|
+
* handler construction.
|
|
217
|
+
*
|
|
218
|
+
* Backoff is tracked per entity and resets once a request completes.
|
|
219
|
+
* When the policy ends, the default (500 ms growing by 1.5x, capped at 10
|
|
220
|
+
* seconds) continues, so restarts never stop; use `disableFatalDefects` to
|
|
221
|
+
* avoid them.
|
|
222
|
+
*/
|
|
197
223
|
readonly defectRetryPolicy?: Schedule.Schedule<any, unknown> | undefined
|
|
198
224
|
readonly spanAttributes?: Record<string, string> | undefined
|
|
199
225
|
}
|
|
@@ -290,7 +316,20 @@ const Proto = {
|
|
|
290
316
|
readonly concurrency?: number | "unbounded" | undefined
|
|
291
317
|
readonly mailboxCapacity?: number | "unbounded" | undefined
|
|
292
318
|
readonly mailboxMetrics?: boolean | undefined
|
|
319
|
+
/**
|
|
320
|
+
* When `true`, a handler defect fails only its request instead of
|
|
321
|
+
* restarting the entity and replaying its in-flight requests.
|
|
322
|
+
*/
|
|
293
323
|
readonly disableFatalDefects?: boolean | undefined
|
|
324
|
+
/**
|
|
325
|
+
* Schedule for restarting an entity after a defect and for retrying failed
|
|
326
|
+
* handler construction.
|
|
327
|
+
*
|
|
328
|
+
* Backoff is tracked per entity and resets once a request completes.
|
|
329
|
+
* When the policy ends, the default (500 ms growing by 1.5x, capped at 10
|
|
330
|
+
* seconds) continues, so restarts never stop; use `disableFatalDefects` to
|
|
331
|
+
* avoid them.
|
|
332
|
+
*/
|
|
294
333
|
readonly defectRetryPolicy?: Schedule.Schedule<any, unknown> | undefined
|
|
295
334
|
readonly spanAttributes?: Record<string, string> | undefined
|
|
296
335
|
}
|
|
@@ -339,7 +378,20 @@ const Proto = {
|
|
|
339
378
|
readonly maxIdleTime?: Duration.Input | undefined
|
|
340
379
|
readonly mailboxCapacity?: number | "unbounded" | undefined
|
|
341
380
|
readonly mailboxMetrics?: boolean | undefined
|
|
381
|
+
/**
|
|
382
|
+
* When `true`, a handler defect fails only its request instead of
|
|
383
|
+
* restarting the entity and replaying its in-flight requests.
|
|
384
|
+
*/
|
|
342
385
|
readonly disableFatalDefects?: boolean | undefined
|
|
386
|
+
/**
|
|
387
|
+
* Schedule for restarting an entity after a defect and for retrying failed
|
|
388
|
+
* handler construction.
|
|
389
|
+
*
|
|
390
|
+
* Backoff is tracked per entity and resets once a request completes.
|
|
391
|
+
* When the policy ends, the default (500 ms growing by 1.5x, capped at 10
|
|
392
|
+
* seconds) continues, so restarts never stop; use `disableFatalDefects` to
|
|
393
|
+
* avoid them.
|
|
394
|
+
*/
|
|
343
395
|
readonly defectRetryPolicy?: Schedule.Schedule<any, unknown> | undefined
|
|
344
396
|
readonly spanAttributes?: Record<string, string> | undefined
|
|
345
397
|
}
|
|
@@ -30,6 +30,7 @@ import * as DeliverAt from "./DeliverAt.ts"
|
|
|
30
30
|
import type { EntityAddress } from "./EntityAddress.ts"
|
|
31
31
|
import * as Envelope from "./Envelope.ts"
|
|
32
32
|
import * as ClusterAbandon from "./internal/clusterAbandon.ts"
|
|
33
|
+
import { TransactionNotifications } from "./internal/transactionNotifications.ts"
|
|
33
34
|
import * as Message from "./Message.ts"
|
|
34
35
|
import * as Reply from "./Reply.ts"
|
|
35
36
|
import * as ShardId from "./ShardId.ts"
|
|
@@ -223,8 +224,8 @@ export class MessageStorage extends Context.Service<MessageStorage, {
|
|
|
223
224
|
/**
|
|
224
225
|
* Wraps requests in storage transactions.
|
|
225
226
|
*
|
|
226
|
-
* `WithExit` replies saved in a transaction notify
|
|
227
|
-
* commit; stream chunks notify immediately. Replies saved in a failed
|
|
227
|
+
* `WithExit` replies and discarded sends saved in a transaction notify after
|
|
228
|
+
* the outermost commit; stream chunks notify immediately. Replies saved in a failed
|
|
228
229
|
* transaction must be discarded. The wrapper may fail only with the wrapped
|
|
229
230
|
* effect's own failure or a defect.
|
|
230
231
|
*/
|
|
@@ -532,10 +533,6 @@ export type EncodedRepliesOptions<A> = {
|
|
|
532
533
|
readonly cursor: Option.Option<A>
|
|
533
534
|
}
|
|
534
535
|
|
|
535
|
-
const TransactionNotifications = Context.Reference<
|
|
536
|
-
Array<Effect.Effect<void, PersistenceError | MalformedMessage>> | undefined
|
|
537
|
-
>("effect/cluster/MessageStorage/TransactionNotifications", { defaultValue: () => undefined })
|
|
538
|
-
|
|
539
536
|
/**
|
|
540
537
|
* Wraps a concrete message storage implementation with reply-handler management.
|
|
541
538
|
*
|
package/src/cluster/Runners.ts
CHANGED
|
@@ -32,6 +32,7 @@ import type { MalformedMessage, PersistenceError } from "./ClusterError.ts"
|
|
|
32
32
|
import { AlreadyProcessingMessage, EntityNotAssignedToRunner, MailboxFull, RunnerUnavailable } from "./ClusterError.ts"
|
|
33
33
|
import { Persisted } from "./ClusterSchema.ts"
|
|
34
34
|
import * as Envelope from "./Envelope.ts"
|
|
35
|
+
import { TransactionNotifications } from "./internal/transactionNotifications.ts"
|
|
35
36
|
import * as Message from "./Message.ts"
|
|
36
37
|
import * as MessageStorage from "./MessageStorage.ts"
|
|
37
38
|
import * as Reply from "./Reply.ts"
|
|
@@ -180,6 +181,19 @@ export const make: (
|
|
|
180
181
|
|
|
181
182
|
const requestIdRewrites = new Map<Snowflake.Snowflake, Snowflake.Snowflake>()
|
|
182
183
|
|
|
184
|
+
// A message saved in a transaction is invisible to readers until the commit,
|
|
185
|
+
// so its wake-up waits for the commit. A failure then can no longer be
|
|
186
|
+
// returned and is dropped; the next storage poll delivers the message.
|
|
187
|
+
const notifyAfterCommit = <E>(message: Message.Outgoing<any>, notify: Effect.Effect<void, E>) => {
|
|
188
|
+
const rpc = message.rpc as any as Rpc.AnyWithProps
|
|
189
|
+
if (!Context.get(rpc.annotations, Persisted)) return notify
|
|
190
|
+
return TransactionNotifications.use((notifications) =>
|
|
191
|
+
notifications === undefined ? notify : Effect.sync(() => {
|
|
192
|
+
notifications.push(Effect.ignore(notify))
|
|
193
|
+
})
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
|
|
183
197
|
function notifyWith<E>(
|
|
184
198
|
message: Message.Outgoing<any>,
|
|
185
199
|
afterPersist: (message: Message.Outgoing<any>, isDuplicate: boolean) => Effect.Effect<void, E>
|
|
@@ -471,7 +485,7 @@ export const make: (
|
|
|
471
485
|
const { discard, message } = options_
|
|
472
486
|
return notifyWith(message, (message, duplicate) => {
|
|
473
487
|
if (discard || message._tag === "OutgoingEnvelope") {
|
|
474
|
-
return options.notify(options_)
|
|
488
|
+
return notifyAfterCommit(message, options.notify(options_))
|
|
475
489
|
} else if (!duplicate && Option.isSome(options_.address)) {
|
|
476
490
|
return Effect.catch(
|
|
477
491
|
options.send({
|
|
@@ -499,7 +513,7 @@ export const make: (
|
|
|
499
513
|
}
|
|
500
514
|
return notifyWith(message, (message) => {
|
|
501
515
|
if (options.discard || message._tag === "OutgoingEnvelope") {
|
|
502
|
-
return notifyEntity(message)
|
|
516
|
+
return notifyAfterCommit(message, notifyEntity(message))
|
|
503
517
|
}
|
|
504
518
|
return Effect.andThen(
|
|
505
519
|
notifyEntity(message),
|