@neurocode-ai/llm 1.18.8
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/AGENTS.md +321 -0
- package/DESIGN.md +1114 -0
- package/README.md +131 -0
- package/example/call-sites.md +591 -0
- package/example/tutorial.ts +255 -0
- package/package.json +51 -0
- package/script/recording-cost-report.ts +250 -0
- package/script/setup-recording-env.ts +542 -0
- package/src/cache-policy.ts +111 -0
- package/src/index.ts +33 -0
- package/src/llm.ts +186 -0
- package/src/protocols/anthropic-messages.ts +855 -0
- package/src/protocols/bedrock-converse.ts +674 -0
- package/src/protocols/bedrock-event-stream.ts +87 -0
- package/src/protocols/gemini.ts +512 -0
- package/src/protocols/index.ts +6 -0
- package/src/protocols/openai-chat.ts +506 -0
- package/src/protocols/openai-compatible-chat.ts +24 -0
- package/src/protocols/openai-responses.ts +1022 -0
- package/src/protocols/shared.ts +326 -0
- package/src/protocols/utils/bedrock-auth.ts +70 -0
- package/src/protocols/utils/bedrock-cache.ts +37 -0
- package/src/protocols/utils/bedrock-media.ts +90 -0
- package/src/protocols/utils/cache.ts +16 -0
- package/src/protocols/utils/gemini-tool-schema.ts +99 -0
- package/src/protocols/utils/lifecycle.ts +102 -0
- package/src/protocols/utils/openai-options.ts +93 -0
- package/src/protocols/utils/tool-schema.ts +86 -0
- package/src/protocols/utils/tool-stream.ts +218 -0
- package/src/provider-error.ts +43 -0
- package/src/provider.ts +36 -0
- package/src/providers/amazon-bedrock.ts +43 -0
- package/src/providers/anthropic.ts +35 -0
- package/src/providers/azure.ts +110 -0
- package/src/providers/cloudflare.ts +127 -0
- package/src/providers/github-copilot.ts +69 -0
- package/src/providers/google.ts +35 -0
- package/src/providers/index.ts +11 -0
- package/src/providers/openai-compatible-profile.ts +20 -0
- package/src/providers/openai-compatible.ts +65 -0
- package/src/providers/openai-options.ts +83 -0
- package/src/providers/openai.ts +63 -0
- package/src/providers/openrouter.ts +98 -0
- package/src/providers/xai.ts +56 -0
- package/src/route/auth-options.ts +57 -0
- package/src/route/auth.ts +156 -0
- package/src/route/client.ts +436 -0
- package/src/route/endpoint.ts +53 -0
- package/src/route/executor.ts +385 -0
- package/src/route/framing.ts +27 -0
- package/src/route/index.ts +25 -0
- package/src/route/protocol.ts +84 -0
- package/src/route/transport/http.ts +155 -0
- package/src/route/transport/index.ts +33 -0
- package/src/route/transport/websocket.ts +280 -0
- package/src/schema/errors.ts +207 -0
- package/src/schema/events.ts +618 -0
- package/src/schema/ids.ts +43 -0
- package/src/schema/index.ts +5 -0
- package/src/schema/messages.ts +312 -0
- package/src/schema/options.ts +276 -0
- package/src/tool-runtime.ts +78 -0
- package/src/tool.ts +253 -0
- package/src/utils/record.ts +3 -0
- package/sst-env.d.ts +10 -0
- package/test/adapter.test.ts +171 -0
- package/test/auth-options.types.ts +168 -0
- package/test/auth.test.ts +103 -0
- package/test/cache-policy.test.ts +262 -0
- package/test/continuation-scenarios.ts +104 -0
- package/test/endpoint.test.ts +58 -0
- package/test/executor.test.ts +458 -0
- package/test/exports.test.ts +76 -0
- package/test/fixtures/media/restroom.png +0 -0
- package/test/fixtures/recordings/anthropic-messages/accepts-malformed-assistant-tool-order-with-default-patch.json +29 -0
- package/test/fixtures/recordings/anthropic-messages/anthropic-opus-4-7-image-tool-result.json +43 -0
- package/test/fixtures/recordings/anthropic-messages/claude-opus-4-7-drives-a-tool-loop.json +56 -0
- package/test/fixtures/recordings/anthropic-messages/rejects-malformed-assistant-tool-order-without-patch.json +29 -0
- package/test/fixtures/recordings/anthropic-messages/streams-text.json +29 -0
- package/test/fixtures/recordings/anthropic-messages/streams-tool-call.json +29 -0
- package/test/fixtures/recordings/anthropic-messages-cache/writes-then-reads-cache-control-on-identical-second-call.json +48 -0
- package/test/fixtures/recordings/bedrock-converse/drives-a-tool-loop.json +55 -0
- package/test/fixtures/recordings/bedrock-converse/streams-a-tool-call.json +29 -0
- package/test/fixtures/recordings/bedrock-converse/streams-text.json +29 -0
- package/test/fixtures/recordings/cloudflare-ai-gateway/cloudflare-ai-gateway-workers-ai-gpt-oss-20b-tools-tool-call.json +32 -0
- package/test/fixtures/recordings/cloudflare-ai-gateway/cloudflare-ai-gateway-workers-ai-llama-3-1-8b-text.json +32 -0
- package/test/fixtures/recordings/cloudflare-workers-ai/cloudflare-workers-ai-gpt-oss-20b-tools-tool-call.json +32 -0
- package/test/fixtures/recordings/cloudflare-workers-ai/cloudflare-workers-ai-llama-3-1-8b-text.json +32 -0
- package/test/fixtures/recordings/gemini/gemini-2-5-flash-image.json +32 -0
- package/test/fixtures/recordings/gemini/streams-text.json +28 -0
- package/test/fixtures/recordings/gemini/streams-tool-call.json +28 -0
- package/test/fixtures/recordings/gemini-cache/reports-cachedcontenttokencount-on-identical-second-call.json +46 -0
- package/test/fixtures/recordings/openai-chat/continues-after-tool-result.json +28 -0
- package/test/fixtures/recordings/openai-chat/drives-a-tool-loop-end-to-end.json +46 -0
- package/test/fixtures/recordings/openai-chat/streams-text.json +28 -0
- package/test/fixtures/recordings/openai-chat/streams-tool-call.json +28 -0
- package/test/fixtures/recordings/openai-compatible-chat/deepseek-streams-text.json +28 -0
- package/test/fixtures/recordings/openai-compatible-chat/groq-llama-3-3-70b-drives-a-tool-loop.json +53 -0
- package/test/fixtures/recordings/openai-compatible-chat/groq-streams-text.json +28 -0
- package/test/fixtures/recordings/openai-compatible-chat/groq-streams-tool-call.json +28 -0
- package/test/fixtures/recordings/openai-compatible-chat/openrouter-claude-opus-4-7-drives-a-tool-loop.json +54 -0
- package/test/fixtures/recordings/openai-compatible-chat/openrouter-gpt-4o-mini-drives-a-tool-loop.json +53 -0
- package/test/fixtures/recordings/openai-compatible-chat/openrouter-gpt-5-5-drives-a-tool-loop.json +54 -0
- package/test/fixtures/recordings/openai-compatible-chat/openrouter-streams-text.json +28 -0
- package/test/fixtures/recordings/openai-compatible-chat/openrouter-streams-tool-call.json +28 -0
- package/test/fixtures/recordings/openai-compatible-chat/togetherai-streams-text.json +28 -0
- package/test/fixtures/recordings/openai-compatible-chat/togetherai-streams-tool-call.json +28 -0
- package/test/fixtures/recordings/openai-responses/gpt-5-5-drives-a-tool-loop.json +54 -0
- package/test/fixtures/recordings/openai-responses/gpt-5-5-streams-text.json +28 -0
- package/test/fixtures/recordings/openai-responses/gpt-5-5-streams-tool-call.json +28 -0
- package/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-image-tool-result.json +42 -0
- package/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-reasoning-continuation.json +58 -0
- package/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-reasoning.json +32 -0
- package/test/fixtures/recordings/openai-responses-cache/reports-cached-tokens-on-identical-second-call.json +46 -0
- package/test/generate-object.test.ts +184 -0
- package/test/lib/effect.ts +50 -0
- package/test/lib/http.ts +98 -0
- package/test/lib/openai-chunks.ts +27 -0
- package/test/lib/sse.ts +17 -0
- package/test/lib/tool-runtime.ts +146 -0
- package/test/llm.test.ts +199 -0
- package/test/prepare.test.ts +178 -0
- package/test/provider/anthropic-messages-cache.recorded.test.ts +53 -0
- package/test/provider/anthropic-messages.recorded.test.ts +45 -0
- package/test/provider/anthropic-messages.test.ts +895 -0
- package/test/provider/bedrock-converse-cache.recorded.test.ts +54 -0
- package/test/provider/bedrock-converse.test.ts +744 -0
- package/test/provider/cloudflare.test.ts +230 -0
- package/test/provider/gemini-cache.recorded.test.ts +48 -0
- package/test/provider/gemini.test.ts +584 -0
- package/test/provider/golden.recorded.test.ts +223 -0
- package/test/provider/openai-chat.test.ts +674 -0
- package/test/provider/openai-compatible-chat.test.ts +238 -0
- package/test/provider/openai-responses-cache.recorded.test.ts +46 -0
- package/test/provider/openai-responses.test.ts +1472 -0
- package/test/provider/openrouter.test.ts +56 -0
- package/test/provider-error.test.ts +30 -0
- package/test/provider.types.ts +41 -0
- package/test/recorded-golden.ts +97 -0
- package/test/recorded-runner.ts +100 -0
- package/test/recorded-scenarios.ts +531 -0
- package/test/recorded-test.ts +94 -0
- package/test/recorded-utils.ts +56 -0
- package/test/recorded-websocket.ts +26 -0
- package/test/response.test.ts +98 -0
- package/test/route.test.ts +43 -0
- package/test/schema.test.ts +86 -0
- package/test/tool-runtime.test.ts +818 -0
- package/test/tool-schema-projection.test.ts +117 -0
- package/test/tool-stream.test.ts +99 -0
- package/test/tool.types.ts +40 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options"
|
|
2
|
+
import type { RouteDefaultsInput } from "../route/client"
|
|
3
|
+
import { ProviderID, type ModelID } from "../schema"
|
|
4
|
+
import * as OpenAICompatibleProfiles from "./openai-compatible-profile"
|
|
5
|
+
import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat"
|
|
6
|
+
import * as OpenAIResponses from "../protocols/openai-responses"
|
|
7
|
+
|
|
8
|
+
export const id = ProviderID.make("xai")
|
|
9
|
+
|
|
10
|
+
export type ModelOptions = RouteDefaultsInput &
|
|
11
|
+
ProviderAuthOption<"optional"> & {
|
|
12
|
+
readonly baseURL?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const routes = [OpenAIResponses.route, OpenAICompatibleChat.route]
|
|
16
|
+
|
|
17
|
+
const auth = (options: ProviderAuthOption<"optional">) => AuthOptions.bearer(options, "XAI_API_KEY")
|
|
18
|
+
|
|
19
|
+
const configuredResponsesRoute = (input: ModelOptions) => {
|
|
20
|
+
const { apiKey: _, auth: _auth, baseURL, ...rest } = input
|
|
21
|
+
return OpenAIResponses.route.with({
|
|
22
|
+
...rest,
|
|
23
|
+
provider: id,
|
|
24
|
+
endpoint: { baseURL: baseURL ?? OpenAICompatibleProfiles.profiles.xai.baseURL },
|
|
25
|
+
auth: auth(input),
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const configuredChatRoute = (input: ModelOptions) => {
|
|
30
|
+
const { apiKey: _, auth: _auth, baseURL, ...rest } = input
|
|
31
|
+
return OpenAICompatibleChat.route.with({
|
|
32
|
+
...rest,
|
|
33
|
+
provider: id,
|
|
34
|
+
endpoint: { baseURL: baseURL ?? OpenAICompatibleProfiles.profiles.xai.baseURL },
|
|
35
|
+
auth: auth(input),
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const configure = (input: ModelOptions = {}) => {
|
|
40
|
+
const responsesRoute = configuredResponsesRoute(input)
|
|
41
|
+
const chatRoute = configuredChatRoute(input)
|
|
42
|
+
const responses = (modelID: string | ModelID) => responsesRoute.model({ id: modelID })
|
|
43
|
+
const chat = (modelID: string | ModelID) => chatRoute.model({ id: modelID })
|
|
44
|
+
return {
|
|
45
|
+
id,
|
|
46
|
+
model: responses,
|
|
47
|
+
responses,
|
|
48
|
+
chat,
|
|
49
|
+
configure,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const provider = configure()
|
|
54
|
+
export const model = provider.model
|
|
55
|
+
export const responses = provider.responses
|
|
56
|
+
export const chat = provider.chat
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Config, Redacted } from "effect"
|
|
2
|
+
import { Auth } from "./auth"
|
|
3
|
+
|
|
4
|
+
export type ApiKeyMode = "optional" | "required"
|
|
5
|
+
|
|
6
|
+
export type AuthOverride = {
|
|
7
|
+
readonly auth: Auth
|
|
8
|
+
readonly apiKey?: never
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type OptionalApiKeyAuth = {
|
|
12
|
+
readonly apiKey?: string | Redacted.Redacted<string> | Config.Config<string | Redacted.Redacted<string>>
|
|
13
|
+
readonly auth?: never
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type RequiredApiKeyAuth = {
|
|
17
|
+
readonly apiKey: string | Redacted.Redacted<string> | Config.Config<string | Redacted.Redacted<string>>
|
|
18
|
+
readonly auth?: never
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type ProviderAuthOption<Mode extends ApiKeyMode> =
|
|
22
|
+
| AuthOverride
|
|
23
|
+
| (Mode extends "optional" ? OptionalApiKeyAuth : RequiredApiKeyAuth)
|
|
24
|
+
|
|
25
|
+
export type ModelOptions<Base, Mode extends ApiKeyMode> = Omit<Base, "apiKey" | "auth"> & ProviderAuthOption<Mode>
|
|
26
|
+
|
|
27
|
+
export type ModelArgs<Base, Mode extends ApiKeyMode> = Mode extends "optional"
|
|
28
|
+
? readonly [options?: ModelOptions<Base, Mode>]
|
|
29
|
+
: readonly [options: ModelOptions<Base, Mode>]
|
|
30
|
+
|
|
31
|
+
export type ModelFactory<Base, Mode extends ApiKeyMode, Model> = (id: string, ...args: ModelArgs<Base, Mode>) => Model
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Require at least one of the keys in `T`. Use for option shapes where any
|
|
35
|
+
* subset of fields is acceptable but at least one must be present (e.g. Azure
|
|
36
|
+
* accepts `resourceName` or `baseURL`).
|
|
37
|
+
*/
|
|
38
|
+
export type AtLeastOne<T> = {
|
|
39
|
+
[K in keyof T]: Required<Pick<T, K>> & Partial<Omit<T, K>>
|
|
40
|
+
}[keyof T]
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Standard bearer-auth resolution for providers: honor an explicit `auth`
|
|
44
|
+
* override, otherwise resolve `apiKey` (option > config var) and apply it as
|
|
45
|
+
* a bearer token.
|
|
46
|
+
*/
|
|
47
|
+
export const bearer = (options: ProviderAuthOption<"optional">, envVar: string | ReadonlyArray<string>): Auth => {
|
|
48
|
+
if ("auth" in options && options.auth) return options.auth
|
|
49
|
+
return (Array.isArray(envVar) ? envVar : [envVar])
|
|
50
|
+
.reduce(
|
|
51
|
+
(auth, name) => auth.orElse(Auth.config(name)),
|
|
52
|
+
Auth.optional("apiKey" in options ? options.apiKey : undefined, "apiKey"),
|
|
53
|
+
)
|
|
54
|
+
.bearer()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export * as AuthOptions from "./auth-options"
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { Config, Effect, Redacted } from "effect"
|
|
2
|
+
import { Headers } from "effect/unstable/http"
|
|
3
|
+
import { AuthenticationReason, InvalidRequestReason, LLMError, type LLMRequest } from "../schema"
|
|
4
|
+
|
|
5
|
+
export class MissingCredentialError extends Error {
|
|
6
|
+
readonly _tag = "MissingCredentialError"
|
|
7
|
+
|
|
8
|
+
constructor(readonly source: string) {
|
|
9
|
+
super(`Missing auth credential: ${source}`)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type CredentialError = MissingCredentialError | Config.ConfigError
|
|
14
|
+
export type AuthError = CredentialError | LLMError
|
|
15
|
+
type Secret = string | Redacted.Redacted | Config.Config<string | Redacted.Redacted>
|
|
16
|
+
|
|
17
|
+
export interface AuthInput {
|
|
18
|
+
readonly request: LLMRequest
|
|
19
|
+
readonly method: "POST" | "GET"
|
|
20
|
+
readonly url: string
|
|
21
|
+
readonly body: string
|
|
22
|
+
readonly headers: Headers.Headers
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface Credential {
|
|
26
|
+
readonly load: Effect.Effect<Redacted.Redacted, CredentialError>
|
|
27
|
+
readonly orElse: (that: Credential) => Credential
|
|
28
|
+
readonly bearer: () => Auth
|
|
29
|
+
readonly header: (name: string) => Auth
|
|
30
|
+
readonly pipe: <A>(f: (self: Credential) => A) => A
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface Auth {
|
|
34
|
+
readonly apply: (input: AuthInput) => Effect.Effect<Headers.Headers, AuthError>
|
|
35
|
+
readonly andThen: (that: Auth) => Auth
|
|
36
|
+
readonly orElse: (that: Auth) => Auth
|
|
37
|
+
readonly pipe: <A>(f: (self: Auth) => A) => A
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const isAuth = (input: unknown): input is Auth =>
|
|
41
|
+
typeof input === "object" && input !== null && "apply" in input && typeof input.apply === "function"
|
|
42
|
+
|
|
43
|
+
const credential = (load: Effect.Effect<Redacted.Redacted, CredentialError>): Credential => {
|
|
44
|
+
const self: Credential = {
|
|
45
|
+
load,
|
|
46
|
+
orElse: (that) => credential(load.pipe(Effect.catch(() => that.load))),
|
|
47
|
+
bearer: () => fromCredential(self, (secret) => ({ authorization: `Bearer ${secret}` })),
|
|
48
|
+
header: (name) => fromCredential(self, (secret) => ({ [name]: secret })),
|
|
49
|
+
pipe: (f) => f(self),
|
|
50
|
+
}
|
|
51
|
+
return self
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const auth = (apply: Auth["apply"]): Auth => {
|
|
55
|
+
const self: Auth = {
|
|
56
|
+
apply,
|
|
57
|
+
andThen: (that) =>
|
|
58
|
+
auth((input) => apply(input).pipe(Effect.flatMap((headers) => that.apply({ ...input, headers })))),
|
|
59
|
+
orElse: (that) => auth((input) => apply(input).pipe(Effect.catch(() => that.apply(input)))),
|
|
60
|
+
pipe: (f) => f(self),
|
|
61
|
+
}
|
|
62
|
+
return self
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const fromCredential = (source: Credential, render: (secret: string) => Headers.Input) =>
|
|
66
|
+
auth((input) =>
|
|
67
|
+
source.load.pipe(Effect.map((secret) => Headers.setAll(input.headers, render(Redacted.value(secret))))),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
const secretEffect = (secret: string | Redacted.Redacted, source: string) => {
|
|
71
|
+
const redacted = typeof secret === "string" ? Redacted.make(secret) : secret
|
|
72
|
+
if (Redacted.value(redacted) === "") return Effect.fail(new MissingCredentialError(source))
|
|
73
|
+
return Effect.succeed(redacted)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const credentialFromSecret = (secret: Secret, source: string) => {
|
|
77
|
+
if (typeof secret === "string" || Redacted.isRedacted(secret)) return credential(secretEffect(secret, source))
|
|
78
|
+
return credential(
|
|
79
|
+
Effect.gen(function* () {
|
|
80
|
+
return yield* secretEffect(yield* secret, source)
|
|
81
|
+
}),
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const value = (secret: string, source = "value") => credentialFromSecret(secret, source)
|
|
86
|
+
|
|
87
|
+
export const optional = (secret: Secret | undefined, source = "optional value") =>
|
|
88
|
+
secret === undefined
|
|
89
|
+
? credential(Effect.fail(new MissingCredentialError(source)))
|
|
90
|
+
: credentialFromSecret(secret, source)
|
|
91
|
+
|
|
92
|
+
export const config = (name: string) => credentialFromSecret(Config.redacted(name), name)
|
|
93
|
+
|
|
94
|
+
export const effect = (load: Effect.Effect<Redacted.Redacted, CredentialError>) => credential(load)
|
|
95
|
+
|
|
96
|
+
export const none = auth((input) => Effect.succeed(input.headers))
|
|
97
|
+
|
|
98
|
+
export const headers = (input: Headers.Input) =>
|
|
99
|
+
auth((inputAuth) => Effect.succeed(Headers.setAll(inputAuth.headers, input)))
|
|
100
|
+
|
|
101
|
+
export const remove = (name: string) => auth((input) => Effect.succeed(Headers.remove(input.headers, name)))
|
|
102
|
+
|
|
103
|
+
export const custom = (apply: (input: AuthInput) => Effect.Effect<Headers.Headers, LLMError>) => auth(apply)
|
|
104
|
+
|
|
105
|
+
export const passthrough = none
|
|
106
|
+
|
|
107
|
+
const credentialInput = (source: Secret | Credential) =>
|
|
108
|
+
typeof source === "string" || Redacted.isRedacted(source) || Config.isConfig(source)
|
|
109
|
+
? credentialFromSecret(source, "value")
|
|
110
|
+
: source
|
|
111
|
+
|
|
112
|
+
export function bearer(source: Secret | Credential): Auth
|
|
113
|
+
export function bearer(source: Secret | Credential) {
|
|
114
|
+
return credentialInput(source).bearer()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const apiKey = bearer
|
|
118
|
+
|
|
119
|
+
export function header(name: string): (source: Secret | Credential) => Auth
|
|
120
|
+
export function header(name: string, source: Secret | Credential): Auth
|
|
121
|
+
export function header(name: string, source?: Secret | Credential) {
|
|
122
|
+
if (source === undefined) {
|
|
123
|
+
return (next: Secret | Credential) => credentialInput(next).header(name)
|
|
124
|
+
}
|
|
125
|
+
return credentialInput(source).header(name)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function bearerHeader(name: string): (source: Secret | Credential) => Auth
|
|
129
|
+
export function bearerHeader(name: string, source: Secret | Credential): Auth
|
|
130
|
+
export function bearerHeader(name: string, source?: Secret | Credential) {
|
|
131
|
+
const render = (input: Secret | Credential) =>
|
|
132
|
+
fromCredential(credentialInput(input), (secret) => ({ [name]: `Bearer ${secret}` }))
|
|
133
|
+
if (source === undefined) return render
|
|
134
|
+
return render(source)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const toLLMError = (error: AuthError): LLMError => {
|
|
138
|
+
if (error instanceof MissingCredentialError || error instanceof Config.ConfigError) {
|
|
139
|
+
return new LLMError({
|
|
140
|
+
module: "Auth",
|
|
141
|
+
method: "apply",
|
|
142
|
+
reason:
|
|
143
|
+
error instanceof MissingCredentialError
|
|
144
|
+
? new AuthenticationReason({ message: error.message, kind: "missing" })
|
|
145
|
+
: new InvalidRequestReason({ message: `Failed to resolve auth config: ${error.message}` }),
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
return error
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export const toEffect =
|
|
152
|
+
(input: Auth) =>
|
|
153
|
+
(authInput: AuthInput): Effect.Effect<Headers.Headers, LLMError> =>
|
|
154
|
+
input.apply(authInput).pipe(Effect.mapError(toLLMError))
|
|
155
|
+
|
|
156
|
+
export * as Auth from "./auth"
|