effect 4.0.0-beta.32 → 4.0.0-beta.34

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 (111) hide show
  1. package/dist/Effect.d.ts +12 -6
  2. package/dist/Effect.d.ts.map +1 -1
  3. package/dist/Effect.js +4 -4
  4. package/dist/Effect.js.map +1 -1
  5. package/dist/FileSystem.d.ts.map +1 -1
  6. package/dist/FileSystem.js +2 -1
  7. package/dist/FileSystem.js.map +1 -1
  8. package/dist/Graph.d.ts.map +1 -1
  9. package/dist/Graph.js +2 -1
  10. package/dist/Graph.js.map +1 -1
  11. package/dist/MutableHashMap.d.ts +7 -0
  12. package/dist/MutableHashMap.d.ts.map +1 -1
  13. package/dist/MutableHashMap.js +8 -0
  14. package/dist/MutableHashMap.js.map +1 -1
  15. package/dist/MutableHashSet.d.ts +7 -0
  16. package/dist/MutableHashSet.d.ts.map +1 -1
  17. package/dist/MutableHashSet.js +8 -0
  18. package/dist/MutableHashSet.js.map +1 -1
  19. package/dist/Queue.d.ts +1 -1
  20. package/dist/Queue.d.ts.map +1 -1
  21. package/dist/Queue.js.map +1 -1
  22. package/dist/Resource.d.ts.map +1 -1
  23. package/dist/Resource.js +2 -1
  24. package/dist/Resource.js.map +1 -1
  25. package/dist/SubscriptionRef.d.ts.map +1 -1
  26. package/dist/SubscriptionRef.js +2 -1
  27. package/dist/SubscriptionRef.js.map +1 -1
  28. package/dist/TxDeferred.d.ts.map +1 -1
  29. package/dist/TxDeferred.js +2 -1
  30. package/dist/TxDeferred.js.map +1 -1
  31. package/dist/TxHashMap.d.ts.map +1 -1
  32. package/dist/TxHashMap.js +2 -1
  33. package/dist/TxHashMap.js.map +1 -1
  34. package/dist/TxHashSet.d.ts +1 -1
  35. package/dist/TxHashSet.d.ts.map +1 -1
  36. package/dist/TxHashSet.js +2 -1
  37. package/dist/TxHashSet.js.map +1 -1
  38. package/dist/TxPriorityQueue.d.ts +1 -1
  39. package/dist/TxPriorityQueue.d.ts.map +1 -1
  40. package/dist/TxPriorityQueue.js +2 -1
  41. package/dist/TxPriorityQueue.js.map +1 -1
  42. package/dist/TxSemaphore.d.ts.map +1 -1
  43. package/dist/TxSemaphore.js +2 -1
  44. package/dist/TxSemaphore.js.map +1 -1
  45. package/dist/internal/effect.js +1 -1
  46. package/dist/internal/effect.js.map +1 -1
  47. package/dist/unstable/ai/AiError.d.ts +1 -3
  48. package/dist/unstable/ai/AiError.d.ts.map +1 -1
  49. package/dist/unstable/ai/AiError.js +1 -3
  50. package/dist/unstable/ai/AiError.js.map +1 -1
  51. package/dist/unstable/ai/EmbeddingModel.d.ts +130 -0
  52. package/dist/unstable/ai/EmbeddingModel.d.ts.map +1 -0
  53. package/dist/unstable/ai/EmbeddingModel.js +127 -0
  54. package/dist/unstable/ai/EmbeddingModel.js.map +1 -0
  55. package/dist/unstable/ai/LanguageModel.d.ts +8 -0
  56. package/dist/unstable/ai/LanguageModel.d.ts.map +1 -1
  57. package/dist/unstable/ai/LanguageModel.js +127 -16
  58. package/dist/unstable/ai/LanguageModel.js.map +1 -1
  59. package/dist/unstable/ai/ResponseIdTracker.d.ts +38 -0
  60. package/dist/unstable/ai/ResponseIdTracker.d.ts.map +1 -0
  61. package/dist/unstable/ai/ResponseIdTracker.js +68 -0
  62. package/dist/unstable/ai/ResponseIdTracker.js.map +1 -0
  63. package/dist/unstable/ai/Tool.d.ts +1 -1
  64. package/dist/unstable/ai/Tool.d.ts.map +1 -1
  65. package/dist/unstable/ai/Tool.js +1 -1
  66. package/dist/unstable/ai/Tool.js.map +1 -1
  67. package/dist/unstable/ai/index.d.ts +21 -0
  68. package/dist/unstable/ai/index.d.ts.map +1 -1
  69. package/dist/unstable/ai/index.js +21 -0
  70. package/dist/unstable/ai/index.js.map +1 -1
  71. package/dist/unstable/http/Url.d.ts +604 -0
  72. package/dist/unstable/http/Url.d.ts.map +1 -0
  73. package/dist/unstable/http/Url.js +256 -0
  74. package/dist/unstable/http/Url.js.map +1 -0
  75. package/dist/unstable/http/index.d.ts +4 -0
  76. package/dist/unstable/http/index.d.ts.map +1 -1
  77. package/dist/unstable/http/index.js +4 -0
  78. package/dist/unstable/http/index.js.map +1 -1
  79. package/dist/unstable/httpapi/HttpApiMiddleware.d.ts +30 -1
  80. package/dist/unstable/httpapi/HttpApiMiddleware.d.ts.map +1 -1
  81. package/dist/unstable/httpapi/HttpApiMiddleware.js +27 -0
  82. package/dist/unstable/httpapi/HttpApiMiddleware.js.map +1 -1
  83. package/dist/unstable/socket/Socket.d.ts +2 -1
  84. package/dist/unstable/socket/Socket.d.ts.map +1 -1
  85. package/dist/unstable/socket/Socket.js +3 -2
  86. package/dist/unstable/socket/Socket.js.map +1 -1
  87. package/package.json +1 -1
  88. package/src/Effect.ts +16 -25
  89. package/src/FileSystem.ts +2 -1
  90. package/src/Graph.ts +2 -1
  91. package/src/MutableHashMap.ts +9 -0
  92. package/src/MutableHashSet.ts +9 -0
  93. package/src/Queue.ts +1 -1
  94. package/src/Resource.ts +2 -1
  95. package/src/SubscriptionRef.ts +2 -1
  96. package/src/TxDeferred.ts +2 -2
  97. package/src/TxHashMap.ts +2 -1
  98. package/src/TxHashSet.ts +2 -2
  99. package/src/TxPriorityQueue.ts +2 -3
  100. package/src/TxSemaphore.ts +2 -1
  101. package/src/internal/effect.ts +11 -4
  102. package/src/unstable/ai/AiError.ts +1 -3
  103. package/src/unstable/ai/EmbeddingModel.ts +209 -0
  104. package/src/unstable/ai/LanguageModel.ts +228 -69
  105. package/src/unstable/ai/ResponseIdTracker.ts +97 -0
  106. package/src/unstable/ai/Tool.ts +2 -2
  107. package/src/unstable/ai/index.ts +23 -0
  108. package/src/unstable/http/Url.ts +650 -0
  109. package/src/unstable/http/index.ts +5 -0
  110. package/src/unstable/httpapi/HttpApiMiddleware.ts +46 -1
  111. package/src/unstable/socket/Socket.ts +12 -8
@@ -51,7 +51,7 @@
51
51
  import type * as Cause from "../../Cause.ts"
52
52
  import * as Effect from "../../Effect.ts"
53
53
  import * as FiberSet from "../../FiberSet.ts"
54
- import { constFalse } from "../../Function.ts"
54
+ import { constFalse, identity, pipe } from "../../Function.ts"
55
55
  import type * as JsonSchema from "../../JsonSchema.ts"
56
56
  import * as Option from "../../Option.ts"
57
57
  import * as Predicate from "../../Predicate.ts"
@@ -69,6 +69,7 @@ import { defaultIdGenerator, IdGenerator } from "./IdGenerator.ts"
69
69
  import * as InternalCodecTransformer from "./internal/codec-transformer.ts"
70
70
  import * as Prompt from "./Prompt.ts"
71
71
  import * as Response from "./Response.ts"
72
+ import * as ResponseIdTracker from "./ResponseIdTracker.ts"
72
73
  import type { SpanTransformer } from "./Telemetry.ts"
73
74
  import { CurrentSpanTransformer } from "./Telemetry.ts"
74
75
  import type * as Tool from "./Tool.ts"
@@ -648,6 +649,16 @@ export interface ProviderOptions {
648
649
  * The span to use to trace interactions with the large language model.
649
650
  */
650
651
  readonly span: Span
652
+
653
+ /**
654
+ * The previous response identifier for incremental provider calls.
655
+ */
656
+ readonly previousResponseId: string | undefined
657
+
658
+ /**
659
+ * The prompt reduced to messages not yet seen by the provider.
660
+ */
661
+ readonly incrementalPrompt: Prompt.Prompt | undefined
651
662
  }
652
663
 
653
664
  /**
@@ -727,7 +738,9 @@ export const make: (params: {
727
738
  tools: [],
728
739
  toolChoice: "none",
729
740
  responseFormat: { type: "text" },
730
- span
741
+ span,
742
+ previousResponseId: undefined,
743
+ incrementalPrompt: undefined
731
744
  }
732
745
  const content = yield* generateContent(options, providerOptions)
733
746
 
@@ -790,7 +803,9 @@ export const make: (params: {
790
803
  objectName,
791
804
  schema: options.schema
792
805
  },
793
- span
806
+ span,
807
+ previousResponseId: undefined,
808
+ incrementalPrompt: undefined
794
809
  }
795
810
 
796
811
  const content = yield* generateContent(options, providerOptions)
@@ -857,7 +872,9 @@ export const make: (params: {
857
872
  tools: [],
858
873
  toolChoice: "none",
859
874
  responseFormat: { type: "text" },
860
- span
875
+ span,
876
+ previousResponseId: undefined,
877
+ incrementalPrompt: undefined
861
878
  }
862
879
 
863
880
  // Resolve the content stream for the request
@@ -917,8 +934,23 @@ export const make: (params: {
917
934
  options: Options & GenerateTextOptions<Tools>,
918
935
  providerOptions: Mutable<ProviderOptions>
919
936
  ) {
937
+ const tracker = Option.getOrUndefined(yield* Effect.serviceOption(ResponseIdTracker.ResponseIdTracker))
920
938
  const toolChoice = options.toolChoice ?? "auto"
921
939
 
940
+ const withNonIncrementalFallback = <R>(
941
+ effect: Effect.Effect<Array<Response.PartEncoded>, AiError.AiError, R>
942
+ ): Effect.Effect<Array<Response.PartEncoded>, AiError.AiError, R | IdGenerator> =>
943
+ providerOptions.incrementalPrompt ?
944
+ effect.pipe(
945
+ Effect.catchReason("AiError", "InvalidRequestError", (_) =>
946
+ params.generateText({
947
+ ...providerOptions,
948
+ incrementalPrompt: undefined,
949
+ previousResponseId: undefined
950
+ }))
951
+ ) :
952
+ effect
953
+
922
954
  // Check for pending approvals that need resolution
923
955
  const { approved, denied } = collectToolApprovals(
924
956
  providerOptions.prompt.content,
@@ -940,11 +972,24 @@ export const make: (params: {
940
972
  })
941
973
  })
942
974
  }
975
+ if (tracker) {
976
+ const prepared = tracker.prepareUnsafe(providerOptions.prompt)
977
+ if (Option.isSome(prepared)) {
978
+ providerOptions.previousResponseId = prepared.value.previousResponseId
979
+ providerOptions.incrementalPrompt = prepared.value.prompt
980
+ }
981
+ }
943
982
  const ResponseSchema = Schema.mutable(
944
983
  Schema.Array(Response.Part(Toolkit.empty))
945
984
  )
946
- const rawContent = yield* params.generateText(providerOptions)
985
+ const rawContent = yield* withNonIncrementalFallback(params.generateText(providerOptions))
947
986
  const content = yield* Schema.decodeEffect(ResponseSchema)(rawContent)
987
+ if (tracker) {
988
+ const responseMetadata = content.find((part) => part.type === "response-metadata")
989
+ if (Predicate.isNotUndefined(responseMetadata) && Predicate.isNotUndefined(responseMetadata.id)) {
990
+ tracker.markParts(providerOptions.prompt.content, responseMetadata.id)
991
+ }
992
+ }
948
993
  return content as Array<Response.Part<Tools>>
949
994
  }
950
995
 
@@ -965,11 +1010,24 @@ export const make: (params: {
965
1010
  })
966
1011
  })
967
1012
  }
1013
+ if (tracker) {
1014
+ const prepared = tracker.prepareUnsafe(providerOptions.prompt)
1015
+ if (Option.isSome(prepared)) {
1016
+ providerOptions.previousResponseId = prepared.value.previousResponseId
1017
+ providerOptions.incrementalPrompt = prepared.value.prompt
1018
+ }
1019
+ }
968
1020
  const ResponseSchema = Schema.mutable(
969
1021
  Schema.Array(Response.Part(Toolkit.empty))
970
1022
  )
971
- const rawContent = yield* params.generateText(providerOptions)
1023
+ const rawContent = yield* withNonIncrementalFallback(params.generateText(providerOptions))
972
1024
  const content = yield* Schema.decodeEffect(ResponseSchema)(rawContent)
1025
+ if (tracker) {
1026
+ const responseMetadata = content.find((part) => part.type === "response-metadata")
1027
+ if (Predicate.isNotUndefined(responseMetadata) && Predicate.isNotUndefined(responseMetadata.id)) {
1028
+ tracker.markParts(providerOptions.prompt.content, responseMetadata.id)
1029
+ }
1030
+ }
973
1031
  return content as Array<Response.Part<Tools>>
974
1032
  }
975
1033
 
@@ -1025,6 +1083,14 @@ export const make: (params: {
1025
1083
  providerOptions.tools = tools
1026
1084
  providerOptions.toolChoice = toolChoice
1027
1085
 
1086
+ if (tracker) {
1087
+ const prepared = tracker.prepareUnsafe(providerOptions.prompt)
1088
+ if (Option.isSome(prepared)) {
1089
+ providerOptions.previousResponseId = prepared.value.previousResponseId
1090
+ providerOptions.incrementalPrompt = prepared.value.prompt
1091
+ }
1092
+ }
1093
+
1028
1094
  // Construct the response schema with the tools from the toolkit
1029
1095
  const ResponseSchema = Schema.mutable(
1030
1096
  Schema.Array(Response.Part(toolkit))
@@ -1033,12 +1099,18 @@ export const make: (params: {
1033
1099
  // If tool call resolution is disabled, return the response without
1034
1100
  // resolving the tool calls that were generated
1035
1101
  if (options.disableToolCallResolution === true) {
1036
- const rawContent = yield* params.generateText(providerOptions)
1102
+ const rawContent = yield* withNonIncrementalFallback(params.generateText(providerOptions))
1037
1103
  const content = yield* Schema.decodeEffect(ResponseSchema)(rawContent)
1104
+ if (tracker) {
1105
+ const responseMetadata = content.find((part) => part.type === "response-metadata")
1106
+ if (Predicate.isNotUndefined(responseMetadata) && Predicate.isNotUndefined(responseMetadata.id)) {
1107
+ tracker.markParts(providerOptions.prompt.content, responseMetadata.id)
1108
+ }
1109
+ }
1038
1110
  return content as Array<Response.Part<Tools>>
1039
1111
  }
1040
1112
 
1041
- const rawContent = yield* params.generateText(providerOptions)
1113
+ const rawContent = yield* withNonIncrementalFallback(params.generateText(providerOptions))
1042
1114
 
1043
1115
  // Resolve the generated tool calls
1044
1116
  const toolResults = yield* resolveToolCalls(
@@ -1057,6 +1129,13 @@ export const make: (params: {
1057
1129
 
1058
1130
  const content = yield* Schema.decodeEffect(ResponseSchema)(rawContent)
1059
1131
 
1132
+ if (tracker) {
1133
+ const responseMetadata = content.find((part) => part.type === "response-metadata")
1134
+ if (Predicate.isNotUndefined(responseMetadata) && Predicate.isNotUndefined(responseMetadata.id)) {
1135
+ tracker.markParts(providerOptions.prompt.content, responseMetadata.id)
1136
+ }
1137
+ }
1138
+
1060
1139
  // Return the content merged with the tool call results
1061
1140
  return [...content, ...toolResults] as Array<Response.Part<Tools>>
1062
1141
  })
@@ -1086,8 +1165,23 @@ export const make: (params: {
1086
1165
  options: Options & GenerateTextOptions<Tools>,
1087
1166
  providerOptions: Mutable<ProviderOptions>
1088
1167
  ) {
1168
+ const tracker = Option.getOrUndefined(yield* Effect.serviceOption(ResponseIdTracker.ResponseIdTracker))
1089
1169
  const toolChoice = options.toolChoice ?? "auto"
1090
1170
 
1171
+ const withNonIncrementalFallback = <R>(
1172
+ stream: Stream.Stream<Response.StreamPartEncoded, AiError.AiError, R>
1173
+ ): Stream.Stream<Response.StreamPartEncoded, AiError.AiError, R | IdGenerator> =>
1174
+ providerOptions.incrementalPrompt ?
1175
+ stream.pipe(
1176
+ Stream.catchReason("AiError", "InvalidRequestError", (_) =>
1177
+ params.streamText({
1178
+ ...providerOptions,
1179
+ incrementalPrompt: undefined,
1180
+ previousResponseId: undefined
1181
+ }))
1182
+ ) :
1183
+ stream
1184
+
1091
1185
  // Check for pending approvals that need resolution
1092
1186
  const { approved: pendingApproved, denied: pendingDenied } = collectToolApprovals(providerOptions.prompt.content, {
1093
1187
  excludeResolved: true
@@ -1108,17 +1202,37 @@ export const make: (params: {
1108
1202
  })
1109
1203
  })
1110
1204
  }
1205
+ if (tracker) {
1206
+ const prepared = tracker.prepareUnsafe(providerOptions.prompt)
1207
+ if (Option.isSome(prepared)) {
1208
+ providerOptions.previousResponseId = prepared.value.previousResponseId
1209
+ providerOptions.incrementalPrompt = prepared.value.prompt
1210
+ }
1211
+ }
1111
1212
  const schema = Schema.NonEmptyArray(Response.StreamPart(Toolkit.empty))
1112
1213
  const decodeParts = Schema.decodeEffect(schema)
1113
- return params
1114
- .streamText(providerOptions)
1115
- .pipe(
1116
- Stream.mapArrayEffect((parts) => decodeParts(parts))
1117
- ) as Stream.Stream<
1118
- Response.StreamPart<Tools>,
1119
- AiError.AiError | Schema.SchemaError,
1120
- IdGenerator
1121
- >
1214
+ return pipe(
1215
+ params.streamText(providerOptions),
1216
+ withNonIncrementalFallback,
1217
+ Stream.mapArrayEffect((parts) =>
1218
+ decodeParts(parts).pipe(
1219
+ tracker ?
1220
+ Effect.tap((decodedParts) => {
1221
+ for (const part of decodedParts) {
1222
+ if (part.type === "response-metadata" && Predicate.isNotUndefined(part.id)) {
1223
+ tracker.markParts(providerOptions.prompt.content, part.id)
1224
+ }
1225
+ }
1226
+ return Effect.void
1227
+ }) :
1228
+ identity
1229
+ )
1230
+ )
1231
+ ) as Stream.Stream<
1232
+ Response.StreamPart<Tools>,
1233
+ AiError.AiError | Schema.SchemaError,
1234
+ IdGenerator
1235
+ >
1122
1236
  }
1123
1237
 
1124
1238
  // If there is a toolkit resolve and apply it to the provider options
@@ -1138,17 +1252,37 @@ export const make: (params: {
1138
1252
  })
1139
1253
  })
1140
1254
  }
1255
+ if (tracker) {
1256
+ const prepared = tracker.prepareUnsafe(providerOptions.prompt)
1257
+ if (Option.isSome(prepared)) {
1258
+ providerOptions.previousResponseId = prepared.value.previousResponseId
1259
+ providerOptions.incrementalPrompt = prepared.value.prompt
1260
+ }
1261
+ }
1141
1262
  const schema = Schema.NonEmptyArray(Response.StreamPart(Toolkit.empty))
1142
1263
  const decodeParts = Schema.decodeEffect(schema)
1143
- return params
1144
- .streamText(providerOptions)
1145
- .pipe(
1146
- Stream.mapArrayEffect((parts) => decodeParts(parts))
1147
- ) as Stream.Stream<
1148
- Response.StreamPart<Tools>,
1149
- AiError.AiError | Schema.SchemaError,
1150
- IdGenerator
1151
- >
1264
+ return pipe(
1265
+ params.streamText(providerOptions),
1266
+ withNonIncrementalFallback,
1267
+ Stream.mapArrayEffect((parts) =>
1268
+ decodeParts(parts).pipe(
1269
+ tracker ?
1270
+ Effect.tap((decodedParts) => {
1271
+ for (const part of decodedParts) {
1272
+ if (part.type === "response-metadata" && part.id) {
1273
+ tracker.markParts(providerOptions.prompt.content, part.id)
1274
+ }
1275
+ }
1276
+ return Effect.void
1277
+ }) :
1278
+ identity
1279
+ )
1280
+ )
1281
+ ) as Stream.Stream<
1282
+ Response.StreamPart<Tools>,
1283
+ AiError.AiError | Schema.SchemaError,
1284
+ IdGenerator
1285
+ >
1152
1286
  }
1153
1287
 
1154
1288
  // Pre-resolve pending tool approvals before calling the LLM
@@ -1222,20 +1356,40 @@ export const make: (params: {
1222
1356
  providerOptions.tools = tools
1223
1357
  providerOptions.toolChoice = toolChoice
1224
1358
 
1359
+ if (tracker) {
1360
+ const prepared = tracker.prepareUnsafe(providerOptions.prompt)
1361
+ if (Option.isSome(prepared)) {
1362
+ providerOptions.previousResponseId = prepared.value.previousResponseId
1363
+ providerOptions.incrementalPrompt = prepared.value.prompt
1364
+ }
1365
+ }
1366
+
1225
1367
  // If tool call resolution is disabled, return the response without
1226
1368
  // resolving the tool calls that were generated
1227
1369
  if (options.disableToolCallResolution === true) {
1228
1370
  const schema = Schema.NonEmptyArray(Response.StreamPart(toolkit))
1229
1371
  const decodeParts = Schema.decodeEffect(schema)
1230
- return params
1231
- .streamText(providerOptions)
1232
- .pipe(
1233
- Stream.mapArrayEffect((parts) => decodeParts(parts))
1234
- ) as Stream.Stream<
1235
- Response.StreamPart<Tools>,
1236
- AiError.AiError | Schema.SchemaError,
1237
- IdGenerator
1238
- >
1372
+ return params.streamText(providerOptions).pipe(
1373
+ withNonIncrementalFallback,
1374
+ Stream.mapArrayEffect((parts) =>
1375
+ decodeParts(parts).pipe(
1376
+ tracker ?
1377
+ Effect.tap((decodedParts) => {
1378
+ for (const part of decodedParts) {
1379
+ if (part.type === "response-metadata" && Predicate.isNotUndefined(part.id)) {
1380
+ tracker.markParts(providerOptions.prompt.content, part.id)
1381
+ }
1382
+ }
1383
+ return Effect.void
1384
+ }) :
1385
+ identity
1386
+ )
1387
+ )
1388
+ ) as Stream.Stream<
1389
+ Response.StreamPart<Tools>,
1390
+ AiError.AiError | Schema.SchemaError,
1391
+ IdGenerator
1392
+ >
1239
1393
  }
1240
1394
 
1241
1395
  const ResponseSchema = Schema.NonEmptyArray(Response.StreamPart(toolkit))
@@ -1260,48 +1414,53 @@ export const make: (params: {
1260
1414
  const toolCallFibers = yield* FiberSet.make<void, AiError.AiError>()
1261
1415
 
1262
1416
  // Helper function to handle tool calls with approval logic
1263
- const handleToolCall = (part: Response.ToolCallPartEncoded) =>
1264
- Effect.gen(function*() {
1265
- const tool = toolkit.tools[part.name]
1266
- if (!tool) {
1267
- return
1268
- }
1417
+ const handleToolCall = Effect.fnUntraced(function*(part: Response.ToolCallPartEncoded) {
1418
+ const tool = toolkit.tools[part.name]
1419
+ if (!tool) return
1269
1420
 
1270
- const needsApproval = yield* isApprovalNeeded(
1271
- tool,
1272
- part,
1273
- providerOptions.prompt.content
1274
- )
1421
+ const needsApproval = yield* isApprovalNeeded(
1422
+ tool,
1423
+ part,
1424
+ providerOptions.prompt.content
1425
+ )
1275
1426
 
1276
- if (needsApproval) {
1277
- const idGen = yield* IdGenerator
1278
- const approvalId = yield* idGen.generateId()
1279
- const approvalPart = Response.makePart("tool-approval-request", {
1280
- approvalId,
1281
- toolCallId: part.id
1282
- }) as Response.StreamPart<Tools>
1283
- yield* Queue.offer(queue, approvalPart)
1284
- return
1285
- }
1427
+ if (needsApproval) {
1428
+ const idGen = yield* IdGenerator
1429
+ const approvalId = yield* idGen.generateId()
1430
+ const approvalPart = Response.makePart("tool-approval-request", {
1431
+ approvalId,
1432
+ toolCallId: part.id
1433
+ }) as Response.StreamPart<Tools>
1434
+ yield* Queue.offer(queue, approvalPart)
1435
+ return
1436
+ }
1286
1437
 
1287
- yield* toolkit.handle(part.name, part.params as any).pipe(
1288
- Stream.unwrap,
1289
- Stream.runForEach((result) => {
1290
- const toolResultPart = Response.makePart("tool-result", {
1291
- id: part.id,
1292
- name: part.name,
1293
- providerExecuted: false,
1294
- ...result
1295
- }) as Response.StreamPart<Tools>
1296
- return Queue.offer(queue, toolResultPart)
1297
- })
1298
- )
1299
- })
1438
+ yield* toolkit.handle(part.name, part.params as any).pipe(
1439
+ Stream.unwrap,
1440
+ Stream.runForEach((result) => {
1441
+ const toolResultPart = Response.makePart("tool-result", {
1442
+ id: part.id,
1443
+ name: part.name,
1444
+ providerExecuted: false,
1445
+ ...result
1446
+ }) as Response.StreamPart<Tools>
1447
+ return Queue.offer(queue, toolResultPart)
1448
+ })
1449
+ )
1450
+ })
1300
1451
 
1301
1452
  yield* params.streamText(providerOptions).pipe(
1453
+ withNonIncrementalFallback,
1302
1454
  Stream.runForEachArray(
1303
1455
  Effect.fnUntraced(function*(chunk) {
1304
1456
  const parts = yield* decodeParts(chunk)
1457
+ if (tracker) {
1458
+ for (const part of parts) {
1459
+ if (part.type === "response-metadata" && part.id) {
1460
+ tracker.markParts(providerOptions.prompt.content, part.id)
1461
+ }
1462
+ }
1463
+ }
1305
1464
  // Add decoded response parts to the output queue
1306
1465
  yield* Queue.offerAll(queue, parts)
1307
1466
  // Fork tool call handlers - use the raw chunk for encoded params
@@ -0,0 +1,97 @@
1
+ /**
2
+ * @since 4.0.0
3
+ */
4
+ import * as Effect from "../../Effect.ts"
5
+ import * as Option from "../../Option.ts"
6
+ import * as ServiceMap from "../../ServiceMap.ts"
7
+ import * as Prompt from "./Prompt.ts"
8
+
9
+ /**
10
+ * @since 4.0.0
11
+ * @category models
12
+ */
13
+ export interface PrepareResult {
14
+ readonly previousResponseId: string
15
+ readonly prompt: Prompt.Prompt
16
+ }
17
+
18
+ /**
19
+ * @since 4.0.0
20
+ * @category models
21
+ */
22
+ export interface Service {
23
+ clearUnsafe(): void
24
+ markParts(parts: ReadonlyArray<object>, responseId: string): void
25
+ prepareUnsafe(prompt: Prompt.Prompt): Option.Option<PrepareResult>
26
+ }
27
+
28
+ /**
29
+ * @since 4.0.0
30
+ * @category Services
31
+ */
32
+ export class ResponseIdTracker
33
+ extends ServiceMap.Service<ResponseIdTracker, Service>()("effect/ai/ResponseIdTracker")
34
+ {}
35
+
36
+ /**
37
+ * @since 4.0.0
38
+ * @category constructors
39
+ */
40
+ export const make: Effect.Effect<Service> = Effect.sync(() => {
41
+ const sentParts = new Map<object, string>()
42
+
43
+ const none = () => {
44
+ sentParts.clear()
45
+ return Option.none<PrepareResult>()
46
+ }
47
+
48
+ return {
49
+ clearUnsafe() {
50
+ sentParts.clear()
51
+ },
52
+ markParts(parts, responseId) {
53
+ for (let i = 0; i < parts.length; i++) {
54
+ sentParts.set(parts[i], responseId)
55
+ }
56
+ },
57
+ prepareUnsafe(prompt) {
58
+ const messages = prompt.content
59
+
60
+ let anyTracked = false
61
+ for (let i = 0; i < messages.length; i++) {
62
+ if (sentParts.has(messages[i])) {
63
+ anyTracked = true
64
+ break
65
+ }
66
+ }
67
+ if (!anyTracked) return none()
68
+
69
+ let lastAssistantIndex = -1
70
+ for (let i = messages.length - 1; i >= 0; i--) {
71
+ if (messages[i].role === "assistant") {
72
+ lastAssistantIndex = i
73
+ break
74
+ }
75
+ }
76
+ if (lastAssistantIndex === -1) return none()
77
+
78
+ let responseId: string | undefined
79
+ for (let i = 0; i < lastAssistantIndex; i++) {
80
+ const id = sentParts.get(messages[i])
81
+ if (id === undefined) return none()
82
+ responseId = id
83
+ }
84
+ if (responseId === undefined) return none()
85
+
86
+ const partsAfterLastAssistant = messages.slice(lastAssistantIndex + 1)
87
+ if (partsAfterLastAssistant.length === 0) {
88
+ return none()
89
+ }
90
+
91
+ return Option.some({
92
+ previousResponseId: responseId,
93
+ prompt: Prompt.fromMessages(partsAfterLastAssistant)
94
+ })
95
+ }
96
+ }
97
+ })
@@ -1149,7 +1149,7 @@ const dynamicProto = <
1149
1149
  */
1150
1150
  export const make = <
1151
1151
  const Name extends string,
1152
- Parameters extends Schema.Top = typeof Schema.Void,
1152
+ Parameters extends Schema.Top = typeof EmptyParams,
1153
1153
  Success extends Schema.Top = typeof Schema.Void,
1154
1154
  Failure extends Schema.Top = typeof Schema.Never,
1155
1155
  Mode extends FailureMode | undefined = undefined,
@@ -1210,7 +1210,7 @@ export const make = <
1210
1210
  return userDefinedProto({
1211
1211
  name,
1212
1212
  description: options?.description,
1213
- parametersSchema: options?.parameters ?? Schema.Void,
1213
+ parametersSchema: options?.parameters ?? EmptyParams,
1214
1214
  successSchema,
1215
1215
  failureSchema,
1216
1216
  failureMode: options?.failureMode ?? "error",
@@ -153,6 +153,24 @@ export * as AnthropicStructuredOutput from "./AnthropicStructuredOutput.ts"
153
153
  */
154
154
  export * as Chat from "./Chat.ts"
155
155
 
156
+ /**
157
+ * The `EmbeddingModel` module provides provider-agnostic text embedding capabilities.
158
+ *
159
+ * @example
160
+ * ```ts
161
+ * import { Effect } from "effect"
162
+ * import { EmbeddingModel } from "effect/unstable/ai"
163
+ *
164
+ * const program = Effect.gen(function*() {
165
+ * const model = yield* EmbeddingModel.EmbeddingModel
166
+ * return yield* model.embed("hello world")
167
+ * })
168
+ * ```
169
+ *
170
+ * @since 4.0.0
171
+ */
172
+ export * as EmbeddingModel from "./EmbeddingModel.ts"
173
+
156
174
  /**
157
175
  * The `IdGenerator` module provides a pluggable system for generating unique identifiers
158
176
  * for tool calls and other items in the Effect AI SDKs.
@@ -388,6 +406,11 @@ export * as Prompt from "./Prompt.ts"
388
406
  */
389
407
  export * as Response from "./Response.ts"
390
408
 
409
+ /**
410
+ * @since 4.0.0
411
+ */
412
+ export * as ResponseIdTracker from "./ResponseIdTracker.ts"
413
+
391
414
  /**
392
415
  * The `Telemetry` module provides OpenTelemetry integration for operations
393
416
  * performed against a large language model provider by defining telemetry