@effect-uai/responses 0.3.0 → 0.5.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/OpenAIEmbedding.d.mts +1 -1
- package/dist/OpenAIEmbedding.d.mts.map +1 -1
- package/dist/OpenAIEmbedding.mjs +1 -1
- package/dist/OpenAIEmbedding.mjs.map +1 -1
- package/dist/Responses.d.mts.map +1 -1
- package/dist/Responses.mjs +2 -4
- package/dist/Responses.mjs.map +1 -1
- package/dist/streamEvents.mjs +13 -28
- package/dist/streamEvents.mjs.map +1 -1
- package/package.json +2 -2
- package/src/OpenAIEmbedding.ts +24 -7
- package/src/Responses.ts +3 -4
- package/src/streamEvents.ts +9 -17
|
@@ -15,7 +15,7 @@ declare namespace OpenAIEmbedding_d_exports {
|
|
|
15
15
|
* error. The generic `EmbeddingModel` registration accepts and silently
|
|
16
16
|
* ignores `task` from `CommonEmbedRequest`.
|
|
17
17
|
*/
|
|
18
|
-
type OpenAIEmbedRequest = Omit<CommonEmbedRequest, "model" | "task"> & {
|
|
18
|
+
type OpenAIEmbedRequest = Omit<CommonEmbedRequest, "model" | "task" | "encoding"> & {
|
|
19
19
|
/** Narrows `CommonEmbedRequest.model` to the typed OpenAI union. */readonly model: OpenAIEmbeddingModel;
|
|
20
20
|
};
|
|
21
21
|
type OpenAIEmbedManyRequest = Omit<OpenAIEmbedRequest, "input"> & {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OpenAIEmbedding.d.mts","names":[],"sources":["../src/OpenAIEmbedding.ts"],"mappings":";;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"OpenAIEmbedding.d.mts","names":[],"sources":["../src/OpenAIEmbedding.ts"],"mappings":";;;;;;;;;;;;;;;;;KA8BY,kBAAA,GAAqB,IAAA,CAAK,kBAAA;+EAE3B,KAAA,EAAO,oBAAA;AAAA;AAAA,KAGN,sBAAA,GAAyB,IAAA,CAAK,kBAAA;EAAA,SAC/B,MAAA,EAAQ,aAAA,CAAc,UAAA;AAAA;AAAA,KAGrB,sBAAA;EAAA,SACD,KAAA,GAAQ,OAAA,EAAS,kBAAA,KAAuB,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,OAAA,CAAQ,OAAA;EAAA,SAC7E,SAAA,GACP,OAAA,EAAS,sBAAA,KACN,MAAA,CAAO,MAAA,CAAO,iBAAA,EAAmB,OAAA,CAAQ,OAAA;AAAA;AAAA,cAC/C,oBAAA;;;;;;cAOY,eAAA,SAAwB,oBAAA;AAAA,KAIzB,MAAA;EAAA,SACD,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SACjB,OAAA;AAAA;;;;;cAgME,IAAA,GACX,GAAA,EAAK,MAAA,KACJ,MAAA,CAAO,MAAA,CAAO,sBAAA,SAA+B,UAAA,CAAW,UAAA;;;;;;;cAc9C,KAAA,GACX,GAAA,EAAK,MAAA,KACJ,KAAA,CAAM,KAAA,CAAM,eAAA,GAAkB,cAAA,SAAuB,UAAA,CAAW,UAAA"}
|
package/dist/OpenAIEmbedding.mjs
CHANGED
|
@@ -111,7 +111,7 @@ const postEmbed = (cfg, body) => Effect.gen(function* () {
|
|
|
111
111
|
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
|
|
112
112
|
if (response.status >= 400) {
|
|
113
113
|
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
|
|
114
|
-
return yield*
|
|
114
|
+
return yield* httpStatusError(response.status, text);
|
|
115
115
|
}
|
|
116
116
|
const json = yield* response.json.pipe(Effect.mapError(transportFailure));
|
|
117
117
|
return yield* Schema.decodeUnknownEffect(WireResponse)(json).pipe(Effect.mapError(transportFailure));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OpenAIEmbedding.mjs","names":[],"sources":["../src/OpenAIEmbedding.ts"],"sourcesContent":["import { Context, Effect, Layer, Redacted, Schema } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { Embedding, EmbedContentPart, EmbedInput, Usage } from \"@effect-uai/core/Embedding\"\nimport {\n type CommonEmbedManyRequest,\n type CommonEmbedRequest,\n EmbeddingModel,\n type EmbeddingModelService,\n type EmbedManyResponse,\n type EmbedResponse,\n} from \"@effect-uai/core/EmbeddingModel\"\nimport type { OpenAIEmbeddingModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * OpenAI's embedding API has no task-type semantics, so `task` is omitted\n * from the request type intentionally - passing it would be a compile\n * error. The generic `EmbeddingModel` registration accepts and silently\n * ignores `task` from `CommonEmbedRequest`.\n */\nexport type OpenAIEmbedRequest = Omit<CommonEmbedRequest, \"model\" | \"task\"> & {\n /** Narrows `CommonEmbedRequest.model` to the typed OpenAI union. */\n readonly model: OpenAIEmbeddingModel\n}\n\nexport type OpenAIEmbedManyRequest = Omit<OpenAIEmbedRequest, \"input\"> & {\n readonly inputs: ReadonlyArray<EmbedInput>\n}\n\nexport type OpenAIEmbeddingService = {\n readonly embed: (request: OpenAIEmbedRequest) => Effect.Effect<EmbedResponse, AiError.AiError>\n readonly embedMany: (\n request: OpenAIEmbedManyRequest,\n ) => Effect.Effect<EmbedManyResponse, AiError.AiError>\n}\n\n/**\n * Provider-typed service tag. Yield this for OpenAI-typed embedding\n * requests; yield the generic `EmbeddingModel` tag for provider-portable\n * code. Both are registered by `layer`.\n */\nexport class OpenAIEmbedding extends Context.Service<OpenAIEmbedding, OpenAIEmbeddingService>()(\n \"@betalyra/effect-uai/providers/responses/OpenAIEmbedding\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Codec - input flattening to the single string OpenAI expects\n// ---------------------------------------------------------------------------\n\nconst imageRejected = (param: string): AiError.AiError =>\n new AiError.InvalidRequest({\n provider: \"openai\",\n param,\n raw: \"OpenAI embeddings are text-only; pass a string or { text }\",\n })\n\nconst partToText = (\n param: string,\n part: EmbedContentPart,\n): Effect.Effect<string, AiError.AiError> =>\n \"text\" in part ? Effect.succeed(part.text) : Effect.fail(imageRejected(param))\n\n/**\n * OpenAI accepts a single string per input. Pure-text `content[]` is\n * concatenated with newlines (treats them as paragraphs of one document);\n * any image part fails with `InvalidRequest`.\n */\nconst inputToString = (input: EmbedInput): Effect.Effect<string, AiError.AiError> => {\n if (typeof input === \"string\") return Effect.succeed(input)\n if (\"text\" in input) return Effect.succeed(input.text)\n if (\"image\" in input) return Effect.fail(imageRejected(\"input.image\"))\n return Effect.forEach(input.content, (p, i) => partToText(`input.content[${i}].image`, p)).pipe(\n Effect.map((texts) => texts.join(\"\\n\")),\n )\n}\n\n// ---------------------------------------------------------------------------\n// Codec - request body\n// ---------------------------------------------------------------------------\n\ntype WireBody = {\n readonly model: string\n readonly input: string | ReadonlyArray<string>\n readonly dimensions?: number\n readonly encoding_format?: \"float\" | \"base64\"\n}\n\nconst buildSingleBody = (request: OpenAIEmbedRequest): Effect.Effect<WireBody, AiError.AiError> =>\n inputToString(request.input).pipe(\n Effect.map((input) => ({\n model: request.model,\n input,\n ...(request.dimensions !== undefined && { dimensions: request.dimensions }),\n })),\n )\n\nconst buildBatchBody = (\n request: OpenAIEmbedManyRequest,\n): Effect.Effect<WireBody, AiError.AiError> =>\n Effect.forEach(request.inputs, inputToString).pipe(\n Effect.map((inputs) => ({\n model: request.model,\n input: inputs,\n ...(request.dimensions !== undefined && { dimensions: request.dimensions }),\n })),\n )\n\n// ---------------------------------------------------------------------------\n// Codec - response\n// ---------------------------------------------------------------------------\n\nconst WireEmbeddingItem = Schema.Struct({\n embedding: Schema.Array(Schema.Number),\n index: Schema.Number,\n})\n\nconst WireUsage = Schema.Struct({\n prompt_tokens: Schema.Number,\n total_tokens: Schema.Number,\n})\n\nconst WireResponse = Schema.Struct({\n data: Schema.Array(WireEmbeddingItem),\n model: Schema.String,\n usage: WireUsage,\n})\n\nconst valuesToEmbedding = (values: ReadonlyArray<number>): Embedding => ({\n _tag: \"float32\",\n vector: Float32Array.from(values),\n})\n\n// OpenAI returns `data[]` unsorted in theory; sort by `index` so caller\n// gets results in the order they passed inputs.\nconst orderedEmbeddings = (\n data: ReadonlyArray<{ readonly embedding: ReadonlyArray<number>; readonly index: number }>,\n): ReadonlyArray<Embedding> =>\n [...data].sort((a, b) => a.index - b.index).map((item) => valuesToEmbedding(item.embedding))\n\n// ---------------------------------------------------------------------------\n// HTTP errors\n// ---------------------------------------------------------------------------\n\nconst httpStatusError = (status: number, body: string): AiError.AiError => {\n const provider = \"openai\"\n const raw = body\n if (status === 429) return new AiError.RateLimited({ provider, raw })\n if (status === 408 || status === 504) return new AiError.Timeout({ provider, raw })\n if (status === 401) return new AiError.AuthFailed({ provider, subtype: \"auth\", raw })\n if (status === 403) return new AiError.AuthFailed({ provider, subtype: \"permission\", raw })\n if (status === 402) return new AiError.AuthFailed({ provider, subtype: \"billing\", raw })\n if (status === 413) return new AiError.ContextLengthExceeded({ provider, raw })\n if (status >= 500) return new AiError.Unavailable({ provider, status, raw })\n return new AiError.InvalidRequest({ provider, raw })\n}\n\nconst transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: \"openai\", raw: cause })\n\n// ---------------------------------------------------------------------------\n// HTTP plumbing\n// ---------------------------------------------------------------------------\n\nconst baseUrl = (cfg: Config): string => cfg.baseUrl ?? \"https://api.openai.com/v1\"\n\nconst postEmbed = (\n cfg: Config,\n body: WireBody,\n): Effect.Effect<typeof WireResponse.Type, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/embeddings`).pipe(\n HttpClientRequest.bearerToken(cfg.apiKey),\n HttpClientRequest.bodyJsonUnsafe(body),\n )\n const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure))\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return yield* Effect.fail(httpStatusError(response.status, text))\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure))\n return yield* Schema.decodeUnknownEffect(WireResponse)(json).pipe(\n Effect.mapError(transportFailure),\n )\n })\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst usageOf = (u: typeof WireUsage.Type): Usage => ({ inputTokens: u.prompt_tokens })\n\nconst embedImpl =\n (cfg: Config) =>\n (\n request: OpenAIEmbedRequest,\n ): Effect.Effect<EmbedResponse, AiError.AiError, HttpClient.HttpClient> =>\n buildSingleBody(request).pipe(\n Effect.flatMap((body) => postEmbed(cfg, body)),\n Effect.flatMap((decoded) => {\n const first = decoded.data[0]\n if (first === undefined) {\n return Effect.fail(transportFailure(\"OpenAI returned empty `data` array\"))\n }\n return Effect.succeed<EmbedResponse>({\n embedding: valuesToEmbedding(first.embedding),\n usage: usageOf(decoded.usage),\n })\n }),\n )\n\nconst embedManyImpl =\n (cfg: Config) =>\n (\n request: OpenAIEmbedManyRequest,\n ): Effect.Effect<EmbedManyResponse, AiError.AiError, HttpClient.HttpClient> =>\n buildBatchBody(request).pipe(\n Effect.flatMap((body) => postEmbed(cfg, body)),\n Effect.map(\n (decoded): EmbedManyResponse => ({\n embeddings: orderedEmbeddings(decoded.data),\n usage: usageOf(decoded.usage),\n }),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Build an `OpenAIEmbeddingService` value. For Layer-based setup, prefer\n * `layer`.\n */\nexport const make = (\n cfg: Config,\n): Effect.Effect<OpenAIEmbeddingService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient.asEffect(), (client) => ({\n embed: (request) =>\n embedImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n embedMany: (request) =>\n embedManyImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n }))\n\n/**\n * Layer that registers both the provider-specific `OpenAIEmbedding` tag\n * and the generic `EmbeddingModel` tag, sharing one underlying\n * implementation. The generic registration trusts the caller; invalid\n * encodings produce an `InvalidRequest` from OpenAI's API.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<OpenAIEmbedding | EmbeddingModel, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(OpenAIEmbedding, make(cfg))\n const generic = Layer.effect(\n EmbeddingModel,\n Effect.map(\n make(cfg),\n (s): EmbeddingModelService => ({\n embed: (req: CommonEmbedRequest) => s.embed(req as OpenAIEmbedRequest),\n embedMany: (req: CommonEmbedManyRequest) => s.embedMany(req as OpenAIEmbedManyRequest),\n }),\n ),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"OpenAIEmbedding.mjs","names":[],"sources":["../src/OpenAIEmbedding.ts"],"sourcesContent":["import { Context, Effect, Layer, Redacted, Schema } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type {\n EmbedContentPart,\n EmbedInput,\n Float32Embedding,\n Usage,\n} from \"@effect-uai/core/Embedding\"\nimport {\n type CommonEmbedManyRequest,\n type CommonEmbedRequest,\n type EmbedEncoding,\n EmbeddingModel,\n type EmbeddingModelService,\n type EmbedManyResponse,\n type EmbedResponse,\n} from \"@effect-uai/core/EmbeddingModel\"\nimport type { OpenAIEmbeddingModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * OpenAI's embedding API has no task-type semantics, so `task` is omitted\n * from the request type intentionally - passing it would be a compile\n * error. The generic `EmbeddingModel` registration accepts and silently\n * ignores `task` from `CommonEmbedRequest`.\n */\nexport type OpenAIEmbedRequest = Omit<CommonEmbedRequest, \"model\" | \"task\" | \"encoding\"> & {\n /** Narrows `CommonEmbedRequest.model` to the typed OpenAI union. */\n readonly model: OpenAIEmbeddingModel\n}\n\nexport type OpenAIEmbedManyRequest = Omit<OpenAIEmbedRequest, \"input\"> & {\n readonly inputs: ReadonlyArray<EmbedInput>\n}\n\nexport type OpenAIEmbeddingService = {\n readonly embed: (request: OpenAIEmbedRequest) => Effect.Effect<EmbedResponse, AiError.AiError>\n readonly embedMany: (\n request: OpenAIEmbedManyRequest,\n ) => Effect.Effect<EmbedManyResponse, AiError.AiError>\n}\n\n/**\n * Provider-typed service tag. Yield this for OpenAI-typed embedding\n * requests; yield the generic `EmbeddingModel` tag for provider-portable\n * code. Both are registered by `layer`.\n */\nexport class OpenAIEmbedding extends Context.Service<OpenAIEmbedding, OpenAIEmbeddingService>()(\n \"@betalyra/effect-uai/providers/responses/OpenAIEmbedding\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Codec - input flattening to the single string OpenAI expects\n// ---------------------------------------------------------------------------\n\nconst imageRejected = (param: string): AiError.AiError =>\n new AiError.InvalidRequest({\n provider: \"openai\",\n param,\n raw: \"OpenAI embeddings are text-only; pass a string or { text }\",\n })\n\nconst partToText = (\n param: string,\n part: EmbedContentPart,\n): Effect.Effect<string, AiError.AiError> =>\n \"text\" in part ? Effect.succeed(part.text) : Effect.fail(imageRejected(param))\n\n/**\n * OpenAI accepts a single string per input. Pure-text `content[]` is\n * concatenated with newlines (treats them as paragraphs of one document);\n * any image part fails with `InvalidRequest`.\n */\nconst inputToString = (input: EmbedInput): Effect.Effect<string, AiError.AiError> => {\n if (typeof input === \"string\") return Effect.succeed(input)\n if (\"text\" in input) return Effect.succeed(input.text)\n if (\"image\" in input) return Effect.fail(imageRejected(\"input.image\"))\n return Effect.forEach(input.content, (p, i) => partToText(`input.content[${i}].image`, p)).pipe(\n Effect.map((texts) => texts.join(\"\\n\")),\n )\n}\n\n// ---------------------------------------------------------------------------\n// Codec - request body\n// ---------------------------------------------------------------------------\n\ntype WireBody = {\n readonly model: string\n readonly input: string | ReadonlyArray<string>\n readonly dimensions?: number\n readonly encoding_format?: \"float\" | \"base64\"\n}\n\nconst buildSingleBody = (request: OpenAIEmbedRequest): Effect.Effect<WireBody, AiError.AiError> =>\n inputToString(request.input).pipe(\n Effect.map((input) => ({\n model: request.model,\n input,\n ...(request.dimensions !== undefined && { dimensions: request.dimensions }),\n })),\n )\n\nconst buildBatchBody = (\n request: OpenAIEmbedManyRequest,\n): Effect.Effect<WireBody, AiError.AiError> =>\n Effect.forEach(request.inputs, inputToString).pipe(\n Effect.map((inputs) => ({\n model: request.model,\n input: inputs,\n ...(request.dimensions !== undefined && { dimensions: request.dimensions }),\n })),\n )\n\n// ---------------------------------------------------------------------------\n// Codec - response\n// ---------------------------------------------------------------------------\n\nconst WireEmbeddingItem = Schema.Struct({\n embedding: Schema.Array(Schema.Number),\n index: Schema.Number,\n})\n\nconst WireUsage = Schema.Struct({\n prompt_tokens: Schema.Number,\n total_tokens: Schema.Number,\n})\n\nconst WireResponse = Schema.Struct({\n data: Schema.Array(WireEmbeddingItem),\n model: Schema.String,\n usage: WireUsage,\n})\n\nconst valuesToEmbedding = (values: ReadonlyArray<number>): Float32Embedding => ({\n _tag: \"float32\",\n vector: Float32Array.from(values),\n})\n\n// OpenAI returns `data[]` unsorted in theory; sort by `index` so caller\n// gets results in the order they passed inputs.\nconst orderedEmbeddings = (\n data: ReadonlyArray<{ readonly embedding: ReadonlyArray<number>; readonly index: number }>,\n): ReadonlyArray<Float32Embedding> =>\n [...data].sort((a, b) => a.index - b.index).map((item) => valuesToEmbedding(item.embedding))\n\n// ---------------------------------------------------------------------------\n// HTTP errors\n// ---------------------------------------------------------------------------\n\nconst httpStatusError = (status: number, body: string): AiError.AiError => {\n const provider = \"openai\"\n const raw = body\n if (status === 429) return new AiError.RateLimited({ provider, raw })\n if (status === 408 || status === 504) return new AiError.Timeout({ provider, raw })\n if (status === 401) return new AiError.AuthFailed({ provider, subtype: \"auth\", raw })\n if (status === 403) return new AiError.AuthFailed({ provider, subtype: \"permission\", raw })\n if (status === 402) return new AiError.AuthFailed({ provider, subtype: \"billing\", raw })\n if (status === 413) return new AiError.ContextLengthExceeded({ provider, raw })\n if (status >= 500) return new AiError.Unavailable({ provider, status, raw })\n return new AiError.InvalidRequest({ provider, raw })\n}\n\nconst transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: \"openai\", raw: cause })\n\n// ---------------------------------------------------------------------------\n// HTTP plumbing\n// ---------------------------------------------------------------------------\n\nconst baseUrl = (cfg: Config): string => cfg.baseUrl ?? \"https://api.openai.com/v1\"\n\nconst postEmbed = (\n cfg: Config,\n body: WireBody,\n): Effect.Effect<typeof WireResponse.Type, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/embeddings`).pipe(\n HttpClientRequest.bearerToken(cfg.apiKey),\n HttpClientRequest.bodyJsonUnsafe(body),\n )\n const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure))\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return yield* httpStatusError(response.status, text)\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure))\n return yield* Schema.decodeUnknownEffect(WireResponse)(json).pipe(\n Effect.mapError(transportFailure),\n )\n })\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst usageOf = (u: typeof WireUsage.Type): Usage => ({ inputTokens: u.prompt_tokens })\n\nconst embedImpl =\n (cfg: Config) =>\n (\n request: OpenAIEmbedRequest,\n ): Effect.Effect<EmbedResponse, AiError.AiError, HttpClient.HttpClient> =>\n buildSingleBody(request).pipe(\n Effect.flatMap((body) => postEmbed(cfg, body)),\n Effect.flatMap((decoded) => {\n const first = decoded.data[0]\n if (first === undefined) {\n return Effect.fail(transportFailure(\"OpenAI returned empty `data` array\"))\n }\n return Effect.succeed<EmbedResponse>({\n embedding: valuesToEmbedding(first.embedding),\n usage: usageOf(decoded.usage),\n })\n }),\n )\n\nconst embedManyImpl =\n (cfg: Config) =>\n (\n request: OpenAIEmbedManyRequest,\n ): Effect.Effect<EmbedManyResponse, AiError.AiError, HttpClient.HttpClient> =>\n buildBatchBody(request).pipe(\n Effect.flatMap((body) => postEmbed(cfg, body)),\n Effect.map(\n (decoded): EmbedManyResponse => ({\n embeddings: orderedEmbeddings(decoded.data),\n usage: usageOf(decoded.usage),\n }),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Build an `OpenAIEmbeddingService` value. For Layer-based setup, prefer\n * `layer`.\n */\nexport const make = (\n cfg: Config,\n): Effect.Effect<OpenAIEmbeddingService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient.asEffect(), (client) => ({\n embed: (request) =>\n embedImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n embedMany: (request) =>\n embedManyImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n }))\n\n/**\n * Layer that registers both the provider-specific `OpenAIEmbedding` tag\n * and the generic `EmbeddingModel` tag, sharing one underlying\n * implementation. The generic registration trusts the caller; invalid\n * encodings produce an `InvalidRequest` from OpenAI's API.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<OpenAIEmbedding | EmbeddingModel, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(OpenAIEmbedding, make(cfg))\n const generic = Layer.effect(\n EmbeddingModel,\n Effect.map(\n make(cfg),\n (s): EmbeddingModelService => ({\n // OpenAI only emits float32; the cast is sound for the 99% case.\n // A caller asking for a non-float32 encoding via the generic tag\n // gets the type they requested but the runtime returns float32.\n embed: <E extends EmbedEncoding | undefined = undefined>(\n req: Omit<CommonEmbedRequest, \"encoding\"> & { readonly encoding?: E },\n ) => s.embed(req as OpenAIEmbedRequest) as Effect.Effect<EmbedResponse<E>, AiError.AiError>,\n embedMany: <E extends EmbedEncoding | undefined = undefined>(\n req: Omit<CommonEmbedManyRequest, \"encoding\"> & { readonly encoding?: E },\n ) =>\n s.embedMany(req as OpenAIEmbedManyRequest) as Effect.Effect<\n EmbedManyResponse<E>,\n AiError.AiError\n >,\n }),\n ),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
|
package/dist/Responses.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Responses.d.mts","names":[],"sources":["../src/Responses.ts"],"mappings":";;;;;;;;;;;;KAwBY,gBAAA,GAAmB,IAAA,CAAK,aAAA;;;;;WAKzB,KAAA,EAAO,WAAA;EAAA,SACP,SAAA;IAAA,SAAuB,MAAA;EAAA;EAAA,SACvB,KAAA;EAAA,SACA,kBAAA;EAAA,SACA,YAAA;EAAA,SACA,iBAAA;EAAA,SACA,QAAA,GAAW,QAAA,CAAS,MAAA;EAAA,SACpB,IAAA;EAAA,SACA,gBAAA;EAAA,SACA,cAAA;EAAA,SACA,UAAA;EAJoB;;;;;EAAA,SAUpB,cAAA;IAAA,SAA4B,IAAA;EAAA;EAf5B;;;;EAAA,SAoBA,SAAA;AAAA;AAAA,KAGC,gBAAA;EAlBU;;;;;EAAA,SAwBX,YAAA,GACP,OAAA,EAAS,gBAAA,KACN,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,OAAA,CAAQ,OAAA;EAhBjC;;;;EAAA,SAqBA,UAAA,GAAa,OAAA,EAAS,gBAAA,KAAqB,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,OAAA,CAAQ,OAAA;EAb3E;;;;;EAAA,SAmBD,WAAA,SACP,CAAA,EAAG,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,CAAA,EAAG,CAAA,MAChC,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,CAAA,EAAG,CAAA;AAAA;AAAA,cAClC,cAAA;;;;;;;cAQY,SAAA,SAAkB,cAAA;AAAA,KAInB,MAAA;EAAA,SACD,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SACjB,OAAA;AAAA;;;;;;
|
|
1
|
+
{"version":3,"file":"Responses.d.mts","names":[],"sources":["../src/Responses.ts"],"mappings":";;;;;;;;;;;;KAwBY,gBAAA,GAAmB,IAAA,CAAK,aAAA;;;;;WAKzB,KAAA,EAAO,WAAA;EAAA,SACP,SAAA;IAAA,SAAuB,MAAA;EAAA;EAAA,SACvB,KAAA;EAAA,SACA,kBAAA;EAAA,SACA,YAAA;EAAA,SACA,iBAAA;EAAA,SACA,QAAA,GAAW,QAAA,CAAS,MAAA;EAAA,SACpB,IAAA;EAAA,SACA,gBAAA;EAAA,SACA,cAAA;EAAA,SACA,UAAA;EAJoB;;;;;EAAA,SAUpB,cAAA;IAAA,SAA4B,IAAA;EAAA;EAf5B;;;;EAAA,SAoBA,SAAA;AAAA;AAAA,KAGC,gBAAA;EAlBU;;;;;EAAA,SAwBX,YAAA,GACP,OAAA,EAAS,gBAAA,KACN,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,OAAA,CAAQ,OAAA;EAhBjC;;;;EAAA,SAqBA,UAAA,GAAa,OAAA,EAAS,gBAAA,KAAqB,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,OAAA,CAAQ,OAAA;EAb3E;;;;;EAAA,SAmBD,WAAA,SACP,CAAA,EAAG,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,CAAA,EAAG,CAAA,MAChC,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,CAAA,EAAG,CAAA;AAAA;AAAA,cAClC,cAAA;;;;;;;cAQY,SAAA,SAAkB,cAAA;AAAA,KAInB,MAAA;EAAA,SACD,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SACjB,OAAA;AAAA;;;;;;cA8KE,WAAA,SACX,CAAA,EAAG,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,CAAA,EAAG,CAAA,MAClC,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,CAAA,EAAG,CAAA;;;;cAelB,IAAA,GAAQ,GAAA,EAAK,MAAA,KAAS,MAAA,CAAO,MAAA,CAAO,gBAAA,SAAyB,UAAA,CAAW,UAAA;;;;;;;;cAkBxE,KAAA,GACX,GAAA,EAAK,MAAA,KACJ,KAAA,CAAM,KAAA,CAAM,SAAA,GAAY,aAAA,SAAsB,UAAA,CAAW,UAAA"}
|
package/dist/Responses.mjs
CHANGED
|
@@ -90,10 +90,8 @@ const eventToError = Match.type().pipe(Match.discriminators("type")({
|
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
92
|
}), Match.option);
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
catch: () => ev.data
|
|
96
|
-
}).pipe(Effect.flatMap((parsed) => decodeKnown(parsed).pipe(Effect.orElseSucceed(() => makeUnknown(parsed)))), Effect.orElseSucceed(() => makeUnknown(ev.data)));
|
|
93
|
+
const parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown));
|
|
94
|
+
const sseEventToProviderEvent = (ev) => parseJsonUnknown(ev.data).pipe(Effect.flatMap((parsed) => decodeKnown(parsed).pipe(Effect.orElseSucceed(() => makeUnknown(parsed)))), Effect.orElseSucceed(() => makeUnknown(ev.data)));
|
|
97
95
|
const httpStatusError = (status, body) => {
|
|
98
96
|
const provider = "responses";
|
|
99
97
|
const raw = body;
|
package/dist/Responses.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Responses.mjs","names":[],"sources":["../src/Responses.ts"],"sourcesContent":["import { Context, Effect, Layer, Match, Option, Redacted, Schema, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport * as StructuredFormat from \"@effect-uai/core/StructuredFormat\"\nimport {\n type CommonRequest,\n LanguageModel,\n type LanguageModelService,\n} from \"@effect-uai/core/LanguageModel\"\nimport * as SSE from \"@effect-uai/core/SSE\"\nimport type { TurnEvent } from \"@effect-uai/core/Turn\"\nimport { itemsToInput } from \"./codec.js\"\nimport type { OpenAIModel } from \"./models.js\"\nimport {\n KnownProviderEvent,\n ProviderEvent,\n eventToDeltas,\n makeCallIdLookup,\n} from \"./streamEvents.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type ResponsesRequest = Omit<CommonRequest, \"model\"> & {\n /**\n * Narrows `CommonRequest.model` (`string`) to the typed `OpenAIModel`\n * literal union for autocomplete.\n */\n readonly model: OpenAIModel\n readonly reasoning?: { readonly effort: \"low\" | \"medium\" | \"high\" }\n readonly store?: boolean\n readonly previousResponseId?: string\n readonly instructions?: string\n readonly parallelToolCalls?: boolean\n readonly metadata?: Readonly<Record<string, string>>\n readonly user?: string\n readonly safetyIdentifier?: string\n readonly promptCacheKey?: string\n readonly truncation?: \"auto\" | \"disabled\"\n /**\n * Free-form JSON output: model emits valid JSON without schema\n * constraints. Mutually exclusive with `structured` (schema wins if\n * both are set).\n */\n readonly responseFormat?: { readonly type: \"json_object\" }\n /**\n * Answer length / level-of-detail hint. GPT-5+ models honour it;\n * earlier models ignore.\n */\n readonly verbosity?: \"low\" | \"medium\" | \"high\"\n}\n\nexport type ResponsesService = {\n /**\n * Stream the provider's native event vocabulary (post-SSE-decode).\n * Use this when you need full vendor fidelity. For provider-portable\n * code, use `streamTurn` instead.\n */\n readonly streamNative: (\n request: ResponsesRequest,\n ) => Stream.Stream<ProviderEvent, AiError.AiError>\n /**\n * Stream canonical `TurnEvent`s. Implemented as\n * `streamNative |> toCanonical`.\n */\n readonly streamTurn: (request: ResponsesRequest) => Stream.Stream<TurnEvent, AiError.AiError>\n /**\n * Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.\n * Exposed for cases where consumers want to compose with `streamNative`\n * directly (e.g. tap into natives + still get canonical downstream).\n */\n readonly toCanonical: <E, R>(\n s: Stream.Stream<ProviderEvent, E, R>,\n ) => Stream.Stream<TurnEvent, E, R>\n}\n\n/**\n * Provider-typed service tag. Yield this when you want Responses-specific\n * options (`reasoning.effort`, `store`, `previousResponseId`) at the call\n * site. Yield the generic `LanguageModel` tag for provider-portable code -\n * both are registered by `layer`.\n */\nexport class Responses extends Context.Service<Responses, ResponsesService>()(\n \"@betalyra/effect-uai/providers/responses/Responses\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Request body\n// ---------------------------------------------------------------------------\n\nconst jsonSchemaFormat = (\n format: StructuredFormat.StructuredFormat<unknown>,\n): Record<string, unknown> => ({\n type: \"json_schema\",\n name: format.name,\n schema: format.schema[\"~standard\"].jsonSchema.input({ target: \"draft-2020-12\" }),\n ...(format.description !== undefined && { description: format.description }),\n ...(format.strict !== undefined && { strict: format.strict }),\n})\n\nconst buildText = (request: ResponsesRequest): Record<string, unknown> | undefined => {\n const format =\n request.structured !== undefined ? jsonSchemaFormat(request.structured) : request.responseFormat\n const text: Record<string, unknown> = {}\n if (format !== undefined) text.format = format\n if (request.verbosity !== undefined) text.verbosity = request.verbosity\n return Object.keys(text).length === 0 ? undefined : text\n}\n\nconst buildBody = (request: ResponsesRequest): Record<string, unknown> => {\n const text = buildText(request)\n return {\n model: request.model,\n input: itemsToInput(request.history),\n stream: true,\n ...(request.tools !== undefined &&\n request.tools.length > 0 && {\n tools: request.tools.map((t) => ({\n type: \"function\",\n name: t.name,\n description: t.description,\n parameters: t.inputSchema,\n ...(t.strict !== undefined && { strict: t.strict }),\n })),\n }),\n ...(request.toolChoice !== undefined && { tool_choice: request.toolChoice }),\n ...(request.temperature !== undefined && { temperature: request.temperature }),\n ...(request.maxOutputTokens !== undefined && {\n max_output_tokens: request.maxOutputTokens,\n }),\n ...(request.reasoning !== undefined && { reasoning: request.reasoning }),\n ...(request.store !== undefined && { store: request.store }),\n ...(request.previousResponseId !== undefined && {\n previous_response_id: request.previousResponseId,\n }),\n ...(request.instructions !== undefined && { instructions: request.instructions }),\n ...(request.topP !== undefined && { top_p: request.topP }),\n ...(request.parallelToolCalls !== undefined && {\n parallel_tool_calls: request.parallelToolCalls,\n }),\n ...(request.metadata !== undefined && { metadata: request.metadata }),\n ...(request.user !== undefined && { user: request.user }),\n ...(request.safetyIdentifier !== undefined && {\n safety_identifier: request.safetyIdentifier,\n }),\n ...(request.promptCacheKey !== undefined && {\n prompt_cache_key: request.promptCacheKey,\n }),\n ...(request.truncation !== undefined && { truncation: request.truncation }),\n ...(text !== undefined && { text }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// SSE event → provider event\n// ---------------------------------------------------------------------------\n\nconst decodeKnown = Schema.decodeUnknownEffect(KnownProviderEvent)\n\nconst makeUnknown = (raw: unknown): ProviderEvent => ({ type: \"_unknown\", raw })\n\n/**\n * Lift events that carry a terminal failure signal (`error`,\n * `response.failed`) to a typed `AiError`. Other events are not failures\n * and produce `Option.none`.\n */\nconst eventToError = Match.type<ProviderEvent>().pipe(\n Match.discriminators(\"type\")({\n error: (e): AiError.AiError => new AiError.Unavailable({ provider: \"responses\", raw: e }),\n \"response.failed\": (e): AiError.AiError => {\n const code = e.response.error?.code\n const message = e.response.error?.message\n return new AiError.GenerationFailed({\n provider: \"responses\",\n ...(code !== undefined && code !== null && { code }),\n ...(message !== undefined && message !== null && { message }),\n raw: e,\n })\n },\n }),\n Match.option,\n)\n\nconst sseEventToProviderEvent = (ev: SSE.Event): Effect.Effect<ProviderEvent> =>\n Effect.try({\n try: () => JSON.parse(ev.data) as unknown,\n catch: () => ev.data,\n }).pipe(\n Effect.flatMap((parsed) =>\n decodeKnown(parsed).pipe(Effect.orElseSucceed(() => makeUnknown(parsed))),\n ),\n Effect.orElseSucceed(() => makeUnknown(ev.data)),\n )\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst httpStatusError = (status: number, body: string): AiError.AiError => {\n const provider = \"responses\"\n const raw = body\n if (status === 429) return new AiError.RateLimited({ provider, raw })\n if (status === 408 || status === 504) return new AiError.Timeout({ provider, raw })\n if (status === 401) return new AiError.AuthFailed({ provider, subtype: \"auth\", raw })\n if (status === 403) return new AiError.AuthFailed({ provider, subtype: \"permission\", raw })\n if (status === 402) return new AiError.AuthFailed({ provider, subtype: \"billing\", raw })\n if (status === 413) return new AiError.ContextLengthExceeded({ provider, raw })\n if (status >= 500) return new AiError.Unavailable({ provider, status, raw })\n return new AiError.InvalidRequest({ provider, raw })\n}\n\nconst buildNativeStream = (cfg: Config) => {\n const url = `${cfg.baseUrl ?? \"https://api.openai.com/v1\"}/responses`\n return (\n request: ResponsesRequest,\n ): Stream.Stream<ProviderEvent, AiError.AiError, HttpClient.HttpClient> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const httpRequest = HttpClientRequest.post(url).pipe(\n HttpClientRequest.bearerToken(cfg.apiKey),\n HttpClientRequest.bodyJsonUnsafe(buildBody(request)),\n HttpClientRequest.accept(\"text/event-stream\"),\n )\n const response = yield* client\n .execute(httpRequest)\n .pipe(\n Effect.mapError(\n (cause) => new AiError.Unavailable({ provider: \"responses\", raw: cause }),\n ),\n )\n if (response.status >= 400) {\n const body = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return Stream.fail(httpStatusError(response.status, body))\n }\n\n return response.stream.pipe(\n Stream.mapError(\n (cause) => new AiError.Unavailable({ provider: \"responses\", raw: cause }),\n ),\n SSE.fromBytes,\n Stream.mapEffect(sseEventToProviderEvent),\n Stream.flatMap((event) =>\n Option.match(eventToError(event), {\n onNone: () => Stream.succeed(event),\n onSome: Stream.fail,\n }),\n ),\n )\n }),\n )\n}\n\n/**\n * Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.\n * Threads a fresh `CallIdLookup` per stream so `function_call_arguments.delta`\n * can be tagged with the right `call_id`.\n */\nexport const toCanonical = <E, R>(\n s: Stream.Stream<ProviderEvent, E, R>,\n): Stream.Stream<TurnEvent, E, R> =>\n Stream.unwrap(\n Effect.sync(() => {\n const lookup = makeCallIdLookup()\n return s.pipe(Stream.flatMap((event) => Stream.fromIterable(eventToDeltas(event, lookup))))\n }),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Build a `ResponsesService` value. For Layer-based setup, prefer `layer`.\n */\nexport const make = (cfg: Config): Effect.Effect<ResponsesService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient.asEffect(), (client) => {\n const streamNative: ResponsesService[\"streamNative\"] = (request) =>\n buildNativeStream(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client))\n return {\n streamNative,\n streamTurn: (request) => toCanonical(streamNative(request)),\n toCanonical,\n }\n })\n\n/**\n * Layer that registers both the provider-specific `Responses` tag and the\n * generic `LanguageModel` tag, sharing one underlying implementation.\n *\n * The generic tag accepts `CommonRequest`; the typed tag accepts the full\n * `ResponsesRequest` surface.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<Responses | LanguageModel, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(Responses, make(cfg))\n const generic = Layer.effect(\n LanguageModel,\n Effect.map(\n make(cfg),\n (s): LanguageModelService => ({\n streamTurn: (request) => s.streamTurn(request as ResponsesRequest),\n }),\n ),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Responses.mjs","names":[],"sources":["../src/Responses.ts"],"sourcesContent":["import { Context, Effect, Layer, Match, Option, Redacted, Schema, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport * as StructuredFormat from \"@effect-uai/core/StructuredFormat\"\nimport {\n type CommonRequest,\n LanguageModel,\n type LanguageModelService,\n} from \"@effect-uai/core/LanguageModel\"\nimport * as SSE from \"@effect-uai/core/SSE\"\nimport type { TurnEvent } from \"@effect-uai/core/Turn\"\nimport { itemsToInput } from \"./codec.js\"\nimport type { OpenAIModel } from \"./models.js\"\nimport {\n KnownProviderEvent,\n ProviderEvent,\n eventToDeltas,\n makeCallIdLookup,\n} from \"./streamEvents.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type ResponsesRequest = Omit<CommonRequest, \"model\"> & {\n /**\n * Narrows `CommonRequest.model` (`string`) to the typed `OpenAIModel`\n * literal union for autocomplete.\n */\n readonly model: OpenAIModel\n readonly reasoning?: { readonly effort: \"low\" | \"medium\" | \"high\" }\n readonly store?: boolean\n readonly previousResponseId?: string\n readonly instructions?: string\n readonly parallelToolCalls?: boolean\n readonly metadata?: Readonly<Record<string, string>>\n readonly user?: string\n readonly safetyIdentifier?: string\n readonly promptCacheKey?: string\n readonly truncation?: \"auto\" | \"disabled\"\n /**\n * Free-form JSON output: model emits valid JSON without schema\n * constraints. Mutually exclusive with `structured` (schema wins if\n * both are set).\n */\n readonly responseFormat?: { readonly type: \"json_object\" }\n /**\n * Answer length / level-of-detail hint. GPT-5+ models honour it;\n * earlier models ignore.\n */\n readonly verbosity?: \"low\" | \"medium\" | \"high\"\n}\n\nexport type ResponsesService = {\n /**\n * Stream the provider's native event vocabulary (post-SSE-decode).\n * Use this when you need full vendor fidelity. For provider-portable\n * code, use `streamTurn` instead.\n */\n readonly streamNative: (\n request: ResponsesRequest,\n ) => Stream.Stream<ProviderEvent, AiError.AiError>\n /**\n * Stream canonical `TurnEvent`s. Implemented as\n * `streamNative |> toCanonical`.\n */\n readonly streamTurn: (request: ResponsesRequest) => Stream.Stream<TurnEvent, AiError.AiError>\n /**\n * Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.\n * Exposed for cases where consumers want to compose with `streamNative`\n * directly (e.g. tap into natives + still get canonical downstream).\n */\n readonly toCanonical: <E, R>(\n s: Stream.Stream<ProviderEvent, E, R>,\n ) => Stream.Stream<TurnEvent, E, R>\n}\n\n/**\n * Provider-typed service tag. Yield this when you want Responses-specific\n * options (`reasoning.effort`, `store`, `previousResponseId`) at the call\n * site. Yield the generic `LanguageModel` tag for provider-portable code -\n * both are registered by `layer`.\n */\nexport class Responses extends Context.Service<Responses, ResponsesService>()(\n \"@betalyra/effect-uai/providers/responses/Responses\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Request body\n// ---------------------------------------------------------------------------\n\nconst jsonSchemaFormat = (\n format: StructuredFormat.StructuredFormat<unknown>,\n): Record<string, unknown> => ({\n type: \"json_schema\",\n name: format.name,\n schema: format.schema[\"~standard\"].jsonSchema.input({ target: \"draft-2020-12\" }),\n ...(format.description !== undefined && { description: format.description }),\n ...(format.strict !== undefined && { strict: format.strict }),\n})\n\nconst buildText = (request: ResponsesRequest): Record<string, unknown> | undefined => {\n const format =\n request.structured !== undefined ? jsonSchemaFormat(request.structured) : request.responseFormat\n const text: Record<string, unknown> = {}\n if (format !== undefined) text.format = format\n if (request.verbosity !== undefined) text.verbosity = request.verbosity\n return Object.keys(text).length === 0 ? undefined : text\n}\n\nconst buildBody = (request: ResponsesRequest): Record<string, unknown> => {\n const text = buildText(request)\n return {\n model: request.model,\n input: itemsToInput(request.history),\n stream: true,\n ...(request.tools !== undefined &&\n request.tools.length > 0 && {\n tools: request.tools.map((t) => ({\n type: \"function\",\n name: t.name,\n description: t.description,\n parameters: t.inputSchema,\n ...(t.strict !== undefined && { strict: t.strict }),\n })),\n }),\n ...(request.toolChoice !== undefined && { tool_choice: request.toolChoice }),\n ...(request.temperature !== undefined && { temperature: request.temperature }),\n ...(request.maxOutputTokens !== undefined && {\n max_output_tokens: request.maxOutputTokens,\n }),\n ...(request.reasoning !== undefined && { reasoning: request.reasoning }),\n ...(request.store !== undefined && { store: request.store }),\n ...(request.previousResponseId !== undefined && {\n previous_response_id: request.previousResponseId,\n }),\n ...(request.instructions !== undefined && { instructions: request.instructions }),\n ...(request.topP !== undefined && { top_p: request.topP }),\n ...(request.parallelToolCalls !== undefined && {\n parallel_tool_calls: request.parallelToolCalls,\n }),\n ...(request.metadata !== undefined && { metadata: request.metadata }),\n ...(request.user !== undefined && { user: request.user }),\n ...(request.safetyIdentifier !== undefined && {\n safety_identifier: request.safetyIdentifier,\n }),\n ...(request.promptCacheKey !== undefined && {\n prompt_cache_key: request.promptCacheKey,\n }),\n ...(request.truncation !== undefined && { truncation: request.truncation }),\n ...(text !== undefined && { text }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// SSE event → provider event\n// ---------------------------------------------------------------------------\n\nconst decodeKnown = Schema.decodeUnknownEffect(KnownProviderEvent)\n\nconst makeUnknown = (raw: unknown): ProviderEvent => ({ type: \"_unknown\", raw })\n\n/**\n * Lift events that carry a terminal failure signal (`error`,\n * `response.failed`) to a typed `AiError`. Other events are not failures\n * and produce `Option.none`.\n */\nconst eventToError = Match.type<ProviderEvent>().pipe(\n Match.discriminators(\"type\")({\n error: (e): AiError.AiError => new AiError.Unavailable({ provider: \"responses\", raw: e }),\n \"response.failed\": (e): AiError.AiError => {\n const code = e.response.error?.code\n const message = e.response.error?.message\n return new AiError.GenerationFailed({\n provider: \"responses\",\n ...(code !== undefined && code !== null && { code }),\n ...(message !== undefined && message !== null && { message }),\n raw: e,\n })\n },\n }),\n Match.option,\n)\n\nconst parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))\n\nconst sseEventToProviderEvent = (ev: SSE.Event): Effect.Effect<ProviderEvent> =>\n parseJsonUnknown(ev.data).pipe(\n Effect.flatMap((parsed) =>\n decodeKnown(parsed).pipe(Effect.orElseSucceed(() => makeUnknown(parsed))),\n ),\n Effect.orElseSucceed(() => makeUnknown(ev.data)),\n )\n\n// ---------------------------------------------------------------------------\n// Service implementation\n// ---------------------------------------------------------------------------\n\nconst httpStatusError = (status: number, body: string): AiError.AiError => {\n const provider = \"responses\"\n const raw = body\n if (status === 429) return new AiError.RateLimited({ provider, raw })\n if (status === 408 || status === 504) return new AiError.Timeout({ provider, raw })\n if (status === 401) return new AiError.AuthFailed({ provider, subtype: \"auth\", raw })\n if (status === 403) return new AiError.AuthFailed({ provider, subtype: \"permission\", raw })\n if (status === 402) return new AiError.AuthFailed({ provider, subtype: \"billing\", raw })\n if (status === 413) return new AiError.ContextLengthExceeded({ provider, raw })\n if (status >= 500) return new AiError.Unavailable({ provider, status, raw })\n return new AiError.InvalidRequest({ provider, raw })\n}\n\nconst buildNativeStream = (cfg: Config) => {\n const url = `${cfg.baseUrl ?? \"https://api.openai.com/v1\"}/responses`\n return (\n request: ResponsesRequest,\n ): Stream.Stream<ProviderEvent, AiError.AiError, HttpClient.HttpClient> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const httpRequest = HttpClientRequest.post(url).pipe(\n HttpClientRequest.bearerToken(cfg.apiKey),\n HttpClientRequest.bodyJsonUnsafe(buildBody(request)),\n HttpClientRequest.accept(\"text/event-stream\"),\n )\n const response = yield* client\n .execute(httpRequest)\n .pipe(\n Effect.mapError(\n (cause) => new AiError.Unavailable({ provider: \"responses\", raw: cause }),\n ),\n )\n if (response.status >= 400) {\n const body = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return Stream.fail(httpStatusError(response.status, body))\n }\n\n return response.stream.pipe(\n Stream.mapError(\n (cause) => new AiError.Unavailable({ provider: \"responses\", raw: cause }),\n ),\n SSE.fromBytes,\n Stream.mapEffect(sseEventToProviderEvent),\n Stream.flatMap((event) =>\n Option.match(eventToError(event), {\n onNone: () => Stream.succeed(event),\n onSome: Stream.fail,\n }),\n ),\n )\n }),\n )\n}\n\n/**\n * Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.\n * Threads a fresh `CallIdLookup` per stream so `function_call_arguments.delta`\n * can be tagged with the right `call_id`.\n */\nexport const toCanonical = <E, R>(\n s: Stream.Stream<ProviderEvent, E, R>,\n): Stream.Stream<TurnEvent, E, R> =>\n Stream.unwrap(\n Effect.sync(() => {\n const lookup = makeCallIdLookup()\n return s.pipe(Stream.flatMap((event) => Stream.fromIterable(eventToDeltas(event, lookup))))\n }),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Build a `ResponsesService` value. For Layer-based setup, prefer `layer`.\n */\nexport const make = (cfg: Config): Effect.Effect<ResponsesService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient.asEffect(), (client) => {\n const streamNative: ResponsesService[\"streamNative\"] = (request) =>\n buildNativeStream(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client))\n return {\n streamNative,\n streamTurn: (request) => toCanonical(streamNative(request)),\n toCanonical,\n }\n })\n\n/**\n * Layer that registers both the provider-specific `Responses` tag and the\n * generic `LanguageModel` tag, sharing one underlying implementation.\n *\n * The generic tag accepts `CommonRequest`; the typed tag accepts the full\n * `ResponsesRequest` surface.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<Responses | LanguageModel, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(Responses, make(cfg))\n const generic = Layer.effect(\n LanguageModel,\n Effect.map(\n make(cfg),\n (s): LanguageModelService => ({\n streamTurn: (request) => s.streamTurn(request as ResponsesRequest),\n }),\n ),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAmFA,IAAa,YAAb,cAA+B,QAAQ,SAAsC,CAC3E,qDACD,CAAC;AAWF,MAAM,oBACJ,YAC6B;CAC7B,MAAM;CACN,MAAM,OAAO;CACb,QAAQ,OAAO,OAAO,aAAa,WAAW,MAAM,EAAE,QAAQ,iBAAiB,CAAC;CAChF,GAAI,OAAO,gBAAgB,KAAA,KAAa,EAAE,aAAa,OAAO,aAAa;CAC3E,GAAI,OAAO,WAAW,KAAA,KAAa,EAAE,QAAQ,OAAO,QAAQ;CAC7D;AAED,MAAM,aAAa,YAAmE;CACpF,MAAM,SACJ,QAAQ,eAAe,KAAA,IAAY,iBAAiB,QAAQ,WAAW,GAAG,QAAQ;CACpF,MAAM,OAAgC,EAAE;AACxC,KAAI,WAAW,KAAA,EAAW,MAAK,SAAS;AACxC,KAAI,QAAQ,cAAc,KAAA,EAAW,MAAK,YAAY,QAAQ;AAC9D,QAAO,OAAO,KAAK,KAAK,CAAC,WAAW,IAAI,KAAA,IAAY;;AAGtD,MAAM,aAAa,YAAuD;CACxE,MAAM,OAAO,UAAU,QAAQ;AAC/B,QAAO;EACL,OAAO,QAAQ;EACf,OAAO,aAAa,QAAQ,QAAQ;EACpC,QAAQ;EACR,GAAI,QAAQ,UAAU,KAAA,KACpB,QAAQ,MAAM,SAAS,KAAK,EAC1B,OAAO,QAAQ,MAAM,KAAK,OAAO;GAC/B,MAAM;GACN,MAAM,EAAE;GACR,aAAa,EAAE;GACf,YAAY,EAAE;GACd,GAAI,EAAE,WAAW,KAAA,KAAa,EAAE,QAAQ,EAAE,QAAQ;GACnD,EAAE,EACJ;EACH,GAAI,QAAQ,eAAe,KAAA,KAAa,EAAE,aAAa,QAAQ,YAAY;EAC3E,GAAI,QAAQ,gBAAgB,KAAA,KAAa,EAAE,aAAa,QAAQ,aAAa;EAC7E,GAAI,QAAQ,oBAAoB,KAAA,KAAa,EAC3C,mBAAmB,QAAQ,iBAC5B;EACD,GAAI,QAAQ,cAAc,KAAA,KAAa,EAAE,WAAW,QAAQ,WAAW;EACvE,GAAI,QAAQ,UAAU,KAAA,KAAa,EAAE,OAAO,QAAQ,OAAO;EAC3D,GAAI,QAAQ,uBAAuB,KAAA,KAAa,EAC9C,sBAAsB,QAAQ,oBAC/B;EACD,GAAI,QAAQ,iBAAiB,KAAA,KAAa,EAAE,cAAc,QAAQ,cAAc;EAChF,GAAI,QAAQ,SAAS,KAAA,KAAa,EAAE,OAAO,QAAQ,MAAM;EACzD,GAAI,QAAQ,sBAAsB,KAAA,KAAa,EAC7C,qBAAqB,QAAQ,mBAC9B;EACD,GAAI,QAAQ,aAAa,KAAA,KAAa,EAAE,UAAU,QAAQ,UAAU;EACpE,GAAI,QAAQ,SAAS,KAAA,KAAa,EAAE,MAAM,QAAQ,MAAM;EACxD,GAAI,QAAQ,qBAAqB,KAAA,KAAa,EAC5C,mBAAmB,QAAQ,kBAC5B;EACD,GAAI,QAAQ,mBAAmB,KAAA,KAAa,EAC1C,kBAAkB,QAAQ,gBAC3B;EACD,GAAI,QAAQ,eAAe,KAAA,KAAa,EAAE,YAAY,QAAQ,YAAY;EAC1E,GAAI,SAAS,KAAA,KAAa,EAAE,MAAM;EACnC;;AAOH,MAAM,cAAc,OAAO,oBAAoB,mBAAmB;AAElE,MAAM,eAAe,SAAiC;CAAE,MAAM;CAAY;CAAK;;;;;;AAO/E,MAAM,eAAe,MAAM,MAAqB,CAAC,KAC/C,MAAM,eAAe,OAAO,CAAC;CAC3B,QAAQ,MAAuB,IAAI,QAAQ,YAAY;EAAE,UAAU;EAAa,KAAK;EAAG,CAAC;CACzF,oBAAoB,MAAuB;EACzC,MAAM,OAAO,EAAE,SAAS,OAAO;EAC/B,MAAM,UAAU,EAAE,SAAS,OAAO;AAClC,SAAO,IAAI,QAAQ,iBAAiB;GAClC,UAAU;GACV,GAAI,SAAS,KAAA,KAAa,SAAS,QAAQ,EAAE,MAAM;GACnD,GAAI,YAAY,KAAA,KAAa,YAAY,QAAQ,EAAE,SAAS;GAC5D,KAAK;GACN,CAAC;;CAEL,CAAC,EACF,MAAM,OACP;AAED,MAAM,mBAAmB,OAAO,oBAAoB,OAAO,eAAe,OAAO,QAAQ,CAAC;AAE1F,MAAM,2BAA2B,OAC/B,iBAAiB,GAAG,KAAK,CAAC,KACxB,OAAO,SAAS,WACd,YAAY,OAAO,CAAC,KAAK,OAAO,oBAAoB,YAAY,OAAO,CAAC,CAAC,CAC1E,EACD,OAAO,oBAAoB,YAAY,GAAG,KAAK,CAAC,CACjD;AAMH,MAAM,mBAAmB,QAAgB,SAAkC;CACzE,MAAM,WAAW;CACjB,MAAM,MAAM;AACZ,KAAI,WAAW,IAAK,QAAO,IAAI,QAAQ,YAAY;EAAE;EAAU;EAAK,CAAC;AACrE,KAAI,WAAW,OAAO,WAAW,IAAK,QAAO,IAAI,QAAQ,QAAQ;EAAE;EAAU;EAAK,CAAC;AACnF,KAAI,WAAW,IAAK,QAAO,IAAI,QAAQ,WAAW;EAAE;EAAU,SAAS;EAAQ;EAAK,CAAC;AACrF,KAAI,WAAW,IAAK,QAAO,IAAI,QAAQ,WAAW;EAAE;EAAU,SAAS;EAAc;EAAK,CAAC;AAC3F,KAAI,WAAW,IAAK,QAAO,IAAI,QAAQ,WAAW;EAAE;EAAU,SAAS;EAAW;EAAK,CAAC;AACxF,KAAI,WAAW,IAAK,QAAO,IAAI,QAAQ,sBAAsB;EAAE;EAAU;EAAK,CAAC;AAC/E,KAAI,UAAU,IAAK,QAAO,IAAI,QAAQ,YAAY;EAAE;EAAU;EAAQ;EAAK,CAAC;AAC5E,QAAO,IAAI,QAAQ,eAAe;EAAE;EAAU;EAAK,CAAC;;AAGtD,MAAM,qBAAqB,QAAgB;CACzC,MAAM,MAAM,GAAG,IAAI,WAAW,4BAA4B;AAC1D,SACE,YAEA,OAAO,OACL,OAAO,IAAI,aAAa;EACtB,MAAM,SAAS,OAAO,WAAW;EACjC,MAAM,cAAc,kBAAkB,KAAK,IAAI,CAAC,KAC9C,kBAAkB,YAAY,IAAI,OAAO,EACzC,kBAAkB,eAAe,UAAU,QAAQ,CAAC,EACpD,kBAAkB,OAAO,oBAAoB,CAC9C;EACD,MAAM,WAAW,OAAO,OACrB,QAAQ,YAAY,CACpB,KACC,OAAO,UACJ,UAAU,IAAI,QAAQ,YAAY;GAAE,UAAU;GAAa,KAAK;GAAO,CAAC,CAC1E,CACF;AACH,MAAI,SAAS,UAAU,KAAK;GAC1B,MAAM,OAAO,OAAO,SAAS,KAAK,KAAK,OAAO,oBAAoB,GAAG,CAAC;AACtE,UAAO,OAAO,KAAK,gBAAgB,SAAS,QAAQ,KAAK,CAAC;;AAG5D,SAAO,SAAS,OAAO,KACrB,OAAO,UACJ,UAAU,IAAI,QAAQ,YAAY;GAAE,UAAU;GAAa,KAAK;GAAO,CAAC,CAC1E,EACD,IAAI,WACJ,OAAO,UAAU,wBAAwB,EACzC,OAAO,SAAS,UACd,OAAO,MAAM,aAAa,MAAM,EAAE;GAChC,cAAc,OAAO,QAAQ,MAAM;GACnC,QAAQ,OAAO;GAChB,CAAC,CACH,CACF;GACD,CACH;;;;;;;AAQL,MAAa,eACX,MAEA,OAAO,OACL,OAAO,WAAW;CAChB,MAAM,SAAS,kBAAkB;AACjC,QAAO,EAAE,KAAK,OAAO,SAAS,UAAU,OAAO,aAAa,cAAc,OAAO,OAAO,CAAC,CAAC,CAAC;EAC3F,CACH;;;;AASH,MAAa,QAAQ,QACnB,OAAO,IAAI,WAAW,WAAW,UAAU,GAAG,WAAW;CACvD,MAAM,gBAAkD,YACtD,kBAAkB,IAAI,CAAC,QAAQ,CAAC,KAAK,OAAO,eAAe,WAAW,YAAY,OAAO,CAAC;AAC5F,QAAO;EACL;EACA,aAAa,YAAY,YAAY,aAAa,QAAQ,CAAC;EAC3D;EACD;EACD;;;;;;;;AASJ,MAAa,SACX,QACyE;CACzE,MAAM,QAAQ,MAAM,OAAO,WAAW,KAAK,IAAI,CAAC;CAChD,MAAM,UAAU,MAAM,OACpB,eACA,OAAO,IACL,KAAK,IAAI,GACR,OAA6B,EAC5B,aAAa,YAAY,EAAE,WAAW,QAA4B,EACnE,EACF,CACF;AACD,QAAO,MAAM,MAAM,OAAO,QAAQ"}
|
package/dist/streamEvents.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
|
|
2
2
|
import { WireOutputItem, WireResponseCompleted, turnFromCompleted } from "./codec.mjs";
|
|
3
3
|
import { Match, Schema } from "effect";
|
|
4
|
+
import { TurnEvent } from "@effect-uai/core/Turn";
|
|
4
5
|
//#region src/streamEvents.ts
|
|
5
6
|
var streamEvents_exports = /* @__PURE__ */ __exportAll({
|
|
6
7
|
KnownProviderEvent: () => KnownProviderEvent,
|
|
@@ -119,47 +120,31 @@ const eventToDeltas = (event, lookup) => Match.value(event).pipe(Match.discrimin
|
|
|
119
120
|
"response.output_item.added": ({ item }) => {
|
|
120
121
|
if (item.type !== "function_call") return [];
|
|
121
122
|
if (item.id !== void 0) lookup.remember(item.id, item.call_id);
|
|
122
|
-
return [{
|
|
123
|
-
type: "tool_call_start",
|
|
123
|
+
return [TurnEvent.ToolCallStart({
|
|
124
124
|
call_id: item.call_id,
|
|
125
125
|
name: item.name
|
|
126
|
-
}];
|
|
126
|
+
})];
|
|
127
127
|
},
|
|
128
|
-
"response.output_text.delta": ({ delta }) => [{
|
|
129
|
-
type: "text_delta",
|
|
130
|
-
text: delta
|
|
131
|
-
}],
|
|
128
|
+
"response.output_text.delta": ({ delta }) => [TurnEvent.TextDelta({ text: delta })],
|
|
132
129
|
"response.function_call_arguments.delta": ({ item_id, delta }) => {
|
|
133
130
|
const call_id = lookup.resolve(item_id);
|
|
134
|
-
return call_id === void 0 ? [] : [{
|
|
135
|
-
type: "tool_call_args_delta",
|
|
131
|
+
return call_id === void 0 ? [] : [TurnEvent.ToolCallArgsDelta({
|
|
136
132
|
call_id,
|
|
137
133
|
delta
|
|
138
|
-
}];
|
|
134
|
+
})];
|
|
139
135
|
},
|
|
140
|
-
"response.reasoning_text.delta": ({ delta }) => [{
|
|
141
|
-
type: "reasoning_delta",
|
|
136
|
+
"response.reasoning_text.delta": ({ delta }) => [TurnEvent.ReasoningDelta({
|
|
142
137
|
text: delta,
|
|
143
138
|
kind: "trace"
|
|
144
|
-
}],
|
|
145
|
-
"response.reasoning_summary_text.delta": ({ delta }) => [{
|
|
146
|
-
type: "reasoning_delta",
|
|
139
|
+
})],
|
|
140
|
+
"response.reasoning_summary_text.delta": ({ delta }) => [TurnEvent.ReasoningDelta({
|
|
147
141
|
text: delta,
|
|
148
142
|
kind: "summary"
|
|
149
|
-
}],
|
|
150
|
-
"response.refusal.delta": ({ delta }) => [{
|
|
151
|
-
type: "refusal_delta",
|
|
152
|
-
text: delta
|
|
153
|
-
}],
|
|
143
|
+
})],
|
|
144
|
+
"response.refusal.delta": ({ delta }) => [TurnEvent.RefusalDelta({ text: delta })],
|
|
154
145
|
"response.refusal.done": () => [],
|
|
155
|
-
"response.completed": ({ response }) => [{
|
|
156
|
-
|
|
157
|
-
turn: turnFromCompleted(response)
|
|
158
|
-
}],
|
|
159
|
-
"response.incomplete": ({ response }) => [{
|
|
160
|
-
type: "turn_complete",
|
|
161
|
-
turn: turnFromCompleted(response)
|
|
162
|
-
}],
|
|
146
|
+
"response.completed": ({ response }) => [TurnEvent.TurnComplete({ turn: turnFromCompleted(response) })],
|
|
147
|
+
"response.incomplete": ({ response }) => [TurnEvent.TurnComplete({ turn: turnFromCompleted(response) })],
|
|
163
148
|
"response.failed": () => [],
|
|
164
149
|
error: () => [],
|
|
165
150
|
_unknown: () => []
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streamEvents.mjs","names":[],"sources":["../src/streamEvents.ts"],"sourcesContent":["import { Match, Schema } from \"effect\"\nimport
|
|
1
|
+
{"version":3,"file":"streamEvents.mjs","names":[],"sources":["../src/streamEvents.ts"],"sourcesContent":["import { Match, Schema } from \"effect\"\nimport { TurnEvent } from \"@effect-uai/core/Turn\"\nimport { WireOutputItem, WireResponseCompleted, turnFromCompleted } from \"./codec.js\"\n\n// ---------------------------------------------------------------------------\n// Schemas for the SSE event payloads we care about. The Responses API ships\n// many event types; we model only the ones that map to a `TurnEvent`. All\n// others are silently ignored at the dispatch site.\n// ---------------------------------------------------------------------------\n\nconst OutputItemAdded = Schema.Struct({\n type: Schema.Literal(\"response.output_item.added\"),\n item: WireOutputItem,\n})\n\nconst OutputTextDelta = Schema.Struct({\n type: Schema.Literal(\"response.output_text.delta\"),\n delta: Schema.String,\n})\n\nconst FunctionArgsDelta = Schema.Struct({\n type: Schema.Literal(\"response.function_call_arguments.delta\"),\n item_id: Schema.String,\n delta: Schema.String,\n})\n\nconst ReasoningTextDelta = Schema.Struct({\n type: Schema.Literal(\"response.reasoning_text.delta\"),\n delta: Schema.String,\n})\n\nconst ReasoningSummaryDelta = Schema.Struct({\n type: Schema.Literal(\"response.reasoning_summary_text.delta\"),\n delta: Schema.String,\n})\n\nconst RefusalDelta = Schema.Struct({\n type: Schema.Literal(\"response.refusal.delta\"),\n delta: Schema.String,\n})\n\nconst RefusalDone = Schema.Struct({\n type: Schema.Literal(\"response.refusal.done\"),\n refusal: Schema.optional(Schema.String),\n})\n\nconst Completed = Schema.Struct({\n type: Schema.Literal(\"response.completed\"),\n response: WireResponseCompleted,\n})\n\nconst Incomplete = Schema.Struct({\n type: Schema.Literal(\"response.incomplete\"),\n response: WireResponseCompleted,\n})\n\nconst Failed = Schema.Struct({\n type: Schema.Literal(\"response.failed\"),\n response: WireResponseCompleted,\n})\n\nconst ErrorEvent = Schema.Struct({\n type: Schema.Literal(\"error\"),\n message: Schema.optional(Schema.String),\n code: Schema.optional(Schema.String),\n})\n\n/**\n * Catch-all variant for wire events that fail to decode against any known\n * schema, plus events that fail to JSON-parse. The decoder never produces\n * this directly - it's synthesized by `sseEventToProviderEvent` when\n * `decodeKnown` fails.\n */\nconst Unknown = Schema.Struct({\n type: Schema.Literal(\"_unknown\"),\n raw: Schema.Unknown,\n})\n\n/**\n * Internal: union of variants we actually know how to decode from the wire.\n * Used as the decode target; failures are caught and re-emitted as `Unknown`.\n */\nexport const KnownProviderEvent = Schema.Union([\n OutputItemAdded,\n OutputTextDelta,\n FunctionArgsDelta,\n ReasoningTextDelta,\n ReasoningSummaryDelta,\n RefusalDelta,\n RefusalDone,\n Completed,\n Incomplete,\n Failed,\n ErrorEvent,\n])\n\n/**\n * Public: every event the native stream can emit. Discriminated on `type`.\n * The `_unknown` branch closes the cardinality so downstream `Match.exhaustive`\n * cannot silently miss a wire event we didn't model.\n */\nexport const ProviderEvent = Schema.Union([\n OutputItemAdded,\n OutputTextDelta,\n FunctionArgsDelta,\n ReasoningTextDelta,\n ReasoningSummaryDelta,\n RefusalDelta,\n RefusalDone,\n Completed,\n Incomplete,\n Failed,\n ErrorEvent,\n Unknown,\n])\nexport type ProviderEvent = typeof ProviderEvent.Type\n\n// `call_id` lookup for `function_call_arguments.delta`. The SSE event\n// references the *item id*, not the call id; we keep a small map populated\n// from the `output_item.added` events.\nexport type CallIdLookup = {\n readonly resolve: (itemId: string) => string | undefined\n readonly remember: (itemId: string, callId: string) => void\n}\n\nexport const makeCallIdLookup = (): CallIdLookup => {\n const map = new Map<string, string>()\n return {\n resolve: (id) => map.get(id),\n remember: (id, callId) => {\n map.set(id, callId)\n },\n }\n}\n\n/**\n * Translate a decoded provider event into zero-or-more `TurnEvent`s.\n * Mutates `lookup` to record `(item_id → call_id)` from `output_item.added`\n * so subsequent `function_call_arguments.delta` events can be tagged with\n * the right call id.\n */\nexport const eventToDeltas = (\n event: ProviderEvent,\n lookup: CallIdLookup,\n): ReadonlyArray<TurnEvent> =>\n Match.value(event).pipe(\n Match.discriminatorsExhaustive(\"type\")({\n \"response.output_item.added\": ({ item }) => {\n if (item.type !== \"function_call\") return []\n if (item.id !== undefined) lookup.remember(item.id, item.call_id)\n return [TurnEvent.ToolCallStart({ call_id: item.call_id, name: item.name })]\n },\n \"response.output_text.delta\": ({ delta }) => [TurnEvent.TextDelta({ text: delta })],\n \"response.function_call_arguments.delta\": ({ item_id, delta }) => {\n const call_id = lookup.resolve(item_id)\n return call_id === undefined ? [] : [TurnEvent.ToolCallArgsDelta({ call_id, delta })]\n },\n \"response.reasoning_text.delta\": ({ delta }) => [\n TurnEvent.ReasoningDelta({ text: delta, kind: \"trace\" }),\n ],\n \"response.reasoning_summary_text.delta\": ({ delta }) => [\n TurnEvent.ReasoningDelta({ text: delta, kind: \"summary\" }),\n ],\n \"response.refusal.delta\": ({ delta }) => [TurnEvent.RefusalDelta({ text: delta })],\n // Terminal marker for the refusal stream; the deltas already covered\n // the text, so no canonical event is emitted here.\n \"response.refusal.done\": () => [],\n \"response.completed\": ({ response }) => [\n TurnEvent.TurnComplete({ turn: turnFromCompleted(response) }),\n ],\n // Incomplete = the model stopped early but produced a usable partial\n // turn (max_tokens, content_filter, max_tool_calls, refusal). Same\n // payload shape as `response.completed`; the stop_reason carries the\n // reason.\n \"response.incomplete\": ({ response }) => [\n TurnEvent.TurnComplete({ turn: turnFromCompleted(response) }),\n ],\n // Failed and error are surfaced separately as `AiError` by the stream\n // pipeline, not as canonical events.\n \"response.failed\": () => [],\n error: () => [],\n // No silent drops: unknown wire events flow through `streamNative` but\n // produce no canonical delta. Step 3 (canonical `other` event) replaces\n // this with a forwarded `other` delta on `TurnEvent`.\n _unknown: () => [],\n }),\n )\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-uai/responses",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "OpenAI Responses API provider for @effect-uai/core.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"effect": "4.0.0-beta.57",
|
|
51
51
|
"typescript": "^6.0.3",
|
|
52
|
-
"@effect-uai/core": "0.
|
|
52
|
+
"@effect-uai/core": "0.5.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@effect-uai/core": ">=0.2.0 <1",
|
package/src/OpenAIEmbedding.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { Context, Effect, Layer, Redacted, Schema } from "effect"
|
|
2
2
|
import { HttpClient, HttpClientRequest } from "effect/unstable/http"
|
|
3
3
|
import * as AiError from "@effect-uai/core/AiError"
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
EmbedContentPart,
|
|
6
|
+
EmbedInput,
|
|
7
|
+
Float32Embedding,
|
|
8
|
+
Usage,
|
|
9
|
+
} from "@effect-uai/core/Embedding"
|
|
5
10
|
import {
|
|
6
11
|
type CommonEmbedManyRequest,
|
|
7
12
|
type CommonEmbedRequest,
|
|
13
|
+
type EmbedEncoding,
|
|
8
14
|
EmbeddingModel,
|
|
9
15
|
type EmbeddingModelService,
|
|
10
16
|
type EmbedManyResponse,
|
|
@@ -22,7 +28,7 @@ import type { OpenAIEmbeddingModel } from "./models.js"
|
|
|
22
28
|
* error. The generic `EmbeddingModel` registration accepts and silently
|
|
23
29
|
* ignores `task` from `CommonEmbedRequest`.
|
|
24
30
|
*/
|
|
25
|
-
export type OpenAIEmbedRequest = Omit<CommonEmbedRequest, "model" | "task"> & {
|
|
31
|
+
export type OpenAIEmbedRequest = Omit<CommonEmbedRequest, "model" | "task" | "encoding"> & {
|
|
26
32
|
/** Narrows `CommonEmbedRequest.model` to the typed OpenAI union. */
|
|
27
33
|
readonly model: OpenAIEmbeddingModel
|
|
28
34
|
}
|
|
@@ -134,7 +140,7 @@ const WireResponse = Schema.Struct({
|
|
|
134
140
|
usage: WireUsage,
|
|
135
141
|
})
|
|
136
142
|
|
|
137
|
-
const valuesToEmbedding = (values: ReadonlyArray<number>):
|
|
143
|
+
const valuesToEmbedding = (values: ReadonlyArray<number>): Float32Embedding => ({
|
|
138
144
|
_tag: "float32",
|
|
139
145
|
vector: Float32Array.from(values),
|
|
140
146
|
})
|
|
@@ -143,7 +149,7 @@ const valuesToEmbedding = (values: ReadonlyArray<number>): Embedding => ({
|
|
|
143
149
|
// gets results in the order they passed inputs.
|
|
144
150
|
const orderedEmbeddings = (
|
|
145
151
|
data: ReadonlyArray<{ readonly embedding: ReadonlyArray<number>; readonly index: number }>,
|
|
146
|
-
): ReadonlyArray<
|
|
152
|
+
): ReadonlyArray<Float32Embedding> =>
|
|
147
153
|
[...data].sort((a, b) => a.index - b.index).map((item) => valuesToEmbedding(item.embedding))
|
|
148
154
|
|
|
149
155
|
// ---------------------------------------------------------------------------
|
|
@@ -185,7 +191,7 @@ const postEmbed = (
|
|
|
185
191
|
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure))
|
|
186
192
|
if (response.status >= 400) {
|
|
187
193
|
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""))
|
|
188
|
-
return yield*
|
|
194
|
+
return yield* httpStatusError(response.status, text)
|
|
189
195
|
}
|
|
190
196
|
const json = yield* response.json.pipe(Effect.mapError(transportFailure))
|
|
191
197
|
return yield* Schema.decodeUnknownEffect(WireResponse)(json).pipe(
|
|
@@ -266,8 +272,19 @@ export const layer = (
|
|
|
266
272
|
Effect.map(
|
|
267
273
|
make(cfg),
|
|
268
274
|
(s): EmbeddingModelService => ({
|
|
269
|
-
|
|
270
|
-
|
|
275
|
+
// OpenAI only emits float32; the cast is sound for the 99% case.
|
|
276
|
+
// A caller asking for a non-float32 encoding via the generic tag
|
|
277
|
+
// gets the type they requested but the runtime returns float32.
|
|
278
|
+
embed: <E extends EmbedEncoding | undefined = undefined>(
|
|
279
|
+
req: Omit<CommonEmbedRequest, "encoding"> & { readonly encoding?: E },
|
|
280
|
+
) => s.embed(req as OpenAIEmbedRequest) as Effect.Effect<EmbedResponse<E>, AiError.AiError>,
|
|
281
|
+
embedMany: <E extends EmbedEncoding | undefined = undefined>(
|
|
282
|
+
req: Omit<CommonEmbedManyRequest, "encoding"> & { readonly encoding?: E },
|
|
283
|
+
) =>
|
|
284
|
+
s.embedMany(req as OpenAIEmbedManyRequest) as Effect.Effect<
|
|
285
|
+
EmbedManyResponse<E>,
|
|
286
|
+
AiError.AiError
|
|
287
|
+
>,
|
|
271
288
|
}),
|
|
272
289
|
),
|
|
273
290
|
)
|
package/src/Responses.ts
CHANGED
|
@@ -187,11 +187,10 @@ const eventToError = Match.type<ProviderEvent>().pipe(
|
|
|
187
187
|
Match.option,
|
|
188
188
|
)
|
|
189
189
|
|
|
190
|
+
const parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))
|
|
191
|
+
|
|
190
192
|
const sseEventToProviderEvent = (ev: SSE.Event): Effect.Effect<ProviderEvent> =>
|
|
191
|
-
|
|
192
|
-
try: () => JSON.parse(ev.data) as unknown,
|
|
193
|
-
catch: () => ev.data,
|
|
194
|
-
}).pipe(
|
|
193
|
+
parseJsonUnknown(ev.data).pipe(
|
|
195
194
|
Effect.flatMap((parsed) =>
|
|
196
195
|
decodeKnown(parsed).pipe(Effect.orElseSucceed(() => makeUnknown(parsed))),
|
|
197
196
|
),
|
package/src/streamEvents.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Match, Schema } from "effect"
|
|
2
|
-
import
|
|
2
|
+
import { TurnEvent } from "@effect-uai/core/Turn"
|
|
3
3
|
import { WireOutputItem, WireResponseCompleted, turnFromCompleted } from "./codec.js"
|
|
4
4
|
|
|
5
5
|
// ---------------------------------------------------------------------------
|
|
@@ -148,40 +148,32 @@ export const eventToDeltas = (
|
|
|
148
148
|
"response.output_item.added": ({ item }) => {
|
|
149
149
|
if (item.type !== "function_call") return []
|
|
150
150
|
if (item.id !== undefined) lookup.remember(item.id, item.call_id)
|
|
151
|
-
return [
|
|
152
|
-
{
|
|
153
|
-
type: "tool_call_start" as const,
|
|
154
|
-
call_id: item.call_id,
|
|
155
|
-
name: item.name,
|
|
156
|
-
},
|
|
157
|
-
]
|
|
151
|
+
return [TurnEvent.ToolCallStart({ call_id: item.call_id, name: item.name })]
|
|
158
152
|
},
|
|
159
|
-
"response.output_text.delta": ({ delta }) => [{
|
|
153
|
+
"response.output_text.delta": ({ delta }) => [TurnEvent.TextDelta({ text: delta })],
|
|
160
154
|
"response.function_call_arguments.delta": ({ item_id, delta }) => {
|
|
161
155
|
const call_id = lookup.resolve(item_id)
|
|
162
|
-
return call_id === undefined
|
|
163
|
-
? []
|
|
164
|
-
: [{ type: "tool_call_args_delta" as const, call_id, delta }]
|
|
156
|
+
return call_id === undefined ? [] : [TurnEvent.ToolCallArgsDelta({ call_id, delta })]
|
|
165
157
|
},
|
|
166
158
|
"response.reasoning_text.delta": ({ delta }) => [
|
|
167
|
-
{
|
|
159
|
+
TurnEvent.ReasoningDelta({ text: delta, kind: "trace" }),
|
|
168
160
|
],
|
|
169
161
|
"response.reasoning_summary_text.delta": ({ delta }) => [
|
|
170
|
-
{
|
|
162
|
+
TurnEvent.ReasoningDelta({ text: delta, kind: "summary" }),
|
|
171
163
|
],
|
|
172
|
-
"response.refusal.delta": ({ delta }) => [{
|
|
164
|
+
"response.refusal.delta": ({ delta }) => [TurnEvent.RefusalDelta({ text: delta })],
|
|
173
165
|
// Terminal marker for the refusal stream; the deltas already covered
|
|
174
166
|
// the text, so no canonical event is emitted here.
|
|
175
167
|
"response.refusal.done": () => [],
|
|
176
168
|
"response.completed": ({ response }) => [
|
|
177
|
-
{
|
|
169
|
+
TurnEvent.TurnComplete({ turn: turnFromCompleted(response) }),
|
|
178
170
|
],
|
|
179
171
|
// Incomplete = the model stopped early but produced a usable partial
|
|
180
172
|
// turn (max_tokens, content_filter, max_tool_calls, refusal). Same
|
|
181
173
|
// payload shape as `response.completed`; the stop_reason carries the
|
|
182
174
|
// reason.
|
|
183
175
|
"response.incomplete": ({ response }) => [
|
|
184
|
-
{
|
|
176
|
+
TurnEvent.TurnComplete({ turn: turnFromCompleted(response) }),
|
|
185
177
|
],
|
|
186
178
|
// Failed and error are surfaced separately as `AiError` by the stream
|
|
187
179
|
// pipeline, not as canonical events.
|