@effect-uai/core 0.6.0 → 0.8.0

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 (89) hide show
  1. package/README.md +1 -1
  2. package/dist/{Audio-BfCTGnH3.d.mts → Audio-DPlTsNdz.d.mts} +3 -2
  3. package/dist/Audio-DPlTsNdz.d.mts.map +1 -0
  4. package/dist/{Items-pXR5BAgW.d.mts → Items-7ljaVxH_.d.mts} +12 -12
  5. package/dist/{Items-pXR5BAgW.d.mts.map → Items-7ljaVxH_.d.mts.map} +1 -1
  6. package/dist/{Tool-DP-AwKOf.d.mts → Tool-BKiieFXk.d.mts} +2 -2
  7. package/dist/{Tool-DP-AwKOf.d.mts.map → Tool-BKiieFXk.d.mts.map} +1 -1
  8. package/dist/capabilities/Capabilities.d.mts +78 -0
  9. package/dist/capabilities/Capabilities.d.mts.map +1 -0
  10. package/dist/capabilities/Capabilities.mjs +65 -0
  11. package/dist/capabilities/Capabilities.mjs.map +1 -0
  12. package/dist/domain/Audio.d.mts +1 -1
  13. package/dist/domain/Audio.mjs.map +1 -1
  14. package/dist/domain/Items.d.mts +1 -1
  15. package/dist/domain/Music.d.mts +131 -72
  16. package/dist/domain/Music.d.mts.map +1 -1
  17. package/dist/domain/Music.mjs +36 -9
  18. package/dist/domain/Music.mjs.map +1 -1
  19. package/dist/domain/Transcript.d.mts +8 -2
  20. package/dist/domain/Transcript.d.mts.map +1 -1
  21. package/dist/domain/Transcript.mjs.map +1 -1
  22. package/dist/domain/Turn.d.mts +1 -1
  23. package/dist/embedding-model/Embedding.d.mts +12 -2
  24. package/dist/embedding-model/Embedding.d.mts.map +1 -1
  25. package/dist/embedding-model/Embedding.mjs.map +1 -1
  26. package/dist/embedding-model/EmbeddingModel.d.mts +36 -23
  27. package/dist/embedding-model/EmbeddingModel.d.mts.map +1 -1
  28. package/dist/embedding-model/EmbeddingModel.mjs +20 -1
  29. package/dist/embedding-model/EmbeddingModel.mjs.map +1 -1
  30. package/dist/embedding-model/EmbeddingModel.test.mjs +15 -3
  31. package/dist/embedding-model/EmbeddingModel.test.mjs.map +1 -1
  32. package/dist/index.d.mts +7 -4
  33. package/dist/index.mjs +5 -2
  34. package/dist/language-model/LanguageModel.d.mts +2 -2
  35. package/dist/music-generator/MusicGenerator.d.mts +48 -25
  36. package/dist/music-generator/MusicGenerator.d.mts.map +1 -1
  37. package/dist/music-generator/MusicGenerator.mjs +17 -9
  38. package/dist/music-generator/MusicGenerator.mjs.map +1 -1
  39. package/dist/music-generator/MusicGenerator.test.mjs +81 -56
  40. package/dist/music-generator/MusicGenerator.test.mjs.map +1 -1
  41. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +21 -36
  42. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts.map +1 -1
  43. package/dist/speech-synthesizer/SpeechSynthesizer.mjs.map +1 -1
  44. package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +3 -3
  45. package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs.map +1 -1
  46. package/dist/testing/MockMusicGenerator.d.mts +12 -7
  47. package/dist/testing/MockMusicGenerator.d.mts.map +1 -1
  48. package/dist/testing/MockMusicGenerator.mjs +4 -3
  49. package/dist/testing/MockMusicGenerator.mjs.map +1 -1
  50. package/dist/testing/MockProvider.d.mts +1 -1
  51. package/dist/testing/MockSpeechSynthesizer.d.mts +1 -1
  52. package/dist/tool/Approval.d.mts +1 -1
  53. package/dist/tool/HistoryCheck.d.mts +1 -1
  54. package/dist/tool/Tool.d.mts +1 -1
  55. package/dist/tool/ToolResult.d.mts +1 -1
  56. package/dist/tool/Toolkit.d.mts +2 -2
  57. package/dist/tool/Toolkit.mjs +1 -1
  58. package/dist/transcriber/Transcriber.d.mts +11 -10
  59. package/dist/transcriber/Transcriber.d.mts.map +1 -1
  60. package/dist/transcriber/Transcriber.mjs.map +1 -1
  61. package/dist/transcriber/Transcriber.test.mjs +3 -3
  62. package/dist/transcriber/Transcriber.test.mjs.map +1 -1
  63. package/dist/web-search/WebSearch.d.mts +119 -0
  64. package/dist/web-search/WebSearch.d.mts.map +1 -0
  65. package/dist/web-search/WebSearch.mjs +42 -0
  66. package/dist/web-search/WebSearch.mjs.map +1 -0
  67. package/dist/web-search/WebSearchTool.d.mts +58 -0
  68. package/dist/web-search/WebSearchTool.d.mts.map +1 -0
  69. package/dist/web-search/WebSearchTool.mjs +69 -0
  70. package/dist/web-search/WebSearchTool.mjs.map +1 -0
  71. package/package.json +13 -1
  72. package/src/capabilities/Capabilities.ts +81 -0
  73. package/src/domain/Audio.ts +2 -1
  74. package/src/domain/Music.ts +142 -68
  75. package/src/domain/Transcript.ts +7 -1
  76. package/src/embedding-model/Embedding.ts +11 -0
  77. package/src/embedding-model/EmbeddingModel.test.ts +15 -4
  78. package/src/embedding-model/EmbeddingModel.ts +48 -21
  79. package/src/index.ts +3 -0
  80. package/src/music-generator/MusicGenerator.test.ts +78 -53
  81. package/src/music-generator/MusicGenerator.ts +65 -24
  82. package/src/speech-synthesizer/SpeechSynthesizer.test.ts +3 -3
  83. package/src/speech-synthesizer/SpeechSynthesizer.ts +18 -33
  84. package/src/testing/MockMusicGenerator.ts +28 -18
  85. package/src/transcriber/Transcriber.test.ts +3 -3
  86. package/src/transcriber/Transcriber.ts +10 -7
  87. package/src/web-search/WebSearch.ts +150 -0
  88. package/src/web-search/WebSearchTool.ts +106 -0
  89. package/dist/Audio-BfCTGnH3.d.mts.map +0 -1
@@ -15,12 +15,13 @@ export type CommonTranscribeRequest = {
15
15
  readonly model: string
16
16
  /** ISO-639-1 / BCP-47. Omit for autodetection (where supported). */
17
17
  readonly language?: string
18
- /**
19
- * Vocab biasing. Single-string covers OpenAI/Whisper-style prompts;
20
- * `terms[]` covers Deepgram `keyterm`, Google adaptation phrases, AWS
21
- * `vocabularyName`. Providers ignore what they don't support.
22
- */
23
- readonly prompt?: string | { readonly terms: ReadonlyArray<string> }
18
+ /** Free-form context / style hint (Whisper-style prose). OpenAI honors
19
+ * it; providers without a prompt field `warnDropped`. */
20
+ readonly prompt?: string
21
+ /** Vocabulary biasing — discrete terms to boost (names, jargon). Maps to
22
+ * Deepgram `keyterm`, ElevenLabs `keyterms`, Google `adaptation`,
23
+ * Inworld `prompts`; others `warnDropped`. */
24
+ readonly biasingTerms?: ReadonlyArray<string>
24
25
  readonly diarization?: boolean
25
26
  readonly wordTimestamps?: boolean
26
27
  }
@@ -28,7 +29,9 @@ export type CommonTranscribeRequest = {
28
29
  /**
29
30
  * Streaming-transcription request. `inputFormat` declares what the
30
31
  * bytes in the input stream will look like — providers reject
31
- * mismatches at stream startup with `AiError.InvalidRequest`.
32
+ * formats they can't ingest at stream startup with
33
+ * `AiError.Unsupported` (a per-Layer capability gap, not a wire-shape
34
+ * mismatch).
32
35
  */
33
36
  export type CommonStreamTranscribeRequest = Omit<CommonTranscribeRequest, "audio"> & {
34
37
  readonly inputFormat: AudioFormat
@@ -0,0 +1,150 @@
1
+ import { Context, type DateTime, Effect, Schema } from "effect"
2
+ import type * as AiError from "../domain/AiError.js"
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // Recency
6
+ // ---------------------------------------------------------------------------
7
+
8
+ /**
9
+ * Convenience freshness enum shared by every provider. A coarse "published
10
+ * within the last …" filter that maps onto each provider's native recency
11
+ * knob (Perplexity `search_recency_filter`, Brave `freshness`, Tavily
12
+ * `time_range`, You.com `freshness`), or onto a computed published-date
13
+ * range where a provider has no enum (Exa).
14
+ *
15
+ * For a precise window use {@link CommonSearchRequest.startDate} /
16
+ * {@link CommonSearchRequest.endDate} instead; `recency` is the sugar for
17
+ * the common "past week / past month" case.
18
+ *
19
+ * Exposed as a `Schema` so {@link "../web-search/WebSearchTool".webSearchTool}
20
+ * can hand it straight to a model as a tool parameter; the
21
+ * {@link SearchRecency} type is derived from it.
22
+ */
23
+ export const SearchRecency = Schema.Literals(["hour", "day", "week", "month", "year"])
24
+
25
+ export type SearchRecency = typeof SearchRecency.Type
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Request
29
+ // ---------------------------------------------------------------------------
30
+
31
+ /**
32
+ * Cross-provider web-search request. A field earns a place here only if
33
+ * most of the supported providers honor it and a developer would expect it
34
+ * from a generic search interface; everything provider-specific (Exa
35
+ * `type`, Tavily `searchDepth`, Brave `goggles`, pagination `offset`,
36
+ * `safeSearch`, `topic`, …) lives on that provider's typed request.
37
+ *
38
+ * Note the one structural difference from `EmbeddingModel` /
39
+ * `Transcriber`: there is no `model`. None of the pure-`/search` endpoints
40
+ * take one - what looks model-shaped on a provider (Exa `type`, Tavily
41
+ * `searchDepth`) is a mode knob, so it stays on the provider-typed request.
42
+ */
43
+ export type CommonSearchRequest = {
44
+ /** The search query. The one field every provider requires. */
45
+ readonly query: string
46
+ /**
47
+ * Upper bound on the number of results to return in this one call.
48
+ * Providers cap differently (Perplexity / Tavily / Brave at ~20, Exa /
49
+ * You.com at ~100); a value above a provider's cap is clamped by that
50
+ * provider. There is no common pagination - only two providers paginate
51
+ * and only shallowly, so `offset` is provider-typed.
52
+ */
53
+ readonly maxResults?: number
54
+ /** Restrict results to these domains (e.g. `["docs.foo.com"]`). */
55
+ readonly includeDomains?: ReadonlyArray<string>
56
+ /** Drop results from these domains. */
57
+ readonly excludeDomains?: ReadonlyArray<string>
58
+ /**
59
+ * Coarse freshness filter - see {@link SearchRecency}. Sugar over an
60
+ * explicit `startDate`; ignored where {@link startDate} is also set and
61
+ * the provider takes only one.
62
+ */
63
+ readonly recency?: SearchRecency
64
+ /** Published-after bound. The precise alternative to {@link recency}. */
65
+ readonly startDate?: DateTime.DateTime
66
+ /** Published-before bound. */
67
+ readonly endDate?: DateTime.DateTime
68
+ /** ISO 3166-1 alpha-2 country code to localize results (e.g. `"us"`). */
69
+ readonly country?: string
70
+ /** ISO 639-1 language code to bias results toward (e.g. `"en"`). */
71
+ readonly language?: string
72
+ }
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Response
76
+ // ---------------------------------------------------------------------------
77
+
78
+ /**
79
+ * One normalized web-search hit. A flat record with optionals rather than a
80
+ * tagged union: the providers differ by *which fields are present*, not by
81
+ * *kind*. The provider's untouched result object is always on `raw`, so a
82
+ * caller holding a typed provider tag can recover anything not promoted
83
+ * here (`author`, `favicon`, multi-chunk `snippets[]`, …).
84
+ */
85
+ export type SearchResult = {
86
+ /** Canonical result URL. Always present. */
87
+ readonly url: string
88
+ /** Result title / headline, when the provider returns one. */
89
+ readonly title?: string
90
+ /**
91
+ * Primary short excerpt - the one text field all providers return (under
92
+ * various wire names: `snippet`, `content`, `description`). The richer
93
+ * extract fields (`text`, `highlights`, `summary`) belong to the
94
+ * out-of-scope extract feature and stay on the provider-typed result.
95
+ */
96
+ readonly snippet?: string
97
+ /** Publication date, when the provider reports one. */
98
+ readonly publishedDate?: DateTime.DateTime
99
+ /**
100
+ * Relevance score, only from providers that rank (Exa, Tavily);
101
+ * `undefined` for providers that return an unscored list. Kept despite
102
+ * being non-universal because ranking is core to what a "search result"
103
+ * means - and `undefined` cleanly says "this backend doesn't score."
104
+ */
105
+ readonly score?: number
106
+ /** The provider-native result object, never lossy. */
107
+ readonly raw: unknown
108
+ }
109
+
110
+ export type SearchResponse = {
111
+ readonly results: ReadonlyArray<SearchResult>
112
+ /**
113
+ * The provider-native top-level response object. Cost / usage reporting
114
+ * is deliberately not modeled on this surface yet: providers disagree on
115
+ * both what (Exa returns USD, Tavily credits, Brave nothing in-body) and
116
+ * where (Brave reports cost in response headers), so it is deferred to the
117
+ * unified usage design (see plans/usage-tracking.md). Anything a provider
118
+ * reports survives here on `raw`.
119
+ */
120
+ readonly raw: unknown
121
+ }
122
+
123
+ // ---------------------------------------------------------------------------
124
+ // Service + helper
125
+ // ---------------------------------------------------------------------------
126
+
127
+ /**
128
+ * The portable web-search surface: one operation, `search`. Pure search is
129
+ * the one thing every provider does, so - unlike `SpeechSynthesizer` /
130
+ * `Transcriber` - this capability needs no marker tags; every provider
131
+ * Layer can answer.
132
+ */
133
+ export type WebSearchService = {
134
+ readonly search: (request: CommonSearchRequest) => Effect.Effect<SearchResponse, AiError.AiError>
135
+ }
136
+
137
+ /**
138
+ * Generic web-search service tag. Yield this for provider-portable code;
139
+ * yield a provider tag (`PerplexitySearch`, `ExaSearch`, …) when you need
140
+ * that provider's own knobs. A provider's `layer` registers both.
141
+ */
142
+ export class WebSearch extends Context.Service<WebSearch, WebSearchService>()(
143
+ "@betalyra/effect-uai/WebSearch",
144
+ ) {}
145
+
146
+ /** Run one web search against whichever provider Layer is in scope. */
147
+ export const search = (
148
+ request: CommonSearchRequest,
149
+ ): Effect.Effect<SearchResponse, AiError.AiError, WebSearch> =>
150
+ Effect.flatMap(WebSearch.asEffect(), (s) => s.search(request))
@@ -0,0 +1,106 @@
1
+ import { Effect, Schema } from "effect"
2
+ import * as Tool from "../tool/Tool.js"
3
+ import { search, SearchRecency, type SearchResult, WebSearch } from "./WebSearch.js"
4
+
5
+ /**
6
+ * Model-facing argument schema. Deliberately narrow: `query` plus the one
7
+ * filter that is genuinely the model's call - `recency` (it knows whether a
8
+ * question is about current events or evergreen). App-policy scoping
9
+ * (`includeDomains` / `excludeDomains`) and the `maxResults` cost ceiling
10
+ * are pinned on the constructor instead, so they are guaranteed rather than
11
+ * left to the model to remember each call.
12
+ */
13
+ const WebSearchToolArgs = Schema.Struct({
14
+ query: Schema.String,
15
+ recency: Schema.optional(SearchRecency),
16
+ })
17
+
18
+ export type WebSearchToolArgs = typeof WebSearchToolArgs.Type
19
+
20
+ /** Numbered `title / url / snippet` list - what a model reads best. */
21
+ const defaultRender = (results: ReadonlyArray<SearchResult>): string =>
22
+ results.length === 0
23
+ ? "No results found."
24
+ : results
25
+ .map((r, i) => {
26
+ const head = `${i + 1}. ${r.title ?? r.url}\n ${r.url}`
27
+ return r.snippet === undefined ? head : `${head}\n ${r.snippet}`
28
+ })
29
+ .join("\n\n")
30
+
31
+ export type WebSearchToolOptions = {
32
+ /** Tool name the model sees. Default `"web_search"`. */
33
+ readonly name?: string
34
+ /**
35
+ * App-fixed ceiling on results per call - a cost guard, not exposed to
36
+ * the model. Default `5`.
37
+ */
38
+ readonly maxResults?: number
39
+ /**
40
+ * App-fixed allowlist applied to every search. A policy scope ("only
41
+ * search our docs"), not a model knob - so it is enforced, not requested.
42
+ */
43
+ readonly includeDomains?: ReadonlyArray<string>
44
+ /** App-fixed denylist applied to every search ("never return X"). */
45
+ readonly excludeDomains?: ReadonlyArray<string>
46
+ /**
47
+ * Override how results are rendered into the model-facing string.
48
+ * Default: a numbered `title / url / snippet` list.
49
+ */
50
+ readonly render?: (results: ReadonlyArray<SearchResult>) => string
51
+ }
52
+
53
+ /**
54
+ * The canonical web-search tool. Its `R` is just `WebSearch`, so providing
55
+ * any search-provider Layer satisfies it and the model-facing contract
56
+ * (name, description, schema) is identical no matter which backend answers
57
+ * - swap `PerplexitySearch.layer` for `ExaSearch.layer` and the tool the
58
+ * model sees does not change. Drops straight into a `Toolkit` and the
59
+ * `LanguageModel` tool-calling loop.
60
+ *
61
+ * The model controls only `query` and `recency`; domain scoping and the
62
+ * result cap are app policy, fixed here on the constructor. `Output` is
63
+ * rendered text, not raw `SearchResult[]`: the model reads the list, while
64
+ * the app still gets the structured results through the normal tool-result
65
+ * channel. Pass `render` to change the format.
66
+ */
67
+ export const webSearchTool = (
68
+ options?: WebSearchToolOptions,
69
+ ): Tool.Tool<string, WebSearchToolArgs, string, WebSearch> => {
70
+ const name = options?.name ?? "web_search"
71
+ const render = options?.render ?? defaultRender
72
+ const maxResults = options?.maxResults ?? 5
73
+ const includeDomains = options?.includeDomains
74
+ const excludeDomains = options?.excludeDomains
75
+ return Tool.make({
76
+ name,
77
+ description:
78
+ "Search the web for current information. Returns ranked results with titles, URLs, and snippets.",
79
+ inputSchema: Tool.fromEffectSchema(WebSearchToolArgs),
80
+ // The call is wrapped in a tracing span so agent traces show the tool
81
+ // invocation (query, filters, result count) nested under the model turn.
82
+ // `withSpan` is a no-op until a Tracer is installed, so this is free by
83
+ // default and adds nothing to the requirements.
84
+ run: (args) =>
85
+ search({
86
+ query: args.query,
87
+ maxResults,
88
+ ...(args.recency !== undefined && { recency: args.recency }),
89
+ ...(includeDomains !== undefined && { includeDomains }),
90
+ ...(excludeDomains !== undefined && { excludeDomains }),
91
+ }).pipe(
92
+ Effect.tap((r) => Effect.annotateCurrentSpan("web_search.result_count", r.results.length)),
93
+ Effect.map((r) => render(r.results)),
94
+ Effect.withSpan(name, {
95
+ kind: "client",
96
+ attributes: {
97
+ "web_search.query": args.query,
98
+ "web_search.max_results": maxResults,
99
+ ...(args.recency !== undefined && { "web_search.recency": args.recency }),
100
+ ...(includeDomains !== undefined && { "web_search.include_domains": includeDomains }),
101
+ ...(excludeDomains !== undefined && { "web_search.exclude_domains": excludeDomains }),
102
+ },
103
+ }),
104
+ ),
105
+ })
106
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"Audio-BfCTGnH3.d.mts","names":[],"sources":["../src/domain/Audio.ts"],"mappings":";;;;;;;;;;;;;;KAUY,aAAA;;;;;AAAZ;;;;;KA0BY,WAAA,GACR,QAAA,CAAS,aAAA,IACT,WAAA,CAAY,aAAA,IACZ,UAAA,CAAW,aAAA;AAAA,cAEF,UAAA,GAAc,CAAA,EAAG,WAAA,KAAc,CAAA,IAAK,QAAA,CAAS,aAAA;AAAA,cAC7C,aAAA,GAAiB,CAAA,EAAG,WAAA,KAAc,CAAA,IAAK,WAAA,CAAY,aAAA;AAAA,cAEnD,YAAA,GAAgB,CAAA,EAAG,WAAA,KAAc,CAAA,IAAK,UAAA,CAAW,aAAA;;;;;;;KAQlD,WAAA;EAAA,SACD,SAAA;EAAA,SACA,QAAA;EAAA,SAUA,UAAA,wDA1BK;EAAA,SA4BL,OAAA;EAAA,SACA,QAAA;AAAA;;AA1BX;;;;KAkCY,UAAA;EAAA,SACD,KAAA,EAAO,UAAA;AAAA;;;;;KAON,SAAA;EAAA,SACD,MAAA,EAAQ,WAAA;EAAA,SACR,KAAA,EAAO,UAAA;EAAA,SACP,eAAA;AAAA"}