@providerprotocol/ai 0.0.39 → 0.0.40
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/README.md +269 -34
- package/dist/anthropic/index.d.ts +3 -3
- package/dist/anthropic/index.js +7 -5
- package/dist/anthropic/index.js.map +1 -1
- package/dist/cerebras/index.d.ts +3 -3
- package/dist/cerebras/index.js +7 -5
- package/dist/cerebras/index.js.map +1 -1
- package/dist/{chunk-WU4U6IHF.js → chunk-6QCV4WXF.js} +4 -13
- package/dist/chunk-6QCV4WXF.js.map +1 -0
- package/dist/{chunk-5XPRVUOK.js → chunk-AC3VHSZJ.js} +2 -2
- package/dist/{chunk-5XPRVUOK.js.map → chunk-AC3VHSZJ.js.map} +1 -1
- package/dist/{chunk-ZDYEDI2A.js → chunk-CWGTARDE.js} +2 -2
- package/dist/{chunk-KNBODIQU.js → chunk-DI47UY2H.js} +2 -2
- package/dist/{chunk-KNBODIQU.js.map → chunk-DI47UY2H.js.map} +1 -1
- package/dist/{chunk-IDZR4ROP.js → chunk-EHR3LIPS.js} +2 -2
- package/dist/{chunk-IDZR4ROP.js.map → chunk-EHR3LIPS.js.map} +1 -1
- package/dist/chunk-EY2LLDGY.js +94 -0
- package/dist/chunk-EY2LLDGY.js.map +1 -0
- package/dist/{chunk-MJI74VEJ.js → chunk-F5ENANMJ.js} +18 -2
- package/dist/chunk-F5ENANMJ.js.map +1 -0
- package/dist/chunk-IKJH5ZSJ.js +1 -0
- package/dist/chunk-IKJH5ZSJ.js.map +1 -0
- package/dist/{chunk-IIMTP3XC.js → chunk-KBI45OXI.js} +2 -2
- package/dist/{chunk-SAMIK4WZ.js → chunk-KVUOTFYZ.js} +2 -2
- package/dist/{chunk-U6M3MXNI.js → chunk-L6QWKFGE.js} +3 -2
- package/dist/chunk-L6QWKFGE.js.map +1 -0
- package/dist/{chunk-RDC5GYST.js → chunk-N4LAFGLX.js} +7 -7
- package/dist/{chunk-ZKNPQBIE.js → chunk-R3T2IYOU.js} +5 -3
- package/dist/{chunk-ZKNPQBIE.js.map → chunk-R3T2IYOU.js.map} +1 -1
- package/dist/chunk-U2G5PHHL.js +25 -0
- package/dist/chunk-U2G5PHHL.js.map +1 -0
- package/dist/{chunk-SBGZJVTJ.js → chunk-VQZPADW6.js} +100 -33
- package/dist/chunk-VQZPADW6.js.map +1 -0
- package/dist/{chunk-O32SBS6S.js → chunk-XTWBAL42.js} +2 -2
- package/dist/{chunk-O32SBS6S.js.map → chunk-XTWBAL42.js.map} +1 -1
- package/dist/{chunk-WNB5PSY6.js → chunk-ZMESKGUY.js} +2 -2
- package/dist/{chunk-7ULSRWDH.js → chunk-ZSZVWLGE.js} +2 -2
- package/dist/{embedding-iNQCeXfk.d.ts → embedding-ts1npsDg.d.ts} +1 -1
- package/dist/google/index.d.ts +38 -4
- package/dist/google/index.js +5 -4
- package/dist/google/index.js.map +1 -1
- package/dist/groq/index.d.ts +3 -3
- package/dist/groq/index.js +7 -5
- package/dist/groq/index.js.map +1 -1
- package/dist/http/index.d.ts +5 -5
- package/dist/http/index.js +19 -22
- package/dist/{image-stream-ARno6XlS.d.ts → image-stream-BPml2YZZ.d.ts} +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.js +306 -112
- package/dist/index.js.map +1 -1
- package/dist/{llm-CZqlijjK.d.ts → llm-BWLaTzzY.d.ts} +75 -29
- package/dist/middleware/logging/index.d.ts +3 -3
- package/dist/middleware/logging/index.js +3 -0
- package/dist/middleware/logging/index.js.map +1 -1
- package/dist/middleware/parsed-object/index.d.ts +3 -3
- package/dist/middleware/parsed-object/index.js +5 -1
- package/dist/middleware/parsed-object/index.js.map +1 -1
- package/dist/middleware/persistence/index.d.ts +3 -3
- package/dist/middleware/persistence/index.js +3 -2
- package/dist/middleware/persistence/index.js.map +1 -1
- package/dist/middleware/pipeline/index.d.ts +195 -0
- package/dist/middleware/pipeline/index.js +61 -0
- package/dist/middleware/pipeline/index.js.map +1 -0
- package/dist/middleware/pubsub/index.d.ts +13 -11
- package/dist/middleware/pubsub/index.js +31 -5
- package/dist/middleware/pubsub/index.js.map +1 -1
- package/dist/middleware/pubsub/server/express/index.d.ts +3 -3
- package/dist/middleware/pubsub/server/express/index.js +2 -2
- package/dist/middleware/pubsub/server/fastify/index.d.ts +3 -3
- package/dist/middleware/pubsub/server/fastify/index.js +2 -2
- package/dist/middleware/pubsub/server/h3/index.d.ts +3 -3
- package/dist/middleware/pubsub/server/h3/index.js +2 -2
- package/dist/middleware/pubsub/server/index.d.ts +50 -9
- package/dist/middleware/pubsub/server/index.js +5 -5
- package/dist/middleware/pubsub/server/index.js.map +1 -1
- package/dist/middleware/pubsub/server/webapi/index.d.ts +3 -3
- package/dist/middleware/pubsub/server/webapi/index.js +2 -2
- package/dist/moonshot/index.d.ts +3 -3
- package/dist/moonshot/index.js +7 -5
- package/dist/moonshot/index.js.map +1 -1
- package/dist/ollama/index.d.ts +24 -4
- package/dist/ollama/index.js +5 -4
- package/dist/ollama/index.js.map +1 -1
- package/dist/openai/index.d.ts +65 -4
- package/dist/openai/index.js +7 -5
- package/dist/openai/index.js.map +1 -1
- package/dist/openrouter/index.d.ts +4 -4
- package/dist/openrouter/index.js +7 -5
- package/dist/openrouter/index.js.map +1 -1
- package/dist/proxy/index.d.ts +5 -5
- package/dist/proxy/index.js +16 -15
- package/dist/proxy/index.js.map +1 -1
- package/dist/proxy/server/express/index.d.ts +8 -9
- package/dist/proxy/server/express/index.js +4 -3
- package/dist/proxy/server/fastify/index.d.ts +8 -9
- package/dist/proxy/server/fastify/index.js +4 -3
- package/dist/proxy/server/h3/index.d.ts +8 -9
- package/dist/proxy/server/h3/index.js +4 -3
- package/dist/proxy/server/index.d.ts +5 -5
- package/dist/proxy/server/index.js +14 -13
- package/dist/proxy/server/webapi/index.d.ts +8 -9
- package/dist/proxy/server/webapi/index.js +4 -3
- package/dist/responses/index.d.ts +3 -3
- package/dist/responses/index.js +7 -5
- package/dist/responses/index.js.map +1 -1
- package/dist/retry-DVfdPLIB.d.ts +322 -0
- package/dist/{stream-DVVUIKpz.d.ts → stream-bBd_4Ipu.d.ts} +27 -4
- package/dist/{tool-D22EhP5F.d.ts → tool-BmAfKNBq.d.ts} +1 -1
- package/dist/{types-CyXF0J7C.d.ts → types-nTwlpyJE.d.ts} +13 -1
- package/dist/utils/index.d.ts +66 -2
- package/dist/utils/index.js +13 -0
- package/dist/xai/index.d.ts +3 -3
- package/dist/xai/index.js +7 -5
- package/dist/xai/index.js.map +1 -1
- package/package.json +6 -1
- package/dist/chunk-ARVM24K2.js +0 -128
- package/dist/chunk-ARVM24K2.js.map +0 -1
- package/dist/chunk-MJI74VEJ.js.map +0 -1
- package/dist/chunk-SBGZJVTJ.js.map +0 -1
- package/dist/chunk-U6M3MXNI.js.map +0 -1
- package/dist/chunk-WU4U6IHF.js.map +0 -1
- package/dist/chunk-Y5H7C5J4.js +0 -263
- package/dist/chunk-Y5H7C5J4.js.map +0 -1
- package/dist/retry-C1eJbEMV.d.ts +0 -531
- /package/dist/{chunk-ZDYEDI2A.js.map → chunk-CWGTARDE.js.map} +0 -0
- /package/dist/{chunk-IIMTP3XC.js.map → chunk-KBI45OXI.js.map} +0 -0
- /package/dist/{chunk-SAMIK4WZ.js.map → chunk-KVUOTFYZ.js.map} +0 -0
- /package/dist/{chunk-RDC5GYST.js.map → chunk-N4LAFGLX.js.map} +0 -0
- /package/dist/{chunk-WNB5PSY6.js.map → chunk-ZMESKGUY.js.map} +0 -0
- /package/dist/{chunk-7ULSRWDH.js.map → chunk-ZSZVWLGE.js.map} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { I as ImageSource, c as ImageBlock,
|
|
2
|
-
import {
|
|
1
|
+
import { I as ImageSource, c as ImageBlock, S as StreamEvent, T as Turn, M as Message, U as UserContent, A as AssistantContent, d as MessageType, a as MessageJSON, e as AssistantMessage, f as TokenUsage, C as ContentBlock, g as StreamResult } from './stream-bBd_4Ipu.js';
|
|
2
|
+
import { e as Tool, d as ToolInput, f as ToolUseStrategy, S as Structure, J as JSONSchema } from './tool-BmAfKNBq.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @fileoverview Error types for the Unified Provider Protocol.
|
|
@@ -393,6 +393,14 @@ interface MiddlewareContext {
|
|
|
393
393
|
readonly startTime: number;
|
|
394
394
|
/** Request end timestamp in milliseconds (set after completion) */
|
|
395
395
|
endTime?: number;
|
|
396
|
+
/**
|
|
397
|
+
* Emit a stream event. Events flow through onStreamEvent for all middleware.
|
|
398
|
+
* Useful for emitting events after streaming completes (e.g., in onTurn hooks).
|
|
399
|
+
* No-op for non-streaming requests.
|
|
400
|
+
*
|
|
401
|
+
* @param event - The stream event to emit
|
|
402
|
+
*/
|
|
403
|
+
emit(event: StreamEvent): void;
|
|
396
404
|
}
|
|
397
405
|
/**
|
|
398
406
|
* Context for stream event hooks.
|
|
@@ -484,6 +492,16 @@ interface Middleware {
|
|
|
484
492
|
* @param ctx - The middleware context
|
|
485
493
|
*/
|
|
486
494
|
onAbort?(error: Error, ctx: MiddlewareContext): void | Promise<void>;
|
|
495
|
+
/**
|
|
496
|
+
* Called before a retry attempt is made after a retryable error.
|
|
497
|
+
* Allows middleware to reset state or perform cleanup before the retry.
|
|
498
|
+
* Called for all middleware that have this hook, in forward order.
|
|
499
|
+
*
|
|
500
|
+
* @param attempt - The retry attempt number (1-indexed)
|
|
501
|
+
* @param error - The error that triggered the retry
|
|
502
|
+
* @param ctx - The middleware context
|
|
503
|
+
*/
|
|
504
|
+
onRetry?(attempt: number, error: Error, ctx: MiddlewareContext): void | Promise<void>;
|
|
487
505
|
/**
|
|
488
506
|
* Called before provider execution. Can modify the request.
|
|
489
507
|
*
|
|
@@ -851,20 +869,15 @@ interface BoundImageModel<TParams = unknown> {
|
|
|
851
869
|
*
|
|
852
870
|
* @example
|
|
853
871
|
* ```typescript
|
|
854
|
-
*
|
|
855
|
-
*
|
|
856
|
-
* private index = 0;
|
|
857
|
-
*
|
|
858
|
-
* constructor(keys: string[]) {
|
|
859
|
-
* this.keys = keys;
|
|
860
|
-
* }
|
|
872
|
+
* // Built-in key strategies
|
|
873
|
+
* const keys = roundRobinKeys(['sk-key-1', 'sk-key-2']);
|
|
861
874
|
*
|
|
875
|
+
* // Custom implementation
|
|
876
|
+
* const custom: KeyStrategy = {
|
|
862
877
|
* getKey(): string {
|
|
863
|
-
*
|
|
864
|
-
* this.index = (this.index + 1) % this.keys.length;
|
|
865
|
-
* return key;
|
|
878
|
+
* return pickKeyFromPool();
|
|
866
879
|
* }
|
|
867
|
-
* }
|
|
880
|
+
* };
|
|
868
881
|
* ```
|
|
869
882
|
*/
|
|
870
883
|
interface KeyStrategy {
|
|
@@ -878,24 +891,32 @@ interface KeyStrategy {
|
|
|
878
891
|
/**
|
|
879
892
|
* Retry strategy interface for handling request failures.
|
|
880
893
|
*
|
|
881
|
-
*
|
|
882
|
-
*
|
|
894
|
+
* Each request receives a fresh strategy instance via the factory pattern,
|
|
895
|
+
* ensuring complete isolation between concurrent requests.
|
|
883
896
|
*
|
|
884
897
|
* @example
|
|
885
898
|
* ```typescript
|
|
886
|
-
*
|
|
887
|
-
*
|
|
888
|
-
*
|
|
889
|
-
*
|
|
890
|
-
*
|
|
891
|
-
*
|
|
899
|
+
* // Using built-in strategies
|
|
900
|
+
* const provider = createOpenAI({
|
|
901
|
+
* retryStrategy: exponentialBackoff({ maxAttempts: 5 })
|
|
902
|
+
* });
|
|
903
|
+
*
|
|
904
|
+
* // Custom strategy factory
|
|
905
|
+
* const customRetry = (): RetryStrategy => ({
|
|
906
|
+
* onRetry(error, attempt) {
|
|
907
|
+
* if (attempt > 3) return null;
|
|
892
908
|
* if (error.code !== 'RATE_LIMITED') return null;
|
|
893
|
-
* return
|
|
909
|
+
* return 1000 * Math.pow(2, attempt - 1);
|
|
894
910
|
* }
|
|
895
|
-
* }
|
|
911
|
+
* });
|
|
896
912
|
* ```
|
|
897
913
|
*/
|
|
898
914
|
interface RetryStrategy {
|
|
915
|
+
/**
|
|
916
|
+
* Maximum number of retry attempts configured for this strategy.
|
|
917
|
+
* Used by streaming retry logic to emit progress events.
|
|
918
|
+
*/
|
|
919
|
+
readonly maxAttempts?: number;
|
|
899
920
|
/**
|
|
900
921
|
* Called when a request fails with a retryable error.
|
|
901
922
|
*
|
|
@@ -905,7 +926,7 @@ interface RetryStrategy {
|
|
|
905
926
|
*/
|
|
906
927
|
onRetry(error: UPPError, attempt: number): number | null | Promise<number | null>;
|
|
907
928
|
/**
|
|
908
|
-
* Called before each request. Can be used to implement pre-emptive
|
|
929
|
+
* Called before each request. Can be used to implement pre-emptive delays.
|
|
909
930
|
*
|
|
910
931
|
* @returns Delay in ms to wait before making the request, or 0 to proceed immediately
|
|
911
932
|
*/
|
|
@@ -914,7 +935,32 @@ interface RetryStrategy {
|
|
|
914
935
|
* Reset the strategy state (e.g., after a successful request).
|
|
915
936
|
*/
|
|
916
937
|
reset?(): void;
|
|
938
|
+
/**
|
|
939
|
+
* Sets the retry delay from a Retry-After header value.
|
|
940
|
+
* Only applicable to strategies that honor server-provided retry timing.
|
|
941
|
+
*
|
|
942
|
+
* @param seconds - The Retry-After value in seconds
|
|
943
|
+
*/
|
|
944
|
+
setRetryAfter?(seconds: number): void;
|
|
917
945
|
}
|
|
946
|
+
/**
|
|
947
|
+
* Factory function that creates a fresh RetryStrategy instance per request.
|
|
948
|
+
*
|
|
949
|
+
* Using a factory ensures each `.stream()`, `.generate()`, `.embed()` call
|
|
950
|
+
* gets its own isolated retry state, preventing cross-request contamination.
|
|
951
|
+
*
|
|
952
|
+
* @example
|
|
953
|
+
* ```typescript
|
|
954
|
+
* // Built-in factory
|
|
955
|
+
* const retry = exponentialBackoff({ maxAttempts: 3 });
|
|
956
|
+
*
|
|
957
|
+
* // Custom factory
|
|
958
|
+
* const customRetry: RetryStrategyFactory = () => ({
|
|
959
|
+
* onRetry: (error, attempt) => attempt <= 3 ? 1000 : null
|
|
960
|
+
* });
|
|
961
|
+
* ```
|
|
962
|
+
*/
|
|
963
|
+
type RetryStrategyFactory = () => RetryStrategy;
|
|
918
964
|
/**
|
|
919
965
|
* Provider identity shape for structural typing.
|
|
920
966
|
*
|
|
@@ -938,12 +984,12 @@ interface ProviderIdentity {
|
|
|
938
984
|
* const config: ProviderConfig = {
|
|
939
985
|
* apiKey: process.env.OPENAI_API_KEY,
|
|
940
986
|
* timeout: 30000,
|
|
941
|
-
* retryStrategy:
|
|
987
|
+
* retryStrategy: exponentialBackoff()
|
|
942
988
|
* };
|
|
943
989
|
*
|
|
944
990
|
* // Or with a key strategy for key rotation
|
|
945
991
|
* const config: ProviderConfig = {
|
|
946
|
-
* apiKey:
|
|
992
|
+
* apiKey: roundRobinKeys(['sk-1', 'sk-2', 'sk-3']),
|
|
947
993
|
* baseUrl: 'https://custom-proxy.example.com'
|
|
948
994
|
* };
|
|
949
995
|
* ```
|
|
@@ -965,8 +1011,8 @@ interface ProviderConfig {
|
|
|
965
1011
|
fetch?: typeof fetch;
|
|
966
1012
|
/** API version override (provider-specific) */
|
|
967
1013
|
apiVersion?: string;
|
|
968
|
-
/** Retry strategy for handling failures and rate limits */
|
|
969
|
-
retryStrategy?:
|
|
1014
|
+
/** Retry strategy factory for handling failures and rate limits */
|
|
1015
|
+
retryStrategy?: RetryStrategyFactory;
|
|
970
1016
|
/**
|
|
971
1017
|
* Custom headers to include in API requests.
|
|
972
1018
|
*
|
|
@@ -1777,4 +1823,4 @@ interface BoundLLMModel<TParams = unknown> {
|
|
|
1777
1823
|
stream(request: LLMRequest<TParams>): LLMStreamResult;
|
|
1778
1824
|
}
|
|
1779
1825
|
|
|
1780
|
-
export { type
|
|
1826
|
+
export { type StreamContext as $, type LLMStreamResult as A, type BoundEmbeddingModel as B, type BoundLLMModel as C, type InferenceInput as D, type EmbeddingInput as E, type ImageInput as F, type ImageEditInput as G, type ImageGenerateOptions as H, type ImageStreamResult as I, type GeneratedImage as J, type KeyStrategy as K, type LLMOptions as L, type Middleware as M, type ImageUsage as N, type ImageResult as O, type ProviderIdentity as P, type ImageStreamEvent as Q, type RetryStrategyFactory as R, type ImageCapabilities as S, Thread as T, UPPError as U, type ImageRequest as V, type ImageEditRequest as W, type ImageResponse as X, type BoundImageModel as Y, type ImageModelInput as Z, type MiddlewareContext as _, type ThreadJSON as a, type MiddlewareModality as a0, type AnyRequest as a1, type AnyResponse as a2, type ImageProviderStreamResult as b, type ProviderConfig as c, type EmbeddingUsage as d, type Provider as e, type Modality as f, ErrorCode as g, type ModelReference as h, type LLMInstance as i, type ImageOptions as j, type ImageInstance as k, type LLMHandler as l, type EmbeddingHandler as m, type ImageHandler as n, Image as o, ModalityType as p, type RetryStrategy as q, type LLMProvider as r, type EmbeddingProvider as s, type ImageProvider as t, type EmbeddingRequest as u, type EmbeddingResponse as v, type EmbeddingVector as w, type LLMCapabilities as x, type LLMRequest as y, type LLMResponse as z };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { M as Middleware } from '../../llm-
|
|
2
|
-
import '../../stream-
|
|
3
|
-
import '../../tool-
|
|
1
|
+
import { M as Middleware } from '../../llm-BWLaTzzY.js';
|
|
2
|
+
import '../../stream-bBd_4Ipu.js';
|
|
3
|
+
import '../../tool-BmAfKNBq.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @fileoverview Logging middleware for request/response visibility.
|
|
@@ -49,6 +49,9 @@ function loggingMiddleware(options = {}) {
|
|
|
49
49
|
const duration = Date.now() - ctx.startTime;
|
|
50
50
|
log("warn", `[${ctx.provider}] Aborted after ${duration}ms: ${error.message}`);
|
|
51
51
|
},
|
|
52
|
+
onRetry(attempt, error, ctx) {
|
|
53
|
+
log("warn", `[${ctx.provider}] Retry attempt ${attempt}: ${error.message}`);
|
|
54
|
+
},
|
|
52
55
|
onStreamEvent(event, ctx) {
|
|
53
56
|
if (logStreamEvents) {
|
|
54
57
|
log("debug", `Stream event: ${event.type}`, { index: event.index });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/middleware/logging.ts"],"sourcesContent":["/**\n * @fileoverview Logging middleware for request/response visibility.\n *\n * Provides configurable logging for LLM, embedding, and image operations,\n * including timing, error tracking, and optional event logging.\n *\n * @module middleware/logging\n */\n\nimport type { Middleware, MiddlewareContext, StreamContext } from '../types/middleware.ts';\nimport type { StreamEvent } from '../types/stream.ts';\n\n/**\n * Log levels for filtering output.\n */\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\n/**\n * Options for logging middleware.\n */\nexport interface LoggingOptions {\n /**\n * Minimum log level to output.\n * @default 'info'\n */\n level?: LogLevel;\n\n /**\n * Log individual stream events.\n * @default false\n */\n logStreamEvents?: boolean;\n\n /**\n * Log tool calls and results.\n * @default true\n */\n logToolCalls?: boolean;\n\n /**\n * Custom logger function. If not provided, uses console.log.\n * @param level - The log level\n * @param message - The log message\n * @param data - Optional additional data\n */\n logger?(level: LogLevel, message: string, data?: Record<string, unknown>): void;\n\n /**\n * Prefix for all log messages.\n * @default '[PP]'\n */\n prefix?: string;\n}\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\n/**\n * Creates a logging middleware for visibility into request lifecycle.\n *\n * This middleware logs the start, end, and errors of requests,\n * with optional logging of stream events and tool calls.\n *\n * @param options - Configuration options\n * @returns A middleware that logs request lifecycle events\n *\n * @example\n * ```typescript\n * import { llm } from '@providerprotocol/ai';\n * import { loggingMiddleware } from '@providerprotocol/ai/middleware/logging';\n * import { anthropic } from '@providerprotocol/ai/anthropic';\n *\n * const model = llm({\n * model: anthropic('claude-sonnet-4-20250514'),\n * middleware: [loggingMiddleware({ level: 'debug' })],\n * });\n *\n * // Logs: [PP] [anthropic] Starting llm request (streaming)\n * // Logs: [PP] [anthropic] Completed in 1234ms\n * const result = await model.generate('Hello');\n * ```\n */\nexport function loggingMiddleware(options: LoggingOptions = {}): Middleware {\n const {\n level = 'info',\n logStreamEvents = false,\n logToolCalls = true,\n logger,\n prefix = '[PP]',\n } = options;\n\n const minLevel = LOG_LEVELS[level];\n\n const log = (logLevel: LogLevel, message: string, data?: Record<string, unknown>) => {\n if (LOG_LEVELS[logLevel] < minLevel) {\n return;\n }\n\n const fullMessage = `${prefix} ${message}`;\n\n if (logger) {\n logger(logLevel, fullMessage, data);\n } else {\n const consoleMethod = logLevel === 'error' ? console.error : logLevel === 'warn' ? console.warn : console.log;\n if (data) {\n consoleMethod(fullMessage, data);\n } else {\n consoleMethod(fullMessage);\n }\n }\n };\n\n return {\n name: 'logging',\n\n onStart(ctx: MiddlewareContext): void {\n const streamingLabel = ctx.streaming ? '(streaming)' : '';\n log('info', `[${ctx.provider}] Starting ${ctx.modality} request ${streamingLabel}`.trim());\n log('debug', `[${ctx.provider}] Model: ${ctx.modelId}`);\n },\n\n onEnd(ctx: MiddlewareContext): void {\n const duration = ctx.endTime ? ctx.endTime - ctx.startTime : 0;\n log('info', `[${ctx.provider}] Completed in ${duration}ms`);\n },\n\n onError(error: Error, ctx: MiddlewareContext): void {\n const duration = Date.now() - ctx.startTime;\n log('error', `[${ctx.provider}] Error after ${duration}ms: ${error.message}`);\n },\n\n onAbort(error: Error, ctx: MiddlewareContext): void {\n const duration = Date.now() - ctx.startTime;\n log('warn', `[${ctx.provider}] Aborted after ${duration}ms: ${error.message}`);\n },\n\n onStreamEvent(event: StreamEvent, ctx: StreamContext): StreamEvent {\n if (logStreamEvents) {\n log('debug', `Stream event: ${event.type}`, { index: event.index });\n }\n return event;\n },\n\n onToolCall(tool, params, ctx: MiddlewareContext): void {\n if (logToolCalls) {\n log('info', `[${ctx.provider}] Tool call: ${tool.name}`);\n log('debug', `[${ctx.provider}] Tool params:`, { params });\n }\n },\n\n onToolResult(tool, result, ctx: MiddlewareContext): void {\n if (logToolCalls) {\n log('debug', `[${ctx.provider}] Tool result: ${tool.name}`, { result });\n }\n },\n };\n}\n"],"mappings":";AAsDA,IAAM,aAAuC;AAAA,EAC3C,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;AA2BO,SAAS,kBAAkB,UAA0B,CAAC,GAAe;AAC1E,QAAM;AAAA,IACJ,QAAQ;AAAA,IACR,kBAAkB;AAAA,IAClB,eAAe;AAAA,IACf;AAAA,IACA,SAAS;AAAA,EACX,IAAI;AAEJ,QAAM,WAAW,WAAW,KAAK;AAEjC,QAAM,MAAM,CAAC,UAAoB,SAAiB,SAAmC;AACnF,QAAI,WAAW,QAAQ,IAAI,UAAU;AACnC;AAAA,IACF;AAEA,UAAM,cAAc,GAAG,MAAM,IAAI,OAAO;AAExC,QAAI,QAAQ;AACV,aAAO,UAAU,aAAa,IAAI;AAAA,IACpC,OAAO;AACL,YAAM,gBAAgB,aAAa,UAAU,QAAQ,QAAQ,aAAa,SAAS,QAAQ,OAAO,QAAQ;AAC1G,UAAI,MAAM;AACR,sBAAc,aAAa,IAAI;AAAA,MACjC,OAAO;AACL,sBAAc,WAAW;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,QAAQ,KAA8B;AACpC,YAAM,iBAAiB,IAAI,YAAY,gBAAgB;AACvD,UAAI,QAAQ,IAAI,IAAI,QAAQ,cAAc,IAAI,QAAQ,YAAY,cAAc,GAAG,KAAK,CAAC;AACzF,UAAI,SAAS,IAAI,IAAI,QAAQ,YAAY,IAAI,OAAO,EAAE;AAAA,IACxD;AAAA,IAEA,MAAM,KAA8B;AAClC,YAAM,WAAW,IAAI,UAAU,IAAI,UAAU,IAAI,YAAY;AAC7D,UAAI,QAAQ,IAAI,IAAI,QAAQ,kBAAkB,QAAQ,IAAI;AAAA,IAC5D;AAAA,IAEA,QAAQ,OAAc,KAA8B;AAClD,YAAM,WAAW,KAAK,IAAI,IAAI,IAAI;AAClC,UAAI,SAAS,IAAI,IAAI,QAAQ,iBAAiB,QAAQ,OAAO,MAAM,OAAO,EAAE;AAAA,IAC9E;AAAA,IAEA,QAAQ,OAAc,KAA8B;AAClD,YAAM,WAAW,KAAK,IAAI,IAAI,IAAI;AAClC,UAAI,QAAQ,IAAI,IAAI,QAAQ,mBAAmB,QAAQ,OAAO,MAAM,OAAO,EAAE;AAAA,IAC/E;AAAA,IAEA,cAAc,OAAoB,KAAiC;AACjE,UAAI,iBAAiB;AACnB,YAAI,SAAS,iBAAiB,MAAM,IAAI,IAAI,EAAE,OAAO,MAAM,MAAM,CAAC;AAAA,MACpE;AACA,aAAO;AAAA,IACT;AAAA,IAEA,WAAW,MAAM,QAAQ,KAA8B;AACrD,UAAI,cAAc;AAChB,YAAI,QAAQ,IAAI,IAAI,QAAQ,gBAAgB,KAAK,IAAI,EAAE;AACvD,YAAI,SAAS,IAAI,IAAI,QAAQ,kBAAkB,EAAE,OAAO,CAAC;AAAA,MAC3D;AAAA,IACF;AAAA,IAEA,aAAa,MAAM,QAAQ,KAA8B;AACvD,UAAI,cAAc;AAChB,YAAI,SAAS,IAAI,IAAI,QAAQ,kBAAkB,KAAK,IAAI,IAAI,EAAE,OAAO,CAAC;AAAA,MACxE;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/middleware/logging.ts"],"sourcesContent":["/**\n * @fileoverview Logging middleware for request/response visibility.\n *\n * Provides configurable logging for LLM, embedding, and image operations,\n * including timing, error tracking, and optional event logging.\n *\n * @module middleware/logging\n */\n\nimport type { Middleware, MiddlewareContext, StreamContext } from '../types/middleware.ts';\nimport type { StreamEvent } from '../types/stream.ts';\n\n/**\n * Log levels for filtering output.\n */\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\n/**\n * Options for logging middleware.\n */\nexport interface LoggingOptions {\n /**\n * Minimum log level to output.\n * @default 'info'\n */\n level?: LogLevel;\n\n /**\n * Log individual stream events.\n * @default false\n */\n logStreamEvents?: boolean;\n\n /**\n * Log tool calls and results.\n * @default true\n */\n logToolCalls?: boolean;\n\n /**\n * Custom logger function. If not provided, uses console.log.\n * @param level - The log level\n * @param message - The log message\n * @param data - Optional additional data\n */\n logger?(level: LogLevel, message: string, data?: Record<string, unknown>): void;\n\n /**\n * Prefix for all log messages.\n * @default '[PP]'\n */\n prefix?: string;\n}\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\n/**\n * Creates a logging middleware for visibility into request lifecycle.\n *\n * This middleware logs the start, end, and errors of requests,\n * with optional logging of stream events and tool calls.\n *\n * @param options - Configuration options\n * @returns A middleware that logs request lifecycle events\n *\n * @example\n * ```typescript\n * import { llm } from '@providerprotocol/ai';\n * import { loggingMiddleware } from '@providerprotocol/ai/middleware/logging';\n * import { anthropic } from '@providerprotocol/ai/anthropic';\n *\n * const model = llm({\n * model: anthropic('claude-sonnet-4-20250514'),\n * middleware: [loggingMiddleware({ level: 'debug' })],\n * });\n *\n * // Logs: [PP] [anthropic] Starting llm request (streaming)\n * // Logs: [PP] [anthropic] Completed in 1234ms\n * const result = await model.generate('Hello');\n * ```\n */\nexport function loggingMiddleware(options: LoggingOptions = {}): Middleware {\n const {\n level = 'info',\n logStreamEvents = false,\n logToolCalls = true,\n logger,\n prefix = '[PP]',\n } = options;\n\n const minLevel = LOG_LEVELS[level];\n\n const log = (logLevel: LogLevel, message: string, data?: Record<string, unknown>) => {\n if (LOG_LEVELS[logLevel] < minLevel) {\n return;\n }\n\n const fullMessage = `${prefix} ${message}`;\n\n if (logger) {\n logger(logLevel, fullMessage, data);\n } else {\n const consoleMethod = logLevel === 'error' ? console.error : logLevel === 'warn' ? console.warn : console.log;\n if (data) {\n consoleMethod(fullMessage, data);\n } else {\n consoleMethod(fullMessage);\n }\n }\n };\n\n return {\n name: 'logging',\n\n onStart(ctx: MiddlewareContext): void {\n const streamingLabel = ctx.streaming ? '(streaming)' : '';\n log('info', `[${ctx.provider}] Starting ${ctx.modality} request ${streamingLabel}`.trim());\n log('debug', `[${ctx.provider}] Model: ${ctx.modelId}`);\n },\n\n onEnd(ctx: MiddlewareContext): void {\n const duration = ctx.endTime ? ctx.endTime - ctx.startTime : 0;\n log('info', `[${ctx.provider}] Completed in ${duration}ms`);\n },\n\n onError(error: Error, ctx: MiddlewareContext): void {\n const duration = Date.now() - ctx.startTime;\n log('error', `[${ctx.provider}] Error after ${duration}ms: ${error.message}`);\n },\n\n onAbort(error: Error, ctx: MiddlewareContext): void {\n const duration = Date.now() - ctx.startTime;\n log('warn', `[${ctx.provider}] Aborted after ${duration}ms: ${error.message}`);\n },\n\n onRetry(attempt: number, error: Error, ctx: MiddlewareContext): void {\n log('warn', `[${ctx.provider}] Retry attempt ${attempt}: ${error.message}`);\n },\n\n onStreamEvent(event: StreamEvent, ctx: StreamContext): StreamEvent {\n if (logStreamEvents) {\n log('debug', `Stream event: ${event.type}`, { index: event.index });\n }\n return event;\n },\n\n onToolCall(tool, params, ctx: MiddlewareContext): void {\n if (logToolCalls) {\n log('info', `[${ctx.provider}] Tool call: ${tool.name}`);\n log('debug', `[${ctx.provider}] Tool params:`, { params });\n }\n },\n\n onToolResult(tool, result, ctx: MiddlewareContext): void {\n if (logToolCalls) {\n log('debug', `[${ctx.provider}] Tool result: ${tool.name}`, { result });\n }\n },\n };\n}\n"],"mappings":";AAsDA,IAAM,aAAuC;AAAA,EAC3C,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;AA2BO,SAAS,kBAAkB,UAA0B,CAAC,GAAe;AAC1E,QAAM;AAAA,IACJ,QAAQ;AAAA,IACR,kBAAkB;AAAA,IAClB,eAAe;AAAA,IACf;AAAA,IACA,SAAS;AAAA,EACX,IAAI;AAEJ,QAAM,WAAW,WAAW,KAAK;AAEjC,QAAM,MAAM,CAAC,UAAoB,SAAiB,SAAmC;AACnF,QAAI,WAAW,QAAQ,IAAI,UAAU;AACnC;AAAA,IACF;AAEA,UAAM,cAAc,GAAG,MAAM,IAAI,OAAO;AAExC,QAAI,QAAQ;AACV,aAAO,UAAU,aAAa,IAAI;AAAA,IACpC,OAAO;AACL,YAAM,gBAAgB,aAAa,UAAU,QAAQ,QAAQ,aAAa,SAAS,QAAQ,OAAO,QAAQ;AAC1G,UAAI,MAAM;AACR,sBAAc,aAAa,IAAI;AAAA,MACjC,OAAO;AACL,sBAAc,WAAW;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,QAAQ,KAA8B;AACpC,YAAM,iBAAiB,IAAI,YAAY,gBAAgB;AACvD,UAAI,QAAQ,IAAI,IAAI,QAAQ,cAAc,IAAI,QAAQ,YAAY,cAAc,GAAG,KAAK,CAAC;AACzF,UAAI,SAAS,IAAI,IAAI,QAAQ,YAAY,IAAI,OAAO,EAAE;AAAA,IACxD;AAAA,IAEA,MAAM,KAA8B;AAClC,YAAM,WAAW,IAAI,UAAU,IAAI,UAAU,IAAI,YAAY;AAC7D,UAAI,QAAQ,IAAI,IAAI,QAAQ,kBAAkB,QAAQ,IAAI;AAAA,IAC5D;AAAA,IAEA,QAAQ,OAAc,KAA8B;AAClD,YAAM,WAAW,KAAK,IAAI,IAAI,IAAI;AAClC,UAAI,SAAS,IAAI,IAAI,QAAQ,iBAAiB,QAAQ,OAAO,MAAM,OAAO,EAAE;AAAA,IAC9E;AAAA,IAEA,QAAQ,OAAc,KAA8B;AAClD,YAAM,WAAW,KAAK,IAAI,IAAI,IAAI;AAClC,UAAI,QAAQ,IAAI,IAAI,QAAQ,mBAAmB,QAAQ,OAAO,MAAM,OAAO,EAAE;AAAA,IAC/E;AAAA,IAEA,QAAQ,SAAiB,OAAc,KAA8B;AACnE,UAAI,QAAQ,IAAI,IAAI,QAAQ,mBAAmB,OAAO,KAAK,MAAM,OAAO,EAAE;AAAA,IAC5E;AAAA,IAEA,cAAc,OAAoB,KAAiC;AACjE,UAAI,iBAAiB;AACnB,YAAI,SAAS,iBAAiB,MAAM,IAAI,IAAI,EAAE,OAAO,MAAM,MAAM,CAAC;AAAA,MACpE;AACA,aAAO;AAAA,IACT;AAAA,IAEA,WAAW,MAAM,QAAQ,KAA8B;AACrD,UAAI,cAAc;AAChB,YAAI,QAAQ,IAAI,IAAI,QAAQ,gBAAgB,KAAK,IAAI,EAAE;AACvD,YAAI,SAAS,IAAI,IAAI,QAAQ,kBAAkB,EAAE,OAAO,CAAC;AAAA,MAC3D;AAAA,IACF;AAAA,IAEA,aAAa,MAAM,QAAQ,KAA8B;AACvD,UAAI,cAAc;AAChB,YAAI,SAAS,IAAI,IAAI,QAAQ,kBAAkB,KAAK,IAAI,IAAI,EAAE,OAAO,CAAC;AAAA,MACxE;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { M as Middleware } from '../../llm-
|
|
2
|
-
import { E as EventDelta, S as StreamEvent } from '../../stream-
|
|
3
|
-
import '../../tool-
|
|
1
|
+
import { M as Middleware } from '../../llm-BWLaTzzY.js';
|
|
2
|
+
import { E as EventDelta, S as StreamEvent } from '../../stream-bBd_4Ipu.js';
|
|
3
|
+
import '../../tool-BmAfKNBq.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @fileoverview Parsed object middleware for incremental JSON parsing.
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "../../chunk-I53CI6ZZ.js";
|
|
4
4
|
import {
|
|
5
5
|
StreamEventType
|
|
6
|
-
} from "../../chunk-
|
|
6
|
+
} from "../../chunk-F5ENANMJ.js";
|
|
7
7
|
|
|
8
8
|
// src/middleware/parsed-object.ts
|
|
9
9
|
var ACCUMULATED_TEXT_KEY = "parsedObject:text";
|
|
@@ -64,6 +64,10 @@ function parsedObjectMiddleware(options = {}) {
|
|
|
64
64
|
onStreamEnd(ctx) {
|
|
65
65
|
ctx.state.delete(ACCUMULATED_TEXT_KEY);
|
|
66
66
|
ctx.state.delete(ACCUMULATED_ARGS_KEY);
|
|
67
|
+
},
|
|
68
|
+
onRetry(_attempt, _error, ctx) {
|
|
69
|
+
ctx.state.delete(ACCUMULATED_TEXT_KEY);
|
|
70
|
+
ctx.state.delete(ACCUMULATED_ARGS_KEY);
|
|
67
71
|
}
|
|
68
72
|
};
|
|
69
73
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/middleware/parsed-object.ts"],"sourcesContent":["/**\n * @fileoverview Parsed object middleware for incremental JSON parsing.\n *\n * This middleware parses partial JSON from ObjectDelta and ToolCallDelta\n * stream events, providing incremental structured data during streaming.\n *\n * @module middleware/parsed-object\n */\n\nimport type { Middleware, StreamContext } from '../types/middleware.ts';\nimport type { EventDelta, StreamEvent } from '../types/stream.ts';\nimport { StreamEventType } from '../types/stream.ts';\nimport { parsePartialJson } from '../utils/partial-json.ts';\n\n/**\n * Event delta with parsed JSON data.\n * Extended by parsedObjectMiddleware when parsing is enabled.\n */\nexport interface ParsedEventDelta extends EventDelta {\n /** Incrementally parsed JSON value */\n parsed?: unknown;\n}\n\n/**\n * Stream event with parsed JSON data.\n * Returned by parsedObjectMiddleware for ObjectDelta and ToolCallDelta events.\n */\nexport interface ParsedStreamEvent extends Omit<StreamEvent, 'delta'> {\n delta: ParsedEventDelta;\n}\n\n/**\n * Options for parsed object middleware.\n */\nexport interface ParsedObjectOptions {\n /**\n * Parse ObjectDelta events (structured output responses).\n * @default true\n */\n parseObjects?: boolean;\n\n /**\n * Parse ToolCallDelta events (tool call arguments).\n * @default true\n */\n parseToolCalls?: boolean;\n}\n\n/**\n * Creates a middleware that parses partial JSON from stream events.\n *\n * This middleware accumulates text from ObjectDelta events and tool\n * argument JSON from ToolCallDelta events, then parses them incrementally\n * using partial JSON parsing. The parsed result is added to the event's\n * `parsed` field.\n *\n * @param options - Configuration options\n * @returns A middleware that adds parsed JSON to stream events\n *\n * @example\n * ```typescript\n * import { llm } from '@providerprotocol/ai';\n * import { parsedObjectMiddleware } from '@providerprotocol/ai/middleware/parsed-object';\n * import { anthropic } from '@providerprotocol/ai/anthropic';\n *\n * const model = llm({\n * model: anthropic('claude-sonnet-4-20250514'),\n * structure: mySchema,\n * middleware: [parsedObjectMiddleware()],\n * });\n *\n * for await (const event of model.stream('Extract data from this text')) {\n * if (event.type === 'object_delta') {\n * // event.delta.parsed contains incrementally parsed object\n * console.log(event.delta.parsed);\n * }\n * }\n * ```\n */\n/** State key for accumulated object text */\nconst ACCUMULATED_TEXT_KEY = 'parsedObject:text';\n/** State key for accumulated tool arguments */\nconst ACCUMULATED_ARGS_KEY = 'parsedObject:args';\n\n/**\n * Gets or creates the accumulated text map from state.\n */\nfunction getAccumulatedText(state: Map<string, unknown>): Map<number, string> {\n let map = state.get(ACCUMULATED_TEXT_KEY) as Map<number, string> | undefined;\n if (!map) {\n map = new Map();\n state.set(ACCUMULATED_TEXT_KEY, map);\n }\n return map;\n}\n\n/**\n * Gets or creates the accumulated args map from state.\n */\nfunction getAccumulatedArgs(state: Map<string, unknown>): Map<number, string> {\n let map = state.get(ACCUMULATED_ARGS_KEY) as Map<number, string> | undefined;\n if (!map) {\n map = new Map();\n state.set(ACCUMULATED_ARGS_KEY, map);\n }\n return map;\n}\n\nexport function parsedObjectMiddleware(options: ParsedObjectOptions = {}): Middleware {\n const { parseObjects = true, parseToolCalls = true } = options;\n\n return {\n name: 'parsed-object',\n\n onStreamEvent(event: StreamEvent, ctx: StreamContext): StreamEvent | StreamEvent[] | null {\n if (parseObjects && event.type === StreamEventType.ObjectDelta) {\n const accumulatedText = getAccumulatedText(ctx.state);\n const current = accumulatedText.get(event.index) ?? '';\n const newText = current + (event.delta.text ?? '');\n accumulatedText.set(event.index, newText);\n\n const parseResult = parsePartialJson(newText);\n\n const parsedEvent: ParsedStreamEvent = {\n ...event,\n delta: {\n ...event.delta,\n parsed: parseResult.value,\n },\n };\n return parsedEvent as StreamEvent;\n }\n\n if (parseToolCalls && event.type === StreamEventType.ToolCallDelta) {\n const accumulatedArgs = getAccumulatedArgs(ctx.state);\n const current = accumulatedArgs.get(event.index) ?? '';\n const newJson = current + (event.delta.argumentsJson ?? '');\n accumulatedArgs.set(event.index, newJson);\n\n const parseResult = parsePartialJson(newJson);\n\n const parsedEvent: ParsedStreamEvent = {\n ...event,\n delta: {\n ...event.delta,\n parsed: parseResult.value,\n },\n };\n return parsedEvent as StreamEvent;\n }\n\n return event;\n },\n\n onStreamEnd(ctx: StreamContext): void {\n
|
|
1
|
+
{"version":3,"sources":["../../../src/middleware/parsed-object.ts"],"sourcesContent":["/**\n * @fileoverview Parsed object middleware for incremental JSON parsing.\n *\n * This middleware parses partial JSON from ObjectDelta and ToolCallDelta\n * stream events, providing incremental structured data during streaming.\n *\n * @module middleware/parsed-object\n */\n\nimport type { Middleware, MiddlewareContext, StreamContext } from '../types/middleware.ts';\nimport type { EventDelta, StreamEvent } from '../types/stream.ts';\nimport { StreamEventType } from '../types/stream.ts';\nimport { parsePartialJson } from '../utils/partial-json.ts';\n\n/**\n * Event delta with parsed JSON data.\n * Extended by parsedObjectMiddleware when parsing is enabled.\n */\nexport interface ParsedEventDelta extends EventDelta {\n /** Incrementally parsed JSON value */\n parsed?: unknown;\n}\n\n/**\n * Stream event with parsed JSON data.\n * Returned by parsedObjectMiddleware for ObjectDelta and ToolCallDelta events.\n */\nexport interface ParsedStreamEvent extends Omit<StreamEvent, 'delta'> {\n delta: ParsedEventDelta;\n}\n\n/**\n * Options for parsed object middleware.\n */\nexport interface ParsedObjectOptions {\n /**\n * Parse ObjectDelta events (structured output responses).\n * @default true\n */\n parseObjects?: boolean;\n\n /**\n * Parse ToolCallDelta events (tool call arguments).\n * @default true\n */\n parseToolCalls?: boolean;\n}\n\n/**\n * Creates a middleware that parses partial JSON from stream events.\n *\n * This middleware accumulates text from ObjectDelta events and tool\n * argument JSON from ToolCallDelta events, then parses them incrementally\n * using partial JSON parsing. The parsed result is added to the event's\n * `parsed` field.\n *\n * @param options - Configuration options\n * @returns A middleware that adds parsed JSON to stream events\n *\n * @example\n * ```typescript\n * import { llm } from '@providerprotocol/ai';\n * import { parsedObjectMiddleware } from '@providerprotocol/ai/middleware/parsed-object';\n * import { anthropic } from '@providerprotocol/ai/anthropic';\n *\n * const model = llm({\n * model: anthropic('claude-sonnet-4-20250514'),\n * structure: mySchema,\n * middleware: [parsedObjectMiddleware()],\n * });\n *\n * for await (const event of model.stream('Extract data from this text')) {\n * if (event.type === 'object_delta') {\n * // event.delta.parsed contains incrementally parsed object\n * console.log(event.delta.parsed);\n * }\n * }\n * ```\n */\n/** State key for accumulated object text */\nconst ACCUMULATED_TEXT_KEY = 'parsedObject:text';\n/** State key for accumulated tool arguments */\nconst ACCUMULATED_ARGS_KEY = 'parsedObject:args';\n\n/**\n * Gets or creates the accumulated text map from state.\n */\nfunction getAccumulatedText(state: Map<string, unknown>): Map<number, string> {\n let map = state.get(ACCUMULATED_TEXT_KEY) as Map<number, string> | undefined;\n if (!map) {\n map = new Map();\n state.set(ACCUMULATED_TEXT_KEY, map);\n }\n return map;\n}\n\n/**\n * Gets or creates the accumulated args map from state.\n */\nfunction getAccumulatedArgs(state: Map<string, unknown>): Map<number, string> {\n let map = state.get(ACCUMULATED_ARGS_KEY) as Map<number, string> | undefined;\n if (!map) {\n map = new Map();\n state.set(ACCUMULATED_ARGS_KEY, map);\n }\n return map;\n}\n\nexport function parsedObjectMiddleware(options: ParsedObjectOptions = {}): Middleware {\n const { parseObjects = true, parseToolCalls = true } = options;\n\n return {\n name: 'parsed-object',\n\n onStreamEvent(event: StreamEvent, ctx: StreamContext): StreamEvent | StreamEvent[] | null {\n if (parseObjects && event.type === StreamEventType.ObjectDelta) {\n const accumulatedText = getAccumulatedText(ctx.state);\n const current = accumulatedText.get(event.index) ?? '';\n const newText = current + (event.delta.text ?? '');\n accumulatedText.set(event.index, newText);\n\n const parseResult = parsePartialJson(newText);\n\n const parsedEvent: ParsedStreamEvent = {\n ...event,\n delta: {\n ...event.delta,\n parsed: parseResult.value,\n },\n };\n return parsedEvent as StreamEvent;\n }\n\n if (parseToolCalls && event.type === StreamEventType.ToolCallDelta) {\n const accumulatedArgs = getAccumulatedArgs(ctx.state);\n const current = accumulatedArgs.get(event.index) ?? '';\n const newJson = current + (event.delta.argumentsJson ?? '');\n accumulatedArgs.set(event.index, newJson);\n\n const parseResult = parsePartialJson(newJson);\n\n const parsedEvent: ParsedStreamEvent = {\n ...event,\n delta: {\n ...event.delta,\n parsed: parseResult.value,\n },\n };\n return parsedEvent as StreamEvent;\n }\n\n return event;\n },\n\n onStreamEnd(ctx: StreamContext): void {\n ctx.state.delete(ACCUMULATED_TEXT_KEY);\n ctx.state.delete(ACCUMULATED_ARGS_KEY);\n },\n\n onRetry(_attempt: number, _error: Error, ctx: MiddlewareContext): void {\n ctx.state.delete(ACCUMULATED_TEXT_KEY);\n ctx.state.delete(ACCUMULATED_ARGS_KEY);\n },\n };\n}\n"],"mappings":";;;;;;;;AAgFA,IAAM,uBAAuB;AAE7B,IAAM,uBAAuB;AAK7B,SAAS,mBAAmB,OAAkD;AAC5E,MAAI,MAAM,MAAM,IAAI,oBAAoB;AACxC,MAAI,CAAC,KAAK;AACR,UAAM,oBAAI,IAAI;AACd,UAAM,IAAI,sBAAsB,GAAG;AAAA,EACrC;AACA,SAAO;AACT;AAKA,SAAS,mBAAmB,OAAkD;AAC5E,MAAI,MAAM,MAAM,IAAI,oBAAoB;AACxC,MAAI,CAAC,KAAK;AACR,UAAM,oBAAI,IAAI;AACd,UAAM,IAAI,sBAAsB,GAAG;AAAA,EACrC;AACA,SAAO;AACT;AAEO,SAAS,uBAAuB,UAA+B,CAAC,GAAe;AACpF,QAAM,EAAE,eAAe,MAAM,iBAAiB,KAAK,IAAI;AAEvD,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,cAAc,OAAoB,KAAwD;AACxF,UAAI,gBAAgB,MAAM,SAAS,gBAAgB,aAAa;AAC9D,cAAM,kBAAkB,mBAAmB,IAAI,KAAK;AACpD,cAAM,UAAU,gBAAgB,IAAI,MAAM,KAAK,KAAK;AACpD,cAAM,UAAU,WAAW,MAAM,MAAM,QAAQ;AAC/C,wBAAgB,IAAI,MAAM,OAAO,OAAO;AAExC,cAAM,cAAc,iBAAiB,OAAO;AAE5C,cAAM,cAAiC;AAAA,UACrC,GAAG;AAAA,UACH,OAAO;AAAA,YACL,GAAG,MAAM;AAAA,YACT,QAAQ,YAAY;AAAA,UACtB;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAEA,UAAI,kBAAkB,MAAM,SAAS,gBAAgB,eAAe;AAClE,cAAM,kBAAkB,mBAAmB,IAAI,KAAK;AACpD,cAAM,UAAU,gBAAgB,IAAI,MAAM,KAAK,KAAK;AACpD,cAAM,UAAU,WAAW,MAAM,MAAM,iBAAiB;AACxD,wBAAgB,IAAI,MAAM,OAAO,OAAO;AAExC,cAAM,cAAc,iBAAiB,OAAO;AAE5C,cAAM,cAAiC;AAAA,UACrC,GAAG;AAAA,UACH,OAAO;AAAA,YACL,GAAG,MAAM;AAAA,YACT,QAAQ,YAAY;AAAA,UACtB;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAEA,aAAO;AAAA,IACT;AAAA,IAEA,YAAY,KAA0B;AACpC,UAAI,MAAM,OAAO,oBAAoB;AACrC,UAAI,MAAM,OAAO,oBAAoB;AAAA,IACvC;AAAA,IAEA,QAAQ,UAAkB,QAAe,KAA8B;AACrE,UAAI,MAAM,OAAO,oBAAoB;AACrC,UAAI,MAAM,OAAO,oBAAoB;AAAA,IACvC;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { T as Thread, a as ThreadJSON, M as Middleware } from '../../llm-
|
|
2
|
-
import { T as Turn } from '../../stream-
|
|
3
|
-
import '../../tool-
|
|
1
|
+
import { T as Thread, a as ThreadJSON, M as Middleware } from '../../llm-BWLaTzzY.js';
|
|
2
|
+
import { T as Turn } from '../../stream-bBd_4Ipu.js';
|
|
3
|
+
import '../../tool-BmAfKNBq.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @fileoverview Persistence middleware for thread storage.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/middleware/persistence.ts"],"sourcesContent":["/**\n * @fileoverview Persistence middleware for thread storage.\n *\n * Loads a conversation thread before execution and saves it after completion.\n * Designed for LLM requests; other modalities are ignored.\n *\n * @module middleware/persistence\n */\n\nimport type { Middleware, MiddlewareContext } from '../types/middleware.ts';\nimport type { LLMRequest } from '../types/llm.ts';\nimport type { Turn } from '../types/turn.ts';\nimport type { ThreadJSON } from '../types/thread.ts';\nimport { Thread } from '../types/thread.ts';\nimport { toError } from '../utils/error.ts';\n\nconst STATE_KEY_THREAD = 'persistence:thread';\nconst STATE_KEY_ID = 'persistence:id';\nconst TURN_START_INDEX_KEY = 'llm:turnStartIndex';\n\nconst isLLMRequest = (request: MiddlewareContext['request']): request is LLMRequest => (\n 'messages' in request\n);\n\n/**\n * Load result for persistence adapters.\n */\nexport type PersistenceLoadResult = Thread | ThreadJSON | null | undefined;\n\n/**\n * Adapter configuration for persistence middleware.\n */\nexport interface PersistenceAdapterConfig {\n /**\n * Unique identifier for the conversation.\n */\n id: string;\n\n /**\n * Loads a thread for the provided ID.\n *\n * Return a Thread instance, ThreadJSON, or null/undefined for new threads.\n *\n * @param id - Conversation identifier\n */\n load(id: string): Promise<PersistenceLoadResult>;\n\n /**\n * Persists the thread after a turn completes.\n *\n * @param id - Conversation identifier\n * @param thread - Updated thread instance\n * @param turn - Completed turn (undefined if not available)\n */\n save(id: string, thread: Thread, turn: Turn | undefined): Promise<void>;\n}\n\n/**\n * Persistence adapter implementation.\n *\n * Provides a thin wrapper around load/save callbacks.\n */\nexport class PersistenceAdapter {\n readonly id: string;\n\n private readonly loader: PersistenceAdapterConfig['load'];\n\n private readonly saver: PersistenceAdapterConfig['save'];\n\n /**\n * Creates a persistence adapter.\n *\n * @param config - Adapter configuration\n */\n constructor(config: PersistenceAdapterConfig) {\n this.id = config.id;\n this.loader = config.load;\n this.saver = config.save;\n }\n\n /**\n * Loads a thread for the provided ID.\n *\n * @param id - Conversation identifier\n */\n async load(id: string): Promise<PersistenceLoadResult> {\n return this.loader(id);\n }\n\n /**\n * Persists the thread after a turn completes.\n *\n * @param id - Conversation identifier\n * @param thread - Updated thread instance\n * @param turn - Completed turn (undefined if not available)\n */\n async save(id: string, thread: Thread, turn: Turn | undefined): Promise<void> {\n await this.saver(id, thread, turn);\n }\n}\n\n/**\n * Options for persistence middleware.\n */\nexport interface PersistenceOptions {\n /**\n * Adapter instance for loading and saving threads.\n */\n adapter: PersistenceAdapter;\n}\n\n/**\n * Gets the loaded thread from middleware state.\n *\n * @param state - Middleware state map\n * @returns Thread instance or undefined if not set\n */\nexport function getThread(state: Map<string, unknown>): Thread | undefined {\n return state.get(STATE_KEY_THREAD) as Thread | undefined;\n}\n\n/**\n * Gets the conversation ID from middleware state.\n *\n * @param state - Middleware state map\n * @returns Conversation ID or undefined if not set\n */\nexport function getThreadId(state: Map<string, unknown>): string | undefined {\n return state.get(STATE_KEY_ID) as string | undefined;\n}\n\n/**\n * Creates persistence middleware for thread storage.\n *\n * Loads a thread before requests and saves it after completion. The middleware\n * prepends loaded messages that are not already present in the request so turn\n * slicing excludes persisted history without duplicating explicit history.\n *\n * @param options - Middleware configuration\n * @returns Middleware instance\n *\n * @example\n * ```typescript\n * import { llm } from '@providerprotocol/ai';\n * import { anthropic } from '@providerprotocol/ai/anthropic';\n * import { persistenceMiddleware, PersistenceAdapter } from '@providerprotocol/ai/middleware/persistence';\n *\n * const model = llm({\n * model: anthropic('claude-sonnet-4-20250514'),\n * system: 'You are a helpful assistant.',\n * middleware: [\n * persistenceMiddleware({\n * adapter: new PersistenceAdapter({\n * id: 'conversation-id',\n * load: async (id) => loadThreadFromMemory(id),\n * save: async (id, thread) => saveThreadToMemory(id, thread),\n * }),\n * }),\n * ],\n * });\n * ```\n */\nexport function persistenceMiddleware(options: PersistenceOptions): Middleware {\n const { adapter } = options;\n\n if (!adapter?.id) {\n throw new Error('persistenceMiddleware requires an adapter with a non-empty id');\n }\n if (typeof adapter.load !== 'function' || typeof adapter.save !== 'function') {\n throw new Error('persistenceMiddleware requires an adapter with load and save functions');\n }\n\n return {\n name: 'persistence',\n\n async onRequest(ctx: MiddlewareContext): Promise<void> {\n if (ctx.modality !== 'llm' || !isLLMRequest(ctx.request)) {\n return;\n }\n\n ctx.state.set(STATE_KEY_ID, adapter.id);\n\n let loaded: PersistenceLoadResult;\n try {\n loaded = await adapter.load(adapter.id);\n } catch (error) {\n const err = toError(error);\n throw new Error(`Persistence adapter failed to load thread \"${adapter.id}\": ${err.message}`, {\n cause: err,\n });\n }\n\n let thread: Thread;\n if (!loaded) {\n thread = new Thread();\n } else if (loaded instanceof Thread) {\n thread = loaded;\n } else {\n try {\n thread = Thread.fromJSON(loaded);\n } catch (error) {\n const err = toError(error);\n throw new Error(`Persistence adapter failed to deserialize thread \"${adapter.id}\": ${err.message}`, {\n cause: err,\n });\n }\n }\n\n ctx.state.set(STATE_KEY_THREAD, thread);\n\n if (thread.messages.length > 0) {\n const existingIds = new Set(ctx.request.messages.map((message) => message.id));\n const missing = thread.messages.filter((message) => !existingIds.has(message.id));\n if (missing.length > 0) {\n ctx.request.messages.unshift(...missing);\n const currentIndex = ctx.state.get(TURN_START_INDEX_KEY);\n const nextIndex = (typeof currentIndex === 'number' ? currentIndex : 0) + missing.length;\n ctx.state.set(TURN_START_INDEX_KEY, nextIndex);\n }\n }\n },\n\n async onTurn(turn: Turn, ctx: MiddlewareContext): Promise<void> {\n if (ctx.modality !== 'llm') {\n return;\n }\n\n const thread = getThread(ctx.state);\n if (!thread) {\n return;\n }\n\n if (isLLMRequest(ctx.request)) {\n const turnMessageIds = new Set(turn.messages.map((message) => message.id));\n const existingIds = new Set(thread.messages.map((message) => message.id));\n for (const message of ctx.request.messages) {\n if (turnMessageIds.has(message.id)) {\n continue;\n }\n if (!existingIds.has(message.id)) {\n thread.push(message);\n existingIds.add(message.id);\n }\n }\n }\n\n thread.append(turn);\n\n try {\n await adapter.save(adapter.id, thread, turn);\n } catch (error) {\n const err = toError(error);\n throw new Error(`Persistence adapter failed to save thread \"${adapter.id}\": ${err.message}`, {\n cause: err,\n });\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/middleware/persistence.ts"],"sourcesContent":["/**\n * @fileoverview Persistence middleware for thread storage.\n *\n * Loads a conversation thread before execution and saves it after completion.\n * Designed for LLM requests; other modalities are ignored.\n *\n * @module middleware/persistence\n */\n\nimport type { Middleware, MiddlewareContext } from '../types/middleware.ts';\nimport type { LLMRequest } from '../types/llm.ts';\nimport type { Turn } from '../types/turn.ts';\nimport type { ThreadJSON } from '../types/thread.ts';\nimport { Thread } from '../types/thread.ts';\nimport { toError } from '../utils/error.ts';\n\nconst STATE_KEY_THREAD = 'persistence:thread';\nconst STATE_KEY_ID = 'persistence:id';\nconst TURN_START_INDEX_KEY = 'llm:turnStartIndex';\n\nconst isLLMRequest = (request: MiddlewareContext['request']): request is LLMRequest => (\n 'messages' in request\n);\n\n/**\n * Load result for persistence adapters.\n */\nexport type PersistenceLoadResult = Thread | ThreadJSON | null | undefined;\n\n/**\n * Adapter configuration for persistence middleware.\n */\nexport interface PersistenceAdapterConfig {\n /**\n * Unique identifier for the conversation.\n */\n id: string;\n\n /**\n * Loads a thread for the provided ID.\n *\n * Return a Thread instance, ThreadJSON, or null/undefined for new threads.\n *\n * @param id - Conversation identifier\n */\n load(id: string): Promise<PersistenceLoadResult>;\n\n /**\n * Persists the thread after a turn completes.\n *\n * @param id - Conversation identifier\n * @param thread - Updated thread instance\n * @param turn - Completed turn (undefined if not available)\n */\n save(id: string, thread: Thread, turn: Turn | undefined): Promise<void>;\n}\n\n/**\n * Persistence adapter implementation.\n *\n * Provides a thin wrapper around load/save callbacks.\n */\nexport class PersistenceAdapter {\n readonly id: string;\n\n private readonly loader: PersistenceAdapterConfig['load'];\n\n private readonly saver: PersistenceAdapterConfig['save'];\n\n /**\n * Creates a persistence adapter.\n *\n * @param config - Adapter configuration\n */\n constructor(config: PersistenceAdapterConfig) {\n this.id = config.id;\n this.loader = config.load;\n this.saver = config.save;\n }\n\n /**\n * Loads a thread for the provided ID.\n *\n * @param id - Conversation identifier\n */\n async load(id: string): Promise<PersistenceLoadResult> {\n return this.loader(id);\n }\n\n /**\n * Persists the thread after a turn completes.\n *\n * @param id - Conversation identifier\n * @param thread - Updated thread instance\n * @param turn - Completed turn (undefined if not available)\n */\n async save(id: string, thread: Thread, turn: Turn | undefined): Promise<void> {\n await this.saver(id, thread, turn);\n }\n}\n\n/**\n * Options for persistence middleware.\n */\nexport interface PersistenceOptions {\n /**\n * Adapter instance for loading and saving threads.\n */\n adapter: PersistenceAdapter;\n}\n\n/**\n * Gets the loaded thread from middleware state.\n *\n * @param state - Middleware state map\n * @returns Thread instance or undefined if not set\n */\nexport function getThread(state: Map<string, unknown>): Thread | undefined {\n return state.get(STATE_KEY_THREAD) as Thread | undefined;\n}\n\n/**\n * Gets the conversation ID from middleware state.\n *\n * @param state - Middleware state map\n * @returns Conversation ID or undefined if not set\n */\nexport function getThreadId(state: Map<string, unknown>): string | undefined {\n return state.get(STATE_KEY_ID) as string | undefined;\n}\n\n/**\n * Creates persistence middleware for thread storage.\n *\n * Loads a thread before requests and saves it after completion. The middleware\n * prepends loaded messages that are not already present in the request so turn\n * slicing excludes persisted history without duplicating explicit history.\n *\n * @param options - Middleware configuration\n * @returns Middleware instance\n *\n * @example\n * ```typescript\n * import { llm } from '@providerprotocol/ai';\n * import { anthropic } from '@providerprotocol/ai/anthropic';\n * import { persistenceMiddleware, PersistenceAdapter } from '@providerprotocol/ai/middleware/persistence';\n *\n * const model = llm({\n * model: anthropic('claude-sonnet-4-20250514'),\n * system: 'You are a helpful assistant.',\n * middleware: [\n * persistenceMiddleware({\n * adapter: new PersistenceAdapter({\n * id: 'conversation-id',\n * load: async (id) => loadThreadFromMemory(id),\n * save: async (id, thread) => saveThreadToMemory(id, thread),\n * }),\n * }),\n * ],\n * });\n * ```\n */\nexport function persistenceMiddleware(options: PersistenceOptions): Middleware {\n const { adapter } = options;\n\n if (!adapter?.id) {\n throw new Error('persistenceMiddleware requires an adapter with a non-empty id');\n }\n if (typeof adapter.load !== 'function' || typeof adapter.save !== 'function') {\n throw new Error('persistenceMiddleware requires an adapter with load and save functions');\n }\n\n return {\n name: 'persistence',\n\n async onRequest(ctx: MiddlewareContext): Promise<void> {\n if (ctx.modality !== 'llm' || !isLLMRequest(ctx.request)) {\n return;\n }\n\n ctx.state.set(STATE_KEY_ID, adapter.id);\n\n let loaded: PersistenceLoadResult;\n try {\n loaded = await adapter.load(adapter.id);\n } catch (error) {\n const err = toError(error);\n throw new Error(`Persistence adapter failed to load thread \"${adapter.id}\": ${err.message}`, {\n cause: err,\n });\n }\n\n let thread: Thread;\n if (!loaded) {\n thread = new Thread();\n } else if (loaded instanceof Thread) {\n thread = loaded;\n } else {\n try {\n thread = Thread.fromJSON(loaded);\n } catch (error) {\n const err = toError(error);\n throw new Error(`Persistence adapter failed to deserialize thread \"${adapter.id}\": ${err.message}`, {\n cause: err,\n });\n }\n }\n\n ctx.state.set(STATE_KEY_THREAD, thread);\n\n if (thread.messages.length > 0) {\n const existingIds = new Set(ctx.request.messages.map((message) => message.id));\n const missing = thread.messages.filter((message) => !existingIds.has(message.id));\n if (missing.length > 0) {\n ctx.request.messages.unshift(...missing);\n const currentIndex = ctx.state.get(TURN_START_INDEX_KEY);\n const nextIndex = (typeof currentIndex === 'number' ? currentIndex : 0) + missing.length;\n ctx.state.set(TURN_START_INDEX_KEY, nextIndex);\n }\n }\n },\n\n async onTurn(turn: Turn, ctx: MiddlewareContext): Promise<void> {\n if (ctx.modality !== 'llm') {\n return;\n }\n\n const thread = getThread(ctx.state);\n if (!thread) {\n return;\n }\n\n if (isLLMRequest(ctx.request)) {\n const turnMessageIds = new Set(turn.messages.map((message) => message.id));\n const existingIds = new Set(thread.messages.map((message) => message.id));\n for (const message of ctx.request.messages) {\n if (turnMessageIds.has(message.id)) {\n continue;\n }\n if (!existingIds.has(message.id)) {\n thread.push(message);\n existingIds.add(message.id);\n }\n }\n }\n\n thread.append(turn);\n\n try {\n await adapter.save(adapter.id, thread, turn);\n } catch (error) {\n const err = toError(error);\n throw new Error(`Persistence adapter failed to save thread \"${adapter.id}\": ${err.message}`, {\n cause: err,\n });\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { M as Middleware } from '../../llm-BWLaTzzY.js';
|
|
2
|
+
import { E as EventDelta, S as StreamEvent, T as Turn } from '../../stream-bBd_4Ipu.js';
|
|
3
|
+
import '../../tool-BmAfKNBq.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @fileoverview Pipeline middleware for post-turn processing stages.
|
|
7
|
+
*
|
|
8
|
+
* Enables running async tasks (image generation, embedding, slug generation, etc.)
|
|
9
|
+
* after the LLM completes, while streaming progress events to connected clients.
|
|
10
|
+
*
|
|
11
|
+
* @module middleware/pipeline
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Pipeline stage event delta with stage-specific data.
|
|
16
|
+
*/
|
|
17
|
+
interface PipelineStageDelta extends EventDelta {
|
|
18
|
+
/** Stage identifier */
|
|
19
|
+
stage: string;
|
|
20
|
+
/** Stage output payload */
|
|
21
|
+
payload: unknown;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Stream event for pipeline stage progress.
|
|
25
|
+
* Extends StreamEvent with pipeline-specific delta containing stage and payload.
|
|
26
|
+
*/
|
|
27
|
+
interface PipelineStageEvent extends StreamEvent {
|
|
28
|
+
type: 'pipeline_stage';
|
|
29
|
+
delta: PipelineStageDelta;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Creates a pipeline stage stream event.
|
|
33
|
+
*
|
|
34
|
+
* @param stage - The stage identifier
|
|
35
|
+
* @param payload - The stage output payload
|
|
36
|
+
* @returns A pipeline stage event
|
|
37
|
+
*/
|
|
38
|
+
declare function pipelineStageEvent(stage: string, payload: unknown): PipelineStageEvent;
|
|
39
|
+
/**
|
|
40
|
+
* Type guard for PipelineStageEvent.
|
|
41
|
+
*
|
|
42
|
+
* @param event - Stream event to check
|
|
43
|
+
* @returns True if the event is a PipelineStageEvent
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* for await (const event of stream) {
|
|
48
|
+
* if (isPipelineStageEvent(event)) {
|
|
49
|
+
* console.log(event.delta.stage, event.delta.payload);
|
|
50
|
+
* }
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
declare function isPipelineStageEvent(event: StreamEvent): event is PipelineStageEvent;
|
|
55
|
+
/**
|
|
56
|
+
* Emit function provided to pipeline stages.
|
|
57
|
+
*/
|
|
58
|
+
type PipelineEmit = (data: unknown) => void;
|
|
59
|
+
/**
|
|
60
|
+
* A single pipeline stage that runs after turn completion.
|
|
61
|
+
*
|
|
62
|
+
* Stages can mutate the turn object to attach computed properties (like slugs,
|
|
63
|
+
* image URLs, etc.) that will be available in the `.then()` callback. Use a
|
|
64
|
+
* type assertion to add properties:
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* // Define extended turn type
|
|
69
|
+
* interface ExtendedTurn { slug?: string; imageUrl?: string; }
|
|
70
|
+
*
|
|
71
|
+
* // In stage run function
|
|
72
|
+
* run: (turn, emit) => {
|
|
73
|
+
* const slug = generateSlug(turn.data!.title);
|
|
74
|
+
* (turn as Turn<TData> & ExtendedTurn).slug = slug;
|
|
75
|
+
* emit({ slug });
|
|
76
|
+
* }
|
|
77
|
+
*
|
|
78
|
+
* // Access in .then() callback
|
|
79
|
+
* model.stream(prompt).then(turn => {
|
|
80
|
+
* const extended = turn as typeof turn & ExtendedTurn;
|
|
81
|
+
* console.log(extended.slug); // Available!
|
|
82
|
+
* });
|
|
83
|
+
* ```
|
|
84
|
+
*
|
|
85
|
+
* @typeParam TData - Type of the turn's structured data
|
|
86
|
+
*/
|
|
87
|
+
interface PipelineStage<TData = unknown> {
|
|
88
|
+
/** Unique identifier for this stage (used in events) */
|
|
89
|
+
type: string;
|
|
90
|
+
/**
|
|
91
|
+
* Execute this stage.
|
|
92
|
+
*
|
|
93
|
+
* @param turn - The completed turn (can mutate via type assertion to add properties)
|
|
94
|
+
* @param emit - Function to emit progress events to subscribers
|
|
95
|
+
*/
|
|
96
|
+
run: (turn: Turn<TData>, emit: PipelineEmit) => Promise<void> | void;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Stage error details passed to onStageError callback.
|
|
100
|
+
*/
|
|
101
|
+
interface PipelineStageError<TData = unknown> {
|
|
102
|
+
/** The stage that failed */
|
|
103
|
+
stage: PipelineStage<TData>;
|
|
104
|
+
/** The error that occurred */
|
|
105
|
+
error: Error;
|
|
106
|
+
/** The turn being processed */
|
|
107
|
+
turn: Turn<TData>;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Pipeline middleware configuration.
|
|
111
|
+
*
|
|
112
|
+
* @typeParam TData - Type of the turn's structured data
|
|
113
|
+
*/
|
|
114
|
+
interface PipelineConfig<TData = unknown> {
|
|
115
|
+
/** Stages to run after turn completion, executed in order */
|
|
116
|
+
stages: PipelineStage<TData>[];
|
|
117
|
+
/** Run stages in parallel instead of sequential (default: false) */
|
|
118
|
+
parallel?: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Continue running subsequent stages even if one fails (default: false).
|
|
121
|
+
* In parallel mode, all stages run regardless; this only affects error propagation.
|
|
122
|
+
*/
|
|
123
|
+
continueOnError?: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Called when a stage throws an error.
|
|
126
|
+
* Useful for logging or cleanup. Does not prevent error propagation unless continueOnError is true.
|
|
127
|
+
*/
|
|
128
|
+
onStageError?: (details: PipelineStageError<TData>) => void | Promise<void>;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Creates pipeline middleware for post-turn processing stages.
|
|
132
|
+
*
|
|
133
|
+
* Pipeline middleware enables running async tasks (image generation, embedding,
|
|
134
|
+
* slug generation, etc.) after the LLM completes, while streaming progress
|
|
135
|
+
* events to connected clients.
|
|
136
|
+
*
|
|
137
|
+
* Events are emitted through the standard middleware pipeline via `ctx.emit()`,
|
|
138
|
+
* so any middleware with `onStreamEvent` (like pubsub) will receive them.
|
|
139
|
+
*
|
|
140
|
+
* **Middleware Order**: Place pipeline AFTER pubsub in the array so that:
|
|
141
|
+
* - `onStart`: pubsub runs first (sets up adapter)
|
|
142
|
+
* - `onTurn`: pipeline runs first (emits events), pubsub runs second (cleans up)
|
|
143
|
+
*
|
|
144
|
+
* @typeParam TData - Type of the turn's structured data
|
|
145
|
+
* @param config - Pipeline middleware configuration
|
|
146
|
+
* @returns A middleware instance
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```typescript
|
|
150
|
+
* import { llm } from '@providerprotocol/ai';
|
|
151
|
+
* import { openai } from '@providerprotocol/ai/openai';
|
|
152
|
+
* import { pubsubMiddleware, memoryAdapter } from '@providerprotocol/ai/middleware/pubsub';
|
|
153
|
+
* import { pipelineMiddleware } from '@providerprotocol/ai/middleware/pipeline';
|
|
154
|
+
*
|
|
155
|
+
* const adapter = memoryAdapter();
|
|
156
|
+
*
|
|
157
|
+
* const model = llm({
|
|
158
|
+
* model: openai('gpt-4o'),
|
|
159
|
+
* structure: BlogPostSchema,
|
|
160
|
+
* middleware: [
|
|
161
|
+
* pubsubMiddleware({ adapter, streamId: postId }),
|
|
162
|
+
* pipelineMiddleware<BlogPost>({
|
|
163
|
+
* stages: [
|
|
164
|
+
* {
|
|
165
|
+
* type: 'slug',
|
|
166
|
+
* run: async (turn, emit) => {
|
|
167
|
+
* const slug = await generateSlug(turn.data!.title);
|
|
168
|
+
* (turn as { slug?: string }).slug = slug;
|
|
169
|
+
* emit({ slug });
|
|
170
|
+
* },
|
|
171
|
+
* },
|
|
172
|
+
* {
|
|
173
|
+
* type: 'embedding',
|
|
174
|
+
* run: async (turn, emit) => {
|
|
175
|
+
* await vectorize(turn.data!);
|
|
176
|
+
* emit({ embedded: true });
|
|
177
|
+
* },
|
|
178
|
+
* },
|
|
179
|
+
* ],
|
|
180
|
+
* }),
|
|
181
|
+
* ],
|
|
182
|
+
* });
|
|
183
|
+
*
|
|
184
|
+
* model.stream(prompt).then(turn => {
|
|
185
|
+
* const extended = turn as typeof turn & { slug?: string };
|
|
186
|
+
* await db.posts.update({ _id: id }, {
|
|
187
|
+
* ...turn.data,
|
|
188
|
+
* slug: extended.slug,
|
|
189
|
+
* });
|
|
190
|
+
* });
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
declare function pipelineMiddleware<TData = unknown>(config: PipelineConfig<TData>): Middleware;
|
|
194
|
+
|
|
195
|
+
export { type PipelineConfig, type PipelineEmit, type PipelineStage, type PipelineStageDelta, type PipelineStageError, type PipelineStageEvent, isPipelineStageEvent, pipelineMiddleware, pipelineStageEvent };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// src/middleware/pipeline/index.ts
|
|
2
|
+
function pipelineStageEvent(stage, payload) {
|
|
3
|
+
return {
|
|
4
|
+
type: "pipeline_stage",
|
|
5
|
+
index: 0,
|
|
6
|
+
delta: { stage, payload }
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function isPipelineStageEvent(event) {
|
|
10
|
+
return event.type === "pipeline_stage";
|
|
11
|
+
}
|
|
12
|
+
function pipelineMiddleware(config) {
|
|
13
|
+
const { stages, parallel = false, continueOnError = false, onStageError } = config;
|
|
14
|
+
const runStage = async (stage, typedTurn, emit) => {
|
|
15
|
+
try {
|
|
16
|
+
await stage.run(typedTurn, emit);
|
|
17
|
+
} catch (err) {
|
|
18
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
19
|
+
if (onStageError) {
|
|
20
|
+
await onStageError({ stage, error, turn: typedTurn });
|
|
21
|
+
}
|
|
22
|
+
if (!continueOnError) {
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
return {
|
|
28
|
+
name: "pipeline",
|
|
29
|
+
async onTurn(turn, ctx) {
|
|
30
|
+
const createEmit = (stageType) => {
|
|
31
|
+
return (data) => {
|
|
32
|
+
ctx.emit(pipelineStageEvent(stageType, data));
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
const typedTurn = turn;
|
|
36
|
+
if (parallel) {
|
|
37
|
+
const results = await Promise.allSettled(
|
|
38
|
+
stages.map((stage) => runStage(stage, typedTurn, createEmit(stage.type)))
|
|
39
|
+
);
|
|
40
|
+
if (!continueOnError) {
|
|
41
|
+
const firstRejection = results.find(
|
|
42
|
+
(r) => r.status === "rejected"
|
|
43
|
+
);
|
|
44
|
+
if (firstRejection) {
|
|
45
|
+
throw firstRejection.reason;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
for (const stage of stages) {
|
|
50
|
+
await runStage(stage, typedTurn, createEmit(stage.type));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
isPipelineStageEvent,
|
|
58
|
+
pipelineMiddleware,
|
|
59
|
+
pipelineStageEvent
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=index.js.map
|