@effect-uai/core 0.8.0 → 0.10.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.
- package/dist/{AiError-CAX_48RU.d.mts → AiError-Bf1pq4BB.d.mts} +9 -4
- package/dist/{AiError-CAX_48RU.d.mts.map → AiError-Bf1pq4BB.d.mts.map} +1 -1
- package/dist/{Audio-DPlTsNdz.d.mts → Audio-CR7CSljJ.d.mts} +1 -1
- package/dist/{Audio-DPlTsNdz.d.mts.map → Audio-CR7CSljJ.d.mts.map} +1 -1
- package/dist/{Image-HNmMpMTh.d.mts → Image-BZmKfIdq.d.mts} +1 -1
- package/dist/{Image-HNmMpMTh.d.mts.map → Image-BZmKfIdq.d.mts.map} +1 -1
- package/dist/{Items-7ljaVxH_.d.mts → Items-BnIduEV2.d.mts} +7 -7
- package/dist/{Items-7ljaVxH_.d.mts.map → Items-BnIduEV2.d.mts.map} +1 -1
- package/dist/Loop-D8nWpowc.d.mts +173 -0
- package/dist/Loop-D8nWpowc.d.mts.map +1 -0
- package/dist/{SandboxError-BvKypkJm.d.mts → SandboxError-CIrVDOMV.d.mts} +2 -2
- package/dist/{SandboxError-BvKypkJm.d.mts.map → SandboxError-CIrVDOMV.d.mts.map} +1 -1
- package/dist/{StructuredFormat-BbN4dosH.d.mts → StructuredFormat-DHaUJBdd.d.mts} +2 -2
- package/dist/{StructuredFormat-BbN4dosH.d.mts.map → StructuredFormat-DHaUJBdd.d.mts.map} +1 -1
- package/dist/Tool-2lC_XMth.d.mts +284 -0
- package/dist/Tool-2lC_XMth.d.mts.map +1 -0
- package/dist/ToolEvent-cWZ2BnbS.d.mts +151 -0
- package/dist/ToolEvent-cWZ2BnbS.d.mts.map +1 -0
- package/dist/ToolResult-CL4hK_9z.d.mts +157 -0
- package/dist/ToolResult-CL4hK_9z.d.mts.map +1 -0
- package/dist/Toolkit-Dr9l_hNy.d.mts +196 -0
- package/dist/Toolkit-Dr9l_hNy.d.mts.map +1 -0
- package/dist/browser/Browser.d.mts +190 -0
- package/dist/browser/Browser.d.mts.map +1 -0
- package/dist/browser/Browser.mjs +34 -0
- package/dist/browser/Browser.mjs.map +1 -0
- package/dist/browser/BrowserError.d.mts +141 -0
- package/dist/browser/BrowserError.d.mts.map +1 -0
- package/dist/browser/BrowserError.mjs +89 -0
- package/dist/browser/BrowserError.mjs.map +1 -0
- package/dist/browser/BrowserTool.d.mts +58 -0
- package/dist/browser/BrowserTool.d.mts.map +1 -0
- package/dist/browser/BrowserTool.mjs +86 -0
- package/dist/browser/BrowserTool.mjs.map +1 -0
- package/dist/{dist-DV5ISja1.mjs → dist-B04dlFHg.mjs} +3 -3
- package/dist/{dist-DV5ISja1.mjs.map → dist-B04dlFHg.mjs.map} +1 -1
- package/dist/dist-DWgRbDN3.mjs +140 -0
- package/dist/dist-DWgRbDN3.mjs.map +1 -0
- package/dist/domain/AiError.d.mts +2 -2
- package/dist/domain/AiError.mjs +22 -3
- package/dist/domain/AiError.mjs.map +1 -1
- package/dist/domain/Audio.d.mts +1 -1
- package/dist/domain/Image.d.mts +1 -1
- package/dist/domain/Items.d.mts +1 -1
- package/dist/domain/Music.d.mts +1 -1
- package/dist/domain/Turn.d.mts +4 -4
- package/dist/domain/Turn.test.mjs +1 -1
- package/dist/embedding-model/Embedding.d.mts +1 -1
- package/dist/embedding-model/EmbeddingModel.d.mts +1 -1
- package/dist/embedding-model/EmbeddingModel.test.mjs +1 -1
- package/dist/embedding-model/EmbeddingModel.test.mjs.map +1 -1
- package/dist/index.d.mts +19 -13
- package/dist/index.mjs +15 -9
- package/dist/language-model/LanguageModel.d.mts +10 -5
- package/dist/language-model/LanguageModel.d.mts.map +1 -1
- package/dist/language-model/LanguageModel.mjs.map +1 -1
- package/dist/language-model/LanguageModel.test.mjs +1 -1
- package/dist/loop/Loop.d.mts +2 -141
- package/dist/loop/Loop.mjs +16 -20
- package/dist/loop/Loop.mjs.map +1 -1
- package/dist/loop/Loop.test.mjs +18 -1
- package/dist/loop/Loop.test.mjs.map +1 -1
- package/dist/{magic-string.es-BgIV5Mu3.mjs → magic-string.es-Dk-MMA0F.mjs} +1 -1
- package/dist/{magic-string.es-BgIV5Mu3.mjs.map → magic-string.es-Dk-MMA0F.mjs.map} +1 -1
- package/dist/music-generator/MusicGenerator.d.mts +2 -2
- package/dist/music-generator/MusicGenerator.test.mjs +1 -1
- package/dist/observability/Metrics.d.mts +116 -28
- package/dist/observability/Metrics.d.mts.map +1 -1
- package/dist/observability/Metrics.mjs +250 -38
- package/dist/observability/Metrics.mjs.map +1 -1
- package/dist/observability/Metrics.test.d.mts +1 -0
- package/dist/observability/Metrics.test.mjs +187 -0
- package/dist/observability/Metrics.test.mjs.map +1 -0
- package/dist/observability/Telemetry.d.mts +37 -0
- package/dist/observability/Telemetry.d.mts.map +1 -0
- package/dist/observability/Telemetry.mjs +57 -0
- package/dist/observability/Telemetry.mjs.map +1 -0
- package/dist/observability/Telemetry.test.d.mts +1 -0
- package/dist/observability/Telemetry.test.mjs +92 -0
- package/dist/observability/Telemetry.test.mjs.map +1 -0
- package/dist/retry/Retry.d.mts +2 -2
- package/dist/retry/Retry.test.mjs +1 -1
- package/dist/sandbox/Sandbox.d.mts +1 -1
- package/dist/sandbox/SandboxError.d.mts +1 -1
- package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +2 -2
- package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +1 -1
- package/dist/streaming/JSONL.d.mts +1 -1
- package/dist/streaming/JSONL.test.mjs +1 -1
- package/dist/streaming/SSE.test.mjs +1 -1
- package/dist/structured-format/StructuredFormat.d.mts +1 -1
- package/dist/structured-format/StructuredFormat.mjs.map +1 -1
- package/dist/structured-format/StructuredFormat.test.mjs +1 -1
- package/dist/testing/MockMusicGenerator.d.mts +1 -1
- package/dist/testing/MockProvider.d.mts +1 -1
- package/dist/testing/MockSpeechSynthesizer.d.mts +1 -1
- package/dist/tool/Approval.d.mts +19 -7
- package/dist/tool/Approval.d.mts.map +1 -1
- package/dist/tool/Approval.mjs +18 -11
- package/dist/tool/Approval.mjs.map +1 -1
- package/dist/tool/Approval.test.mjs +80 -40
- package/dist/tool/Approval.test.mjs.map +1 -1
- package/dist/tool/HistoryCheck.d.mts +2 -2
- package/dist/tool/Tool.d.mts +2 -2
- package/dist/tool/Tool.mjs +190 -36
- package/dist/tool/Tool.mjs.map +1 -1
- package/dist/tool/Tool.test.mjs +41 -3
- package/dist/tool/Tool.test.mjs.map +1 -1
- package/dist/tool/ToolEvent.d.mts +2 -151
- package/dist/tool/ToolEvent.mjs +5 -0
- package/dist/tool/ToolEvent.mjs.map +1 -1
- package/dist/tool/ToolResult.d.mts +2 -138
- package/dist/tool/ToolResult.mjs +32 -8
- package/dist/tool/ToolResult.mjs.map +1 -1
- package/dist/tool/Toolkit.d.mts +2 -73
- package/dist/tool/Toolkit.mjs +153 -35
- package/dist/tool/Toolkit.mjs.map +1 -1
- package/dist/tool/Toolkit.test.mjs +271 -11
- package/dist/tool/Toolkit.test.mjs.map +1 -1
- package/dist/transcriber/Transcriber.d.mts +2 -2
- package/dist/transcriber/Transcriber.test.mjs +1 -1
- package/dist/web-read/WebRead.d.mts +44 -0
- package/dist/web-read/WebRead.d.mts.map +1 -0
- package/dist/web-read/WebRead.mjs +18 -0
- package/dist/web-read/WebRead.mjs.map +1 -0
- package/dist/web-read/WebReadTool.d.mts +57 -0
- package/dist/web-read/WebReadTool.d.mts.map +1 -0
- package/dist/web-read/WebReadTool.mjs +59 -0
- package/dist/web-read/WebReadTool.mjs.map +1 -0
- package/dist/web-search/WebSearch.d.mts +1 -1
- package/dist/web-search/WebSearchTool.d.mts +12 -6
- package/dist/web-search/WebSearchTool.d.mts.map +1 -1
- package/dist/web-search/WebSearchTool.mjs +10 -5
- package/dist/web-search/WebSearchTool.mjs.map +1 -1
- package/package.json +21 -1
- package/src/browser/Browser.ts +268 -0
- package/src/browser/BrowserError.ts +145 -0
- package/src/browser/BrowserTool.ts +161 -0
- package/src/domain/AiError.ts +27 -1
- package/src/embedding-model/EmbeddingModel.test.ts +4 -0
- package/src/index.ts +6 -0
- package/src/language-model/LanguageModel.ts +7 -2
- package/src/loop/Loop.test.ts +22 -0
- package/src/loop/Loop.ts +27 -4
- package/src/observability/Metrics.test.ts +266 -0
- package/src/observability/Metrics.ts +505 -65
- package/src/observability/Telemetry.test.ts +123 -0
- package/src/observability/Telemetry.ts +96 -0
- package/src/structured-format/StructuredFormat.ts +4 -0
- package/src/tool/Approval.test.ts +77 -26
- package/src/tool/Approval.ts +72 -27
- package/src/tool/Tool.test.ts +55 -2
- package/src/tool/Tool.ts +335 -70
- package/src/tool/ToolEvent.ts +5 -0
- package/src/tool/ToolResult.ts +36 -9
- package/src/tool/Toolkit.test.ts +339 -9
- package/src/tool/Toolkit.ts +470 -120
- package/src/web-read/WebRead.ts +70 -0
- package/src/web-read/WebReadTool.ts +96 -0
- package/src/web-search/WebSearchTool.ts +11 -5
- package/dist/Tool-BKiieFXk.d.mts +0 -102
- package/dist/Tool-BKiieFXk.d.mts.map +0 -1
- package/dist/loop/Loop.d.mts.map +0 -1
- package/dist/tool/ToolEvent.d.mts.map +0 -1
- package/dist/tool/ToolResult.d.mts.map +0 -1
- package/dist/tool/Toolkit.d.mts.map +0 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
|
|
2
|
+
import { isMetricEvent } from "./Metrics.mjs";
|
|
3
|
+
import { Effect, Layer, Match, Metric, Stream } from "effect";
|
|
4
|
+
import { OtlpMetrics, OtlpSerialization } from "effect/unstable/observability";
|
|
5
|
+
//#region src/observability/Telemetry.ts
|
|
6
|
+
var Telemetry_exports = /* @__PURE__ */ __exportAll({
|
|
7
|
+
layerOtlp: () => layerOtlp,
|
|
8
|
+
record: () => record
|
|
9
|
+
});
|
|
10
|
+
/**
|
|
11
|
+
* Histogram bucket boundaries used for every histogram measurement (the only
|
|
12
|
+
* built-in one is throughput). Custom histogram metrics share these; a metric
|
|
13
|
+
* that needs bespoke boundaries can update its own instrument directly instead
|
|
14
|
+
* of routing through `record`.
|
|
15
|
+
*/
|
|
16
|
+
const histogramBoundaries = Metric.exponentialBoundaries({
|
|
17
|
+
start: 1,
|
|
18
|
+
factor: 2,
|
|
19
|
+
count: 16
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Build the effect `Metric` instrument a measurement maps to. Dispatches on
|
|
23
|
+
* `kind`, never on the event `_tag`, so a new built-in or custom metric needs
|
|
24
|
+
* no change here. Instruments dedupe by name in the global registry, so a fresh
|
|
25
|
+
* instance per call accumulates onto the same series (no cache needed).
|
|
26
|
+
*/
|
|
27
|
+
const buildInstrument = (m) => Match.value(m.kind).pipe(Match.when("counter", () => Metric.counter(m.name, { incremental: true })), Match.when("histogram", () => Metric.histogram(m.name, { boundaries: histogramBoundaries })), Match.when("timer", () => Metric.timer(m.name)), Match.when("gauge", () => Metric.gauge(m.name)), Match.exhaustive);
|
|
28
|
+
const mergeAttributes = (base, extra) => base === void 0 ? extra : extra === void 0 ? base : {
|
|
29
|
+
...base,
|
|
30
|
+
...extra
|
|
31
|
+
};
|
|
32
|
+
const recordEvent = (ev, baseAttributes) => Effect.forEach(ev.measurements, (m) => {
|
|
33
|
+
const attributes = mergeAttributes(baseAttributes, m.attributes);
|
|
34
|
+
const base = buildInstrument(m);
|
|
35
|
+
const instrument = attributes === void 0 ? base : Metric.withAttributes(base, attributes);
|
|
36
|
+
return Metric.update(instrument, m.value);
|
|
37
|
+
}, { discard: true });
|
|
38
|
+
/**
|
|
39
|
+
* Record every measurement on every metric event into effect `Metric`
|
|
40
|
+
* instruments, then pass events through unchanged. Matches events structurally
|
|
41
|
+
* (`isMetricEvent`), so it records built-in and custom metrics alike, and
|
|
42
|
+
* dispatches on `measurement.kind`, never on the event tag. Sits mid-pipeline
|
|
43
|
+
* before the transport. Provide an OTLP (or Prometheus) layer to export the
|
|
44
|
+
* instruments it populates.
|
|
45
|
+
*/
|
|
46
|
+
const record = (options) => (self) => self.pipe(Stream.tap((ev) => isMetricEvent(ev) ? recordEvent(ev, options?.attributes) : Effect.void));
|
|
47
|
+
/**
|
|
48
|
+
* Convenience layer that exports recorded metrics over OTLP. Provides the JSON
|
|
49
|
+
* serializer and leaves `HttpClient` as a requirement, so the platform's client
|
|
50
|
+
* (FetchHttpClient, NodeHttpClient, ...) is provided at the edge. This keeps
|
|
51
|
+
* the library platform-independent.
|
|
52
|
+
*/
|
|
53
|
+
const layerOtlp = (options) => OtlpMetrics.layer(options).pipe(Layer.provide(OtlpSerialization.layerJson));
|
|
54
|
+
//#endregion
|
|
55
|
+
export { layerOtlp, record, Telemetry_exports as t };
|
|
56
|
+
|
|
57
|
+
//# sourceMappingURL=Telemetry.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Telemetry.mjs","names":[],"sources":["../../src/observability/Telemetry.ts"],"sourcesContent":["import { Duration, Effect, Layer, Match, Metric, Stream } from \"effect\"\nimport type { HttpClient } from \"effect/unstable/http\"\nimport { OtlpMetrics, OtlpSerialization } from \"effect/unstable/observability\"\nimport { isMetricEvent, type Measurement, type MetricEvent } from \"./Metrics.js\"\n\n// ---------------------------------------------------------------------------\n// The agnostic recorder\n// ---------------------------------------------------------------------------\n\n/**\n * Histogram bucket boundaries used for every histogram measurement (the only\n * built-in one is throughput). Custom histogram metrics share these; a metric\n * that needs bespoke boundaries can update its own instrument directly instead\n * of routing through `record`.\n */\nconst histogramBoundaries = Metric.exponentialBoundaries({ start: 1, factor: 2, count: 16 })\n\n/**\n * Build the effect `Metric` instrument a measurement maps to. Dispatches on\n * `kind`, never on the event `_tag`, so a new built-in or custom metric needs\n * no change here. Instruments dedupe by name in the global registry, so a fresh\n * instance per call accumulates onto the same series (no cache needed).\n */\nconst buildInstrument = (m: Measurement): Metric.Metric<any, any> =>\n Match.value(m.kind).pipe(\n Match.when(\"counter\", () => Metric.counter(m.name, { incremental: true })),\n Match.when(\"histogram\", () => Metric.histogram(m.name, { boundaries: histogramBoundaries })),\n Match.when(\"timer\", () => Metric.timer(m.name)),\n Match.when(\"gauge\", () => Metric.gauge(m.name)),\n Match.exhaustive,\n )\n\nconst mergeAttributes = (\n base: Readonly<Record<string, string>> | undefined,\n extra: Readonly<Record<string, string>> | undefined,\n): Readonly<Record<string, string>> | undefined =>\n base === undefined ? extra : extra === undefined ? base : { ...base, ...extra }\n\nconst recordEvent = (\n ev: MetricEvent,\n baseAttributes: Readonly<Record<string, string>> | undefined,\n): Effect.Effect<void> =>\n Effect.forEach(\n ev.measurements,\n (m) => {\n const attributes = mergeAttributes(baseAttributes, m.attributes)\n const base = buildInstrument(m)\n const instrument = attributes === undefined ? base : Metric.withAttributes(base, attributes)\n return Metric.update(instrument, m.value)\n },\n { discard: true },\n )\n\nexport type RecordOptions = {\n /** Attributes applied to every recorded metric, e.g. `{ model, provider }`. */\n readonly attributes?: Readonly<Record<string, string>>\n}\n\n/**\n * Record every measurement on every metric event into effect `Metric`\n * instruments, then pass events through unchanged. Matches events structurally\n * (`isMetricEvent`), so it records built-in and custom metrics alike, and\n * dispatches on `measurement.kind`, never on the event tag. Sits mid-pipeline\n * before the transport. Provide an OTLP (or Prometheus) layer to export the\n * instruments it populates.\n */\nexport const record =\n (options?: RecordOptions) =>\n <A, E, R>(self: Stream.Stream<A, E, R>): Stream.Stream<A, E, R> =>\n self.pipe(\n Stream.tap((ev) => (isMetricEvent(ev) ? recordEvent(ev, options?.attributes) : Effect.void)),\n )\n\n// ---------------------------------------------------------------------------\n// OTLP wiring\n// ---------------------------------------------------------------------------\n\nexport type LayerOtlpOptions = {\n readonly url: string\n readonly resource?: {\n readonly serviceName?: string\n readonly serviceVersion?: string\n }\n readonly exportInterval?: Duration.Input\n}\n\n/**\n * Convenience layer that exports recorded metrics over OTLP. Provides the JSON\n * serializer and leaves `HttpClient` as a requirement, so the platform's client\n * (FetchHttpClient, NodeHttpClient, ...) is provided at the edge. This keeps\n * the library platform-independent.\n */\nexport const layerOtlp = (\n options: LayerOtlpOptions,\n): Layer.Layer<never, never, HttpClient.HttpClient> =>\n OtlpMetrics.layer(options).pipe(Layer.provide(OtlpSerialization.layerJson))\n"],"mappings":";;;;;;;;;;;;;;;AAeA,MAAM,sBAAsB,OAAO,sBAAsB;CAAE,OAAO;CAAG,QAAQ;CAAG,OAAO;CAAI,CAAC;;;;;;;AAQ5F,MAAM,mBAAmB,MACvB,MAAM,MAAM,EAAE,KAAK,CAAC,KAClB,MAAM,KAAK,iBAAiB,OAAO,QAAQ,EAAE,MAAM,EAAE,aAAa,MAAM,CAAC,CAAC,EAC1E,MAAM,KAAK,mBAAmB,OAAO,UAAU,EAAE,MAAM,EAAE,YAAY,qBAAqB,CAAC,CAAC,EAC5F,MAAM,KAAK,eAAe,OAAO,MAAM,EAAE,KAAK,CAAC,EAC/C,MAAM,KAAK,eAAe,OAAO,MAAM,EAAE,KAAK,CAAC,EAC/C,MAAM,WACP;AAEH,MAAM,mBACJ,MACA,UAEA,SAAS,KAAA,IAAY,QAAQ,UAAU,KAAA,IAAY,OAAO;CAAE,GAAG;CAAM,GAAG;CAAO;AAEjF,MAAM,eACJ,IACA,mBAEA,OAAO,QACL,GAAG,eACF,MAAM;CACL,MAAM,aAAa,gBAAgB,gBAAgB,EAAE,WAAW;CAChE,MAAM,OAAO,gBAAgB,EAAE;CAC/B,MAAM,aAAa,eAAe,KAAA,IAAY,OAAO,OAAO,eAAe,MAAM,WAAW;AAC5F,QAAO,OAAO,OAAO,YAAY,EAAE,MAAM;GAE3C,EAAE,SAAS,MAAM,CAClB;;;;;;;;;AAeH,MAAa,UACV,aACS,SACR,KAAK,KACH,OAAO,KAAK,OAAQ,cAAc,GAAG,GAAG,YAAY,IAAI,SAAS,WAAW,GAAG,OAAO,KAAM,CAC7F;;;;;;;AAqBL,MAAa,aACX,YAEA,YAAY,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,kBAAkB,UAAU,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { makeEvent } from "./Metrics.mjs";
|
|
2
|
+
import { layerOtlp, record } from "./Telemetry.mjs";
|
|
3
|
+
import { a as describe, n as globalExpect } from "../dist-B04dlFHg.mjs";
|
|
4
|
+
import { t as it } from "../dist-DWgRbDN3.mjs";
|
|
5
|
+
import { Array, Duration, Effect, Layer, Metric, Option, Ref, Stream } from "effect";
|
|
6
|
+
import { HttpClient, HttpClientResponse } from "effect/unstable/http";
|
|
7
|
+
//#region src/observability/Telemetry.test.ts
|
|
8
|
+
const event = (measurements) => makeEvent({
|
|
9
|
+
_tag: "Test",
|
|
10
|
+
turnIndex: 0,
|
|
11
|
+
measurements
|
|
12
|
+
});
|
|
13
|
+
const isolated = (self) => Effect.provideService(self, Metric.MetricRegistry, /* @__PURE__ */ new Map());
|
|
14
|
+
const findById = (snap, id) => Option.getOrThrow(Array.findFirst(snap, (s) => s.id === id));
|
|
15
|
+
describe("record", () => {
|
|
16
|
+
it.effect("accumulates a counter measurement across events", () => isolated(Effect.gen(function* () {
|
|
17
|
+
const ev = event([{
|
|
18
|
+
name: "rec_counter",
|
|
19
|
+
kind: "counter",
|
|
20
|
+
value: 5
|
|
21
|
+
}]);
|
|
22
|
+
yield* Stream.make(ev, ev).pipe(record(), Stream.runDrain);
|
|
23
|
+
const c = findById(yield* Metric.snapshot, "rec_counter");
|
|
24
|
+
globalExpect(c.type).toBe("Counter");
|
|
25
|
+
globalExpect(c.state.count).toBe(10);
|
|
26
|
+
})));
|
|
27
|
+
it.effect("records a timer measurement as a histogram observation", () => isolated(Effect.gen(function* () {
|
|
28
|
+
const ev = event([{
|
|
29
|
+
name: "rec_timer",
|
|
30
|
+
kind: "timer",
|
|
31
|
+
value: Duration.millis(100)
|
|
32
|
+
}]);
|
|
33
|
+
yield* Stream.make(ev).pipe(record(), Stream.runDrain);
|
|
34
|
+
const h = findById(yield* Metric.snapshot, "rec_timer");
|
|
35
|
+
globalExpect(h.type).toBe("Histogram");
|
|
36
|
+
globalExpect(h.state.count).toBe(1);
|
|
37
|
+
})));
|
|
38
|
+
it.effect("applies base attributes to every recorded metric", () => isolated(Effect.gen(function* () {
|
|
39
|
+
const ev = event([{
|
|
40
|
+
name: "rec_attr",
|
|
41
|
+
kind: "counter",
|
|
42
|
+
value: 1
|
|
43
|
+
}]);
|
|
44
|
+
yield* Stream.make(ev).pipe(record({ attributes: { model: "test-model" } }), Stream.runDrain);
|
|
45
|
+
globalExpect(findById(yield* Metric.snapshot, "rec_attr").attributes).toMatchObject({ model: "test-model" });
|
|
46
|
+
})));
|
|
47
|
+
it.effect("records a custom metric with a novel name, unchanged recorder", () => isolated(Effect.gen(function* () {
|
|
48
|
+
const custom = makeEvent({
|
|
49
|
+
_tag: "ToolLatency",
|
|
50
|
+
turnIndex: 0,
|
|
51
|
+
measurements: [{
|
|
52
|
+
name: "myapp_tool_latency_total",
|
|
53
|
+
kind: "counter",
|
|
54
|
+
value: 3
|
|
55
|
+
}]
|
|
56
|
+
});
|
|
57
|
+
yield* Stream.make(custom).pipe(record(), Stream.runDrain);
|
|
58
|
+
globalExpect(findById(yield* Metric.snapshot, "myapp_tool_latency_total").state.count).toBe(3);
|
|
59
|
+
})));
|
|
60
|
+
it.effect("passes non-metric elements through untouched", () => isolated(Effect.gen(function* () {
|
|
61
|
+
globalExpect(yield* Stream.make("a", "b", "c").pipe(record(), Stream.runCollect)).toEqual([
|
|
62
|
+
"a",
|
|
63
|
+
"b",
|
|
64
|
+
"c"
|
|
65
|
+
]);
|
|
66
|
+
})));
|
|
67
|
+
});
|
|
68
|
+
const bodyToString = (body) => body._tag === "Uint8Array" ? new TextDecoder().decode(body.body) : "";
|
|
69
|
+
describe("layerOtlp", () => {
|
|
70
|
+
it.effect("exports recorded metrics over OTLP via a mock HttpClient", () => isolated(Effect.gen(function* () {
|
|
71
|
+
const bodies = yield* Ref.make([]);
|
|
72
|
+
const mockClient = HttpClient.make((request) => Ref.update(bodies, (b) => [...b, bodyToString(request.body)]).pipe(Effect.as(HttpClientResponse.fromWeb(request, new Response("{}", { status: 200 })))));
|
|
73
|
+
const clientLayer = Layer.succeed(HttpClient.HttpClient, mockClient);
|
|
74
|
+
const ev = event([{
|
|
75
|
+
name: "otlp_export_counter",
|
|
76
|
+
kind: "counter",
|
|
77
|
+
value: 3
|
|
78
|
+
}]);
|
|
79
|
+
yield* Stream.make(ev).pipe(record(), Stream.runDrain, Effect.provide(layerOtlp({
|
|
80
|
+
url: "http://localhost:4318/v1/metrics",
|
|
81
|
+
resource: { serviceName: "test-service" },
|
|
82
|
+
exportInterval: "10 minutes"
|
|
83
|
+
}).pipe(Layer.provide(clientLayer))));
|
|
84
|
+
const captured = yield* Ref.get(bodies);
|
|
85
|
+
globalExpect(captured.length).toBeGreaterThanOrEqual(1);
|
|
86
|
+
globalExpect(captured.join("\n")).toContain("otlp_export_counter");
|
|
87
|
+
})));
|
|
88
|
+
});
|
|
89
|
+
//#endregion
|
|
90
|
+
export {};
|
|
91
|
+
|
|
92
|
+
//# sourceMappingURL=Telemetry.test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Telemetry.test.mjs","names":["Arr"],"sources":["../../src/observability/Telemetry.test.ts"],"sourcesContent":["import { describe, it } from \"@effect/vitest\"\nimport { Array as Arr, Duration, Effect, Layer, Metric, Option, Ref, Stream } from \"effect\"\nimport { type HttpBody, HttpClient, HttpClientResponse } from \"effect/unstable/http\"\nimport { expect } from \"vitest\"\nimport { type Measurement, makeEvent } from \"./Metrics.js\"\nimport { layerOtlp, record } from \"./Telemetry.js\"\n\nconst event = (measurements: ReadonlyArray<Measurement>) =>\n makeEvent({ _tag: \"Test\", turnIndex: 0, measurements })\n\n// Each test gets a fresh registry so instruments do not leak across tests.\nconst isolated = <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>\n Effect.provideService(self, Metric.MetricRegistry, new Map())\n\nconst findById = (snap: ReadonlyArray<Metric.Metric.Snapshot>, id: string) =>\n Option.getOrThrow(Arr.findFirst(snap, (s) => s.id === id))\n\ndescribe(\"record\", () => {\n it.effect(\"accumulates a counter measurement across events\", () =>\n isolated(\n Effect.gen(function* () {\n const ev = event([{ name: \"rec_counter\", kind: \"counter\", value: 5 }])\n yield* Stream.make(ev, ev).pipe(record(), Stream.runDrain)\n const snap = yield* Metric.snapshot\n const c = findById(snap, \"rec_counter\")\n expect(c.type).toBe(\"Counter\")\n expect((c.state as { readonly count: number }).count).toBe(10)\n }),\n ),\n )\n\n it.effect(\"records a timer measurement as a histogram observation\", () =>\n isolated(\n Effect.gen(function* () {\n const ev = event([{ name: \"rec_timer\", kind: \"timer\", value: Duration.millis(100) }])\n yield* Stream.make(ev).pipe(record(), Stream.runDrain)\n const snap = yield* Metric.snapshot\n const h = findById(snap, \"rec_timer\")\n expect(h.type).toBe(\"Histogram\")\n expect((h.state as { readonly count: number }).count).toBe(1)\n }),\n ),\n )\n\n it.effect(\"applies base attributes to every recorded metric\", () =>\n isolated(\n Effect.gen(function* () {\n const ev = event([{ name: \"rec_attr\", kind: \"counter\", value: 1 }])\n yield* Stream.make(ev).pipe(\n record({ attributes: { model: \"test-model\" } }),\n Stream.runDrain,\n )\n const snap = yield* Metric.snapshot\n const c = findById(snap, \"rec_attr\")\n expect(c.attributes).toMatchObject({ model: \"test-model\" })\n }),\n ),\n )\n\n it.effect(\"records a custom metric with a novel name, unchanged recorder\", () =>\n isolated(\n Effect.gen(function* () {\n // A user-defined event the recorder has never seen: still exported.\n const custom = makeEvent({\n _tag: \"ToolLatency\",\n turnIndex: 0,\n measurements: [{ name: \"myapp_tool_latency_total\", kind: \"counter\", value: 3 }],\n })\n yield* Stream.make(custom).pipe(record(), Stream.runDrain)\n const snap = yield* Metric.snapshot\n const c = findById(snap, \"myapp_tool_latency_total\")\n expect((c.state as { readonly count: number }).count).toBe(3)\n }),\n ),\n )\n\n it.effect(\"passes non-metric elements through untouched\", () =>\n isolated(\n Effect.gen(function* () {\n const out = yield* Stream.make(\"a\", \"b\", \"c\").pipe(record(), Stream.runCollect)\n expect(out).toEqual([\"a\", \"b\", \"c\"])\n }),\n ),\n )\n})\n\nconst bodyToString = (body: HttpBody.HttpBody): string =>\n body._tag === \"Uint8Array\" ? new TextDecoder().decode(body.body) : \"\"\n\ndescribe(\"layerOtlp\", () => {\n it.effect(\"exports recorded metrics over OTLP via a mock HttpClient\", () =>\n isolated(\n Effect.gen(function* () {\n const bodies = yield* Ref.make<ReadonlyArray<string>>([])\n const mockClient = HttpClient.make((request) =>\n Ref.update(bodies, (b) => [...b, bodyToString(request.body)]).pipe(\n Effect.as(HttpClientResponse.fromWeb(request, new Response(\"{}\", { status: 200 }))),\n ),\n )\n const clientLayer = Layer.succeed(HttpClient.HttpClient, mockClient)\n const ev = event([{ name: \"otlp_export_counter\", kind: \"counter\", value: 3 }])\n\n // Provide the OTLP layer scoped to the recording program. The exporter\n // flushes on scope close (interval is long, so we rely on the flush).\n yield* Stream.make(ev).pipe(\n record(),\n Stream.runDrain,\n Effect.provide(\n layerOtlp({\n url: \"http://localhost:4318/v1/metrics\",\n resource: { serviceName: \"test-service\" },\n exportInterval: \"10 minutes\",\n }).pipe(Layer.provide(clientLayer)),\n ),\n )\n\n const captured = yield* Ref.get(bodies)\n expect(captured.length).toBeGreaterThanOrEqual(1)\n expect(captured.join(\"\\n\")).toContain(\"otlp_export_counter\")\n }),\n ),\n )\n})\n"],"mappings":"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"}
|
package/dist/retry/Retry.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { d as RateLimited, m as Unavailable, p as Timeout, t as AiError } from "../AiError-
|
|
1
|
+
import { d as RateLimited, m as Unavailable, p as Timeout, t as AiError } from "../AiError-Bf1pq4BB.mjs";
|
|
2
2
|
import { Effect, Schedule, Stream } from "effect";
|
|
3
|
-
import * as _$effect_Types0 from "effect/Types";
|
|
4
3
|
import * as _$effect_Cause0 from "effect/Cause";
|
|
4
|
+
import * as _$effect_Types0 from "effect/Types";
|
|
5
5
|
|
|
6
6
|
//#region src/retry/Retry.d.ts
|
|
7
7
|
declare namespace Retry_d_exports {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ContentFiltered, RateLimited, Timeout, Unavailable } from "../domain/AiError.mjs";
|
|
2
2
|
import { effect, stream } from "./Retry.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { a as describe, n as globalExpect, o as it } from "../dist-B04dlFHg.mjs";
|
|
4
4
|
import { Cause, Effect, Exit, Option, Ref, Schedule, Stream } from "effect";
|
|
5
5
|
//#region src/retry/Retry.test.ts
|
|
6
6
|
const planned = (attempts, plan) => Stream.unwrap(Ref.getAndUpdate(attempts, (n) => n + 1).pipe(Effect.map((n) => plan[Math.min(n, plan.length - 1)])));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as SandboxError } from "../SandboxError-
|
|
1
|
+
import { a as SandboxError } from "../SandboxError-CIrVDOMV.mjs";
|
|
2
2
|
import { Brand, Context, Data, Duration, Effect, Redacted, Scope, Stream } from "effect";
|
|
3
3
|
import * as _$effect_Unify0 from "effect/Unify";
|
|
4
4
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as SandboxError, c as SandboxInvalidRequest, d as SandboxQuotaExceeded, f as SandboxTimeout, i as SandboxCreateFailed, l as SandboxNetworkPolicyDenied, n as SandboxAuthFailed, p as SandboxUnsupported, r as SandboxAuthSubtype, s as SandboxExecFailed, t as SandboxAlreadyExists, u as SandboxNotFound } from "../SandboxError-
|
|
1
|
+
import { a as SandboxError, c as SandboxInvalidRequest, d as SandboxQuotaExceeded, f as SandboxTimeout, i as SandboxCreateFailed, l as SandboxNetworkPolicyDenied, n as SandboxAuthFailed, p as SandboxUnsupported, r as SandboxAuthSubtype, s as SandboxExecFailed, t as SandboxAlreadyExists, u as SandboxNotFound } from "../SandboxError-CIrVDOMV.mjs";
|
|
2
2
|
export { SandboxAlreadyExists, SandboxAuthFailed, SandboxAuthSubtype, SandboxCreateFailed, SandboxError, SandboxExecFailed, SandboxInvalidRequest, SandboxNetworkPolicyDenied, SandboxNotFound, SandboxQuotaExceeded, SandboxTimeout, SandboxUnsupported };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as AiError } from "../AiError-
|
|
2
|
-
import { n as AudioChunk, r as AudioFormat, t as AudioBlob } from "../Audio-
|
|
1
|
+
import { t as AiError } from "../AiError-Bf1pq4BB.mjs";
|
|
2
|
+
import { n as AudioChunk, r as AudioFormat, t as AudioBlob } from "../Audio-CR7CSljJ.mjs";
|
|
3
3
|
import { Context, Effect, Stream } from "effect";
|
|
4
4
|
|
|
5
5
|
//#region src/speech-synthesizer/SpeechSynthesizer.d.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { streamSynthesis, streamSynthesisFrom, streamSynthesizeDialogue, synthesize, synthesizeDialogue } from "./SpeechSynthesizer.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { a as describe, n as globalExpect, o as it, t as import_dist } from "../dist-B04dlFHg.mjs";
|
|
3
3
|
import { layer, layerWithoutIncremental, layerWithoutMultiSpeaker } from "../testing/MockSpeechSynthesizer.mjs";
|
|
4
4
|
import { Duration, Effect, Stream } from "effect";
|
|
5
5
|
//#region src/speech-synthesizer/SpeechSynthesizer.test.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect, Schema, Stream } from "effect";
|
|
2
|
-
import * as _$effect_Types0 from "effect/Types";
|
|
3
2
|
import * as _$effect_Cause0 from "effect/Cause";
|
|
3
|
+
import * as _$effect_Types0 from "effect/Types";
|
|
4
4
|
|
|
5
5
|
//#region src/streaming/JSONL.d.ts
|
|
6
6
|
declare namespace JSONL_d_exports {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fromBytes, parse, toBytes } from "./JSONL.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { a as describe, n as globalExpect, o as it } from "../dist-B04dlFHg.mjs";
|
|
3
3
|
import { Effect, Result, Schema, Stream } from "effect";
|
|
4
4
|
//#region src/streaming/JSONL.test.ts
|
|
5
5
|
const enc = new TextEncoder();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fromBytes, toBytes } from "./SSE.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { a as describe, n as globalExpect, o as it } from "../dist-B04dlFHg.mjs";
|
|
3
3
|
import { Effect, Stream } from "effect";
|
|
4
4
|
//#region src/streaming/SSE.test.ts
|
|
5
5
|
const enc = new TextEncoder();
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { c as decodeJsonLines, d as parseJson, i as StructuredFormat, l as decodeJsonLinesRecoverable, n as JsonParseError, o as StructuredSchema, r as StructuredDecodeError, s as decode, t as DecodeIssue, u as fromEffectSchema } from "../StructuredFormat-
|
|
1
|
+
import { c as decodeJsonLines, d as parseJson, i as StructuredFormat, l as decodeJsonLinesRecoverable, n as JsonParseError, o as StructuredSchema, r as StructuredDecodeError, s as decode, t as DecodeIssue, u as fromEffectSchema } from "../StructuredFormat-DHaUJBdd.mjs";
|
|
2
2
|
export { DecodeIssue, JsonParseError, StructuredDecodeError, StructuredFormat, StructuredSchema, decode, decodeJsonLines, decodeJsonLinesRecoverable, fromEffectSchema, parseJson };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StructuredFormat.mjs","names":[],"sources":["../../src/structured-format/StructuredFormat.ts"],"sourcesContent":["import type { StandardJSONSchemaV1, StandardSchemaV1 } from \"@standard-schema/spec\"\nimport { Data, Effect, Match, Result, Schema, Stream, pipe } from \"effect\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Cross-validator schema constraint for structured outputs. Any schema\n * implementing both Standard Schema (runtime validation) and Standard\n * JSON Schema (wire encoding) works directly: Zod 4+, Valibot, ArkType,\n * and Effect Schema after `fromEffectSchema`.\n */\nexport type StructuredSchema<Output = unknown> = StandardSchemaV1<unknown, Output> &\n StandardJSONSchemaV1<unknown, Output>\n\n/**\n * A schema-bound output the user wants the model to produce. Pairs the\n * cross-validator schema with metadata providers need (name, description,\n * strict-mode flag).\n */\nexport type StructuredFormat<A> = {\n readonly name: string\n readonly description?: string\n readonly schema: StructuredSchema<A>\n /**\n * Provider strict-mode flag. OpenAI, Anthropic, and Mistral honour it\n * (constrained decoding); other providers ignore.\n */\n readonly strict?: boolean\n}\n\n/** A single path-scoped validation problem. Library-agnostic shape. */\nexport type DecodeIssue = {\n readonly path: ReadonlyArray<string | number>\n readonly message: string\n}\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/**\n * Schema validation failed. `raw` is the original text (or stringified\n * value) that failed; `issues` is a flat list of per-field problems.\n */\nexport class StructuredDecodeError extends Data.TaggedError(\"StructuredDecodeError\")<{\n readonly raw: string\n readonly issues: ReadonlyArray<DecodeIssue>\n}> {}\n\n/**\n * `JSON.parse` threw on a string that was supposed to be JSON. Distinct\n * from `StructuredDecodeError`: the bytes weren't even JSON.\n */\nexport class JsonParseError extends Data.TaggedError(\"StructuredJsonParseError\")<{\n readonly raw: string\n readonly cause: unknown\n}> {}\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap an Effect `Schema` as a `StructuredFormat`. Effect Schema doesn't\n * natively implement Standard Schema; this helper installs the\n * `~standard` and JSON Schema interfaces.\n */\nexport const fromEffectSchema = <S extends Schema.Codec<any, any, never, any>>(\n schema: S,\n options?: {\n readonly name?: string\n readonly description?: string\n readonly strict?: boolean\n },\n): StructuredFormat<S[\"Type\"]> => ({\n name: options?.name ?? \"output\",\n schema: Schema.toStandardJSONSchemaV1(Schema.toStandardSchemaV1(schema)),\n ...(options?.description !== undefined && {\n description: options.description,\n }),\n ...(options?.strict !== undefined && { strict: options.strict }),\n})\n\n// ---------------------------------------------------------------------------\n// Standard Schema → DecodeIssue\n// ---------------------------------------------------------------------------\n\nconst propertyKeyToScalar = Match.type<PropertyKey>().pipe(\n Match.when(Match.string, (s) => s),\n Match.when(Match.number, (n) => n),\n Match.when(Match.symbol, (s) => s.toString()),\n Match.exhaustive,\n)\n\nconst segmentToKey = Match.type<PropertyKey | StandardSchemaV1.PathSegment>().pipe(\n Match.when(Match.string, (s) => s),\n Match.when(Match.number, (n) => n),\n Match.when(Match.symbol, (s) => s.toString()),\n Match.orElse((segment) => propertyKeyToScalar(segment.key)),\n)\n\nconst issueToDecode = (issue: StandardSchemaV1.Issue): DecodeIssue => ({\n path: (issue.path ?? []).map(segmentToKey),\n message: issue.message,\n})\n\n// ---------------------------------------------------------------------------\n// Decoding\n// ---------------------------------------------------------------------------\n\n/**\n * Validate an `unknown` against the format's schema. Returns the typed\n * value or a `StructuredDecodeError`. Standard Schema's `validate` may\n * be async; this function handles both sync and async results.\n */\nexport const decode =\n <A>(format: StructuredFormat<A>) =>\n (raw: unknown): Effect.Effect<A, StructuredDecodeError> =>\n pipe(\n Effect.promise(async () => format.schema[\"~standard\"].validate(raw)),\n Effect.flatMap((result) =>\n result.issues === undefined\n ? Effect.succeed(result.value)\n : Effect.fail(\n new StructuredDecodeError({\n raw: typeof raw === \"string\" ? raw : JSON.stringify(raw),\n issues: result.issues.map(issueToDecode),\n }),\n ),\n ),\n )\n\n/**\n * Parse a JSON string then validate against the format's schema. Two\n * failure modes: `JsonParseError` (bytes weren't JSON) and\n * `StructuredDecodeError` (JSON didn't match the schema).\n */\nexport const parseJson =\n <A>(format: StructuredFormat<A>) =>\n (raw: string): Effect.Effect<A, JsonParseError | StructuredDecodeError> =>\n pipe(\n Effect.try({\n try: () => JSON.parse(raw),\n catch: (cause) => new JsonParseError({ raw, cause }),\n }),\n Effect.flatMap(decode(format)),\n )\n\n/**\n * Stream operator: each input string is JSON-parsed and validated.\n * Failures surface in the stream's failure channel, distinguished by tag.\n */\nexport const decodeJsonLines =\n <A>(format: StructuredFormat<A>) =>\n <E, R>(\n self: Stream.Stream<string, E, R>,\n ): Stream.Stream<A, E | JsonParseError | StructuredDecodeError, R> =>\n self.pipe(Stream.mapEffect(parseJson(format)))\n\n/**\n * Like {@link decodeJsonLines}, but each line yields a `Result` instead of\n * failing the stream. Use when one bad line shouldn't abort the rest —\n * log-and-continue, or partial-recovery with a corrective re-prompt.\n * Upstream errors (the input stream's own `E`) still propagate normally.\n */\nexport const decodeJsonLinesRecoverable =\n <A>(format: StructuredFormat<A>) =>\n <E, R>(\n self: Stream.Stream<string, E, R>,\n ): Stream.Stream<Result.Result<A, JsonParseError | StructuredDecodeError>, E, R> =>\n self.pipe(Stream.mapEffect((line) => Effect.result(parseJson(format)(line))))\n"],"mappings":";;;;;;;;;;;;;;;;AA8CA,IAAa,wBAAb,cAA2C,KAAK,YAAY,wBAAwB,CAGjF;;;;;AAMH,IAAa,iBAAb,cAAoC,KAAK,YAAY,2BAA2B,CAG7E;;;;;;AAWH,MAAa,oBACX,QACA,aAKiC;CACjC,MAAM,SAAS,QAAQ;CACvB,QAAQ,OAAO,uBAAuB,OAAO,mBAAmB,OAAO,CAAC;CACxE,GAAI,SAAS,gBAAgB,KAAA,KAAa,EACxC,aAAa,QAAQ,aACtB;CACD,GAAI,SAAS,WAAW,KAAA,KAAa,EAAE,QAAQ,QAAQ,QAAQ;CAChE;AAMD,MAAM,sBAAsB,MAAM,MAAmB,CAAC,KACpD,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,EAClC,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,EAClC,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,UAAU,CAAC,EAC7C,MAAM,WACP;AAED,MAAM,eAAe,MAAM,MAAkD,CAAC,KAC5E,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,EAClC,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,EAClC,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,UAAU,CAAC,EAC7C,MAAM,QAAQ,YAAY,oBAAoB,QAAQ,IAAI,CAAC,CAC5D;AAED,MAAM,iBAAiB,WAAgD;CACrE,OAAO,MAAM,QAAQ,EAAE,EAAE,IAAI,aAAa;CAC1C,SAAS,MAAM;CAChB;;;;;;AAWD,MAAa,UACP,YACH,QACC,KACE,OAAO,QAAQ,YAAY,OAAO,OAAO,aAAa,SAAS,IAAI,CAAC,EACpE,OAAO,SAAS,WACd,OAAO,WAAW,KAAA,IACd,OAAO,QAAQ,OAAO,MAAM,GAC5B,OAAO,KACL,IAAI,sBAAsB;CACxB,KAAK,OAAO,QAAQ,WAAW,MAAM,KAAK,UAAU,IAAI;CACxD,QAAQ,OAAO,OAAO,IAAI,cAAc;CACzC,CAAC,CACH,CACN,CACF;;;;;;AAOL,MAAa,aACP,YACH,QACC,KACE,OAAO,IAAI;
|
|
1
|
+
{"version":3,"file":"StructuredFormat.mjs","names":[],"sources":["../../src/structured-format/StructuredFormat.ts"],"sourcesContent":["import type { StandardJSONSchemaV1, StandardSchemaV1 } from \"@standard-schema/spec\"\nimport { Data, Effect, Match, Result, Schema, Stream, pipe } from \"effect\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Cross-validator schema constraint for structured outputs. Any schema\n * implementing both Standard Schema (runtime validation) and Standard\n * JSON Schema (wire encoding) works directly: Zod 4+, Valibot, ArkType,\n * and Effect Schema after `fromEffectSchema`.\n */\nexport type StructuredSchema<Output = unknown> = StandardSchemaV1<unknown, Output> &\n StandardJSONSchemaV1<unknown, Output>\n\n/**\n * A schema-bound output the user wants the model to produce. Pairs the\n * cross-validator schema with metadata providers need (name, description,\n * strict-mode flag).\n */\nexport type StructuredFormat<A> = {\n readonly name: string\n readonly description?: string\n readonly schema: StructuredSchema<A>\n /**\n * Provider strict-mode flag. OpenAI, Anthropic, and Mistral honour it\n * (constrained decoding); other providers ignore.\n */\n readonly strict?: boolean\n}\n\n/** A single path-scoped validation problem. Library-agnostic shape. */\nexport type DecodeIssue = {\n readonly path: ReadonlyArray<string | number>\n readonly message: string\n}\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/**\n * Schema validation failed. `raw` is the original text (or stringified\n * value) that failed; `issues` is a flat list of per-field problems.\n */\nexport class StructuredDecodeError extends Data.TaggedError(\"StructuredDecodeError\")<{\n readonly raw: string\n readonly issues: ReadonlyArray<DecodeIssue>\n}> {}\n\n/**\n * `JSON.parse` threw on a string that was supposed to be JSON. Distinct\n * from `StructuredDecodeError`: the bytes weren't even JSON.\n */\nexport class JsonParseError extends Data.TaggedError(\"StructuredJsonParseError\")<{\n readonly raw: string\n readonly cause: unknown\n}> {}\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap an Effect `Schema` as a `StructuredFormat`. Effect Schema doesn't\n * natively implement Standard Schema; this helper installs the\n * `~standard` and JSON Schema interfaces.\n */\nexport const fromEffectSchema = <S extends Schema.Codec<any, any, never, any>>(\n schema: S,\n options?: {\n readonly name?: string\n readonly description?: string\n readonly strict?: boolean\n },\n): StructuredFormat<S[\"Type\"]> => ({\n name: options?.name ?? \"output\",\n schema: Schema.toStandardJSONSchemaV1(Schema.toStandardSchemaV1(schema)),\n ...(options?.description !== undefined && {\n description: options.description,\n }),\n ...(options?.strict !== undefined && { strict: options.strict }),\n})\n\n// ---------------------------------------------------------------------------\n// Standard Schema → DecodeIssue\n// ---------------------------------------------------------------------------\n\nconst propertyKeyToScalar = Match.type<PropertyKey>().pipe(\n Match.when(Match.string, (s) => s),\n Match.when(Match.number, (n) => n),\n Match.when(Match.symbol, (s) => s.toString()),\n Match.exhaustive,\n)\n\nconst segmentToKey = Match.type<PropertyKey | StandardSchemaV1.PathSegment>().pipe(\n Match.when(Match.string, (s) => s),\n Match.when(Match.number, (n) => n),\n Match.when(Match.symbol, (s) => s.toString()),\n Match.orElse((segment) => propertyKeyToScalar(segment.key)),\n)\n\nconst issueToDecode = (issue: StandardSchemaV1.Issue): DecodeIssue => ({\n path: (issue.path ?? []).map(segmentToKey),\n message: issue.message,\n})\n\n// ---------------------------------------------------------------------------\n// Decoding\n// ---------------------------------------------------------------------------\n\n/**\n * Validate an `unknown` against the format's schema. Returns the typed\n * value or a `StructuredDecodeError`. Standard Schema's `validate` may\n * be async; this function handles both sync and async results.\n */\nexport const decode =\n <A>(format: StructuredFormat<A>) =>\n (raw: unknown): Effect.Effect<A, StructuredDecodeError> =>\n pipe(\n Effect.promise(async () => format.schema[\"~standard\"].validate(raw)),\n Effect.flatMap((result) =>\n result.issues === undefined\n ? Effect.succeed(result.value)\n : Effect.fail(\n new StructuredDecodeError({\n raw: typeof raw === \"string\" ? raw : JSON.stringify(raw),\n issues: result.issues.map(issueToDecode),\n }),\n ),\n ),\n )\n\n/**\n * Parse a JSON string then validate against the format's schema. Two\n * failure modes: `JsonParseError` (bytes weren't JSON) and\n * `StructuredDecodeError` (JSON didn't match the schema).\n */\nexport const parseJson =\n <A>(format: StructuredFormat<A>) =>\n (raw: string): Effect.Effect<A, JsonParseError | StructuredDecodeError> =>\n pipe(\n Effect.try({\n // Validation runs through Standard Schema (Zod / Valibot / Effect), not\n // Effect Schema, so its JSON codecs don't apply here; and JSON.parse is\n // a separate step on purpose, to surface JsonParseError distinctly.\n // @effect-diagnostics-next-line effect/preferSchemaOverJson:off\n try: () => JSON.parse(raw),\n catch: (cause) => new JsonParseError({ raw, cause }),\n }),\n Effect.flatMap(decode(format)),\n )\n\n/**\n * Stream operator: each input string is JSON-parsed and validated.\n * Failures surface in the stream's failure channel, distinguished by tag.\n */\nexport const decodeJsonLines =\n <A>(format: StructuredFormat<A>) =>\n <E, R>(\n self: Stream.Stream<string, E, R>,\n ): Stream.Stream<A, E | JsonParseError | StructuredDecodeError, R> =>\n self.pipe(Stream.mapEffect(parseJson(format)))\n\n/**\n * Like {@link decodeJsonLines}, but each line yields a `Result` instead of\n * failing the stream. Use when one bad line shouldn't abort the rest —\n * log-and-continue, or partial-recovery with a corrective re-prompt.\n * Upstream errors (the input stream's own `E`) still propagate normally.\n */\nexport const decodeJsonLinesRecoverable =\n <A>(format: StructuredFormat<A>) =>\n <E, R>(\n self: Stream.Stream<string, E, R>,\n ): Stream.Stream<Result.Result<A, JsonParseError | StructuredDecodeError>, E, R> =>\n self.pipe(Stream.mapEffect((line) => Effect.result(parseJson(format)(line))))\n"],"mappings":";;;;;;;;;;;;;;;;AA8CA,IAAa,wBAAb,cAA2C,KAAK,YAAY,wBAAwB,CAGjF;;;;;AAMH,IAAa,iBAAb,cAAoC,KAAK,YAAY,2BAA2B,CAG7E;;;;;;AAWH,MAAa,oBACX,QACA,aAKiC;CACjC,MAAM,SAAS,QAAQ;CACvB,QAAQ,OAAO,uBAAuB,OAAO,mBAAmB,OAAO,CAAC;CACxE,GAAI,SAAS,gBAAgB,KAAA,KAAa,EACxC,aAAa,QAAQ,aACtB;CACD,GAAI,SAAS,WAAW,KAAA,KAAa,EAAE,QAAQ,QAAQ,QAAQ;CAChE;AAMD,MAAM,sBAAsB,MAAM,MAAmB,CAAC,KACpD,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,EAClC,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,EAClC,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,UAAU,CAAC,EAC7C,MAAM,WACP;AAED,MAAM,eAAe,MAAM,MAAkD,CAAC,KAC5E,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,EAClC,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,EAClC,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,UAAU,CAAC,EAC7C,MAAM,QAAQ,YAAY,oBAAoB,QAAQ,IAAI,CAAC,CAC5D;AAED,MAAM,iBAAiB,WAAgD;CACrE,OAAO,MAAM,QAAQ,EAAE,EAAE,IAAI,aAAa;CAC1C,SAAS,MAAM;CAChB;;;;;;AAWD,MAAa,UACP,YACH,QACC,KACE,OAAO,QAAQ,YAAY,OAAO,OAAO,aAAa,SAAS,IAAI,CAAC,EACpE,OAAO,SAAS,WACd,OAAO,WAAW,KAAA,IACd,OAAO,QAAQ,OAAO,MAAM,GAC5B,OAAO,KACL,IAAI,sBAAsB;CACxB,KAAK,OAAO,QAAQ,WAAW,MAAM,KAAK,UAAU,IAAI;CACxD,QAAQ,OAAO,OAAO,IAAI,cAAc;CACzC,CAAC,CACH,CACN,CACF;;;;;;AAOL,MAAa,aACP,YACH,QACC,KACE,OAAO,IAAI;CAKT,WAAW,KAAK,MAAM,IAAI;CAC1B,QAAQ,UAAU,IAAI,eAAe;EAAE;EAAK;EAAO,CAAC;CACrD,CAAC,EACF,OAAO,QAAQ,OAAO,OAAO,CAAC,CAC/B;;;;;AAML,MAAa,mBACP,YAEF,SAEA,KAAK,KAAK,OAAO,UAAU,UAAU,OAAO,CAAC,CAAC;;;;;;;AAQlD,MAAa,8BACP,YAEF,SAEA,KAAK,KAAK,OAAO,WAAW,SAAS,OAAO,OAAO,UAAU,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { JsonParseError, StructuredDecodeError, decodeJsonLines, decodeJsonLinesRecoverable, fromEffectSchema } from "./StructuredFormat.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { a as describe, n as globalExpect, o as it } from "../dist-B04dlFHg.mjs";
|
|
3
3
|
import { Effect, Exit, Filter, Result, Schema, Stream } from "effect";
|
|
4
4
|
//#region src/structured-format/StructuredFormat.test.ts
|
|
5
5
|
const itemFormat = fromEffectSchema(Schema.Struct({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as AudioChunk } from "../Audio-
|
|
1
|
+
import { n as AudioChunk } from "../Audio-CR7CSljJ.mjs";
|
|
2
2
|
import { CommonGenerateMusicRequest, CommonStreamGenerateMusicRequest, GenerateResult, MusicStreamEvent, audioEvent } from "../domain/Music.mjs";
|
|
3
3
|
import { MusicGenerator, MusicInteractiveSession } from "../music-generator/MusicGenerator.mjs";
|
|
4
4
|
import { Effect, Layer } from "effect";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { o as HistoryItem } from "../Items-
|
|
1
|
+
import { o as HistoryItem } from "../Items-BnIduEV2.mjs";
|
|
2
2
|
import { Turn } from "../domain/Turn.mjs";
|
|
3
3
|
import { LanguageModel, LanguageModelService } from "../language-model/LanguageModel.mjs";
|
|
4
4
|
import { Duration, Effect, Layer } from "effect";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as AudioChunk, t as AudioBlob } from "../Audio-
|
|
1
|
+
import { n as AudioChunk, t as AudioBlob } from "../Audio-CR7CSljJ.mjs";
|
|
2
2
|
import { CommonStreamSynthesizeRequest, CommonSynthesizeDialogueRequest, CommonSynthesizeRequest, MultiSpeakerTts, SpeechSynthesizer, TtsIncrementalText } from "../speech-synthesizer/SpeechSynthesizer.mjs";
|
|
3
3
|
import { Effect, Layer } from "effect";
|
|
4
4
|
|
package/dist/tool/Approval.d.mts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { v as ToolCall } from "../Items-
|
|
2
|
-
import { ToolResult } from "
|
|
3
|
-
import { ToolEvent } from "
|
|
4
|
-
import { Data, Effect, Queue, Scope, Stream } from "effect";
|
|
1
|
+
import { v as ToolCall } from "../Items-BnIduEV2.mjs";
|
|
2
|
+
import { t as ToolResult } from "../ToolResult-CL4hK_9z.mjs";
|
|
3
|
+
import { t as ToolEvent } from "../ToolEvent-cWZ2BnbS.mjs";
|
|
4
|
+
import { Data, Duration, Effect, Queue, Scope, Stream } from "effect";
|
|
5
5
|
import * as _$effect_Unify0 from "effect/Unify";
|
|
6
6
|
|
|
7
7
|
//#region src/tool/Approval.d.ts
|
|
8
8
|
declare namespace Approval_d_exports {
|
|
9
|
-
export { ApprovalDecision, ApprovalMapEntry, ToolCallPlan, Verdict, approvalRequested, approve, fromMap, fromQueue, reject, splitApprovalDecisions };
|
|
9
|
+
export { ApprovalDecision, ApprovalMapEntry, FromQueueOptions, ToolCallPlan, Verdict, approvalRequested, approve, fromMap, fromQueue, reject, splitApprovalDecisions };
|
|
10
10
|
}
|
|
11
11
|
type ToolCallPlan = {
|
|
12
12
|
readonly approved: ReadonlyArray<ToolCall>;
|
|
@@ -84,12 +84,24 @@ type Verdict = {
|
|
|
84
84
|
readonly decision: "approve" | "deny";
|
|
85
85
|
readonly reason?: string;
|
|
86
86
|
};
|
|
87
|
+
type FromQueueOptions = {
|
|
88
|
+
/**
|
|
89
|
+
* Resolve any still-pending gated call as `cancelled` once this elapses,
|
|
90
|
+
* so a lost or never-sent verdict cannot hang the decisions stream
|
|
91
|
+
* forever. Omit to wait indefinitely.
|
|
92
|
+
*/
|
|
93
|
+
readonly timeout?: Duration.Input;
|
|
94
|
+
};
|
|
87
95
|
/**
|
|
88
96
|
* Queue-backed approval planner. Safe calls are returned immediately in
|
|
89
97
|
* `approved`; gated calls emit `ApprovalRequested` events and later produce
|
|
90
98
|
* one `ApprovalDecision` when their matching verdict arrives.
|
|
99
|
+
*
|
|
100
|
+
* The verdict queue is a single-consumer transport: run one approval round
|
|
101
|
+
* at a time (the router retires once every gated call of the round is
|
|
102
|
+
* resolved). For overlapping rounds, use one queue per round.
|
|
91
103
|
*/
|
|
92
|
-
declare const fromQueue: (predicate: (call: ToolCall) => boolean, verdicts: Queue.Dequeue<Verdict
|
|
104
|
+
declare const fromQueue: (predicate: (call: ToolCall) => boolean, verdicts: Queue.Dequeue<Verdict>, options?: FromQueueOptions) => (calls: ReadonlyArray<ToolCall>) => Effect.Effect<{
|
|
93
105
|
readonly approved: ReadonlyArray<ToolCall>;
|
|
94
106
|
readonly decisions: Stream.Stream<ApprovalDecision>;
|
|
95
107
|
readonly approvalRequests: Stream.Stream<ToolEvent>;
|
|
@@ -102,5 +114,5 @@ type ApprovalMapEntry = {
|
|
|
102
114
|
};
|
|
103
115
|
declare const fromMap: (predicate: (call: ToolCall) => boolean, approvals: ReadonlyMap<string, ApprovalMapEntry>) => (calls: ReadonlyArray<ToolCall>) => ToolCallPlan;
|
|
104
116
|
//#endregion
|
|
105
|
-
export { ApprovalDecision, ApprovalMapEntry, ToolCallPlan, Verdict, approvalRequested, approve, fromMap, fromQueue, reject, splitApprovalDecisions, Approval_d_exports as t };
|
|
117
|
+
export { ApprovalDecision, ApprovalMapEntry, FromQueueOptions, ToolCallPlan, Verdict, approvalRequested, approve, fromMap, fromQueue, reject, splitApprovalDecisions, Approval_d_exports as t };
|
|
106
118
|
//# sourceMappingURL=Approval.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Approval.d.mts","names":[],"sources":["../../src/tool/Approval.ts"],"mappings":";;;;;;;;;;KAYY,YAAA;EAAA,SACD,QAAA,EAAU,aAAA,CAAc,QAAA;EAAA,SACxB,QAAA,EAAU,aAAA,CAAc,UAAA;AAAA;AAAA,KAGvB,gBAAA,GAAmB,IAAA,CAAK,UAAA;EAClC,QAAA;IAAA,SAAqB,IAAA,EAAM,QAAA;EAAA;EAC3B,QAAA;IAAA,SAAqB,MAAA,EAAQ,UAAA;EAAA;AAAA;AAAA,cAGlB,gBAAA;EAAA;;mBAJgB,QAAA;EAAA;EAAA;;qBACE,UAAA;EAAA;EAAA;;mBADF,QAAA;EAAA;IAAA;;;qBACE,UAAA;EAAA;IAAA;;;;;;uBADF,QAAA;MAAA;MAAA;;yBACE,UAAA;MAAA;IAAA;;qBADF,QAAA;IAAA;MAAA;uBACE,UAAA;IAAA;;;;uBADF,QAAA;MAAA;MAAA;;yBACE,UAAA;MAAA;IAAA;;qBADF,QAAA;IAAA;MAAA;uBACE,UAAA;IAAA;;;cAKlB,OAAA,GAAW,IAAA,EAAM,QAAA,KAAW,gBAAA;AAAA,cAE5B,MAAA,GAAU,MAAA,EAAQ,UAAA,KAAa,gBAAA;AAAA,cAG/B,sBAAA,GAA0B,SAAA,EAAW,aAAA,CAAc,gBAAA,MAAoB,YAAA;AAAA,cASvE,iBAAA,GAAqB,IAAA,EAAM,QAAA,KAAW,SAAA;AAAA,KAWvC,OAAA;EAAA,SACD,OAAA;EAAA,SACA,QAAA;EAAA,SACA,MAAA;AAAA;;;;;;
|
|
1
|
+
{"version":3,"file":"Approval.d.mts","names":[],"sources":["../../src/tool/Approval.ts"],"mappings":";;;;;;;;;;KAYY,YAAA;EAAA,SACD,QAAA,EAAU,aAAA,CAAc,QAAA;EAAA,SACxB,QAAA,EAAU,aAAA,CAAc,UAAA;AAAA;AAAA,KAGvB,gBAAA,GAAmB,IAAA,CAAK,UAAA;EAClC,QAAA;IAAA,SAAqB,IAAA,EAAM,QAAA;EAAA;EAC3B,QAAA;IAAA,SAAqB,MAAA,EAAQ,UAAA;EAAA;AAAA;AAAA,cAGlB,gBAAA;EAAA;;mBAJgB,QAAA;EAAA;EAAA;;qBACE,UAAA;EAAA;EAAA;;mBADF,QAAA;EAAA;IAAA;;;qBACE,UAAA;EAAA;IAAA;;;;;;uBADF,QAAA;MAAA;MAAA;;yBACE,UAAA;MAAA;IAAA;;qBADF,QAAA;IAAA;MAAA;uBACE,UAAA;IAAA;;;;uBADF,QAAA;MAAA;MAAA;;yBACE,UAAA;MAAA;IAAA;;qBADF,QAAA;IAAA;MAAA;uBACE,UAAA;IAAA;;;cAKlB,OAAA,GAAW,IAAA,EAAM,QAAA,KAAW,gBAAA;AAAA,cAE5B,MAAA,GAAU,MAAA,EAAQ,UAAA,KAAa,gBAAA;AAAA,cAG/B,sBAAA,GAA0B,SAAA,EAAW,aAAA,CAAc,gBAAA,MAAoB,YAAA;AAAA,cASvE,iBAAA,GAAqB,IAAA,EAAM,QAAA,KAAW,SAAA;AAAA,KAWvC,OAAA;EAAA,SACD,OAAA;EAAA,SACA,QAAA;EAAA,SACA,MAAA;AAAA;AAAA,KAGC,gBAAA;;;;;;WAMD,OAAA,GAAU,QAAA,CAAS,KAAA;AAAA;;;;;;;;;;cAYjB,SAAA,GAET,SAAA,GAAY,IAAA,EAAM,QAAA,cAClB,QAAA,EAAU,KAAA,CAAM,OAAA,CAAQ,OAAA,GACxB,OAAA,GAAU,gBAAA,MAGV,KAAA,EAAO,aAAA,CAAc,QAAA,MACpB,MAAA,CAAO,MAAA;EAAA,SAEG,QAAA,EAAU,aAAA,CAAc,QAAA;EAAA,SACxB,SAAA,EAAW,MAAA,CAAO,MAAA,CAAO,gBAAA;EAAA,SACzB,gBAAA,EAAkB,MAAA,CAAO,MAAA,CAAO,SAAA;AAAA,UAG3C,KAAA,CAAM,KAAA;AAAA,KA8EE,gBAAA;EAAA,SACG,QAAA;AAAA;EAAA,SACA,QAAA;EAAA,SAA2B,MAAA;AAAA;AAAA,cAE7B,OAAA,GACV,SAAA,GAAY,IAAA,EAAM,QAAA,cAAsB,SAAA,EAAW,WAAA,SAAoB,gBAAA,OACvE,KAAA,EAAO,aAAA,CAAc,QAAA,MAAY,YAAA"}
|
package/dist/tool/Approval.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
|
|
2
2
|
import { cancelled, denied } from "./ToolResult.mjs";
|
|
3
3
|
import { ToolEvent } from "./ToolEvent.mjs";
|
|
4
|
-
import { Data, Deferred, Effect, Queue, Stream } from "effect";
|
|
4
|
+
import { Data, Deferred, Effect, Queue, Ref, Stream } from "effect";
|
|
5
5
|
//#region src/tool/Approval.ts
|
|
6
6
|
/**
|
|
7
7
|
* Approval helpers for the two transport flavors.
|
|
@@ -41,23 +41,30 @@ const approvalRequested = (call) => ToolEvent.ApprovalRequested({
|
|
|
41
41
|
* Queue-backed approval planner. Safe calls are returned immediately in
|
|
42
42
|
* `approved`; gated calls emit `ApprovalRequested` events and later produce
|
|
43
43
|
* one `ApprovalDecision` when their matching verdict arrives.
|
|
44
|
+
*
|
|
45
|
+
* The verdict queue is a single-consumer transport: run one approval round
|
|
46
|
+
* at a time (the router retires once every gated call of the round is
|
|
47
|
+
* resolved). For overlapping rounds, use one queue per round.
|
|
44
48
|
*/
|
|
45
|
-
const fromQueue = (predicate, verdicts) => (calls) => Effect.gen(function* () {
|
|
49
|
+
const fromQueue = (predicate, verdicts, options) => (calls) => Effect.gen(function* () {
|
|
46
50
|
const gated = calls.filter(predicate);
|
|
47
51
|
const approved = calls.filter((call) => !predicate(call));
|
|
48
52
|
const entries = yield* Effect.forEach(gated, (call) => Deferred.make().pipe(Effect.map((d) => [call.call_id, d])));
|
|
49
53
|
const deferreds = new Map(entries);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
const callById = new Map(gated.map((c) => [c.call_id, c]));
|
|
55
|
+
const outstanding = yield* Ref.make(gated.length);
|
|
56
|
+
const resolve = (call, decision) => Deferred.succeed(deferreds.get(call.call_id), decision).pipe(Effect.flatMap((won) => won ? Ref.update(outstanding, (n) => n - 1) : Effect.void));
|
|
57
|
+
const route = Effect.suspend(() => Ref.get(outstanding).pipe(Effect.flatMap((n) => n <= 0 ? Effect.void : Queue.take(verdicts).pipe(Effect.flatMap((v) => {
|
|
58
|
+
const call = callById.get(v.call_id);
|
|
59
|
+
return (call === void 0 ? Effect.void : resolve(call, v.decision === "approve" ? approve(call) : reject(denied(call, v.reason)))).pipe(Effect.andThen(route));
|
|
60
|
+
})))));
|
|
61
|
+
if (gated.length > 0) {
|
|
62
|
+
yield* Effect.forkScoped(route);
|
|
63
|
+
if (options?.timeout !== void 0) yield* Effect.forkScoped(Effect.sleep(options.timeout).pipe(Effect.andThen(Effect.forEach(gated, (call) => resolve(call, reject(cancelled(call, "approval timed out")))))));
|
|
64
|
+
}
|
|
55
65
|
return {
|
|
56
66
|
approved,
|
|
57
|
-
decisions: Stream.fromIterable(gated).pipe(Stream.flatMap((call) => {
|
|
58
|
-
const d = deferreds.get(call.call_id);
|
|
59
|
-
return Stream.fromEffect(Deferred.await(d).pipe(Effect.map((v) => v.decision === "approve" ? approve(call) : reject(denied(call, v.reason)))));
|
|
60
|
-
}, { concurrency: "unbounded" })),
|
|
67
|
+
decisions: Stream.fromIterable(gated).pipe(Stream.flatMap((call) => Stream.fromEffect(Deferred.await(deferreds.get(call.call_id))), { concurrency: "unbounded" })),
|
|
61
68
|
approvalRequests: Stream.fromIterable(gated.map(approvalRequested))
|
|
62
69
|
};
|
|
63
70
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Approval.mjs","names":[],"sources":["../../src/tool/Approval.ts"],"sourcesContent":["/**\n * Approval helpers for the two transport flavors.\n *\n * These helpers only decide which calls are approved and which synthetic\n * results must be returned to the model. Tool execution stays explicit at\n * the recipe boundary via `Toolkit.run`.\n */\nimport { Data, Deferred, Effect, Queue, Scope, Stream } from \"effect\"\nimport type { ToolCall } from \"../domain/Items.js\"\nimport { type ToolResult, cancelled, denied } from \"./ToolResult.js\"\nimport { ToolEvent } from \"./ToolEvent.js\"\n\nexport type ToolCallPlan = {\n readonly approved: ReadonlyArray<ToolCall>\n readonly rejected: ReadonlyArray<ToolResult>\n}\n\nexport type ApprovalDecision = Data.TaggedEnum<{\n Approved: { readonly call: ToolCall }\n Rejected: { readonly result: ToolResult }\n}>\n\nexport const ApprovalDecision = Data.taggedEnum<ApprovalDecision>()\n\nexport const approve = (call: ToolCall): ApprovalDecision => ApprovalDecision.Approved({ call })\n\nexport const reject = (result: ToolResult): ApprovalDecision =>\n ApprovalDecision.Rejected({ result })\n\nexport const splitApprovalDecisions = (decisions: ReadonlyArray<ApprovalDecision>): ToolCallPlan =>\n decisions.reduce<ToolCallPlan>(\n (acc, decision) =>\n decision._tag === \"Approved\"\n ? { ...acc, approved: [...acc.approved, decision.call] }\n : { ...acc, rejected: [...acc.rejected, decision.result] },\n { approved: [], rejected: [] },\n )\n\nexport const approvalRequested = (call: ToolCall): ToolEvent =>\n ToolEvent.ApprovalRequested({\n call_id: call.call_id,\n tool: call.name,\n arguments: call.arguments,\n })\n\n// ---------------------------------------------------------------------------\n// Verdict queue (WebSocket-style transport).\n// ---------------------------------------------------------------------------\n\nexport type Verdict = {\n readonly call_id: string\n readonly decision: \"approve\" | \"deny\"\n readonly reason?: string\n}\n\n/**\n * Queue-backed approval planner. Safe calls are returned immediately in\n * `approved`; gated calls emit `ApprovalRequested` events and later produce\n * one `ApprovalDecision` when their matching verdict arrives.\n */\nexport const fromQueue =\n (predicate: (call: ToolCall) => boolean
|
|
1
|
+
{"version":3,"file":"Approval.mjs","names":[],"sources":["../../src/tool/Approval.ts"],"sourcesContent":["/**\n * Approval helpers for the two transport flavors.\n *\n * These helpers only decide which calls are approved and which synthetic\n * results must be returned to the model. Tool execution stays explicit at\n * the recipe boundary via `Toolkit.run`.\n */\nimport { Data, Deferred, type Duration, Effect, Queue, Ref, Scope, Stream } from \"effect\"\nimport type { ToolCall } from \"../domain/Items.js\"\nimport { type ToolResult, cancelled, denied } from \"./ToolResult.js\"\nimport { ToolEvent } from \"./ToolEvent.js\"\n\nexport type ToolCallPlan = {\n readonly approved: ReadonlyArray<ToolCall>\n readonly rejected: ReadonlyArray<ToolResult>\n}\n\nexport type ApprovalDecision = Data.TaggedEnum<{\n Approved: { readonly call: ToolCall }\n Rejected: { readonly result: ToolResult }\n}>\n\nexport const ApprovalDecision = Data.taggedEnum<ApprovalDecision>()\n\nexport const approve = (call: ToolCall): ApprovalDecision => ApprovalDecision.Approved({ call })\n\nexport const reject = (result: ToolResult): ApprovalDecision =>\n ApprovalDecision.Rejected({ result })\n\nexport const splitApprovalDecisions = (decisions: ReadonlyArray<ApprovalDecision>): ToolCallPlan =>\n decisions.reduce<ToolCallPlan>(\n (acc, decision) =>\n decision._tag === \"Approved\"\n ? { ...acc, approved: [...acc.approved, decision.call] }\n : { ...acc, rejected: [...acc.rejected, decision.result] },\n { approved: [], rejected: [] },\n )\n\nexport const approvalRequested = (call: ToolCall): ToolEvent =>\n ToolEvent.ApprovalRequested({\n call_id: call.call_id,\n tool: call.name,\n arguments: call.arguments,\n })\n\n// ---------------------------------------------------------------------------\n// Verdict queue (WebSocket-style transport).\n// ---------------------------------------------------------------------------\n\nexport type Verdict = {\n readonly call_id: string\n readonly decision: \"approve\" | \"deny\"\n readonly reason?: string\n}\n\nexport type FromQueueOptions = {\n /**\n * Resolve any still-pending gated call as `cancelled` once this elapses,\n * so a lost or never-sent verdict cannot hang the decisions stream\n * forever. Omit to wait indefinitely.\n */\n readonly timeout?: Duration.Input\n}\n\n/**\n * Queue-backed approval planner. Safe calls are returned immediately in\n * `approved`; gated calls emit `ApprovalRequested` events and later produce\n * one `ApprovalDecision` when their matching verdict arrives.\n *\n * The verdict queue is a single-consumer transport: run one approval round\n * at a time (the router retires once every gated call of the round is\n * resolved). For overlapping rounds, use one queue per round.\n */\nexport const fromQueue =\n (\n predicate: (call: ToolCall) => boolean,\n verdicts: Queue.Dequeue<Verdict>,\n options?: FromQueueOptions,\n ) =>\n (\n calls: ReadonlyArray<ToolCall>,\n ): Effect.Effect<\n {\n readonly approved: ReadonlyArray<ToolCall>\n readonly decisions: Stream.Stream<ApprovalDecision>\n readonly approvalRequests: Stream.Stream<ToolEvent>\n },\n never,\n Scope.Scope\n > =>\n Effect.gen(function* () {\n const gated = calls.filter(predicate)\n const approved = calls.filter((call) => !predicate(call))\n\n const entries = yield* Effect.forEach(gated, (call) =>\n Deferred.make<ApprovalDecision>().pipe(Effect.map((d) => [call.call_id, d] as const)),\n )\n const deferreds: ReadonlyMap<string, Deferred.Deferred<ApprovalDecision>> = new Map(entries)\n const callById: ReadonlyMap<string, ToolCall> = new Map(gated.map((c) => [c.call_id, c]))\n const outstanding = yield* Ref.make(gated.length)\n\n // Complete a deferred once; decrement only on the winning transition so\n // a late verdict for an already-resolved call is a no-op.\n const resolve = (call: ToolCall, decision: ApprovalDecision) =>\n Deferred.succeed(deferreds.get(call.call_id)!, decision).pipe(\n Effect.flatMap((won) => (won ? Ref.update(outstanding, (n) => n - 1) : Effect.void)),\n )\n\n // Router retires once every gated call is resolved (by verdict or\n // timeout), so sequential rounds over one queue do not accumulate\n // racing routers. Recursive + suspended: stack-safe, no imperative loop.\n const route: Effect.Effect<void> = Effect.suspend(() =>\n Ref.get(outstanding).pipe(\n Effect.flatMap((n) =>\n n <= 0\n ? Effect.void\n : Queue.take(verdicts).pipe(\n Effect.flatMap((v) => {\n const call = callById.get(v.call_id)\n return (\n call === undefined\n ? Effect.void\n : resolve(\n call,\n v.decision === \"approve\"\n ? approve(call)\n : reject(denied(call, v.reason)),\n )\n ).pipe(Effect.andThen(route))\n }),\n ),\n ),\n ),\n )\n\n if (gated.length > 0) {\n yield* Effect.forkScoped(route)\n if (options?.timeout !== undefined) {\n yield* Effect.forkScoped(\n Effect.sleep(options.timeout).pipe(\n Effect.andThen(\n Effect.forEach(gated, (call) =>\n resolve(call, reject(cancelled(call, \"approval timed out\"))),\n ),\n ),\n ),\n )\n }\n }\n\n const decisions = Stream.fromIterable(gated).pipe(\n Stream.flatMap((call) => Stream.fromEffect(Deferred.await(deferreds.get(call.call_id)!)), {\n concurrency: \"unbounded\",\n }),\n )\n\n const approvalRequests = Stream.fromIterable<ToolEvent>(gated.map(approvalRequested))\n\n return { approved, decisions, approvalRequests }\n })\n\n// ---------------------------------------------------------------------------\n// Approval map (HTTP-style transport). Verdicts arrive synchronously\n// bundled in the request payload. Missing entries → cancelled.\n// ---------------------------------------------------------------------------\n\nexport type ApprovalMapEntry =\n | { readonly decision: \"approve\" }\n | { readonly decision: \"deny\"; readonly reason?: string }\n\nexport const fromMap =\n (predicate: (call: ToolCall) => boolean, approvals: ReadonlyMap<string, ApprovalMapEntry>) =>\n (calls: ReadonlyArray<ToolCall>): ToolCallPlan =>\n splitApprovalDecisions(\n calls.map((call) => {\n if (!predicate(call)) return approve(call)\n const v = approvals.get(call.call_id)\n if (v === undefined) return reject(cancelled(call))\n return v.decision === \"approve\" ? approve(call) : reject(denied(call, v.reason))\n }),\n )\n"],"mappings":"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"}
|