Skip to content

Commit 8b09f64

Browse files
authored
refactor(codemode): export runtime value classes as Values (#48000)
1 parent e791afd commit 8b09f64

16 files changed

Lines changed: 221 additions & 258 deletions

File tree

‎packages/codemode/README.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ runtime.execute(source) // Effect<CodeMode.Result, never, ToolServices>
9191
The Effect environment is inferred from the supplied tools. `onToolCallStart` observes admitted calls with decoded
9292
input; `onToolCallEnd` observes settled outcomes and duration. Both hooks return Effects and must not fail.
9393

94+
### `Values`
95+
96+
`Values` exports the runtime's non-JSON value classes: `Values.URL`, `Values.URLSearchParams`, `Values.Date`,
97+
`Values.RegExp`, `Values.Map`, `Values.Set`, and `Values.Promise`. The interpreter recognizes these by class; a
98+
program's `new URL(...)` is a `Values.URL` wrapping the host `URL`. `Values.isValue` narrows to the data-like kinds.
99+
94100
### OpenAPI tools
95101

96102
`OpenAPI.fromSpec` converts an OpenAPI 3.x document into one tool per supported operation. Dotted `operationId` values

‎packages/codemode/src/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ export * as CodeMode from "./codemode.js"
22
export * as Namespace from "./namespace.js"
33
export * as Tool from "./tool.js"
44
export * as OpenAPI from "./openapi/index.js"
5+
export { Values } from "./values.js"
56
export { searchSignature, toolExpression } from "./codemode.js"
67
export { ToolError, toolError } from "./tool-error.js"

‎packages/codemode/src/interpreter/methods.ts‎

Lines changed: 28 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,7 @@ import {
1616
} from "./model.js"
1717
import { containsOpaqueReference, isRuntimeReference, rejectCircularInsertion, typeofValue } from "./references.js"
1818
import { isBlockedMember, type SafeObject } from "../tool-runtime.js"
19-
import {
20-
CodeModeDate,
21-
CodeModeMap,
22-
CodeModePromise,
23-
CodeModeRegExp,
24-
CodeModeSet,
25-
CodeModeURL,
26-
CodeModeURLSearchParams,
27-
isCodeModeValue,
28-
} from "../values.js"
19+
import { Values } from "../values.js"
2920
import { dateSetterArgumentCount, invokeDateMethod, invokeDateStatic } from "../stdlib/date.js"
3021
import { invokeMathMethod } from "../stdlib/math.js"
3122
import { invokeNumberMethod, invokeNumberStatic } from "../stdlib/number.js"
@@ -43,7 +34,7 @@ export type CallbackRunner<R> = {
4334
args: Array<unknown>,
4435
node: AstNode,
4536
) => Effect.Effect<unknown, unknown, R>
46-
readonly settlePromise: (promise: CodeModePromise) => Effect.Effect<unknown, unknown, never>
37+
readonly settlePromise: (promise: Values.Promise) => Effect.Effect<unknown, unknown, never>
4738
}
4839

4940
// The single acceptance list for callbacks: collections, sort, string replacers,
@@ -100,7 +91,7 @@ export const invokeIntrinsic = <R>(
10091
if (Array.isArray(ref.receiver)) {
10192
return invokeArrayMethod(runner, ref.receiver, ref.name, args, node)
10293
}
103-
if (ref.receiver instanceof CodeModeDate) {
94+
if (ref.receiver instanceof Values.Date) {
10495
const target = ref.receiver
10596
const argumentCount = dateSetterArgumentCount(ref.name)
10697
if (argumentCount === undefined) return Effect.succeed(invokeDateMethod(target, ref.name, [], node))
@@ -113,19 +104,19 @@ export const invokeIntrinsic = <R>(
113104
(values) => invokeDateMethod(target, ref.name, values, node, initialTime),
114105
)
115106
}
116-
if (ref.receiver instanceof CodeModeRegExp) {
107+
if (ref.receiver instanceof Values.RegExp) {
117108
return Effect.succeed(invokeRegExpMethod(ref.receiver, ref.name, args, node))
118109
}
119-
if (ref.receiver instanceof CodeModeMap) {
110+
if (ref.receiver instanceof Values.Map) {
120111
return invokeMapMethod(runner, ref.receiver, ref.name, args, node)
121112
}
122-
if (ref.receiver instanceof CodeModeSet) {
113+
if (ref.receiver instanceof Values.Set) {
123114
return invokeSetMethod(runner, ref.receiver, ref.name, args, node)
124115
}
125-
if (ref.receiver instanceof CodeModeURL) {
116+
if (ref.receiver instanceof Values.URL) {
126117
return Effect.succeed(invokeURLMethod(ref.receiver, ref.name, node))
127118
}
128-
if (ref.receiver instanceof CodeModeURLSearchParams) {
119+
if (ref.receiver instanceof Values.URLSearchParams) {
129120
return invokeURLSearchParamsMethod(runner, ref.receiver, ref.name, args, node)
130121
}
131122
throw new InterpreterRuntimeError(`Method '${ref.name}' is not available.`, node)
@@ -136,7 +127,7 @@ const coerceNumericArgument = <R>(
136127
value: unknown,
137128
node: AstNode,
138129
): Effect.Effect<number, unknown, R> => {
139-
if (value === null || typeof value !== "object" || Array.isArray(value) || isCodeModeValue(value)) {
130+
if (value === null || typeof value !== "object" || Array.isArray(value) || Values.isValue(value)) {
140131
return Effect.succeed(coerceToNumber(value))
141132
}
142133
const object = value as Record<string, unknown>
@@ -192,7 +183,7 @@ const invokeStringMethod = (value: string, name: string, args: Array<unknown>, n
192183
const optNum = (index: number): number | undefined => (args[index] === undefined ? undefined : num(index))
193184
const optStr = (index: number): string | undefined => (args[index] === undefined ? undefined : str(index))
194185
const rejectRegex = (): void => {
195-
if (args[0] instanceof CodeModeRegExp) {
186+
if (args[0] instanceof Values.RegExp) {
196187
throw new InterpreterRuntimeError(
197188
`String.${name} cannot take a regular expression; use regex.test(string) or String.search instead.`,
198189
node,
@@ -241,7 +232,7 @@ const invokeStringMethod = (value: string, name: string, args: Array<unknown>, n
241232
result = requestedLimit !== undefined && requestedLimit >>> 0 === 0 ? [] : [value]
242233
break
243234
}
244-
if (args[0] instanceof CodeModeRegExp) {
235+
if (args[0] instanceof Values.RegExp) {
245236
result = value.split(args[0].regex, optNum(1))
246237
break
247238
}
@@ -272,7 +263,7 @@ const invokeStringMethod = (value: string, name: string, args: Array<unknown>, n
272263
break
273264
case "replace":
274265
case "replaceAll": {
275-
if (args[0] instanceof CodeModeRegExp) {
266+
if (args[0] instanceof Values.RegExp) {
276267
const pattern = args[0].regex
277268
const replacement = str(1)
278269
if (name === "replaceAll" && !pattern.global) {
@@ -368,7 +359,7 @@ const invokeArrayStatic = (name: string, args: Array<unknown>, node: AstNode): u
368359
}
369360

370361
const arrayLikeSource = (source: unknown, node: AstNode): { readonly length: number; readonly source: object } => {
371-
if (source instanceof CodeModePromise) {
362+
if (source instanceof Values.Promise) {
372363
throw new InterpreterRuntimeError(
373364
"Array.from received an un-awaited Promise; await it before creating the array.",
374365
node,
@@ -445,7 +436,7 @@ export const invokeGroupBy = <R>(
445436
throw new InterpreterRuntimeError(`${namespace}.groupBy expects an iterable collection.`, node).as("TypeError")
446437
}
447438
if (namespace === "Map") {
448-
const result = new CodeModeMap()
439+
const result = new Values.Map()
449440
let index = 0
450441
while (true) {
451442
const step = yield* cursor.next
@@ -488,10 +479,10 @@ const coerceGroupByPropertyKey = <R>(
488479
value: unknown,
489480
node: AstNode,
490481
): Effect.Effect<string, unknown, R> => {
491-
if (value === null || typeof value !== "object" || Array.isArray(value) || isCodeModeValue(value)) {
482+
if (value === null || typeof value !== "object" || Array.isArray(value) || Values.isValue(value)) {
492483
return Effect.succeed(coerceToString(value))
493484
}
494-
if (value instanceof CodeModePromise) return Effect.succeed("[object Promise]")
485+
if (value instanceof Values.Promise) return Effect.succeed("[object Promise]")
495486
if (isRuntimeReference(value)) {
496487
throw new InterpreterRuntimeError("Object.groupBy callback must return a data value.", node, "InvalidDataValue")
497488
}
@@ -543,7 +534,7 @@ const invokeStringReplacer = <R>(
543534
}
544535

545536
const pattern = args[0]
546-
if (pattern instanceof CodeModeRegExp) {
537+
if (pattern instanceof Values.RegExp) {
547538
if (name === "replaceAll" && !pattern.regex.global) {
548539
throw new InterpreterRuntimeError(
549540
`String.replaceAll requires a regular expression with the global (g) flag: write /${pattern.regex.source}/${pattern.regex.flags}g, or use String.replace to replace only the first match.`,
@@ -566,7 +557,7 @@ const invokeStringReplacer = <R>(
566557
// Error values are branded plain objects; boundedData would strip the brand before coercion.
567558
output.push(
568559
value.slice(end, match.offset),
569-
replacement instanceof CodeModePromise
560+
replacement instanceof Values.Promise
570561
? "[object Promise]"
571562
: errorBrandName(replacement)
572563
? coerceToString(replacement)
@@ -599,7 +590,7 @@ export const applyCollectionCallback = <R>(
599590

600591
const invokeMapMethod = <R>(
601592
runner: CallbackRunner<R>,
602-
target: CodeModeMap,
593+
target: Values.Map,
603594
name: string,
604595
args: Array<unknown>,
605596
node: AstNode,
@@ -641,7 +632,7 @@ const invokeMapMethod = <R>(
641632

642633
const invokeSetMethod = <R>(
643634
runner: CallbackRunner<R>,
644-
target: CodeModeSet,
635+
target: Values.Set,
645636
name: string,
646637
args: Array<unknown>,
647638
node: AstNode,
@@ -688,7 +679,7 @@ const invokeSetMethod = <R>(
688679

689680
const invokeSetOperation = <R>(
690681
runner: CallbackRunner<R>,
691-
target: CodeModeSet,
682+
target: Values.Set,
692683
name: string,
693684
source: unknown,
694685
node: AstNode,
@@ -701,7 +692,7 @@ const invokeSetOperation = <R>(
701692
return result
702693
}
703694
if (name === "intersection") {
704-
const result = new CodeModeSet()
695+
const result = new Values.Set()
705696
if (target.set.size <= other.size) {
706697
for (const item of target.set.values()) {
707698
if (yield* other.has(item)) result.set.add(item)
@@ -758,28 +749,28 @@ const invokeSetOperation = <R>(
758749
return true
759750
})
760751

761-
const copySet = (source: CodeModeSet): CodeModeSet => {
762-
const result = new CodeModeSet()
752+
const copySet = (source: Values.Set): Values.Set => {
753+
const result = new Values.Set()
763754
for (const item of source.set.values()) result.set.add(item)
764755
return result
765756
}
766757

767758
const loadSetRecord = <R>(runner: CallbackRunner<R>, source: unknown, name: string, node: AstNode) => {
768-
if (source instanceof CodeModeSet) {
759+
if (source instanceof Values.Set) {
769760
return Effect.succeed({
770761
size: source.set.size,
771762
has: (item: unknown) => Effect.succeed(source.set.has(item)),
772763
keys: () => Effect.succeed(source.set.values()),
773764
})
774765
}
775-
if (source instanceof CodeModeMap) {
766+
if (source instanceof Values.Map) {
776767
return Effect.succeed({
777768
size: source.map.size,
778769
has: (item: unknown) => Effect.succeed(source.map.has(item)),
779770
keys: () => Effect.succeed(source.map.keys()),
780771
})
781772
}
782-
if (source === null || typeof source !== "object" || isCodeModeValue(source)) {
773+
if (source === null || typeof source !== "object" || Values.isValue(source)) {
783774
throw new InterpreterRuntimeError(`Set.${name} expects a Set-like object.`, node).as("TypeError")
784775
}
785776
const object = source as Record<string, unknown>
@@ -809,7 +800,7 @@ const loadSetRecord = <R>(runner: CallbackRunner<R>, source: unknown, name: stri
809800

810801
const invokeURLSearchParamsMethod = <R>(
811802
runner: CallbackRunner<R>,
812-
target: CodeModeURLSearchParams,
803+
target: Values.URLSearchParams,
813804
name: string,
814805
args: Array<unknown>,
815806
node: AstNode,

‎packages/codemode/src/interpreter/model.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Effect } from "effect"
22
import type { SafeObject } from "../tool-runtime.js"
3-
import type { CodeModePromise, CodeModeRegExp, CodeModeURL } from "../values.js"
3+
import type { Values } from "../values.js"
44

55
export type SourcePosition = {
66
line: number
@@ -36,7 +36,7 @@ export type StatementResult =
3636
| { kind: "continue"; label?: string }
3737

3838
export type MemberReference = {
39-
target: SafeObject | Array<unknown> | CodeModeRegExp | CodeModeURL
39+
target: SafeObject | Array<unknown> | Values.RegExp | Values.URL
4040
key: PropertyKey
4141
}
4242

@@ -99,7 +99,7 @@ export type PromiseInstanceMethodName = "then" | "catch" | "finally"
9999

100100
export class PromiseInstanceMethodReference {
101101
constructor(
102-
readonly promise: CodeModePromise,
102+
readonly promise: Values.Promise,
103103
readonly name: PromiseInstanceMethodName,
104104
) {}
105105
}

0 commit comments

Comments
 (0)