effect 4.0.0-beta.105 → 4.0.0-beta.106

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.
Files changed (134) hide show
  1. package/dist/Config.js +3 -3
  2. package/dist/Config.js.map +1 -1
  3. package/dist/ConfigProvider.d.ts +35 -3
  4. package/dist/ConfigProvider.d.ts.map +1 -1
  5. package/dist/ConfigProvider.js +41 -8
  6. package/dist/ConfigProvider.js.map +1 -1
  7. package/dist/Fiber.d.ts +1 -1
  8. package/dist/Fiber.d.ts.map +1 -1
  9. package/dist/Function.d.ts +26 -0
  10. package/dist/Function.d.ts.map +1 -1
  11. package/dist/Function.js +36 -0
  12. package/dist/Function.js.map +1 -1
  13. package/dist/JsonSchema.d.ts.map +1 -1
  14. package/dist/JsonSchema.js +34 -17
  15. package/dist/JsonSchema.js.map +1 -1
  16. package/dist/Schema.d.ts +32 -84
  17. package/dist/Schema.d.ts.map +1 -1
  18. package/dist/Schema.js +58 -54
  19. package/dist/Schema.js.map +1 -1
  20. package/dist/SchemaAST.d.ts +2 -17
  21. package/dist/SchemaAST.d.ts.map +1 -1
  22. package/dist/SchemaAST.js +39 -28
  23. package/dist/SchemaAST.js.map +1 -1
  24. package/dist/SchemaRepresentation.d.ts +15 -3
  25. package/dist/SchemaRepresentation.d.ts.map +1 -1
  26. package/dist/SchemaRepresentation.js +8 -2
  27. package/dist/SchemaRepresentation.js.map +1 -1
  28. package/dist/ScopedRef.d.ts.map +1 -1
  29. package/dist/ScopedRef.js +1 -1
  30. package/dist/ScopedRef.js.map +1 -1
  31. package/dist/TxQueue.js +1 -1
  32. package/dist/TxQueue.js.map +1 -1
  33. package/dist/internal/effect.js +2 -4
  34. package/dist/internal/effect.js.map +1 -1
  35. package/dist/internal/executionPlan.js +3 -2
  36. package/dist/internal/executionPlan.js.map +1 -1
  37. package/dist/internal/rcRef.js +18 -13
  38. package/dist/internal/rcRef.js.map +1 -1
  39. package/dist/internal/schema/fromJsonSchemaDocument.js +10 -6
  40. package/dist/internal/schema/fromJsonSchemaDocument.js.map +1 -1
  41. package/dist/internal/schema/toArbitrary.d.ts +1 -4
  42. package/dist/internal/schema/toArbitrary.d.ts.map +1 -1
  43. package/dist/internal/schema/toArbitrary.js +0 -74
  44. package/dist/internal/schema/toArbitrary.js.map +1 -1
  45. package/dist/internal/schema/toJsonSchemaDocument.js +18 -4
  46. package/dist/internal/schema/toJsonSchemaDocument.js.map +1 -1
  47. package/dist/internal/schema/toRepresentation.js +45 -43
  48. package/dist/internal/schema/toRepresentation.js.map +1 -1
  49. package/dist/testing/TestSchema.js +2 -2
  50. package/dist/testing/TestSchema.js.map +1 -1
  51. package/dist/unstable/ai/McpSchema.d.ts +110 -110
  52. package/dist/unstable/ai/McpSchema.d.ts.map +1 -1
  53. package/dist/unstable/ai/McpServer.d.ts.map +1 -1
  54. package/dist/unstable/ai/McpServer.js +23 -11
  55. package/dist/unstable/ai/McpServer.js.map +1 -1
  56. package/dist/unstable/ai/Prompt.d.ts +4 -3
  57. package/dist/unstable/ai/Prompt.d.ts.map +1 -1
  58. package/dist/unstable/ai/Prompt.js +38 -13
  59. package/dist/unstable/ai/Prompt.js.map +1 -1
  60. package/dist/unstable/cluster/Entity.d.ts +2 -2
  61. package/dist/unstable/cluster/Entity.d.ts.map +1 -1
  62. package/dist/unstable/cluster/Entity.js.map +1 -1
  63. package/dist/unstable/cluster/EntityProxy.d.ts +3 -3
  64. package/dist/unstable/cluster/EntityProxy.d.ts.map +1 -1
  65. package/dist/unstable/cluster/EntityProxy.js +4 -3
  66. package/dist/unstable/cluster/EntityProxy.js.map +1 -1
  67. package/dist/unstable/cluster/Sharding.d.ts +2 -2
  68. package/dist/unstable/cluster/Sharding.d.ts.map +1 -1
  69. package/dist/unstable/cluster/Sharding.js +14 -5
  70. package/dist/unstable/cluster/Sharding.js.map +1 -1
  71. package/dist/unstable/eventlog/EventLogEncryption.d.ts +6 -5
  72. package/dist/unstable/eventlog/EventLogEncryption.d.ts.map +1 -1
  73. package/dist/unstable/eventlog/EventLogEncryption.js +14 -12
  74. package/dist/unstable/eventlog/EventLogEncryption.js.map +1 -1
  75. package/dist/unstable/eventlog/EventLogMessage.d.ts +3 -3
  76. package/dist/unstable/eventlog/EventLogMessage.d.ts.map +1 -1
  77. package/dist/unstable/eventlog/EventLogMessage.js +2 -3
  78. package/dist/unstable/eventlog/EventLogMessage.js.map +1 -1
  79. package/dist/unstable/eventlog/EventLogRemote.js +4 -4
  80. package/dist/unstable/eventlog/EventLogRemote.js.map +1 -1
  81. package/dist/unstable/eventlog/EventLogServerEncrypted.js +3 -2
  82. package/dist/unstable/eventlog/EventLogServerEncrypted.js.map +1 -1
  83. package/dist/unstable/http/HttpServerRequest.d.ts.map +1 -1
  84. package/dist/unstable/http/HttpServerRequest.js +7 -1
  85. package/dist/unstable/http/HttpServerRequest.js.map +1 -1
  86. package/dist/unstable/http/MultipartParser/internal/multipart.d.ts.map +1 -1
  87. package/dist/unstable/http/MultipartParser/internal/multipart.js +18 -6
  88. package/dist/unstable/http/MultipartParser/internal/multipart.js.map +1 -1
  89. package/dist/unstable/httpapi/OpenApi.d.ts.map +1 -1
  90. package/dist/unstable/httpapi/OpenApi.js +1 -1
  91. package/dist/unstable/httpapi/OpenApi.js.map +1 -1
  92. package/dist/unstable/rpc/RpcClient.d.ts.map +1 -1
  93. package/dist/unstable/rpc/RpcClient.js +5 -0
  94. package/dist/unstable/rpc/RpcClient.js.map +1 -1
  95. package/dist/unstable/sql/SqlResolver.d.ts.map +1 -1
  96. package/dist/unstable/sql/SqlResolver.js +20 -16
  97. package/dist/unstable/sql/SqlResolver.js.map +1 -1
  98. package/dist/unstable/workers/Worker.d.ts.map +1 -1
  99. package/dist/unstable/workers/Worker.js +11 -12
  100. package/dist/unstable/workers/Worker.js.map +1 -1
  101. package/package.json +1 -1
  102. package/src/Config.ts +3 -3
  103. package/src/ConfigProvider.ts +43 -8
  104. package/src/Fiber.ts +1 -1
  105. package/src/Function.ts +37 -0
  106. package/src/JsonSchema.ts +43 -42
  107. package/src/Schema.ts +68 -146
  108. package/src/SchemaAST.ts +50 -30
  109. package/src/SchemaRepresentation.ts +15 -3
  110. package/src/ScopedRef.ts +3 -1
  111. package/src/TxQueue.ts +1 -1
  112. package/src/internal/effect.ts +3 -6
  113. package/src/internal/executionPlan.ts +3 -2
  114. package/src/internal/rcRef.ts +24 -16
  115. package/src/internal/schema/fromJsonSchemaDocument.ts +19 -14
  116. package/src/internal/schema/toArbitrary.ts +0 -72
  117. package/src/internal/schema/toJsonSchemaDocument.ts +24 -4
  118. package/src/internal/schema/toRepresentation.ts +66 -48
  119. package/src/testing/TestSchema.ts +2 -2
  120. package/src/unstable/ai/McpServer.ts +23 -11
  121. package/src/unstable/ai/Prompt.ts +37 -15
  122. package/src/unstable/cluster/Entity.ts +7 -2
  123. package/src/unstable/cluster/EntityProxy.ts +12 -5
  124. package/src/unstable/cluster/Sharding.ts +29 -17
  125. package/src/unstable/eventlog/EventLogEncryption.ts +15 -16
  126. package/src/unstable/eventlog/EventLogMessage.ts +2 -3
  127. package/src/unstable/eventlog/EventLogRemote.ts +4 -4
  128. package/src/unstable/eventlog/EventLogServerEncrypted.ts +2 -2
  129. package/src/unstable/http/HttpServerRequest.ts +8 -3
  130. package/src/unstable/http/MultipartParser/internal/multipart.ts +19 -6
  131. package/src/unstable/httpapi/OpenApi.ts +4 -1
  132. package/src/unstable/rpc/RpcClient.ts +5 -0
  133. package/src/unstable/sql/SqlResolver.ts +28 -20
  134. package/src/unstable/workers/Worker.ts +13 -14
@@ -7,17 +7,24 @@ import * as InternalAnnotations from "./annotations.ts"
7
7
 
8
8
  /** @internal */
9
9
  export function toRepresentation(
10
- ast: SchemaAST.AST
10
+ ast: SchemaAST.AST,
11
+ options?: Options
11
12
  ): SchemaRepresentation.Document {
12
- const { references, representations } = toRepresentations([ast])
13
+ const { references, representations } = toRepresentations([ast], options)
13
14
  return { representation: representations[0], references }
14
15
  }
15
16
 
16
17
  /** @internal */
17
18
  export function toRepresentations(
18
- asts: readonly [SchemaAST.AST, ...Array<SchemaAST.AST>]
19
+ asts: readonly [SchemaAST.AST, ...Array<SchemaAST.AST>],
20
+ options?: Options
19
21
  ): SchemaRepresentation.MultiDocument {
20
- return fromASTs(asts)
22
+ return fromASTs(asts, options)
23
+ }
24
+
25
+ /** @internal */
26
+ export interface Options {
27
+ readonly isAnonymousReferenceAllowed?: ((ast: SchemaAST.AST) => boolean) | undefined
21
28
  }
22
29
 
23
30
  type CheckRepresentationAnnotation = SchemaRepresentation.CheckRepresentationAnnotation<
@@ -28,11 +35,49 @@ function annotationsField<A>(annotations: A | undefined): { readonly annotations
28
35
  return annotations === undefined ? undefined : { annotations }
29
36
  }
30
37
 
31
- // Preserve repeated structural nodes as references without adding noise for leaf nodes.
32
- function isShareable(ast: SchemaAST.AST): boolean {
33
- return SchemaAST.isArrays(ast) ||
34
- SchemaAST.isObjects(ast) ||
35
- (SchemaAST.isUnion(ast) && ast.types.some(isShareable))
38
+ function hasShareableStructure(
39
+ ast: SchemaAST.AST,
40
+ isAnonymousReferenceAllowed: Options["isAnonymousReferenceAllowed"]
41
+ ): boolean {
42
+ if (isAnonymousReferenceAllowed?.(ast) === false) return false
43
+ switch (ast._tag) {
44
+ case "Arrays":
45
+ case "Objects":
46
+ case "Suspend":
47
+ return true
48
+ case "Declaration":
49
+ return true
50
+ case "Union":
51
+ return ast.types.some((ast) => hasShareableStructure(ast, isAnonymousReferenceAllowed))
52
+ default:
53
+ return false
54
+ }
55
+ }
56
+
57
+ function isWorthReferencing(bodyCost: number, occurrences: number): boolean {
58
+ return occurrences * bodyCost > bodyCost + occurrences + 1
59
+ }
60
+
61
+ function isAnonymousReferenceEligible(
62
+ ast: SchemaAST.AST,
63
+ occurrences: number,
64
+ isAnonymousReferenceAllowed: Options["isAnonymousReferenceAllowed"]
65
+ ): boolean {
66
+ if (isAnonymousReferenceAllowed?.(ast) === false) return false
67
+ if (hasShareableStructure(ast, isAnonymousReferenceAllowed)) return true
68
+ switch (ast._tag) {
69
+ case "Union":
70
+ return isWorthReferencing(ast.types.length + 1, occurrences)
71
+ case "Enum":
72
+ return isWorthReferencing(ast.enums.length + 1, occurrences)
73
+ case "TemplateLiteral":
74
+ return isWorthReferencing(ast.parts.length + 1, occurrences)
75
+ case "Literal":
76
+ return typeof ast.literal === "string" &&
77
+ isWorthReferencing(ast.literal.length / 32 + 1, occurrences)
78
+ default:
79
+ return false
80
+ }
36
81
  }
37
82
 
38
83
  interface ReferenceIdentifier {
@@ -54,17 +99,15 @@ function resolveReferenceIdentifier(
54
99
  }
55
100
 
56
101
  function fromASTs(
57
- asts: readonly [SchemaAST.AST, ...Array<SchemaAST.AST>]
102
+ asts: readonly [SchemaAST.AST, ...Array<SchemaAST.AST>],
103
+ options: Options | undefined
58
104
  ): SchemaRepresentation.MultiDocument {
59
105
  const references: Record<string, SchemaRepresentation.Representation> = {}
60
106
  const anonymousReferences = new Map<SchemaAST.AST, string>()
61
107
  const referenceOwners = new Map<string, SchemaAST.AST>()
62
- const valueIds = new Map<unknown, number>()
63
- const canonicalByKey = new Map<string, SchemaAST.AST>()
64
- let nextValueId = 0
65
108
  const buildingReferences = new Set<string>()
66
109
  const visiting = new Set<SchemaAST.AST>()
67
- const visited = new Set<SchemaAST.AST>()
110
+ const occurrences = new Map<SchemaAST.AST, number>()
68
111
  const shared = new Set<SchemaAST.AST>()
69
112
 
70
113
  for (const ast of asts) visit(ast)
@@ -84,35 +127,6 @@ function fromASTs(
84
127
  return candidate
85
128
  }
86
129
 
87
- function getValueId(value: unknown): number {
88
- if (typeof value === "number" && globalThis.Number.isNaN(value)) {
89
- return nextValueId++
90
- }
91
- const found = valueIds.get(value)
92
- if (found !== undefined) return found
93
- const id = nextValueId++
94
- valueIds.set(value, id)
95
- return id
96
- }
97
-
98
- function getIdentityKey(ast: SchemaAST.AST): string {
99
- let identity = ast._tag
100
- for (const [key, value] of Object.entries(ast)) {
101
- if (key !== "_tag" && key !== "context") identity += `:${getValueId(value)}`
102
- }
103
- return identity
104
- }
105
-
106
- function getCanonicalAST(ast: SchemaAST.AST): SchemaAST.AST {
107
- const key = getIdentityKey(ast)
108
- const canonical = canonicalByKey.get(key)
109
- if (canonical === undefined) {
110
- canonicalByKey.set(key, ast)
111
- return ast
112
- }
113
- return canonical
114
- }
115
-
116
130
  function annotateReference(
117
131
  ast: SchemaAST.AST,
118
132
  referenceIdentifier: ReferenceIdentifier,
@@ -143,12 +157,16 @@ function fromASTs(
143
157
 
144
158
  function visit(input: SchemaAST.AST): void {
145
159
  const ast = SchemaAST.getLastEncoding(input)
146
- const owner = getCanonicalAST(ast)
147
- if (visited.has(owner)) {
148
- if (isShareable(ast)) shared.add(owner)
160
+ const owner = SchemaAST.getContextOwner(ast)
161
+ const count = (occurrences.get(owner) ?? 0) + 1
162
+ occurrences.set(owner, count)
163
+ if (count > 1) {
164
+ if (
165
+ !shared.has(owner) &&
166
+ isAnonymousReferenceEligible(owner, count, options?.isAnonymousReferenceAllowed)
167
+ ) shared.add(owner)
149
168
  return
150
169
  }
151
- visited.add(owner)
152
170
  visitChecks(ast.checks)
153
171
  switch (ast._tag) {
154
172
  case "Declaration":
@@ -178,7 +196,7 @@ function fromASTs(
178
196
 
179
197
  function recur(input: SchemaAST.AST): SchemaRepresentation.Representation {
180
198
  const ast = SchemaAST.getLastEncoding(input)
181
- const owner = getCanonicalAST(ast)
199
+ const owner = SchemaAST.getContextOwner(ast)
182
200
  const referenceIdentifier = resolveReferenceIdentifier(input, ast)
183
201
  if (referenceIdentifier !== undefined) {
184
202
  const reference = getReference(referenceIdentifier.identifier, owner)
@@ -172,7 +172,7 @@ export class Asserts<S extends Schema.Constraint> {
172
172
  }) {
173
173
  const decodeUnknownEffect = SchemaParser.decodeUnknownEffect(this.schema)
174
174
  const encodeEffect = SchemaParser.encodeEffect(this.schema)
175
- const arbitrary = Schema.toArbitrary(this.schema)
175
+ const arbitrary = Schema.toArbitrary(this.schema)(FastCheck)
176
176
  return FastCheck.assert(
177
177
  FastCheck.asyncProperty(arbitrary, async (t) => {
178
178
  const r = await Effect.runPromise(
@@ -280,7 +280,7 @@ export class Asserts<S extends Schema.Constraint> {
280
280
  }) {
281
281
  const params = options?.params
282
282
  const is = Schema.is(schema)
283
- const arb = Schema.toArbitrary(schema)
283
+ const arb = Schema.toArbitrary(schema)(FastCheck)
284
284
  FastCheck.assert(FastCheck.property(arb, (a) => is(a)), { numRuns: 20, ...params })
285
285
  }
286
286
  }
@@ -489,7 +489,7 @@ const runWithProtocolState = Effect.fnUntraced(function*(options: {
489
489
  const isHttp = Option.isSome(yield* Effect.serviceOption(HttpRouter.HttpRouter))
490
490
  const sessions = protocolState.sessions
491
491
  const clientProtocols = new Map<number, McpProtocol.ProtocolAdapter>()
492
- const cancelledRequests = new Map<number, Set<string>>()
492
+ const activeRequests = new Map<number, Map<string, boolean>>()
493
493
  const handlers = yield* Layer.build(layerHandlers(options, {
494
494
  sessions,
495
495
  protocolRegistry
@@ -577,11 +577,13 @@ const runWithProtocolState = Effect.fnUntraced(function*(options: {
577
577
  ...protocol,
578
578
  send: (clientId, response) => {
579
579
  if (response._tag === "Exit") {
580
- const requests = cancelledRequests.get(clientId)
581
- if (requests?.delete(requestKey(response.requestId)) === true) {
582
- if (requests.size === 0) {
583
- cancelledRequests.delete(clientId)
584
- }
580
+ const requests = activeRequests.get(clientId)
581
+ const key = requestKey(response.requestId)
582
+ const cancelled = requests?.get(key)
583
+ if (requests !== undefined && requests.delete(key) && requests.size === 0) {
584
+ activeRequests.delete(clientId)
585
+ }
586
+ if (cancelled === true) {
585
587
  return Effect.void
586
588
  }
587
589
  }
@@ -670,9 +672,12 @@ const runWithProtocolState = Effect.fnUntraced(function*(options: {
670
672
  if (request.tag === "notifications/cancelled") {
671
673
  return decodeCancelledNotification(request.payload).pipe(
672
674
  Effect.flatMap(({ requestId }) => {
673
- const requests = cancelledRequests.get(clientId) ?? new Set<string>()
674
- requests.add(requestKey(requestId))
675
- cancelledRequests.set(clientId, requests)
675
+ const key = requestKey(requestId)
676
+ const requests = activeRequests.get(clientId)
677
+ if (requests?.has(key) !== true) {
678
+ return Effect.void
679
+ }
680
+ requests.set(key, true)
676
681
  return f(clientId, {
677
682
  _tag: "Interrupt",
678
683
  requestId: RpcMessage.RequestId(requestId)
@@ -732,7 +737,14 @@ const runWithProtocolState = Effect.fnUntraced(function*(options: {
732
737
  }
733
738
  return selectedProtocol.payloadCodecs(rpc).decode(request.payload).pipe(
734
739
  Effect.matchEffect({
735
- onSuccess: () => f(clientId, routedRequest),
740
+ onSuccess: () => {
741
+ if (request.isNotification !== true) {
742
+ const requests = activeRequests.get(clientId) ?? new Map<string, boolean>()
743
+ requests.set(requestKey(request.id), false)
744
+ activeRequests.set(clientId, requests)
745
+ }
746
+ return f(clientId, routedRequest)
747
+ },
736
748
  onFailure: () =>
737
749
  request.isNotification
738
750
  ? Effect.void
@@ -752,7 +764,7 @@ const runWithProtocolState = Effect.fnUntraced(function*(options: {
752
764
  case "Interrupt":
753
765
  return f(clientId, request)
754
766
  case "Eof":
755
- cancelledRequests.delete(clientId)
767
+ activeRequests.delete(clientId)
756
768
  return f(clientId, request)
757
769
  case "Pong":
758
770
  case "Exit":
@@ -2004,11 +2004,23 @@ export const make = (input: RawInput): Prompt => {
2004
2004
  */
2005
2005
  export const fromMessages = (messages: ReadonlyArray<Message>): Prompt => makePrompt(messages)
2006
2006
 
2007
+ const mergeOptions = (left: ProviderOptions, right: ProviderOptions): ProviderOptions => {
2008
+ const result: Record<string, ProviderOptions[string]> = { ...left }
2009
+ for (const [provider, metadata] of Object.entries(right)) {
2010
+ const previous = result[provider]
2011
+ result[provider] = Predicate.isObject(previous) && Predicate.isObject(metadata)
2012
+ ? Object.assign({}, previous, metadata)
2013
+ : metadata
2014
+ }
2015
+ return result
2016
+ }
2017
+
2007
2018
  /**
2008
2019
  * Creates a `Prompt` from response parts by folding completed text and
2009
- * reasoning streams into assistant parts, placing tool calls and approval
2010
- * requests in an assistant message, and placing non-preliminary tool results
2011
- * in a tool message using their encoded results.
2020
+ * reasoning streams into assistant parts, preserving provider metadata as
2021
+ * prompt options, placing tool calls and approval requests in an assistant
2022
+ * message, and placing non-preliminary tool results in a tool message using
2023
+ * their encoded results.
2012
2024
  *
2013
2025
  * **Example** (Creating prompts from response parts)
2014
2026
  *
@@ -2052,55 +2064,63 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
2052
2064
  const assistantParts: Array<AssistantMessagePart> = []
2053
2065
  const toolParts: Array<ToolMessagePart> = []
2054
2066
 
2055
- const activeTextDeltas = new Map<string, { text: string }>()
2056
- const activeReasoningDeltas = new Map<string, { text: string }>()
2067
+ const activeTextDeltas = new Map<string, { text: string; options: ProviderOptions }>()
2068
+ const activeReasoningDeltas = new Map<string, { text: string; options: ProviderOptions }>()
2057
2069
 
2058
2070
  for (const part of parts) {
2059
2071
  switch (part.type) {
2060
2072
  // Text Parts
2061
2073
  case "text": {
2062
- assistantParts.push(makePart("text", { text: part.text }))
2074
+ assistantParts.push(makePart("text", { text: part.text, options: part.metadata }))
2063
2075
  break
2064
2076
  }
2065
2077
 
2066
2078
  // Text Parts (streaming)
2067
2079
  case "text-start": {
2068
- activeTextDeltas.set(part.id, { text: "" })
2080
+ activeTextDeltas.set(part.id, { text: "", options: part.metadata })
2069
2081
  break
2070
2082
  }
2071
2083
  case "text-delta": {
2072
2084
  if (activeTextDeltas.has(part.id)) {
2073
- activeTextDeltas.get(part.id)!.text += part.delta
2085
+ const active = activeTextDeltas.get(part.id)!
2086
+ active.text += part.delta
2087
+ active.options = mergeOptions(active.options, part.metadata)
2074
2088
  }
2075
2089
  break
2076
2090
  }
2077
2091
  case "text-end": {
2078
2092
  if (activeTextDeltas.has(part.id)) {
2079
- assistantParts.push(makePart("text", activeTextDeltas.get(part.id)!))
2093
+ const active = activeTextDeltas.get(part.id)!
2094
+ active.options = mergeOptions(active.options, part.metadata)
2095
+ assistantParts.push(makePart("text", active))
2080
2096
  }
2081
2097
  break
2082
2098
  }
2083
2099
 
2084
2100
  // Reasoning Parts
2085
2101
  case "reasoning": {
2086
- assistantParts.push(makePart("reasoning", { text: part.text }))
2102
+ assistantParts.push(makePart("reasoning", { text: part.text, options: part.metadata }))
2087
2103
  break
2088
2104
  }
2089
2105
 
2090
2106
  // Reasoning Parts (streaming)
2091
2107
  case "reasoning-start": {
2092
- activeReasoningDeltas.set(part.id, { text: "" })
2108
+ activeReasoningDeltas.set(part.id, { text: "", options: part.metadata })
2093
2109
  break
2094
2110
  }
2095
2111
  case "reasoning-delta": {
2096
2112
  if (activeReasoningDeltas.has(part.id)) {
2097
- activeReasoningDeltas.get(part.id)!.text += part.delta
2113
+ const active = activeReasoningDeltas.get(part.id)!
2114
+ active.text += part.delta
2115
+ active.options = mergeOptions(active.options, part.metadata)
2098
2116
  }
2099
2117
  break
2100
2118
  }
2101
2119
  case "reasoning-end": {
2102
2120
  if (activeReasoningDeltas.has(part.id)) {
2103
- assistantParts.push(makePart("reasoning", activeReasoningDeltas.get(part.id)!))
2121
+ const active = activeReasoningDeltas.get(part.id)!
2122
+ active.options = mergeOptions(active.options, part.metadata)
2123
+ assistantParts.push(makePart("reasoning", active))
2104
2124
  }
2105
2125
  break
2106
2126
  }
@@ -2111,7 +2131,8 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
2111
2131
  id: part.id,
2112
2132
  name: part.name,
2113
2133
  params: part.params,
2114
- providerExecuted: part.providerExecuted ?? false
2134
+ providerExecuted: part.providerExecuted ?? false,
2135
+ options: part.metadata
2115
2136
  }))
2116
2137
  break
2117
2138
  }
@@ -2125,7 +2146,8 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
2125
2146
  name: part.name,
2126
2147
  isFailure: part.isFailure,
2127
2148
  result: part.encodedResult,
2128
- providerExecuted: part.providerExecuted ?? false
2149
+ providerExecuted: part.providerExecuted ?? false,
2150
+ options: part.metadata
2129
2151
  }))
2130
2152
  }
2131
2153
  break
@@ -34,7 +34,12 @@ import * as RpcClient from "../rpc/RpcClient.ts"
34
34
  import * as RpcGroup from "../rpc/RpcGroup.ts"
35
35
  import * as RpcSchema from "../rpc/RpcSchema.ts"
36
36
  import * as RpcServer from "../rpc/RpcServer.ts"
37
- import type { AlreadyProcessingMessage, MailboxFull, PersistenceError } from "./ClusterError.ts"
37
+ import type {
38
+ AlreadyProcessingMessage,
39
+ EntityNotAssignedToRunner,
40
+ MailboxFull,
41
+ PersistenceError
42
+ } from "./ClusterError.ts"
38
43
  import { Persisted, ShardGroup, Uninterruptible } from "./ClusterSchema.ts"
39
44
  import { EntityAddress } from "./EntityAddress.ts"
40
45
  import type { EntityId } from "./EntityId.ts"
@@ -117,7 +122,7 @@ export interface Entity<
117
122
  entityId: string
118
123
  ) => RpcClient.RpcClient.From<
119
124
  Rpcs,
120
- MailboxFull | AlreadyProcessingMessage | PersistenceError
125
+ MailboxFull | AlreadyProcessingMessage | PersistenceError | EntityNotAssignedToRunner
121
126
  >,
122
127
  never,
123
128
  Sharding
@@ -14,7 +14,7 @@ import * as HttpApiEndpoint from "../httpapi/HttpApiEndpoint.ts"
14
14
  import * as HttpApiGroup from "../httpapi/HttpApiGroup.ts"
15
15
  import * as Rpc from "../rpc/Rpc.ts"
16
16
  import * as RpcGroup from "../rpc/RpcGroup.ts"
17
- import { AlreadyProcessingMessage, MailboxFull, PersistenceError } from "./ClusterError.ts"
17
+ import { AlreadyProcessingMessage, EntityNotAssignedToRunner, MailboxFull, PersistenceError } from "./ClusterError.ts"
18
18
  import type * as Entity from "./Entity.ts"
19
19
  import type { EntityId } from "./EntityId.ts"
20
20
 
@@ -24,6 +24,11 @@ const clientErrors = [
24
24
  PersistenceError
25
25
  ] as const
26
26
 
27
+ const requestErrors = [
28
+ ...clientErrors,
29
+ EntityNotAssignedToRunner
30
+ ] as const
31
+
27
32
  /**
28
33
  * Derives an `RpcGroup` from an `Entity`.
29
34
  *
@@ -75,7 +80,7 @@ export const toRpcGroup = <Type extends string, Rpcs extends Rpc.Any>(
75
80
  }
76
81
  const rpc = Rpc.make(`${entity.type}.${parentRpc._tag}`, {
77
82
  payload: payloadSchema,
78
- error: Schema.Union([parentRpc.errorSchema, ...clientErrors]),
83
+ error: Schema.Union([parentRpc.errorSchema, ...requestErrors]),
79
84
  success: parentRpc.successSchema
80
85
  }).annotateMerge(parentRpc.annotations)
81
86
  const rpcDiscard = Rpc.make(`${entity.type}.${parentRpc._tag}Discard`, {
@@ -115,11 +120,12 @@ export type ConvertRpcs<Rpcs extends Rpc.Any, Prefix extends string> = Rpcs exte
115
120
  }>,
116
121
  _Success,
117
122
  Schema.Codec<
118
- _Error["Type"] | MailboxFull | AlreadyProcessingMessage | PersistenceError,
123
+ _Error["Type"] | MailboxFull | AlreadyProcessingMessage | PersistenceError | EntityNotAssignedToRunner,
119
124
  | _Error["Encoded"]
120
125
  | typeof MailboxFull["Encoded"]
121
126
  | typeof AlreadyProcessingMessage["Encoded"]
122
- | typeof PersistenceError["Encoded"],
127
+ | typeof PersistenceError["Encoded"]
128
+ | typeof EntityNotAssignedToRunner["Encoded"],
123
129
  _Error["DecodingServices"],
124
130
  _Error["EncodingServices"]
125
131
  >
@@ -193,7 +199,7 @@ export const toHttpApiGroup = <const Name extends string, Type extends string, R
193
199
  params: entityIdPath,
194
200
  payload: parentRpc.payloadSchema,
195
201
  success: parentRpc.successSchema,
196
- error: [parentRpc.errorSchema, ...clientErrors]
202
+ error: [parentRpc.errorSchema, ...requestErrors]
197
203
  }).annotateMerge(parentRpc.annotations)
198
204
  const endpointDiscard = HttpApiEndpoint.post(
199
205
  `${parentRpc._tag}Discard`,
@@ -250,6 +256,7 @@ export type ConvertHttpApi<Rpcs extends Rpc.Any> = Rpcs extends Rpc.Rpc<
250
256
  | typeof MailboxFull
251
257
  | typeof AlreadyProcessingMessage
252
258
  | typeof PersistenceError
259
+ | typeof EntityNotAssignedToRunner
253
260
  >
254
261
  | HttpApiEndpoint.HttpApiEndpoint<
255
262
  `${_Tag}Discard`,
@@ -124,7 +124,7 @@ export class Sharding extends Context.Service<Sharding, {
124
124
  entityId: string
125
125
  ) => RpcClient.RpcClient.From<
126
126
  Rpcs,
127
- MailboxFull | AlreadyProcessingMessage | PersistenceError
127
+ MailboxFull | AlreadyProcessingMessage | PersistenceError | EntityNotAssignedToRunner
128
128
  >
129
129
  >
130
130
 
@@ -178,7 +178,7 @@ export class Sharding extends Context.Service<Sharding, {
178
178
  discard: boolean
179
179
  ) => Effect.Effect<
180
180
  void,
181
- MailboxFull | AlreadyProcessingMessage | PersistenceError
181
+ MailboxFull | AlreadyProcessingMessage | PersistenceError | EntityNotAssignedToRunner
182
182
  >
183
183
 
184
184
  /**
@@ -967,18 +967,40 @@ const make = Effect.gen(function*() {
967
967
  retries?: number
968
968
  ): Effect.Effect<
969
969
  void,
970
- MailboxFull | AlreadyProcessingMessage | PersistenceError
970
+ MailboxFull | AlreadyProcessingMessage | PersistenceError | EntityNotAssignedToRunner
971
971
  > {
972
972
  const isPersisted = Context.get(
973
973
  message._tag === "OutgoingRequest" ? message.annotations : message.rpc.annotations,
974
974
  Persisted
975
975
  )
976
+ const shouldFail = !discard &&
977
+ (message._tag === "OutgoingRequest" || message.envelope._tag === "AckChunk")
978
+ const abandon = (error: EntityNotAssignedToRunner) => {
979
+ if (!isPersisted) {
980
+ return shouldFail
981
+ ? Effect.fail(error)
982
+ : Effect.logDebug("Abandoning outgoing message during shutdown", message.envelope.address)
983
+ }
984
+ const persist = message._tag === "OutgoingRequest"
985
+ ? storage.saveRequest(message)
986
+ : storage.saveEnvelope(message)
987
+ return Effect.catchTag(persist, "MalformedMessage", Effect.die).pipe(
988
+ Effect.andThen(
989
+ shouldFail
990
+ ? Effect.fail(error)
991
+ : Effect.logWarning("Persisting outgoing message abandoned during shutdown", message.envelope.address)
992
+ )
993
+ )
994
+ }
976
995
  return Effect.catchFilter(
977
996
  Effect.suspend(() => {
978
997
  const address = message.envelope.address
979
998
  if (isPersisted && !storageEnabled) {
980
999
  return Effect.die("Sharding.sendOutgoing: Persisted messages require MessageStorage")
981
1000
  }
1001
+ if (shouldFail && MutableRef.get(isShutdown)) {
1002
+ return Effect.fail(new EntityNotAssignedToRunner({ address }))
1003
+ }
982
1004
  const maybeRunner = MutableHashMap.get(shardAssignments, address.shardId)
983
1005
  const runnerIsLocal = Option.isSome(maybeRunner) && isLocalRunner(maybeRunner.value)
984
1006
  if (isPersisted) {
@@ -1003,17 +1025,7 @@ const make = Effect.gen(function*() {
1003
1025
  const cannotRecover = MutableRef.get(isShutdown) ||
1004
1026
  (targetManager !== undefined && targetManager.status !== "alive")
1005
1027
  if (cannotRecover) {
1006
- if (!isPersisted) {
1007
- return Effect.logDebug("Abandoning outgoing message during shutdown", message.envelope.address)
1008
- }
1009
- const persist = message._tag === "OutgoingRequest"
1010
- ? storage.saveRequest(message)
1011
- : storage.saveEnvelope(message)
1012
- return Effect.catchTag(persist, "MalformedMessage", Effect.die).pipe(
1013
- Effect.andThen(
1014
- Effect.logWarning("Persisting outgoing message abandoned during shutdown", message.envelope.address)
1015
- )
1016
- )
1028
+ return abandon(error)
1017
1029
  }
1018
1030
  }
1019
1031
  if (retries === 0) {
@@ -1183,7 +1195,7 @@ const make = Effect.gen(function*() {
1183
1195
  Entity<any, any>,
1184
1196
  (entityId: string) => RpcClient.RpcClient<
1185
1197
  any,
1186
- MailboxFull | AlreadyProcessingMessage
1198
+ MailboxFull | AlreadyProcessingMessage | EntityNotAssignedToRunner
1187
1199
  >,
1188
1200
  never
1189
1201
  > = yield* ResourceMap.make(
@@ -1196,7 +1208,7 @@ const make = Effect.gen(function*() {
1196
1208
  flatten: true,
1197
1209
  onFromClient(options): Effect.Effect<
1198
1210
  void,
1199
- MailboxFull | AlreadyProcessingMessage | PersistenceError
1211
+ MailboxFull | AlreadyProcessingMessage | PersistenceError | EntityNotAssignedToRunner
1200
1212
  > {
1201
1213
  const address = Context.getUnsafe(options.context, ClientAddressTag)
1202
1214
  switch (options.message._tag) {
@@ -1337,7 +1349,7 @@ const make = Effect.gen(function*() {
1337
1349
  const makeClient = <Type extends string, Rpcs extends Rpc.Any>(entity: Entity<Type, Rpcs>): Effect.Effect<
1338
1350
  (
1339
1351
  entityId: string
1340
- ) => RpcClient.RpcClient.From<Rpcs, MailboxFull | AlreadyProcessingMessage>
1352
+ ) => RpcClient.RpcClient.From<Rpcs, MailboxFull | AlreadyProcessingMessage | EntityNotAssignedToRunner>
1341
1353
  > => clients.get(entity) as any
1342
1354
 
1343
1355
  const clientRespondDiscard = (_reply: Reply.Reply<any>) => Effect.void
@@ -19,14 +19,15 @@ import type { Identity } from "./EventLog.ts"
19
19
  import { makeGetIdentityRootSecretMaterial } from "./internal/identityRootSecretDerivation.ts"
20
20
 
21
21
  /**
22
- * Schema for an encrypted journal entry paired with the id of the original
23
- * entry.
22
+ * Schema for an encrypted journal entry paired with its initialization vector
23
+ * and the id of the original entry.
24
24
  *
25
25
  * @category models
26
26
  * @since 4.0.0
27
27
  */
28
28
  export const EncryptedEntry = Schema.Struct({
29
29
  entryId: EntryId,
30
+ iv: Transferable.Uint8Array,
30
31
  encryptedEntry: Transferable.Uint8Array
31
32
  })
32
33
 
@@ -76,10 +77,12 @@ export class EventLogEncryption extends Context.Service<EventLogEncryption, {
76
77
  readonly encrypt: (
77
78
  identity: Identity["Service"],
78
79
  entries: ReadonlyArray<Entry>
79
- ) => Effect.Effect<{
80
- readonly iv: Uint8Array<ArrayBuffer>
81
- readonly encryptedEntries: ReadonlyArray<Uint8Array<ArrayBuffer>>
82
- }>
80
+ ) => Effect.Effect<
81
+ ReadonlyArray<{
82
+ readonly iv: Uint8Array<ArrayBuffer>
83
+ readonly encryptedEntry: Uint8Array<ArrayBuffer>
84
+ }>
85
+ >
83
86
  readonly decrypt: (
84
87
  identity: Identity["Service"],
85
88
  entries: ReadonlyArray<EncryptedRemoteEntry>
@@ -104,22 +107,18 @@ export const makeEncryptionSubtle = (crypto: Crypto): Effect.Effect<EventLogEncr
104
107
  encrypt: Effect.fnUntraced(function*(identity, entries) {
105
108
  const data = yield* Effect.orDie(Entry.encodeArray(entries))
106
109
  const key = (yield* getIdentityRootSecretMaterial(identity)).encryptionKey
107
- const iv = crypto.getRandomValues(new Uint8Array(12))
108
- const encryptedEntries = yield* Effect.promise(() =>
110
+ return yield* Effect.promise(() =>
109
111
  Promise.all(
110
- data.map((entry) =>
111
- crypto.subtle.encrypt(
112
+ data.map((entry) => {
113
+ const iv = crypto.getRandomValues(new Uint8Array(12))
114
+ return crypto.subtle.encrypt(
112
115
  { name: "AES-GCM", iv: toBufferSource(iv), tagLength: 128 },
113
116
  key,
114
117
  toBufferSource(entry)
115
- )
116
- )
118
+ ).then((encryptedEntry) => ({ iv, encryptedEntry: new Uint8Array(encryptedEntry) }))
119
+ })
117
120
  )
118
121
  )
119
- return {
120
- iv,
121
- encryptedEntries: encryptedEntries.map((entry) => new Uint8Array(entry))
122
- }
123
122
  }),
124
123
  decrypt: Effect.fnUntraced(function*(identity, entries) {
125
124
  const key = (yield* getIdentityRootSecretMaterial(identity)).encryptionKey
@@ -261,8 +261,8 @@ export class WriteChunkedRpc extends Rpc.make("EventLog.WriteChunked", {
261
261
  *
262
262
  * **Details**
263
263
  *
264
- * It includes the client public key, target store id, AES-GCM initialization
265
- * vector, and encrypted entries.
264
+ * It includes the client public key, target store id, and encrypted entries
265
+ * with their AES-GCM initialization vectors.
266
266
  *
267
267
  * @category protocols
268
268
  * @since 4.0.0
@@ -270,7 +270,6 @@ export class WriteChunkedRpc extends Rpc.make("EventLog.WriteChunked", {
270
270
  export class WriteEntries extends Schema.Class<WriteEntries>("effect/eventlog/EventLogRemote/WriteEntries")({
271
271
  publicKey: Schema.String,
272
272
  storeId: StoreId,
273
- iv: Transferable.Uint8Array,
274
273
  encryptedEntries: Schema.Array(EncryptedEntry)
275
274
  }) {
276
275
  static FromMsgpack = Msgpack.schema(WriteEntries)
@@ -307,14 +307,14 @@ export const makeEncrypted = Effect.gen(function*(): Effect.fn.Return<
307
307
  return yield* makeWith({
308
308
  encodeWrite: (options) =>
309
309
  encryption.encrypt(options.identity, options.entries).pipe(
310
- Effect.flatMap((msg) =>
310
+ Effect.flatMap((encryptedEntries) =>
311
311
  new WriteEntries({
312
312
  publicKey: options.identity.publicKey,
313
313
  storeId: options.storeId,
314
- iv: msg.iv,
315
- encryptedEntries: msg.encryptedEntries.map((entry, i) => ({
314
+ encryptedEntries: encryptedEntries.map((entry, i) => ({
316
315
  entryId: options.entries[i].id,
317
- encryptedEntry: entry
316
+ iv: entry.iv,
317
+ encryptedEntry: entry.encryptedEntry
318
318
  }))
319
319
  }).encoded
320
320
  )