mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-13 19:46:16 +03:00
refactor(ai): deduplicate JSON, record, and URL query helpers (#57604)
This commit is contained in:
committed by
GitHub
Unverified
parent
51626eee90
commit
cc996eece1
@@ -37,19 +37,17 @@ export type RequestInput<SelectedLanguageModel extends LanguageModel = LanguageM
|
||||
}
|
||||
|
||||
export function generate<const Model extends LanguageModel>(
|
||||
input: RequestInput<Model>,
|
||||
input: RequestInput<Model> | LLMRequest,
|
||||
options?: StreamOptions,
|
||||
): Effect.Effect<LLMResponse, AIError, Service>
|
||||
export function generate(input: LLMRequest, options?: StreamOptions): Effect.Effect<LLMResponse, AIError, Service>
|
||||
export function generate(input: RequestInput | LLMRequest, options?: StreamOptions) {
|
||||
return requestEffect(input).pipe(Effect.flatMap((request) => LLMClient.generate(request, options)))
|
||||
}
|
||||
|
||||
export function stream<const Model extends LanguageModel>(
|
||||
input: RequestInput<Model>,
|
||||
input: RequestInput<Model> | LLMRequest,
|
||||
options?: StreamOptions,
|
||||
): Stream.Stream<LLMEvent, AIError, Service>
|
||||
export function stream(input: LLMRequest, options?: StreamOptions): Stream.Stream<LLMEvent, AIError, Service>
|
||||
export function stream(input: RequestInput | LLMRequest, options?: StreamOptions) {
|
||||
return Stream.unwrap(requestEffect(input).pipe(Effect.map((request) => LLMClient.stream(request, options))))
|
||||
}
|
||||
|
||||
@@ -4,9 +4,8 @@ import type { AwaitOptions, Event, Generation, Snapshot } from "./generation.js"
|
||||
import { Image, type ImageModel, type ImageRequest, type ImageRequestInput } from "./image.js"
|
||||
import { LLM } from "./index.js"
|
||||
import { Media } from "./media.js"
|
||||
import { tryRequest } from "./media-model.js"
|
||||
import { RequestExecutor } from "./route/executor.js"
|
||||
import { AIError, InvalidRequestError, LanguageModel, LLMRequest } from "./schema/index.js"
|
||||
import { AIError, InvalidRequestError, LanguageModel, type LLMRequest } from "./schema/index.js"
|
||||
import type { RequestInput } from "./llm.js"
|
||||
import { Speech, type SpeechModel, type SpeechRequest, type SpeechRequestInput } from "./speech.js"
|
||||
import {
|
||||
@@ -93,9 +92,6 @@ export const make = (options: Options = {}) => {
|
||||
cancel: (options) => run(generation.cancel(), options),
|
||||
})
|
||||
|
||||
const llmRequest = (input: RequestInput | LLMRequest) =>
|
||||
input instanceof LLMRequest ? Effect.succeed(input) : tryRequest(() => LLM.request(input))
|
||||
|
||||
return {
|
||||
run,
|
||||
/** Decoded asset bytes, downloading `url` sources through the executor. */
|
||||
@@ -137,9 +133,9 @@ export const make = (options: Options = {}) => {
|
||||
llm: {
|
||||
request: LLM.request,
|
||||
generate: <const Model extends LanguageModel>(input: RequestInput<Model> | LLMRequest, options?: RunOptions) =>
|
||||
run(Effect.flatMap(llmRequest(input), LLM.generate), options),
|
||||
run(LLM.generate(input), options),
|
||||
stream: <const Model extends LanguageModel>(input: RequestInput<Model> | LLMRequest, options?: RunOptions) =>
|
||||
iterate(Stream.unwrap(Effect.map(llmRequest(input), LLM.stream)), options),
|
||||
iterate(LLM.stream(input), options),
|
||||
},
|
||||
image: {
|
||||
request: Image.request,
|
||||
|
||||
@@ -26,13 +26,14 @@
|
||||
*/
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { decodeJson } from "../../utils/json.js"
|
||||
import { Allow } from "./partial-json-options.js"
|
||||
export * from "./partial-json-options.js"
|
||||
|
||||
export class PartialJSON extends Error {}
|
||||
export class MalformedJSON extends Error {}
|
||||
|
||||
const decodeJson = Schema.decodeUnknownSync(Schema.fromJsonString(Schema.Unknown))
|
||||
const decodeJsonString = Schema.decodeUnknownSync(Schema.fromJsonString(Schema.String))
|
||||
|
||||
/** Parse complete or incomplete JSON, restricted by the supplied partial-value flags. */
|
||||
export function parseJSON(jsonString: string, allowPartial = Allow.ALL): unknown {
|
||||
@@ -175,16 +176,16 @@ const _parseJSON = (jsonString: string, allow: number) => {
|
||||
}
|
||||
if (jsonString.charAt(index) === '"') {
|
||||
try {
|
||||
return decodeJson(jsonString.substring(start, ++index - Number(escape))) as string
|
||||
return decodeJsonString(jsonString.substring(start, ++index - Number(escape)))
|
||||
} catch (error) {
|
||||
throwMalformedError(String(error))
|
||||
}
|
||||
}
|
||||
if (Allow.STR & allow) {
|
||||
try {
|
||||
return decodeJson(`${jsonString.substring(start, index - Number(escape))}"`) as string
|
||||
return decodeJsonString(`${jsonString.substring(start, index - Number(escape))}"`)
|
||||
} catch {
|
||||
return decodeJson(`${jsonString.substring(start, jsonString.lastIndexOf("\\"))}"`) as string
|
||||
return decodeJsonString(`${jsonString.substring(start, jsonString.lastIndexOf("\\"))}"`)
|
||||
}
|
||||
}
|
||||
return markPartialJSON("Unterminated string literal")
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
type HttpContext,
|
||||
type HttpRateLimitDetails,
|
||||
} from "./schema/index.js"
|
||||
import { Json } from "./utils/json.js"
|
||||
import { isRecord } from "./utils/record.js"
|
||||
|
||||
const patterns = [
|
||||
/prompt is too long/i,
|
||||
@@ -115,7 +117,7 @@ export const isRetryable = (error: AIError) => {
|
||||
}
|
||||
}
|
||||
|
||||
const decodeJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown))
|
||||
const decodeJson = Schema.decodeUnknownOption(Json)
|
||||
// OpenCode Zen reports account caps as typed 429/402 errors that are not throttles.
|
||||
const QUOTA_CODES = new Set([
|
||||
"insufficient_quota",
|
||||
@@ -333,7 +335,3 @@ function providerCodes(value: unknown) {
|
||||
exception?.type,
|
||||
].filter((value): value is string => typeof value === "string")
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Schema, type Config, type Redacted } from "effect"
|
||||
import type { Config, Redacted } from "effect"
|
||||
import type { ProviderPackage } from "../provider-package.js"
|
||||
import { AnthropicMessages, OpenAIChat, OpenAIResponses } from "../protocols/index.js"
|
||||
import { Auth } from "../route/auth.js"
|
||||
import type { AtLeastOne, ProviderAuthOption } from "../route/auth-options.js"
|
||||
import type { RouteDefaultsInput } from "../route/client.js"
|
||||
import { ProviderConfigurationError, ProviderID, type ModelID } from "../schema/index.js"
|
||||
import { encodeJson } from "../utils/json.js"
|
||||
import type { OpenAIProviderOptionsInput } from "./openai-options.js"
|
||||
|
||||
export const id = ProviderID.make("cloudflare-ai-gateway")
|
||||
@@ -86,9 +87,7 @@ const credential = (input: LanguageModelOptions) =>
|
||||
.orElse(Auth.config(authEnvVars[1]))
|
||||
|
||||
const headers = (input: LanguageModelOptions) => ({
|
||||
...(input.metadata === undefined
|
||||
? {}
|
||||
: { "cf-aig-metadata": Schema.encodeSync(Schema.fromJsonString(Schema.Unknown))(input.metadata) }),
|
||||
...(input.metadata === undefined ? {} : { "cf-aig-metadata": encodeJson(input.metadata) }),
|
||||
...(input.cacheTtl === undefined ? {} : { "cf-aig-cache-ttl": String(input.cacheTtl) }),
|
||||
...(input.cacheKey === undefined ? {} : { "cf-aig-cache-key": input.cacheKey }),
|
||||
...(input.skipCache === undefined ? {} : { "cf-aig-skip-cache": String(input.skipCache) }),
|
||||
|
||||
@@ -32,13 +32,6 @@ export interface HttpPrepared<Frame> {
|
||||
readonly middleware?: HttpMiddleware
|
||||
}
|
||||
|
||||
const applyQuery = (url: string, query: Record<string, string> | undefined) => {
|
||||
if (!query) return url
|
||||
const next = new URL(url)
|
||||
Object.entries(query).forEach(([key, value]) => next.searchParams.set(key, value))
|
||||
return next.toString()
|
||||
}
|
||||
|
||||
const bodyWithOverlay = <Body>(body: Body, request: LLMRequest, encodeBody: (body: Body) => string) =>
|
||||
Effect.gen(function* () {
|
||||
if (request.http?.body === undefined) return { jsonBody: body, bodyText: encodeBody(body) }
|
||||
@@ -51,10 +44,11 @@ const bodyWithOverlay = <Body>(body: Body, request: LLMRequest, encodeBody: (bod
|
||||
|
||||
export const jsonRequestParts = <Body>(input: JsonRequestInput<Body>) =>
|
||||
Effect.gen(function* () {
|
||||
const url = applyQuery(
|
||||
renderEndpoint(input.endpoint, { request: input.request, body: input.body }).toString(),
|
||||
input.request.http?.query,
|
||||
)
|
||||
const endpoint = renderEndpoint(input.endpoint, { request: input.request, body: input.body })
|
||||
if (input.request.http?.query) {
|
||||
Object.entries(input.request.http.query).forEach(([key, value]) => endpoint.searchParams.set(key, value))
|
||||
}
|
||||
const url = endpoint.toString()
|
||||
const body = yield* bodyWithOverlay(input.body, input.request, input.encodeBody)
|
||||
const headers = yield* Auth.toEffect(input.auth)({
|
||||
request: input.request,
|
||||
|
||||
Reference in New Issue
Block a user