effect 4.0.0-beta.34 → 4.0.0-beta.36

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 (84) hide show
  1. package/dist/Channel.d.ts +10 -1
  2. package/dist/Channel.d.ts.map +1 -1
  3. package/dist/Channel.js +14 -8
  4. package/dist/Channel.js.map +1 -1
  5. package/dist/Config.d.ts +4 -0
  6. package/dist/Config.d.ts.map +1 -1
  7. package/dist/Config.js.map +1 -1
  8. package/dist/Cron.d.ts +7 -0
  9. package/dist/Cron.d.ts.map +1 -1
  10. package/dist/Cron.js +109 -45
  11. package/dist/Cron.js.map +1 -1
  12. package/dist/Effect.d.ts +2 -2
  13. package/dist/Effect.d.ts.map +1 -1
  14. package/dist/Effect.js.map +1 -1
  15. package/dist/Equivalence.d.ts +52 -0
  16. package/dist/Equivalence.d.ts.map +1 -1
  17. package/dist/Equivalence.js +52 -0
  18. package/dist/Equivalence.js.map +1 -1
  19. package/dist/Layer.d.ts +5 -2
  20. package/dist/Layer.d.ts.map +1 -1
  21. package/dist/Layer.js +14 -1
  22. package/dist/Layer.js.map +1 -1
  23. package/dist/LayerMap.d.ts +5 -4
  24. package/dist/LayerMap.d.ts.map +1 -1
  25. package/dist/LayerMap.js.map +1 -1
  26. package/dist/PubSub.d.ts.map +1 -1
  27. package/dist/PubSub.js +9 -3
  28. package/dist/PubSub.js.map +1 -1
  29. package/dist/Schema.d.ts +50 -5
  30. package/dist/Schema.d.ts.map +1 -1
  31. package/dist/Schema.js +40 -0
  32. package/dist/Schema.js.map +1 -1
  33. package/dist/SchemaParser.d.ts +29 -0
  34. package/dist/SchemaParser.d.ts.map +1 -1
  35. package/dist/SchemaParser.js +38 -0
  36. package/dist/SchemaParser.js.map +1 -1
  37. package/dist/Stream.d.ts +10 -1
  38. package/dist/Stream.d.ts.map +1 -1
  39. package/dist/Stream.js +5 -1
  40. package/dist/Stream.js.map +1 -1
  41. package/dist/internal/dateTime.js +8 -1
  42. package/dist/internal/dateTime.js.map +1 -1
  43. package/dist/internal/effect.js +4 -3
  44. package/dist/internal/effect.js.map +1 -1
  45. package/dist/unstable/ai/AiError.d.ts +1 -0
  46. package/dist/unstable/ai/AiError.d.ts.map +1 -1
  47. package/dist/unstable/ai/AiError.js +7 -3
  48. package/dist/unstable/ai/AiError.js.map +1 -1
  49. package/dist/unstable/ai/LanguageModel.d.ts.map +1 -1
  50. package/dist/unstable/ai/LanguageModel.js +49 -21
  51. package/dist/unstable/ai/LanguageModel.js.map +1 -1
  52. package/dist/unstable/cli/CliOutput.js +4 -3
  53. package/dist/unstable/cli/CliOutput.js.map +1 -1
  54. package/dist/unstable/cli/Prompt.js +146 -13
  55. package/dist/unstable/cli/Prompt.js.map +1 -1
  56. package/dist/unstable/httpapi/HttpApiEndpoint.js +2 -2
  57. package/dist/unstable/httpapi/HttpApiEndpoint.js.map +1 -1
  58. package/dist/unstable/httpapi/HttpApiMiddleware.d.ts +15 -13
  59. package/dist/unstable/httpapi/HttpApiMiddleware.d.ts.map +1 -1
  60. package/dist/unstable/httpapi/HttpApiMiddleware.js +5 -3
  61. package/dist/unstable/httpapi/HttpApiMiddleware.js.map +1 -1
  62. package/dist/unstable/rpc/RpcSerialization.js +1 -1
  63. package/dist/unstable/rpc/RpcSerialization.js.map +1 -1
  64. package/package.json +1 -1
  65. package/src/Channel.ts +23 -11
  66. package/src/Config.ts +5 -0
  67. package/src/Cron.ts +142 -45
  68. package/src/Effect.ts +3 -3
  69. package/src/Equivalence.ts +56 -0
  70. package/src/Layer.ts +27 -9
  71. package/src/LayerMap.ts +7 -5
  72. package/src/PubSub.ts +13 -5
  73. package/src/Schema.ts +62 -8
  74. package/src/SchemaParser.ts +53 -0
  75. package/src/Stream.ts +11 -1
  76. package/src/internal/dateTime.ts +8 -1
  77. package/src/internal/effect.ts +16 -13
  78. package/src/unstable/ai/AiError.ts +4 -2
  79. package/src/unstable/ai/LanguageModel.ts +59 -40
  80. package/src/unstable/cli/CliOutput.ts +5 -4
  81. package/src/unstable/cli/Prompt.ts +147 -14
  82. package/src/unstable/httpapi/HttpApiEndpoint.ts +2 -2
  83. package/src/unstable/httpapi/HttpApiMiddleware.ts +36 -20
  84. package/src/unstable/rpc/RpcSerialization.ts +1 -1
@@ -937,19 +937,23 @@ export const make: (params: {
937
937
  const tracker = Option.getOrUndefined(yield* Effect.serviceOption(ResponseIdTracker.ResponseIdTracker))
938
938
  const toolChoice = options.toolChoice ?? "auto"
939
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
940
+ const generateWithNonIncrementalFallback = () => {
941
+ const requestOptions: ProviderOptions = {
942
+ ...providerOptions
943
+ }
944
+ const fallbackPrompt = requestOptions.prompt
945
+ const fallbackOptions: ProviderOptions = {
946
+ ...requestOptions,
947
+ prompt: fallbackPrompt,
948
+ incrementalPrompt: undefined,
949
+ previousResponseId: undefined
950
+ }
951
+ return requestOptions.incrementalPrompt
952
+ ? params.generateText(requestOptions).pipe(
953
+ Effect.catchReason("AiError", "InvalidRequestError", (_) => params.generateText(fallbackOptions))
954
+ )
955
+ : params.generateText(requestOptions)
956
+ }
953
957
 
954
958
  // Check for pending approvals that need resolution
955
959
  const { approved, denied } = collectToolApprovals(
@@ -982,7 +986,7 @@ export const make: (params: {
982
986
  const ResponseSchema = Schema.mutable(
983
987
  Schema.Array(Response.Part(Toolkit.empty))
984
988
  )
985
- const rawContent = yield* withNonIncrementalFallback(params.generateText(providerOptions))
989
+ const rawContent = yield* generateWithNonIncrementalFallback()
986
990
  const content = yield* Schema.decodeEffect(ResponseSchema)(rawContent)
987
991
  if (tracker) {
988
992
  const responseMetadata = content.find((part) => part.type === "response-metadata")
@@ -1020,7 +1024,7 @@ export const make: (params: {
1020
1024
  const ResponseSchema = Schema.mutable(
1021
1025
  Schema.Array(Response.Part(Toolkit.empty))
1022
1026
  )
1023
- const rawContent = yield* withNonIncrementalFallback(params.generateText(providerOptions))
1027
+ const rawContent = yield* generateWithNonIncrementalFallback()
1024
1028
  const content = yield* Schema.decodeEffect(ResponseSchema)(rawContent)
1025
1029
  if (tracker) {
1026
1030
  const responseMetadata = content.find((part) => part.type === "response-metadata")
@@ -1099,7 +1103,7 @@ export const make: (params: {
1099
1103
  // If tool call resolution is disabled, return the response without
1100
1104
  // resolving the tool calls that were generated
1101
1105
  if (options.disableToolCallResolution === true) {
1102
- const rawContent = yield* withNonIncrementalFallback(params.generateText(providerOptions))
1106
+ const rawContent = yield* generateWithNonIncrementalFallback()
1103
1107
  const content = yield* Schema.decodeEffect(ResponseSchema)(rawContent)
1104
1108
  if (tracker) {
1105
1109
  const responseMetadata = content.find((part) => part.type === "response-metadata")
@@ -1110,7 +1114,7 @@ export const make: (params: {
1110
1114
  return content as Array<Response.Part<Tools>>
1111
1115
  }
1112
1116
 
1113
- const rawContent = yield* withNonIncrementalFallback(params.generateText(providerOptions))
1117
+ const rawContent = yield* generateWithNonIncrementalFallback()
1114
1118
 
1115
1119
  // Resolve the generated tool calls
1116
1120
  const toolResults = yield* resolveToolCalls(
@@ -1168,19 +1172,23 @@ export const make: (params: {
1168
1172
  const tracker = Option.getOrUndefined(yield* Effect.serviceOption(ResponseIdTracker.ResponseIdTracker))
1169
1173
  const toolChoice = options.toolChoice ?? "auto"
1170
1174
 
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
1175
+ const streamWithNonIncrementalFallback = () => {
1176
+ const requestOptions: ProviderOptions = {
1177
+ ...providerOptions
1178
+ }
1179
+ const fallbackPrompt = requestOptions.prompt
1180
+ const fallbackOptions: ProviderOptions = {
1181
+ ...requestOptions,
1182
+ prompt: fallbackPrompt,
1183
+ incrementalPrompt: undefined,
1184
+ previousResponseId: undefined
1185
+ }
1186
+ return requestOptions.incrementalPrompt
1187
+ ? params.streamText(requestOptions).pipe(
1188
+ Stream.catchReason("AiError", "InvalidRequestError", (_) => params.streamText(fallbackOptions))
1189
+ )
1190
+ : params.streamText(requestOptions)
1191
+ }
1184
1192
 
1185
1193
  // Check for pending approvals that need resolution
1186
1194
  const { approved: pendingApproved, denied: pendingDenied } = collectToolApprovals(providerOptions.prompt.content, {
@@ -1212,8 +1220,7 @@ export const make: (params: {
1212
1220
  const schema = Schema.NonEmptyArray(Response.StreamPart(Toolkit.empty))
1213
1221
  const decodeParts = Schema.decodeEffect(schema)
1214
1222
  return pipe(
1215
- params.streamText(providerOptions),
1216
- withNonIncrementalFallback,
1223
+ streamWithNonIncrementalFallback(),
1217
1224
  Stream.mapArrayEffect((parts) =>
1218
1225
  decodeParts(parts).pipe(
1219
1226
  tracker ?
@@ -1262,8 +1269,7 @@ export const make: (params: {
1262
1269
  const schema = Schema.NonEmptyArray(Response.StreamPart(Toolkit.empty))
1263
1270
  const decodeParts = Schema.decodeEffect(schema)
1264
1271
  return pipe(
1265
- params.streamText(providerOptions),
1266
- withNonIncrementalFallback,
1272
+ streamWithNonIncrementalFallback(),
1267
1273
  Stream.mapArrayEffect((parts) =>
1268
1274
  decodeParts(parts).pipe(
1269
1275
  tracker ?
@@ -1369,8 +1375,7 @@ export const make: (params: {
1369
1375
  if (options.disableToolCallResolution === true) {
1370
1376
  const schema = Schema.NonEmptyArray(Response.StreamPart(toolkit))
1371
1377
  const decodeParts = Schema.decodeEffect(schema)
1372
- return params.streamText(providerOptions).pipe(
1373
- withNonIncrementalFallback,
1378
+ return streamWithNonIncrementalFallback().pipe(
1374
1379
  Stream.mapArrayEffect((parts) =>
1375
1380
  decodeParts(parts).pipe(
1376
1381
  tracker ?
@@ -1402,6 +1407,7 @@ export const make: (params: {
1402
1407
  | Cause.Done
1403
1408
  | Schema.SchemaError
1404
1409
  >()
1410
+ const deferredFinishParts: Array<Response.StreamPart<Tools>> = []
1405
1411
 
1406
1412
  // Emit pre-resolved tool results so Chat.streamText persists them to
1407
1413
  // history. This ensures collectToolApprovals({ excludeResolved }) can
@@ -1449,8 +1455,7 @@ export const make: (params: {
1449
1455
  )
1450
1456
  })
1451
1457
 
1452
- yield* params.streamText(providerOptions).pipe(
1453
- withNonIncrementalFallback,
1458
+ yield* streamWithNonIncrementalFallback().pipe(
1454
1459
  Stream.runForEachArray(
1455
1460
  Effect.fnUntraced(function*(chunk) {
1456
1461
  const parts = yield* decodeParts(chunk)
@@ -1461,8 +1466,19 @@ export const make: (params: {
1461
1466
  }
1462
1467
  }
1463
1468
  }
1464
- // Add decoded response parts to the output queue
1465
- yield* Queue.offerAll(queue, parts)
1469
+ // Defer finish parts until all tool handlers complete. This guarantees
1470
+ // tool results are emitted before finish in streaming mode.
1471
+ const immediateParts: Array<Response.StreamPart<Tools>> = []
1472
+ for (const part of parts) {
1473
+ if (part.type === "finish") {
1474
+ deferredFinishParts.push(part)
1475
+ } else {
1476
+ immediateParts.push(part)
1477
+ }
1478
+ }
1479
+ if (immediateParts.length > 0) {
1480
+ yield* Queue.offerAll(queue, immediateParts)
1481
+ }
1466
1482
  // Fork tool call handlers - use the raw chunk for encoded params
1467
1483
  for (const part of chunk) {
1468
1484
  if (part.type === "tool-call" && part.providerExecuted !== true) {
@@ -1480,6 +1496,9 @@ export const make: (params: {
1480
1496
  FiberSet.awaitEmpty(toolCallFibers)
1481
1497
  )
1482
1498
  ),
1499
+ Effect.andThen(
1500
+ Queue.offerAll(queue, deferredFinishParts)
1501
+ ),
1483
1502
  // And then end the queue
1484
1503
  Effect.andThen(Queue.end(queue)),
1485
1504
  Effect.tapCause((cause) => Queue.failCause(queue, cause)),
@@ -406,8 +406,9 @@ interface Row {
406
406
  * Renders a table with aligned columns.
407
407
  * @internal
408
408
  */
409
- const renderTable = (rows: ReadonlyArray<Row>, widthCap: number) => {
410
- const col = Math.min(Math.max(...rows.map((r) => visualLength(r.left))) + 4, widthCap)
409
+ const renderTable = (rows: ReadonlyArray<Row>, widthCap?: number) => {
410
+ const maxColumn = Math.max(...rows.map((r) => visualLength(r.left))) + 4
411
+ const col = widthCap === undefined ? maxColumn : Math.min(maxColumn, widthCap)
411
412
  return rows.map(({ left, right }) => ` ${pad(left, col)}${right}`).join("\n")
412
413
  }
413
414
 
@@ -497,7 +498,7 @@ const formatHelpDocImpl = (doc: HelpDoc, colors: ColorFunctions): string => {
497
498
  }
498
499
  })
499
500
 
500
- sections.push(renderTable(flagRows, 30))
501
+ sections.push(renderTable(flagRows))
501
502
  sections.push("")
502
503
  }
503
504
 
@@ -525,7 +526,7 @@ const formatHelpDocImpl = (doc: HelpDoc, colors: ColorFunctions): string => {
525
526
  }
526
527
  })
527
528
 
528
- sections.push(renderTable(globalFlagRows, 30))
529
+ sections.push(renderTable(globalFlagRows))
529
530
  sections.push("")
530
531
  }
531
532
 
@@ -739,7 +739,7 @@ export const file = (options: FileOptions = {}): Prompt<string> => {
739
739
  const currentPath = yield* resolveCurrentPath(Option.none(), opts)
740
740
  const files = yield* getFileList(currentPath, opts)
741
741
  const confirm = Confirm.Hide()
742
- return { cursor: 0, files, path: Option.none(), confirm }
742
+ return { cursor: 0, files, allFiles: files, query: "", path: Option.none(), confirm }
743
743
  })
744
744
  return custom(initialState, {
745
745
  render: handleFileRender(opts),
@@ -1855,11 +1855,16 @@ interface FileOptionsReq extends Required<Omit<FileOptions, "startingPath">> {
1855
1855
  interface FileState {
1856
1856
  readonly cursor: number
1857
1857
  readonly files: ReadonlyArray<string>
1858
+ readonly allFiles: ReadonlyArray<string>
1859
+ readonly query: string
1858
1860
  readonly path: Option.Option<string>
1859
1861
  readonly confirm: Confirm
1860
1862
  }
1861
1863
 
1862
1864
  const CONFIRM_MESSAGE = "The selected directory contains files. Would you like to traverse the selected directory?"
1865
+ const FILE_FILTER_LABEL = "filter"
1866
+ const FILE_FILTER_PLACEHOLDER = "type to filter"
1867
+ const FILE_EMPTY_MESSAGE = "No matches"
1863
1868
  type Confirm = Data.TaggedEnum<{
1864
1869
  readonly Show: {}
1865
1870
  readonly Hide: {}
@@ -1914,6 +1919,40 @@ const getFileList = Effect.fnUntraced(function*(directory: string, options: File
1914
1919
  }, { concurrency: files.length })
1915
1920
  })
1916
1921
 
1922
+ const filterFiles = (files: ReadonlyArray<string>, query: string) => {
1923
+ if (query.length === 0) {
1924
+ return files
1925
+ }
1926
+ const normalizedQuery = query.toLowerCase()
1927
+ const filtered: Array<string> = []
1928
+ for (let index = 0; index < files.length; index++) {
1929
+ if (files[index].toLowerCase().includes(normalizedQuery)) {
1930
+ filtered.push(files[index])
1931
+ }
1932
+ }
1933
+ return filtered
1934
+ }
1935
+
1936
+ const updateFileState = (
1937
+ state: FileState,
1938
+ query: string,
1939
+ allFiles: ReadonlyArray<string> = state.allFiles
1940
+ ): FileState => {
1941
+ const files = filterFiles(allFiles, query)
1942
+ if (files.length === 0) {
1943
+ return { ...state, query, allFiles, files, cursor: 0 }
1944
+ }
1945
+ const selected = state.files[state.cursor]
1946
+ const cursor = selected === undefined ? 0 : files.indexOf(selected)
1947
+ return {
1948
+ ...state,
1949
+ query,
1950
+ allFiles,
1951
+ files,
1952
+ cursor: cursor === -1 ? 0 : cursor
1953
+ }
1954
+ }
1955
+
1917
1956
  const handleFileClear = (options: FileOptionsReq) => {
1918
1957
  return Effect.fnUntraced(function*(state: FileState, _: Action<FileState, string>) {
1919
1958
  const terminal = yield* Terminal.Terminal
@@ -1922,12 +1961,14 @@ const handleFileClear = (options: FileOptionsReq) => {
1922
1961
  const figures = yield* platformFigures
1923
1962
  const currentPath = yield* resolveCurrentPath(state.path, options)
1924
1963
  const selectedPath = state.files[state.cursor]
1925
- const resolvedPath = path.resolve(currentPath, selectedPath)
1964
+ const resolvedPath = selectedPath === undefined ? currentPath : path.resolve(currentPath, selectedPath)
1926
1965
  const resolvedPathText = `${figures.pointerSmall} ${resolvedPath}`
1927
1966
  const isConfirming = showConfirmation(state.confirm)
1928
1967
  const promptText = isConfirming
1929
1968
  ? renderPrompt("(Y/n)", CONFIRM_MESSAGE, "?", figures.pointerSmall, { plain: true })
1930
- : renderPrompt("", options.message, figures.tick, figures.ellipsis, { plain: true })
1969
+ : renderPrompt(renderFileFilter(state, { plain: true }), options.message, figures.tick, figures.ellipsis, {
1970
+ plain: true
1971
+ })
1931
1972
  const filesText = isConfirming
1932
1973
  ? ""
1933
1974
  : renderFiles(state, state.files, figures, options, { plain: true })
@@ -1995,6 +2036,17 @@ const renderFileName = (file: string, isSelected: boolean, renderOptions?: Rende
1995
2036
  : file
1996
2037
  }
1997
2038
 
2039
+ const renderFileFilter = (state: FileState, renderOptions?: RenderOptions | undefined) => {
2040
+ const filterValue = state.query.length === 0
2041
+ ? renderOptions?.plain === true
2042
+ ? FILE_FILTER_PLACEHOLDER
2043
+ : Ansi.annotate(FILE_FILTER_PLACEHOLDER, Ansi.blackBright)
2044
+ : renderOptions?.plain === true
2045
+ ? state.query
2046
+ : Ansi.annotate(state.query, Ansi.combine(Ansi.underlined, Ansi.cyanBright))
2047
+ return `[${FILE_FILTER_LABEL}: ${filterValue}]`
2048
+ }
2049
+
1998
2050
  const renderFiles = (
1999
2051
  state: FileState,
2000
2052
  files: ReadonlyArray<string>,
@@ -2003,6 +2055,11 @@ const renderFiles = (
2003
2055
  renderOptions?: RenderOptions | undefined
2004
2056
  ) => {
2005
2057
  const length = files.length
2058
+ if (length === 0) {
2059
+ return renderOptions?.plain === true
2060
+ ? FILE_EMPTY_MESSAGE
2061
+ : Ansi.annotate(FILE_EMPTY_MESSAGE, Ansi.blackBright)
2062
+ }
2006
2063
  const toDisplay = entriesToDisplay(state.cursor, length, options.maxPerPage)
2007
2064
  const documents: Array<string> = []
2008
2065
  for (let index = toDisplay.startIndex; index < toDisplay.endIndex; index++) {
@@ -2019,7 +2076,7 @@ const renderFileNextFrame = Effect.fnUntraced(function*(state: FileState, option
2019
2076
  const figures = yield* platformFigures
2020
2077
  const currentPath = yield* resolveCurrentPath(state.path, options)
2021
2078
  const selectedPath = state.files[state.cursor]
2022
- const resolvedPath = path.resolve(currentPath, selectedPath)
2079
+ const resolvedPath = selectedPath === undefined ? currentPath : path.resolve(currentPath, selectedPath)
2023
2080
  const resolvedPathMsg = Ansi.annotate(figures.pointerSmall + " " + resolvedPath, Ansi.blackBright)
2024
2081
 
2025
2082
  if (showConfirmation(state.confirm)) {
@@ -2031,33 +2088,36 @@ const renderFileNextFrame = Effect.fnUntraced(function*(state: FileState, option
2031
2088
  }
2032
2089
  const leadingSymbol = Ansi.annotate(figures.tick, Ansi.green)
2033
2090
  const trailingSymbol = Ansi.annotate(figures.ellipsis, Ansi.blackBright)
2034
- const promptMsg = renderPrompt("", options.message, leadingSymbol, trailingSymbol)
2091
+ const promptMsg = renderPrompt(renderFileFilter(state), options.message, leadingSymbol, trailingSymbol)
2035
2092
  const files = renderFiles(state, state.files, figures, options)
2036
2093
  return Ansi.cursorHide + promptMsg + "\n" + resolvedPathMsg + "\n" + files
2037
2094
  })
2038
2095
 
2039
- const renderFileSubmission = Effect.fnUntraced(function*(value: string, options: FileOptionsReq) {
2096
+ const renderFileSubmission = Effect.fnUntraced(function*(state: FileState, value: string, options: FileOptionsReq) {
2040
2097
  const figures = yield* platformFigures
2041
2098
  const leadingSymbol = Ansi.annotate(figures.tick, Ansi.green)
2042
2099
  const trailingSymbol = Ansi.annotate(figures.ellipsis, Ansi.blackBright)
2043
- const promptMsg = renderPrompt("", options.message, leadingSymbol, trailingSymbol)
2100
+ const promptMsg = renderPrompt(renderFileFilter(state), options.message, leadingSymbol, trailingSymbol)
2044
2101
  return promptMsg + " " + Ansi.annotate(value, Ansi.white) + "\n"
2045
2102
  })
2046
2103
 
2047
2104
  const handleFileRender = (options: FileOptionsReq) => {
2048
2105
  return (
2049
- _: FileState,
2106
+ state: FileState,
2050
2107
  action: Action<FileState, string>
2051
2108
  ): Effect.Effect<string, never, Path.Path | FileSystem.FileSystem> => {
2052
2109
  return Action.$match(action, {
2053
2110
  Beep: () => Effect.succeed(renderBeep),
2054
2111
  NextFrame: ({ state }) => renderFileNextFrame(state, options),
2055
- Submit: ({ value }) => renderFileSubmission(value, options)
2112
+ Submit: ({ value }) => renderFileSubmission(state, value, options)
2056
2113
  })
2057
2114
  }
2058
2115
  }
2059
2116
 
2060
2117
  const processFileCursorUp = (state: FileState) => {
2118
+ if (state.files.length === 0) {
2119
+ return Effect.succeed(Action.Beep())
2120
+ }
2061
2121
  const cursor = state.cursor - 1
2062
2122
  return Effect.succeed(Action.NextFrame({
2063
2123
  state: { ...state, cursor: cursor < 0 ? state.files.length - 1 : cursor }
@@ -2065,12 +2125,36 @@ const processFileCursorUp = (state: FileState) => {
2065
2125
  }
2066
2126
 
2067
2127
  const processFileCursorDown = (state: FileState) => {
2128
+ if (state.files.length === 0) {
2129
+ return Effect.succeed(Action.Beep())
2130
+ }
2068
2131
  return Effect.succeed(Action.NextFrame({
2069
2132
  state: { ...state, cursor: (state.cursor + 1) % state.files.length }
2070
2133
  }))
2071
2134
  }
2072
2135
 
2136
+ const processFileBackspace = (state: FileState) => {
2137
+ if (state.query.length === 0) {
2138
+ return Effect.succeed(Action.Beep())
2139
+ }
2140
+ const query = state.query.slice(0, state.query.length - 1)
2141
+ return Effect.succeed(Action.NextFrame({ state: updateFileState(state, query) }))
2142
+ }
2143
+
2144
+ const processFileClear = (state: FileState) => Effect.succeed(Action.NextFrame({ state: updateFileState(state, "") }))
2145
+
2146
+ const processFileInput = (input: string, state: FileState) => {
2147
+ if (input.length === 0) {
2148
+ return Effect.succeed(Action.Beep())
2149
+ }
2150
+ const query = state.query + input
2151
+ return Effect.succeed(Action.NextFrame({ state: updateFileState(state, query) }))
2152
+ }
2153
+
2073
2154
  const processSelection = Effect.fnUntraced(function*(state: FileState, options: FileOptionsReq) {
2155
+ if (state.files.length === 0) {
2156
+ return Action.Beep()
2157
+ }
2074
2158
  const fs = yield* FileSystem.FileSystem
2075
2159
  const path = yield* Path.Path
2076
2160
  const currentPath = yield* resolveCurrentPath(state.path, options)
@@ -2097,6 +2181,8 @@ const processSelection = Effect.fnUntraced(function*(state: FileState, options:
2097
2181
  state: {
2098
2182
  cursor: 0,
2099
2183
  files,
2184
+ allFiles: files,
2185
+ query: "",
2100
2186
  path: Option.some(resolvedPath),
2101
2187
  confirm: Confirm.Hide()
2102
2188
  }
@@ -2107,6 +2193,12 @@ const processSelection = Effect.fnUntraced(function*(state: FileState, options:
2107
2193
 
2108
2194
  const handleFileProcess = (options: FileOptionsReq) => {
2109
2195
  return Effect.fnUntraced(function*(input: Terminal.UserInput, state: FileState) {
2196
+ if (input.key.ctrl && input.key.name === "u") {
2197
+ if (showConfirmation(state.confirm)) {
2198
+ return Action.Beep()
2199
+ }
2200
+ return yield* processFileClear(state)
2201
+ }
2110
2202
  switch (input.key.name) {
2111
2203
  case "k":
2112
2204
  case "up": {
@@ -2117,6 +2209,12 @@ const handleFileProcess = (options: FileOptionsReq) => {
2117
2209
  case "tab": {
2118
2210
  return yield* processFileCursorDown(state)
2119
2211
  }
2212
+ case "backspace": {
2213
+ if (showConfirmation(state.confirm)) {
2214
+ return Action.Beep()
2215
+ }
2216
+ return yield* processFileBackspace(state)
2217
+ }
2120
2218
  case "enter":
2121
2219
  case "return": {
2122
2220
  return yield* processSelection(state, options)
@@ -2133,12 +2231,14 @@ const handleFileProcess = (options: FileOptionsReq) => {
2133
2231
  state: {
2134
2232
  cursor: 0,
2135
2233
  files,
2234
+ allFiles: files,
2235
+ query: "",
2136
2236
  path: Option.some(resolvedPath),
2137
2237
  confirm: Confirm.Hide()
2138
2238
  }
2139
2239
  })
2140
2240
  }
2141
- return Action.Beep()
2241
+ return yield* processFileInput(Option.getOrElse(input.input, () => ""), state)
2142
2242
  }
2143
2243
  case "n":
2144
2244
  case "f": {
@@ -2149,10 +2249,13 @@ const handleFileProcess = (options: FileOptionsReq) => {
2149
2249
  const resolvedPath = path.resolve(currentPath, selectedPath)
2150
2250
  return Action.Submit({ value: resolvedPath })
2151
2251
  }
2152
- return Action.Beep()
2252
+ return yield* processFileInput(Option.getOrElse(input.input, () => ""), state)
2153
2253
  }
2154
2254
  default: {
2155
- return Action.Beep()
2255
+ if (showConfirmation(state.confirm)) {
2256
+ return Action.Beep()
2257
+ }
2258
+ return yield* processFileInput(Option.getOrElse(input.input, () => ""), state)
2156
2259
  }
2157
2260
  }
2158
2261
  })
@@ -3259,6 +3362,20 @@ const processTextCursorRight = (state: TextState) => {
3259
3362
  )
3260
3363
  }
3261
3364
 
3365
+ const processTextCursorStart = (state: TextState) =>
3366
+ Effect.succeed(
3367
+ Action.NextFrame({
3368
+ state: { ...state, cursor: 0, error: Option.none() }
3369
+ })
3370
+ )
3371
+
3372
+ const processTextCursorEnd = (state: TextState) =>
3373
+ Effect.succeed(
3374
+ Action.NextFrame({
3375
+ state: { ...state, cursor: state.value.length, error: Option.none() }
3376
+ })
3377
+ )
3378
+
3262
3379
  const processTab = (state: TextState, options: TextOptionsReq) => {
3263
3380
  if (state.value === options.default) {
3264
3381
  return Effect.succeed(Action.Beep())
@@ -3295,8 +3412,18 @@ const handleTextRender = (options: TextOptionsReq) => {
3295
3412
 
3296
3413
  const handleTextProcess = (options: TextOptionsReq) => {
3297
3414
  return (input: Terminal.UserInput, state: TextState) => {
3298
- if (input.key.ctrl && input.key.name === "u") {
3299
- return processTextClear(state)
3415
+ if (input.key.ctrl) {
3416
+ switch (input.key.name) {
3417
+ case "u": {
3418
+ return processTextClear(state)
3419
+ }
3420
+ case "a": {
3421
+ return processTextCursorStart(state)
3422
+ }
3423
+ case "e": {
3424
+ return processTextCursorEnd(state)
3425
+ }
3426
+ }
3300
3427
  }
3301
3428
  switch (input.key.name) {
3302
3429
  case "backspace": {
@@ -3308,6 +3435,12 @@ const handleTextProcess = (options: TextOptionsReq) => {
3308
3435
  case "right": {
3309
3436
  return processTextCursorRight(state)
3310
3437
  }
3438
+ case "home": {
3439
+ return processTextCursorStart(state)
3440
+ }
3441
+ case "end": {
3442
+ return processTextCursorEnd(state)
3443
+ }
3311
3444
  case "enter":
3312
3445
  case "return": {
3313
3446
  const value = state.value
@@ -175,8 +175,8 @@ export function getErrorSchemas(endpoint: AnyWithProps): [Schema.Top, ...Array<S
175
175
  const schemas = new Set<Schema.Top>(endpoint.error)
176
176
  for (const middleware of endpoint.middlewares) {
177
177
  const key = middleware as any as HttpApiMiddleware.AnyService
178
- if (key.error !== undefined) {
179
- schemas.add(key.error)
178
+ for (const schema of key.error) {
179
+ schemas.add(schema)
180
180
  }
181
181
  }
182
182
  return Arr.append(Array.from(schemas), BadRequestFromSchemaError)