mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-10 20:33:36 +00:00
refactor(codemode): split runtime into focused interpreter modules (#36540)
This commit is contained in:
parent
430750e2f8
commit
49cb73b348
24 changed files with 1911 additions and 2277 deletions
|
|
@ -31,10 +31,8 @@ export type {
|
|||
} from "./types.js"
|
||||
|
||||
/**
|
||||
* Builds a CodeMode tool subtree from an OpenAPI 3.x document, one tool per
|
||||
* operation. Auth is resolved host-side via `auth.resolve` and never
|
||||
* model-visible. Tools require `HttpClient.HttpClient`; unrepresentable
|
||||
* operations land in `skipped`.
|
||||
* Builds one CodeMode tool per representable OpenAPI 3.x operation. Auth remains host-side,
|
||||
* tools require `HttpClient.HttpClient`, and unrepresentable operations land in `skipped`.
|
||||
*/
|
||||
export const fromSpec = (options: Options): Result => {
|
||||
const document = options.spec
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ const buildRequest = (
|
|||
input: Readonly<Record<string, unknown>>,
|
||||
): Effect.Effect<HttpClientRequest.HttpClientRequest, ToolError> =>
|
||||
Effect.gen(function* () {
|
||||
// Validate every model-controlled value before auth resolution, which may refresh tokens.
|
||||
// Validate model input before auth resolution can refresh credentials.
|
||||
const url = buildUrl(plan, input)
|
||||
if (url instanceof ToolError) return yield* Effect.fail(url)
|
||||
const missing = plan.fields.find(
|
||||
|
|
@ -77,7 +77,6 @@ const buildRequest = (
|
|||
request = serialized
|
||||
}
|
||||
|
||||
// Host headers first, then declared header parameters.
|
||||
request = HttpClientRequest.setHeaders(request, plan.headers)
|
||||
for (const field of plan.fields) {
|
||||
if (field.location !== "header") continue
|
||||
|
|
@ -169,7 +168,7 @@ const applyCredentials = (
|
|||
continue
|
||||
}
|
||||
if (credential.type === "basic") {
|
||||
// Buffer instead of btoa: btoa throws on non-Latin-1 credentials.
|
||||
// Basic auth credentials are UTF-8; btoa rejects non-Latin-1 input.
|
||||
const duplicate = add(
|
||||
"header",
|
||||
"authorization",
|
||||
|
|
@ -183,7 +182,6 @@ const applyCredentials = (
|
|||
if (duplicate !== undefined) return duplicate
|
||||
continue
|
||||
}
|
||||
// apiKey: the carrier comes from the scheme declaration.
|
||||
if (definition.type !== "apiKey") {
|
||||
return toolError(
|
||||
`Security scheme '${name}' is not an apiKey scheme; resolve a bearer, basic, or header credential for it.`,
|
||||
|
|
@ -212,8 +210,7 @@ const buildUrl = (plan: Plan, input: Readonly<Record<string, unknown>>): string
|
|||
),
|
||||
)
|
||||
if (fieldValue instanceof ToolError) return fieldValue
|
||||
// '.'/'..' survive encoding and URL normalization collapses them, letting a
|
||||
// model-supplied value retarget the request to a different endpoint.
|
||||
// URL normalization collapses encoded `.` and `..`, which could retarget the request.
|
||||
if (fieldValue === "" || fieldValue === "." || fieldValue === "..") {
|
||||
return toolError(`Invalid path parameter '${field.inputName}'.`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,7 @@ const asArray = (value: unknown): ReadonlyArray<unknown> => (Array.isArray(value
|
|||
export const nonEmptyString = (value: unknown): string | undefined =>
|
||||
typeof value === "string" && value !== "" ? value : undefined
|
||||
|
||||
// Guards record lookups keyed by spec- or model-controlled names against
|
||||
// prototype-inherited values (e.g. a parameter named `toString`).
|
||||
// Spec- and model-controlled keys must not resolve inherited properties.
|
||||
export const own = <T>(record: Readonly<Record<string, T>>, key: string): T | undefined =>
|
||||
Object.hasOwn(record, key) ? record[key] : undefined
|
||||
|
||||
|
|
@ -106,7 +105,7 @@ const operationParameters = (
|
|||
pathItem: Record<string, unknown>,
|
||||
operation: Record<string, unknown>,
|
||||
): Parsed<ReadonlyArray<PlannedField>> => {
|
||||
// Operation-level parameters override path-level ones sharing (location, name).
|
||||
// OpenAPI operation parameters override path parameters with the same location and name.
|
||||
const declared = new Map<
|
||||
string,
|
||||
{ readonly name: string; readonly location: string; readonly parameter: Record<string, unknown> }
|
||||
|
|
|
|||
|
|
@ -22,9 +22,8 @@ export type SecurityScheme =
|
|||
| { readonly type: "openIdConnect" }
|
||||
|
||||
/**
|
||||
* Credential material returned by a host auth resolver. The carrier for `apiKey`
|
||||
* comes from the scheme definition, not the credential. `header` is the escape
|
||||
* hatch for nonstandard schemes.
|
||||
* Credential material returned by a host auth resolver. `apiKey` uses the scheme's carrier;
|
||||
* `header` supports nonstandard schemes.
|
||||
*/
|
||||
export type Credential =
|
||||
| { readonly type: "bearer"; readonly token: string }
|
||||
|
|
@ -33,9 +32,7 @@ export type Credential =
|
|||
| { readonly type: "header"; readonly name: string; readonly value: string }
|
||||
|
||||
/**
|
||||
* Resolves credential material for one named security scheme at call time.
|
||||
* `undefined` means unavailable, try the next OR alternative; a failure aborts
|
||||
* the call rather than falling through.
|
||||
* Resolves credentials at call time. `undefined` tries the next OR alternative; failure aborts.
|
||||
*/
|
||||
export type AuthResolver = (context: {
|
||||
readonly name: string
|
||||
|
|
@ -74,9 +71,7 @@ export type Parsed<T> = { readonly ok: true; readonly value: T } | { readonly ok
|
|||
export type InputLocation = "path" | "query" | "header" | "body"
|
||||
|
||||
export type InputField = {
|
||||
/** Model-visible field name after cross-location collision handling. */
|
||||
readonly inputName: string
|
||||
/** Original parameter or body-property name used on the wire. */
|
||||
readonly name: string
|
||||
readonly location: InputLocation
|
||||
readonly required: boolean
|
||||
|
|
@ -92,7 +87,6 @@ export type OperationInput = {
|
|||
readonly body: Body | undefined
|
||||
}
|
||||
|
||||
/** One OR alternative: scheme name -> required scopes. Empty object = unauthenticated is acceptable. */
|
||||
export type SecurityRequirement = Readonly<Record<string, ReadonlyArray<string>>>
|
||||
|
||||
export type Plan = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue