effect 4.0.0-beta.11 → 4.0.0-beta.12

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 (95) hide show
  1. package/dist/Config.d.ts +157 -0
  2. package/dist/Config.d.ts.map +1 -1
  3. package/dist/Config.js +56 -1
  4. package/dist/Config.js.map +1 -1
  5. package/dist/Effect.d.ts +79 -0
  6. package/dist/Effect.d.ts.map +1 -1
  7. package/dist/Effect.js +26 -0
  8. package/dist/Effect.js.map +1 -1
  9. package/dist/Fiber.d.ts +2 -2
  10. package/dist/Fiber.d.ts.map +1 -1
  11. package/dist/Fiber.js.map +1 -1
  12. package/dist/Graph.d.ts.map +1 -1
  13. package/dist/Graph.js +3 -6
  14. package/dist/Graph.js.map +1 -1
  15. package/dist/Random.d.ts +17 -0
  16. package/dist/Random.d.ts.map +1 -1
  17. package/dist/Random.js +17 -0
  18. package/dist/Random.js.map +1 -1
  19. package/dist/Schema.d.ts +3 -1
  20. package/dist/Schema.d.ts.map +1 -1
  21. package/dist/internal/effect.js +59 -44
  22. package/dist/internal/effect.js.map +1 -1
  23. package/dist/unstable/ai/LanguageModel.d.ts.map +1 -1
  24. package/dist/unstable/ai/LanguageModel.js +49 -18
  25. package/dist/unstable/ai/LanguageModel.js.map +1 -1
  26. package/dist/unstable/ai/McpSchema.d.ts +112 -36
  27. package/dist/unstable/ai/McpSchema.d.ts.map +1 -1
  28. package/dist/unstable/ai/McpSchema.js +47 -10
  29. package/dist/unstable/ai/McpSchema.js.map +1 -1
  30. package/dist/unstable/ai/McpServer.d.ts.map +1 -1
  31. package/dist/unstable/ai/McpServer.js +33 -6
  32. package/dist/unstable/ai/McpServer.js.map +1 -1
  33. package/dist/unstable/ai/Tool.d.ts +16 -0
  34. package/dist/unstable/ai/Tool.d.ts.map +1 -1
  35. package/dist/unstable/ai/Tool.js +14 -0
  36. package/dist/unstable/ai/Tool.js.map +1 -1
  37. package/dist/unstable/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
  38. package/dist/unstable/cluster/ClusterWorkflowEngine.js +2 -2
  39. package/dist/unstable/cluster/ClusterWorkflowEngine.js.map +1 -1
  40. package/dist/unstable/http/HttpClient.d.ts +28 -4
  41. package/dist/unstable/http/HttpClient.d.ts.map +1 -1
  42. package/dist/unstable/http/HttpClient.js.map +1 -1
  43. package/dist/unstable/http/HttpEffect.d.ts +3 -8
  44. package/dist/unstable/http/HttpEffect.d.ts.map +1 -1
  45. package/dist/unstable/http/HttpEffect.js +13 -24
  46. package/dist/unstable/http/HttpEffect.js.map +1 -1
  47. package/dist/unstable/http/HttpMiddleware.d.ts.map +1 -1
  48. package/dist/unstable/http/HttpMiddleware.js +4 -8
  49. package/dist/unstable/http/HttpMiddleware.js.map +1 -1
  50. package/dist/unstable/http/HttpServerError.d.ts +6 -1
  51. package/dist/unstable/http/HttpServerError.d.ts.map +1 -1
  52. package/dist/unstable/http/HttpServerError.js +26 -17
  53. package/dist/unstable/http/HttpServerError.js.map +1 -1
  54. package/dist/unstable/http/internal/preResponseHandler.d.ts +2 -0
  55. package/dist/unstable/http/internal/preResponseHandler.d.ts.map +1 -0
  56. package/dist/unstable/http/internal/preResponseHandler.js +10 -0
  57. package/dist/unstable/http/internal/preResponseHandler.js.map +1 -0
  58. package/dist/unstable/httpapi/HttpApiBuilder.d.ts +1 -1
  59. package/dist/unstable/httpapi/HttpApiBuilder.d.ts.map +1 -1
  60. package/dist/unstable/reactivity/Atom.js +1 -1
  61. package/dist/unstable/reactivity/Atom.js.map +1 -1
  62. package/dist/unstable/rpc/RpcSchema.d.ts +13 -0
  63. package/dist/unstable/rpc/RpcSchema.d.ts.map +1 -1
  64. package/dist/unstable/rpc/RpcSchema.js +14 -0
  65. package/dist/unstable/rpc/RpcSchema.js.map +1 -1
  66. package/dist/unstable/rpc/RpcSerialization.d.ts.map +1 -1
  67. package/dist/unstable/rpc/RpcSerialization.js +34 -9
  68. package/dist/unstable/rpc/RpcSerialization.js.map +1 -1
  69. package/dist/unstable/rpc/RpcServer.d.ts +0 -7
  70. package/dist/unstable/rpc/RpcServer.d.ts.map +1 -1
  71. package/dist/unstable/rpc/RpcServer.js +5 -10
  72. package/dist/unstable/rpc/RpcServer.js.map +1 -1
  73. package/package.json +1 -1
  74. package/src/Config.ts +171 -9
  75. package/src/Effect.ts +80 -0
  76. package/src/Fiber.ts +9 -2
  77. package/src/Graph.ts +16 -6
  78. package/src/Random.ts +18 -0
  79. package/src/Schema.ts +1 -1
  80. package/src/internal/effect.ts +82 -49
  81. package/src/unstable/ai/LanguageModel.ts +54 -24
  82. package/src/unstable/ai/McpSchema.ts +57 -11
  83. package/src/unstable/ai/McpServer.ts +44 -6
  84. package/src/unstable/ai/Tool.ts +15 -0
  85. package/src/unstable/cluster/ClusterWorkflowEngine.ts +2 -2
  86. package/src/unstable/http/HttpClient.ts +45 -10
  87. package/src/unstable/http/HttpEffect.ts +20 -39
  88. package/src/unstable/http/HttpMiddleware.ts +4 -14
  89. package/src/unstable/http/HttpServerError.ts +29 -18
  90. package/src/unstable/http/internal/preResponseHandler.ts +15 -0
  91. package/src/unstable/httpapi/HttpApiBuilder.ts +1 -1
  92. package/src/unstable/reactivity/Atom.ts +1 -1
  93. package/src/unstable/rpc/RpcSchema.ts +17 -0
  94. package/src/unstable/rpc/RpcSerialization.ts +44 -9
  95. package/src/unstable/rpc/RpcServer.ts +10 -19
@@ -492,33 +492,33 @@ const fiberIdStore = { id: 0 }
492
492
  export const getCurrentFiber = (): Fiber.Fiber<any, any> | undefined => (globalThis as any)[currentFiberTypeId]
493
493
 
494
494
  const keepAlive = (() => {
495
- const start = (() => {
496
- const setInterval = globalThis.setInterval
497
- const clearInterval = globalThis.clearInterval
495
+ let isAvailable: boolean | undefined
496
+ const start = () => {
497
+ if (isAvailable === true) return setInterval(constVoid, 2_147_483_647)
498
+ else if (isAvailable === false) return undefined
499
+
498
500
  try {
499
501
  const running = setInterval(constVoid, 2_147_483_647)
500
- clearInterval(running)
501
- return {
502
- setInterval,
503
- clearInterval
504
- }
502
+ isAvailable = true
503
+ return running
505
504
  } catch {
505
+ isAvailable = false
506
506
  return undefined
507
507
  }
508
- })()
508
+ }
509
509
  let count = 0
510
510
  let running: ReturnType<typeof globalThis.setInterval> | undefined = undefined
511
511
  return ({
512
512
  increment() {
513
513
  count++
514
- if (start !== undefined && running === undefined) {
515
- running = start.setInterval(constVoid, 2_147_483_647)
514
+ if (running === undefined) {
515
+ running = start()
516
516
  }
517
517
  },
518
518
  decrement() {
519
519
  count--
520
- if (count === 0 && start !== undefined && running !== undefined) {
521
- start.clearInterval(running)
520
+ if (count === 0 && running !== undefined) {
521
+ clearInterval(running)
522
522
  running = undefined
523
523
  }
524
524
  }
@@ -832,13 +832,29 @@ export const fiberInterrupt = <A, E>(
832
832
 
833
833
  /** @internal */
834
834
  export const fiberInterruptAs: {
835
- (fiberId: number): <A, E>(self: Fiber.Fiber<A, E>) => Effect.Effect<void>
836
- <A, E>(self: Fiber.Fiber<A, E>, fiberId: number): Effect.Effect<void>
837
- } = dual(2, <A, E>(self: Fiber.Fiber<A, E>, fiberId: number): Effect.Effect<void> =>
838
- withFiber((parent) => {
839
- self.interruptUnsafe(fiberId, fiberStackAnnotations(parent))
840
- return asVoid(fiberAwait(self))
841
- }))
835
+ (
836
+ fiberId: number | undefined,
837
+ annotations?: ServiceMap.ServiceMap<never> | undefined
838
+ ): <A, E>(self: Fiber.Fiber<A, E>) => Effect.Effect<void>
839
+ <A, E>(
840
+ self: Fiber.Fiber<A, E>,
841
+ fiberId: number | undefined,
842
+ annotations?: ServiceMap.ServiceMap<never> | undefined
843
+ ): Effect.Effect<void>
844
+ } = dual(
845
+ (args) => hasProperty(args[0], FiberTypeId),
846
+ <A, E>(
847
+ self: Fiber.Fiber<A, E>,
848
+ fiberId: number | undefined,
849
+ annotations?: ServiceMap.ServiceMap<never> | undefined
850
+ ): Effect.Effect<void> =>
851
+ withFiber((parent) => {
852
+ let ann = fiberStackAnnotations(parent)
853
+ ann = ann && annotations ? ServiceMap.merge(ann, annotations) : ann ?? annotations
854
+ self.interruptUnsafe(fiberId, ann)
855
+ return asVoid(fiberAwait(self))
856
+ })
857
+ )
842
858
 
843
859
  /** @internal */
844
860
  export const fiberInterruptAll = <A extends Iterable<Fiber.Fiber<any, any>>>(
@@ -2003,23 +2019,8 @@ export const updateServices: {
2003
2019
  const nextServices = f(prev)
2004
2020
  if (prev === nextServices) return self as any
2005
2021
  fiber.setServices(nextServices)
2006
- const newServices = new Map<string, unknown>()
2007
- for (const [key, value] of fiber.services.mapUnsafe) {
2008
- if (!prev.mapUnsafe.has(key) || value !== prev.mapUnsafe.get(key)) {
2009
- newServices.set(key, value)
2010
- }
2011
- }
2012
- return onExitPrimitive(self as any, () => {
2013
- const map = new Map(fiber.services.mapUnsafe)
2014
- for (const [key, value] of newServices) {
2015
- if (value !== map.get(key)) continue
2016
- if (prev.mapUnsafe.has(key)) {
2017
- map.set(key, prev.mapUnsafe.get(key))
2018
- } else {
2019
- map.delete(key)
2020
- }
2021
- }
2022
- fiber.setServices(ServiceMap.makeUnsafe(map))
2022
+ return onExitPrimitive(self, () => {
2023
+ fiber.setServices(prev)
2023
2024
  return undefined
2024
2025
  })
2025
2026
  })
@@ -2043,12 +2044,11 @@ export const updateService: {
2043
2044
  service: ServiceMap.Service<I, A>,
2044
2045
  f: (value: A) => A
2045
2046
  ): Effect.Effect<XA, E, R | I> =>
2046
- withFiber((fiber) => {
2047
- const prev = ServiceMap.getUnsafe(fiber.services, service)
2047
+ updateServices(self, (s) => {
2048
+ const prev = ServiceMap.getUnsafe(s, service)
2048
2049
  const next = f(prev)
2049
- if (prev === next) return self
2050
- fiber.setServices(ServiceMap.add(fiber.services, service, next))
2051
- return onExit(self, () => sync(() => fiber.setServices(ServiceMap.add(fiber.services, service, prev))))
2050
+ if (prev === next) return s
2051
+ return ServiceMap.add(s, service, next)
2052
2052
  })
2053
2053
  )
2054
2054
 
@@ -2115,11 +2115,10 @@ const provideServiceImpl = <A, E, R, I, S>(
2115
2115
  service: ServiceMap.Service<I, S>,
2116
2116
  implementation: S
2117
2117
  ): Effect.Effect<A, E, Exclude<R, I>> =>
2118
- withFiber((fiber) => {
2119
- const prev = ServiceMap.getOption(fiber.services, service)
2120
- if (prev._tag === "Some" && prev.value === implementation) return self
2121
- fiber.setServices(ServiceMap.add(fiber.services, service, implementation))
2122
- return onExit(self, () => sync(() => fiber.setServices(ServiceMap.addOrOmit(fiber.services, service, prev))))
2118
+ updateServices(self, (s) => {
2119
+ const prev = s.mapUnsafe.get(service.key)
2120
+ if (prev === implementation) return s
2121
+ return ServiceMap.add(s, service, implementation)
2123
2122
  }) as any
2124
2123
 
2125
2124
  /** @internal */
@@ -3666,11 +3665,11 @@ export const provideScope: {
3666
3665
  /** @internal */
3667
3666
  export const scoped = <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, Exclude<R, Scope.Scope>> =>
3668
3667
  withFiber((fiber) => {
3669
- const prev = ServiceMap.getOption(fiber.services, scopeTag)
3668
+ const prev = fiber.services
3670
3669
  const scope = scopeMakeUnsafe()
3671
3670
  fiber.setServices(ServiceMap.add(fiber.services, scopeTag, scope))
3672
3671
  return onExitPrimitive(self, (exit) => {
3673
- fiber.setServices(ServiceMap.addOrOmit(fiber.services, scopeTag, prev))
3672
+ fiber.setServices(prev)
3674
3673
  return scopeCloseUnsafe(scope, exit)
3675
3674
  })
3676
3675
  }) as any
@@ -5555,6 +5554,40 @@ export const LogToStderr = ServiceMap.Reference<boolean>("effect/Logger/LogToStd
5555
5554
  defaultValue: constFalse
5556
5555
  })
5557
5556
 
5557
+ /** @internal */
5558
+ export const annotateLogsScoped: {
5559
+ (key: string, value: unknown): Effect.Effect<void, never, Scope.Scope>
5560
+ (values: Record<string, unknown>): Effect.Effect<void, never, Scope.Scope>
5561
+ } = function() {
5562
+ const entries = typeof arguments[0] === "string" ?
5563
+ [[arguments[0], arguments[1]]] :
5564
+ Object.entries(arguments[0])
5565
+ return uninterruptible(withFiber((fiber) => {
5566
+ const prev = fiber.getRef(CurrentLogAnnotations)
5567
+ const next = { ...prev }
5568
+ for (let i = 0; i < entries.length; i++) {
5569
+ const [key, value] = entries[i]
5570
+ next[key] = value
5571
+ }
5572
+ fiber.setServices(ServiceMap.add(fiber.services, CurrentLogAnnotations, next))
5573
+ return scopeAddFinalizerExit(ServiceMap.getUnsafe(fiber.services, scopeTag), (_) => {
5574
+ const current = fiber.getRef(CurrentLogAnnotations)
5575
+ const next = { ...current }
5576
+ for (let i = 0; i < entries.length; i++) {
5577
+ const [key, value] = entries[i]
5578
+ if (current[key] !== value) continue
5579
+ if (key in prev) {
5580
+ next[key] = prev[key]
5581
+ } else {
5582
+ delete next[key]
5583
+ }
5584
+ }
5585
+ fiber.setServices(ServiceMap.add(fiber.services, CurrentLogAnnotations, next))
5586
+ return void_
5587
+ })
5588
+ }))
5589
+ }
5590
+
5558
5591
  /** @internal */
5559
5592
  export const LoggerTypeId = "~effect/Logger"
5560
5593
 
@@ -949,9 +949,8 @@ export const make: (params: ConstructorParams) => Effect.Effect<Service> = Effec
949
949
  return content as Array<Response.Part<Tools>>
950
950
  }
951
951
 
952
- // Pre-resolve tool approvals before calling the LLM
952
+ // Pre-resolve pending tool approvals before calling the LLM
953
953
  if (hasPendingApprovals) {
954
- // Validate all approved tools exist in the toolkit
955
954
  for (const approval of approved) {
956
955
  if (approval.toolCall && !toolkit.tools[approval.toolCall.name]) {
957
956
  return yield* AiError.make({
@@ -965,7 +964,6 @@ export const make: (params: ConstructorParams) => Effect.Effect<Service> = Effec
965
964
  }
966
965
  }
967
966
 
968
- // Execute approved tools and create denial results
969
967
  const approvedResults = yield* executeApprovedToolCalls(
970
968
  approved,
971
969
  toolkit,
@@ -974,23 +972,27 @@ export const make: (params: ConstructorParams) => Effect.Effect<Service> = Effec
974
972
  const deniedResults = createDenialResults(denied)
975
973
  const preResolvedResults = [...approvedResults, ...deniedResults]
976
974
 
977
- // Add pre-resolved results to the prompt
978
975
  if (preResolvedResults.length > 0) {
979
- const toolMessage = Prompt.makeMessage("tool", {
980
- content: preResolvedResults
981
- })
982
976
  providerOptions.prompt = Prompt.fromMessages([
983
977
  ...providerOptions.prompt.content,
984
- toolMessage
978
+ Prompt.makeMessage("tool", { content: preResolvedResults })
985
979
  ])
986
980
  }
981
+ }
987
982
 
988
- // Strip consumed approval artifacts so they don't reach the provider
989
- providerOptions.prompt = stripResolvedApprovals(
990
- providerOptions.prompt,
991
- approved,
992
- denied
983
+ // Strip all resolved approval artifacts (both current and from previous
984
+ // rounds) in a single pass before sending to the provider.
985
+ {
986
+ const { approved: allResolved, denied: allDenied } = collectToolApprovals(
987
+ providerOptions.prompt.content
993
988
  )
989
+ if (allResolved.length > 0 || allDenied.length > 0) {
990
+ providerOptions.prompt = stripResolvedApprovals(
991
+ providerOptions.prompt,
992
+ allResolved,
993
+ allDenied
994
+ )
995
+ }
994
996
  }
995
997
 
996
998
  const tools = typeof toolChoice === "object" && "oneOf" in toolChoice
@@ -1137,9 +1139,10 @@ export const make: (params: ConstructorParams) => Effect.Effect<Service> = Effec
1137
1139
  >
1138
1140
  }
1139
1141
 
1140
- // Pre-resolve tool approvals before calling the LLM
1142
+ // Pre-resolve pending tool approvals before calling the LLM
1143
+ let preResolvedStreamParts: Array<Response.StreamPart<Tools>> = []
1144
+
1141
1145
  if (hasPendingApprovals) {
1142
- // Validate all approved tools exist in the toolkit
1143
1146
  for (const approval of pendingApproved) {
1144
1147
  if (approval.toolCall && !toolkit.tools[approval.toolCall.name]) {
1145
1148
  return yield* AiError.make({
@@ -1153,7 +1156,6 @@ export const make: (params: ConstructorParams) => Effect.Effect<Service> = Effec
1153
1156
  }
1154
1157
  }
1155
1158
 
1156
- // Execute approved tools and create denial results
1157
1159
  const approvedResults = yield* executeApprovedToolCalls(
1158
1160
  pendingApproved,
1159
1161
  toolkit,
@@ -1162,22 +1164,43 @@ export const make: (params: ConstructorParams) => Effect.Effect<Service> = Effec
1162
1164
  const deniedResults = createDenialResults(pendingDenied)
1163
1165
  const preResolvedResults = [...approvedResults, ...deniedResults]
1164
1166
 
1165
- // Add pre-resolved results to the prompt
1166
1167
  if (preResolvedResults.length > 0) {
1167
- const toolMessage = Prompt.makeMessage("tool", {
1168
- content: preResolvedResults
1169
- })
1170
1168
  providerOptions.prompt = Prompt.fromMessages([
1171
1169
  ...providerOptions.prompt.content,
1172
- toolMessage
1170
+ Prompt.makeMessage("tool", { content: preResolvedResults })
1173
1171
  ])
1174
1172
  }
1175
1173
 
1176
- // Strip consumed approval artifacts so they don't reach the provider
1174
+ // Emit pre-resolved tool-results as stream parts so Chat.streamText
1175
+ // persists them to history. This lets collectToolApprovals find them
1176
+ // on subsequent rounds and skip the now-resolved approvals.
1177
+ // Note: r.result is already encoded (from executeApprovedToolCalls /
1178
+ // createDenialResults), so it goes into both result and encodedResult.
1179
+ for (const r of preResolvedResults) {
1180
+ preResolvedStreamParts.push(
1181
+ Response.makePart("tool-result", {
1182
+ id: r.id,
1183
+ name: r.name,
1184
+ providerExecuted: false,
1185
+ preliminary: false,
1186
+ result: r.result,
1187
+ encodedResult: r.result,
1188
+ isFailure: r.isFailure
1189
+ }) as Response.StreamPart<Tools>
1190
+ )
1191
+ }
1192
+ }
1193
+
1194
+ // Strip all resolved approval artifacts (both current and from previous
1195
+ // rounds) in a single pass before sending to the provider.
1196
+ const { approved: allResolved, denied: allDenied } = collectToolApprovals(
1197
+ providerOptions.prompt.content
1198
+ )
1199
+ if (allResolved.length > 0 || allDenied.length > 0) {
1177
1200
  providerOptions.prompt = stripResolvedApprovals(
1178
1201
  providerOptions.prompt,
1179
- pendingApproved,
1180
- pendingDenied
1202
+ allResolved,
1203
+ allDenied
1181
1204
  )
1182
1205
  }
1183
1206
 
@@ -1215,6 +1238,13 @@ export const make: (params: ConstructorParams) => Effect.Effect<Service> = Effec
1215
1238
  | Schema.SchemaError
1216
1239
  >()
1217
1240
 
1241
+ // Emit pre-resolved tool results so Chat.streamText persists them to
1242
+ // history. This ensures collectToolApprovals({ excludeResolved }) can
1243
+ // find the corresponding tool-results on future rounds.
1244
+ if (preResolvedStreamParts.length > 0) {
1245
+ yield* Queue.offerAll(queue, preResolvedStreamParts)
1246
+ }
1247
+
1218
1248
  // FiberSet to track concurrent tool call handlers
1219
1249
  const toolCallFibers = yield* FiberSet.make<void, AiError.AiError>()
1220
1250
 
@@ -240,6 +240,11 @@ export class ClientCapabilities extends Schema.Class<ClientCapabilities>(
240
240
  * Experimental, non-standard capabilities that the client supports.
241
241
  */
242
242
  experimental: optional(Schema.Record(Schema.String, Schema.Struct({}))),
243
+ /**
244
+ * Optional extensions capabilities advertised by the client.
245
+ * Keys are extension identifiers following <vendor-prefix>/<extension-name> (e.g. "io.modelcontextprotocol/ui").
246
+ */
247
+ extensions: optional(Schema.Record(Schema.TemplateLiteral([Schema.String, "/", Schema.String]), Schema.Unknown)),
243
248
  /**
244
249
  * Present if the client supports listing roots.
245
250
  */
@@ -272,6 +277,11 @@ export class ServerCapabilities extends Schema.Opaque<ServerCapabilities>()(Sche
272
277
  * Experimental, non-standard capabilities that the server supports.
273
278
  */
274
279
  experimental: optional(Schema.Record(Schema.String, Schema.Struct({}))),
280
+ /**
281
+ * Optional extensions capabilities advertised by the server.
282
+ * Keys are extension identifiers following <vendor-prefix>/<extension-name> (e.g. "io.modelcontextprotocol/ui").
283
+ */
284
+ extensions: optional(Schema.Record(Schema.TemplateLiteral([Schema.String, "/", Schema.String]), Schema.Unknown)),
275
285
  /**
276
286
  * Present if the server supports sending log messages to the client.
277
287
  */
@@ -321,8 +331,8 @@ export class ServerCapabilities extends Schema.Opaque<ServerCapabilities>()(Sche
321
331
  * @since 4.0.0
322
332
  * @category errors
323
333
  */
324
- export class McpError extends Schema.Class<McpError>(
325
- "@effect/ai/McpSchema/McpError"
334
+ export class McpErrorBase extends Schema.Class<McpErrorBase>(
335
+ "@effect/ai/McpSchema/McpErrorBase"
326
336
  )({
327
337
  /**
328
338
  * The error type that occurred.
@@ -371,7 +381,7 @@ export const PARSE_ERROR_CODE = -32700 as const
371
381
  * @category errors
372
382
  */
373
383
  export class ParseError extends Schema.ErrorClass<ParseError>("effect/ai/McpSchema/ParseError")({
374
- ...McpError.fields,
384
+ ...McpErrorBase.fields,
375
385
  _tag: Schema.tag("ParseError"),
376
386
  code: Schema.tag(PARSE_ERROR_CODE)
377
387
  }) {}
@@ -381,7 +391,7 @@ export class ParseError extends Schema.ErrorClass<ParseError>("effect/ai/McpSche
381
391
  * @category errors
382
392
  */
383
393
  export class InvalidRequest extends Schema.ErrorClass<InvalidRequest>("effect/ai/McpSchema/InvalidRequest")({
384
- ...McpError.fields,
394
+ ...McpErrorBase.fields,
385
395
  _tag: Schema.tag("InvalidRequest"),
386
396
  code: Schema.tag(INVALID_REQUEST_ERROR_CODE)
387
397
  }) {}
@@ -391,7 +401,7 @@ export class InvalidRequest extends Schema.ErrorClass<InvalidRequest>("effect/ai
391
401
  * @category errors
392
402
  */
393
403
  export class MethodNotFound extends Schema.ErrorClass<MethodNotFound>("effect/ai/McpSchema/MethodNotFound")({
394
- ...McpError.fields,
404
+ ...McpErrorBase.fields,
395
405
  _tag: Schema.tag("MethodNotFound"),
396
406
  code: Schema.tag(METHOD_NOT_FOUND_ERROR_CODE)
397
407
  }) {}
@@ -401,7 +411,7 @@ export class MethodNotFound extends Schema.ErrorClass<MethodNotFound>("effect/ai
401
411
  * @category errors
402
412
  */
403
413
  export class InvalidParams extends Schema.ErrorClass<InvalidParams>("effect/ai/McpSchema/InvalidParams")({
404
- ...McpError.fields,
414
+ ...McpErrorBase.fields,
405
415
  _tag: Schema.tag("InvalidParams"),
406
416
  code: Schema.tag(INVALID_PARAMS_ERROR_CODE)
407
417
  }) {}
@@ -411,13 +421,26 @@ export class InvalidParams extends Schema.ErrorClass<InvalidParams>("effect/ai/M
411
421
  * @category errors
412
422
  */
413
423
  export class InternalError extends Schema.ErrorClass<InternalError>("effect/ai/McpSchema/InternalError")({
414
- ...McpError.fields,
424
+ ...McpErrorBase.fields,
415
425
  _tag: Schema.tag("InternalError"),
416
426
  code: Schema.tag(INTERNAL_ERROR_CODE)
417
427
  }) {
418
428
  static readonly notImplemented = new InternalError({ message: "Not implemented" })
419
429
  }
420
430
 
431
+ /**
432
+ * @since 4.0.0
433
+ * @category errors
434
+ */
435
+ export const McpError = Schema.Union([
436
+ ParseError,
437
+ InvalidRequest,
438
+ MethodNotFound,
439
+ InvalidParams,
440
+ InternalError,
441
+ McpErrorBase
442
+ ])
443
+
421
444
  // =============================================================================
422
445
  // Ping
423
446
  // =============================================================================
@@ -615,7 +638,14 @@ export class Resource extends Schema.Class<Resource>(
615
638
  * This can be used by Hosts to display file sizes and estimate context
616
639
  * window usage.
617
640
  */
618
- size: optional(Schema.Number)
641
+ size: optional(Schema.Number),
642
+ /**
643
+ * Optional additional metadata for the client.
644
+ *
645
+ * This parameter name is reserved by MCP to allow clients and servers to
646
+ * attach additional metadata to resources.
647
+ */
648
+ _meta: optional(Schema.Record(Schema.String, Schema.Unknown))
619
649
  }) {}
620
650
 
621
651
  /**
@@ -656,7 +686,12 @@ export class ResourceTemplate extends Schema.Class<ResourceTemplate>(
656
686
  /**
657
687
  * Optional annotations for the client.
658
688
  */
659
- annotations: optional(Annotations)
689
+ annotations: optional(Annotations),
690
+
691
+ /**
692
+ * Optional additional metadata for the client.
693
+ */
694
+ _meta: optional(Schema.Record(Schema.String, Schema.Unknown))
660
695
  }) {}
661
696
 
662
697
  /**
@@ -673,7 +708,11 @@ export class ResourceContents extends Schema.Opaque<ResourceContents>()(Schema.S
673
708
  /**
674
709
  * The MIME type of this resource, if known.
675
710
  */
676
- mimeType: optional(Schema.String)
711
+ mimeType: optional(Schema.String),
712
+ /**
713
+ * Optional additional metadata for the client.
714
+ */
715
+ _meta: optional(Schema.Record(Schema.String, Schema.Unknown))
677
716
  })) {}
678
717
 
679
718
  /**
@@ -1183,7 +1222,14 @@ export class Tool extends Schema.Class<Tool>(
1183
1222
  /**
1184
1223
  * Optional additional tool information.
1185
1224
  */
1186
- annotations: optional(ToolAnnotations)
1225
+ annotations: optional(ToolAnnotations),
1226
+ /**
1227
+ * Optional additional metadata for the client.
1228
+ *
1229
+ * This parameter name is reserved by MCP to allow clients and servers to
1230
+ * attach additional metadata to resources.
1231
+ */
1232
+ _meta: optional(Schema.Record(Schema.String, Schema.Unknown))
1187
1233
  }) {}
1188
1234
 
1189
1235
  /**
@@ -9,6 +9,7 @@ import * as Layer from "../../Layer.ts"
9
9
  import * as Option from "../../Option.ts"
10
10
  import * as Queue from "../../Queue.ts"
11
11
  import * as RcMap from "../../RcMap.ts"
12
+ import { CurrentLogLevel } from "../../References.ts"
12
13
  import * as Schema from "../../Schema.ts"
13
14
  import * as AST from "../../SchemaAST.ts"
14
15
  import * as ServiceMap from "../../ServiceMap.ts"
@@ -548,6 +549,7 @@ export const registerToolkit: <Tools extends Record<string, Tool.Any>>(
548
549
  >)
549
550
  const services = yield* Effect.services<never>()
550
551
  for (const tool of Object.values(built.tools)) {
552
+ const toolMeta = ServiceMap.getOrUndefined(tool.annotations, Tool.Meta)
551
553
  const mcpTool = new McpTool({
552
554
  name: tool.name,
553
555
  description: Tool.getDescription(tool),
@@ -561,7 +563,8 @@ export const registerToolkit: <Tools extends Record<string, Tool.Any>>(
561
563
  destructiveHint: ServiceMap.get(tool.annotations, Tool.Destructive),
562
564
  idempotentHint: ServiceMap.get(tool.annotations, Tool.Idempotent),
563
565
  openWorldHint: ServiceMap.get(tool.annotations, Tool.OpenWorld)
564
- }
566
+ },
567
+ _meta: toolMeta
565
568
  })
566
569
  yield* registry.addTool({
567
570
  tool: mcpTool,
@@ -1075,6 +1078,7 @@ const layerHandlers = (serverInfo: {
1075
1078
  ClientRpcs.toLayer(
1076
1079
  Effect.gen(function*() {
1077
1080
  const server = yield* McpServer
1081
+ let currentLogLevel = yield* CurrentLogLevel
1078
1082
 
1079
1083
  return ClientRpcs.of({
1080
1084
  // Requests
@@ -1105,17 +1109,51 @@ const layerHandlers = (serverInfo: {
1105
1109
  : LATEST_PROTOCOL_VERSION
1106
1110
  })
1107
1111
  },
1108
- "completion/complete": server.completion,
1109
- "logging/setLevel": () => InternalError.notImplemented.asEffect(),
1110
- "prompts/get": server.getPromptResult,
1112
+ "completion/complete": (r) =>
1113
+ server.completion(r).pipe(
1114
+ Effect.provideService(CurrentLogLevel, currentLogLevel)
1115
+ ),
1116
+ "logging/setLevel": ({ level }) =>
1117
+ Effect.sync(() => {
1118
+ switch (level) {
1119
+ case "notice":
1120
+ case "info":
1121
+ currentLogLevel = "Info"
1122
+ break
1123
+ case "error":
1124
+ currentLogLevel = "Error"
1125
+ break
1126
+ case "debug":
1127
+ currentLogLevel = "Debug"
1128
+ break
1129
+ case "warning":
1130
+ currentLogLevel = "Warn"
1131
+ break
1132
+ case "critical":
1133
+ case "alert":
1134
+ case "emergency":
1135
+ currentLogLevel = "Fatal"
1136
+ break
1137
+ }
1138
+ }),
1139
+ "prompts/get": (r) =>
1140
+ server.getPromptResult(r).pipe(
1141
+ Effect.provideService(CurrentLogLevel, currentLogLevel)
1142
+ ),
1111
1143
  "prompts/list": () => Effect.sync(() => new ListPromptsResult({ prompts: server.prompts })),
1112
1144
  "resources/list": () => Effect.sync(() => new ListResourcesResult({ resources: server.resources })),
1113
- "resources/read": ({ uri }) => server.findResource(uri),
1145
+ "resources/read": ({ uri }) =>
1146
+ server.findResource(uri).pipe(
1147
+ Effect.provideService(CurrentLogLevel, currentLogLevel)
1148
+ ),
1114
1149
  "resources/subscribe": () => InternalError.notImplemented.asEffect(),
1115
1150
  "resources/unsubscribe": () => InternalError.notImplemented.asEffect(),
1116
1151
  "resources/templates/list": () =>
1117
1152
  Effect.sync(() => new ListResourceTemplatesResult({ resourceTemplates: server.resourceTemplates })),
1118
- "tools/call": server.callTool,
1153
+ "tools/call": (r) =>
1154
+ server.callTool(r).pipe(
1155
+ Effect.provideService(CurrentLogLevel, currentLogLevel)
1156
+ ),
1119
1157
  "tools/list": () => Effect.sync(() => new ListToolsResult({ tools: server.tools })),
1120
1158
 
1121
1159
  // Notifications
@@ -1661,6 +1661,21 @@ export const getJsonSchemaFromSchema = <S extends Schema.Top>(schema: S, options
1661
1661
  */
1662
1662
  export class Title extends ServiceMap.Service<Title, string>()("effect/ai/Tool/Title") {}
1663
1663
 
1664
+ /**
1665
+ * Annotation for providing tool metadata for MCP.
1666
+ *
1667
+ * @example
1668
+ * ```ts
1669
+ * import { Tool } from "effect/unstable/ai"
1670
+ *
1671
+ * const myCalculatorUi = Tool.make("calculator_ui", {})
1672
+ * .annotate(Tool.Meta, { ui: { resourceUri: "ui://example/calculator-ui" } })
1673
+ * ```
1674
+ * @since 1.0.0
1675
+ * @category annotations
1676
+ */
1677
+ export class Meta extends ServiceMap.Service<Meta, Record<string, unknown>>()("effect/ai/Tool/Meta") {}
1678
+
1664
1679
  /**
1665
1680
  * Annotation indicating whether a tool only reads data without making changes.
1666
1681
  *
@@ -16,7 +16,7 @@ import * as Schema from "../../Schema.ts"
16
16
  import type * as Scope from "../../Scope.ts"
17
17
  import * as ServiceMap from "../../ServiceMap.ts"
18
18
  import * as Rpc from "../rpc/Rpc.ts"
19
- import * as RpcServer from "../rpc/RpcServer.ts"
19
+ import { ClientAbort } from "../rpc/RpcSchema.ts"
20
20
  import * as Activity from "../workflow/Activity.ts"
21
21
  import * as DurableClock from "../workflow/DurableClock.ts"
22
22
  import * as DurableDeferred from "../workflow/DurableDeferred.ts"
@@ -315,7 +315,7 @@ export const make = Effect.gen(function*() {
315
315
  // we only want to store interrupts as suspends when the
316
316
  // client requested it
317
317
  const suspend = cause.reasons.some((f) =>
318
- f._tag === "Interrupt" && f.fiberId === RpcServer.fiberIdClientInterrupt
318
+ f._tag === "Interrupt" && f.annotations.has(ClientAbort.key)
319
319
  )
320
320
  if (suspend) {
321
321
  interruptedActivities.add(activityId)