mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-01 07:20:53 +00:00
feat(codemode): cross Set, RegExp, and URLSearchParams to the host in a useful form (#49065)
This commit is contained in:
parent
a54f2c12a8
commit
00a2fc97f7
5 changed files with 58 additions and 12 deletions
|
|
@ -59,13 +59,18 @@ export const fromData = (protos: Prototypes, value: unknown, label: string): unk
|
|||
* non-finite numbers become null, and array holes become null. `undefined` object properties are
|
||||
* dropped ("json") or become null ("result", for program results where the consumer must never see
|
||||
* undefined); a bare `undefined` follows the same rule.
|
||||
*
|
||||
* At the host boundary (tool arguments and program results) `__proto__` keys are dropped and values
|
||||
* `JSON.stringify` would flatten to `{}` cross in a useful form instead: a Set as an array, a RegExp
|
||||
* and URLSearchParams as their strings. `JSON.stringify` itself passes `boundary: false` to keep JS
|
||||
* behavior.
|
||||
*/
|
||||
export const toData = (
|
||||
value: unknown,
|
||||
label: string,
|
||||
undefinedAs: "json" | "result" = "json",
|
||||
stripProto = true,
|
||||
): unknown => copy(value, label, undefinedAs, 0, new Set(), undefined, stripProto)
|
||||
boundary = true,
|
||||
): unknown => copy(value, label, undefinedAs, 0, new Set(), undefined, boundary)
|
||||
|
||||
// "program" and "data" build program objects; "json" and "result" build ordinary objects for the host.
|
||||
type Mode = "program" | "data" | "json" | "result"
|
||||
|
|
@ -77,9 +82,9 @@ const copy = (
|
|||
depth: number,
|
||||
seen: Set<object>,
|
||||
protos?: Prototypes,
|
||||
stripProto = true,
|
||||
boundary = true,
|
||||
): unknown => {
|
||||
const next = (item: unknown) => copy(item, label, mode, depth + 1, seen, protos, stripProto)
|
||||
const next = (item: unknown) => copy(item, label, mode, depth + 1, seen, protos, boundary)
|
||||
if (depth > MAX_VALUE_DEPTH) {
|
||||
throw new ToolRuntimeError("InvalidDataValue", `${label} exceeds the maximum value depth of ${MAX_VALUE_DEPTH}.`)
|
||||
}
|
||||
|
|
@ -132,6 +137,17 @@ const copy = (
|
|||
if (value instanceof Date) return Number.isFinite(value.getTime()) ? value.toISOString() : null
|
||||
if (value instanceof ProgramURL) return value.url.href
|
||||
if (value instanceof URL) return value.href
|
||||
if (boundary && protos === undefined) {
|
||||
if (value instanceof ProgramRegExp) return String(value.regex)
|
||||
if (value instanceof ProgramURLSearchParams) return value.params.toString()
|
||||
if (value instanceof ProgramSet) {
|
||||
if (seen.has(value)) throw new ToolRuntimeError("InvalidDataValue", `${label} contains a circular value.`)
|
||||
seen.add(value)
|
||||
const copied = Array.from(value.set, (item) => next(item) ?? null)
|
||||
seen.delete(value)
|
||||
return copied
|
||||
}
|
||||
}
|
||||
// Remaining wrappers and their host counterparts serialize as empty objects, like JSON.stringify.
|
||||
if (
|
||||
isWrapper(value) ||
|
||||
|
|
@ -161,7 +177,7 @@ const copy = (
|
|||
defineHost(copied, "message", next(get(value, "message")))
|
||||
}
|
||||
for (const [key, item] of entries(value)) {
|
||||
if (stripProto && key === "__proto__") continue
|
||||
if (boundary && key === "__proto__") continue
|
||||
const copiedItem = next(item)
|
||||
if (copiedItem === undefined && mode === "json") continue
|
||||
defineHost(copied, key, copiedItem)
|
||||
|
|
@ -197,7 +213,7 @@ const copy = (
|
|||
}
|
||||
const copied: Record<string, unknown> = {}
|
||||
for (const [key, item] of Object.entries(value)) {
|
||||
if (stripProto && key === "__proto__") continue
|
||||
if (boundary && key === "__proto__") continue
|
||||
const copiedItem = next(item)
|
||||
if (copiedItem === undefined && mode === "json") continue
|
||||
defineHost(copied, key, copiedItem)
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ const stringify = <R>(runner: Runner<R>, args: Array<unknown>): Effect.Effect<un
|
|||
.filter((item): item is string | number => typeof item === "string" || typeof item === "number")
|
||||
.map(String)
|
||||
: null
|
||||
// A string cannot pollute, so __proto__ stays: JSON.stringify includes own __proto__ keys, like JS.
|
||||
// Not a host boundary: __proto__ stays and Set/RegExp/URLSearchParams serialize as {}, like JS.
|
||||
const text = JSON.stringify(toData(args[0], "JSON.stringify value", "json", false), properties, indent)
|
||||
if (text !== undefined) checkStringLength(text.length)
|
||||
return Effect.succeed(text)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue