Skip to content

Commit 5bb7b23

Browse files
authored
Add native LLM core foundation (#24712)
1 parent dc7d665 commit 5bb7b23

144 files changed

Lines changed: 17052 additions & 2 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ node_modules
33
.worktrees
44
.sst
55
.env
6+
.env.local
67
.idea
78
.vscode
89
.codex

.gitleaksignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Fake secret-looking strings used by HTTP recorder redaction tests.
2+
afa57acfda894e0ebf3c637dd710310b705c0a2f:packages/http-recorder/test/record-replay.test.ts:generic-api-key:69
3+
afa57acfda894e0ebf3c637dd710310b705c0a2f:packages/http-recorder/test/record-replay.test.ts:generic-api-key:92
4+
afa57acfda894e0ebf3c637dd710310b705c0a2f:packages/http-recorder/test/record-replay.test.ts:generic-api-key:146
5+
afa57acfda894e0ebf3c637dd710310b705c0a2f:packages/http-recorder/test/record-replay.test.ts:gcp-api-key:71

bun.lock

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/console/app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"zod": "catalog:"
3636
},
3737
"devDependencies": {
38+
"@types/bun": "catalog:",
3839
"@typescript/native-preview": "catalog:",
3940
"@webgpu/types": "0.1.54",
4041
"typescript": "catalog:",

packages/console/app/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"allowJs": true,
1313
"strict": true,
1414
"noEmit": true,
15-
"types": ["vite/client", "@webgpu/types"],
15+
"types": ["vite/client", "@webgpu/types", "bun"],
1616
"isolatedModules": true,
1717
"paths": {
1818
"~/*": ["./src/*"]

packages/enterprise/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"@cloudflare/workers-types": "catalog:",
3333
"@tailwindcss/vite": "catalog:",
3434
"@typescript/native-preview": "catalog:",
35+
"@types/bun": "catalog:",
3536
"@types/luxon": "catalog:",
3637
"tailwindcss": "catalog:",
3738
"typescript": "catalog:",

packages/enterprise/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"allowJs": true,
1212
"noEmit": true,
1313
"strict": true,
14-
"types": ["@cloudflare/workers-types", "vite/client"],
14+
"types": ["@cloudflare/workers-types", "vite/client", "bun"],
1515
"isolatedModules": true,
1616
"paths": {
1717
"~/*": ["./src/*"]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"$schema": "https://json.schemastore.org/package.json",
3+
"version": "0.0.0",
4+
"name": "@opencode-ai/http-recorder",
5+
"type": "module",
6+
"license": "MIT",
7+
"private": true,
8+
"scripts": {
9+
"test": "bun test --timeout 30000",
10+
"test:ci": "mkdir -p .artifacts/unit && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml",
11+
"typecheck": "tsgo --noEmit"
12+
},
13+
"exports": {
14+
".": "./src/index.ts",
15+
"./*": "./src/*.ts"
16+
},
17+
"devDependencies": {
18+
"@tsconfig/bun": "catalog:",
19+
"@types/bun": "catalog:",
20+
"@typescript/native-preview": "catalog:"
21+
},
22+
"dependencies": {
23+
"@effect/platform-node": "catalog:",
24+
"effect": "catalog:"
25+
}
26+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { Context, Effect, FileSystem, Layer, PlatformError, Ref } from "effect"
2+
import * as path from "node:path"
3+
import { cassetteSecretFindings, type SecretFinding } from "./redaction"
4+
import type { Cassette, CassetteMetadata, Interaction } from "./schema"
5+
import { cassetteFor, cassettePath, DEFAULT_RECORDINGS_DIR, formatCassette, parseCassette } from "./storage"
6+
7+
export interface Entry {
8+
readonly name: string
9+
readonly path: string
10+
}
11+
12+
export interface Interface {
13+
readonly path: (name: string) => string
14+
readonly read: (name: string) => Effect.Effect<Cassette, PlatformError.PlatformError>
15+
readonly write: (name: string, cassette: Cassette) => Effect.Effect<void, PlatformError.PlatformError>
16+
readonly append: (
17+
name: string,
18+
interaction: Interaction,
19+
metadata: CassetteMetadata | undefined,
20+
) => Effect.Effect<
21+
{
22+
readonly cassette: Cassette
23+
readonly findings: ReadonlyArray<SecretFinding>
24+
},
25+
PlatformError.PlatformError
26+
>
27+
readonly exists: (name: string) => Effect.Effect<boolean>
28+
readonly list: () => Effect.Effect<ReadonlyArray<Entry>, PlatformError.PlatformError>
29+
readonly scan: (cassette: Cassette) => ReadonlyArray<SecretFinding>
30+
}
31+
32+
export class Service extends Context.Service<Service, Interface>()("@opencode-ai/http-recorder/Cassette") {}
33+
34+
export const layer = (options: { readonly directory?: string } = {}) =>
35+
Layer.effect(
36+
Service,
37+
Effect.gen(function* () {
38+
const fileSystem = yield* FileSystem.FileSystem
39+
const directory = options.directory ?? DEFAULT_RECORDINGS_DIR
40+
const recorded = yield* Ref.make(new Map<string, ReadonlyArray<Interaction>>())
41+
42+
const pathFor = (name: string) => cassettePath(name, directory)
43+
44+
const walk = (directory: string): Effect.Effect<ReadonlyArray<string>, PlatformError.PlatformError> =>
45+
Effect.gen(function* () {
46+
const entries = yield* fileSystem
47+
.readDirectory(directory)
48+
.pipe(Effect.catch(() => Effect.succeed([] as string[])))
49+
const nested = yield* Effect.forEach(entries, (entry) => {
50+
const full = path.join(directory, entry)
51+
return fileSystem.stat(full).pipe(
52+
Effect.flatMap((stat) => (stat.type === "Directory" ? walk(full) : Effect.succeed([full]))),
53+
Effect.catch(() => Effect.succeed([] as string[])),
54+
)
55+
})
56+
return nested.flat()
57+
})
58+
59+
const read = Effect.fn("Cassette.read")(function* (name: string) {
60+
return parseCassette(yield* fileSystem.readFileString(pathFor(name)))
61+
})
62+
63+
const write = Effect.fn("Cassette.write")(function* (name: string, cassette: Cassette) {
64+
yield* fileSystem.makeDirectory(path.dirname(pathFor(name)), { recursive: true })
65+
yield* fileSystem.writeFileString(pathFor(name), formatCassette(cassette))
66+
})
67+
68+
const append = Effect.fn("Cassette.append")(function* (
69+
name: string,
70+
interaction: Interaction,
71+
metadata: CassetteMetadata | undefined,
72+
) {
73+
const interactions = yield* Ref.updateAndGet(recorded, (previous) =>
74+
new Map(previous).set(name, [...(previous.get(name) ?? []), interaction]),
75+
)
76+
const cassette = cassetteFor(name, interactions.get(name) ?? [], metadata)
77+
const findings = cassetteSecretFindings(cassette)
78+
if (findings.length === 0) yield* write(name, cassette)
79+
return { cassette, findings }
80+
})
81+
82+
const exists = Effect.fn("Cassette.exists")(function* (name: string) {
83+
return yield* fileSystem.access(pathFor(name)).pipe(
84+
Effect.as(true),
85+
Effect.catch(() => Effect.succeed(false)),
86+
)
87+
})
88+
89+
const list = Effect.fn("Cassette.list")(function* () {
90+
return (yield* walk(directory))
91+
.filter((file) => file.endsWith(".json"))
92+
.map((file) => ({
93+
name: path.relative(directory, file).replace(/\\/g, "/").replace(/\.json$/, ""),
94+
path: file,
95+
}))
96+
.toSorted((a, b) => a.name.localeCompare(b.name))
97+
})
98+
99+
return Service.of({ path: pathFor, read, write, append, exists, list, scan: cassetteSecretFindings })
100+
}),
101+
)
102+
103+
export const defaultLayer = layer()
104+
105+
export * as Cassette from "./cassette"

packages/http-recorder/src/diff.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { Option } from "effect"
2+
import { Headers, HttpBody, HttpClientRequest, UrlParams } from "effect/unstable/http"
3+
import { decodeJson } from "./matching"
4+
import { REDACTED, redactUrl, secretFindings } from "./redaction"
5+
import { httpInteractions, type Cassette, type RequestSnapshot } from "./schema"
6+
7+
const safeText = (value: unknown) => {
8+
if (value === undefined) return "undefined"
9+
if (secretFindings(value).length > 0) return JSON.stringify(REDACTED)
10+
const text = typeof value === "string" ? JSON.stringify(value) : JSON.stringify(value)
11+
if (!text) return String(value)
12+
return text.length > 300 ? `${text.slice(0, 300)}...` : text
13+
}
14+
15+
const jsonBody = (body: string) => Option.getOrUndefined(decodeJson(body))
16+
17+
const valueDiffs = (expected: unknown, received: unknown, base = "$", limit = 8): ReadonlyArray<string> => {
18+
if (Object.is(expected, received)) return []
19+
if (
20+
expected &&
21+
received &&
22+
typeof expected === "object" &&
23+
typeof received === "object" &&
24+
!Array.isArray(expected) &&
25+
!Array.isArray(received)
26+
) {
27+
return [...new Set([...Object.keys(expected), ...Object.keys(received)])]
28+
.toSorted()
29+
.flatMap((key) =>
30+
valueDiffs(
31+
(expected as Record<string, unknown>)[key],
32+
(received as Record<string, unknown>)[key],
33+
`${base}.${key}`,
34+
limit,
35+
),
36+
)
37+
.slice(0, limit)
38+
}
39+
if (Array.isArray(expected) && Array.isArray(received)) {
40+
return Array.from({ length: Math.max(expected.length, received.length) }, (_, index) => index)
41+
.flatMap((index) => valueDiffs(expected[index], received[index], `${base}[${index}]`, limit))
42+
.slice(0, limit)
43+
}
44+
return [`${base} expected ${safeText(expected)}, received ${safeText(received)}`]
45+
}
46+
47+
const headerDiffs = (expected: Record<string, string>, received: Record<string, string>) =>
48+
[...new Set([...Object.keys(expected), ...Object.keys(received)])].toSorted().flatMap((key) => {
49+
if (expected[key] === received[key]) return []
50+
if (expected[key] === undefined) return [` ${key} unexpected ${safeText(received[key])}`]
51+
if (received[key] === undefined) return [` ${key} missing expected ${safeText(expected[key])}`]
52+
return [` ${key} expected ${safeText(expected[key])}, received ${safeText(received[key])}`]
53+
})
54+
55+
export const requestDiff = (expected: RequestSnapshot, received: RequestSnapshot) => {
56+
const lines = []
57+
if (expected.method !== received.method) {
58+
lines.push("method:", ` expected ${expected.method}, received ${received.method}`)
59+
}
60+
if (expected.url !== received.url) {
61+
lines.push("url:", ` expected ${expected.url}`, ` received ${received.url}`)
62+
}
63+
const headers = headerDiffs(expected.headers, received.headers)
64+
if (headers.length > 0) lines.push("headers:", ...headers.slice(0, 8))
65+
const expectedBody = jsonBody(expected.body)
66+
const receivedBody = jsonBody(received.body)
67+
const body =
68+
expectedBody !== undefined && receivedBody !== undefined
69+
? valueDiffs(expectedBody, receivedBody).map((line) => ` ${line}`)
70+
: expected.body === received.body
71+
? []
72+
: [` expected ${safeText(expected.body)}, received ${safeText(received.body)}`]
73+
if (body.length > 0) lines.push("body:", ...body)
74+
return lines
75+
}
76+
77+
export const mismatchDetail = (cassette: Cassette, incoming: RequestSnapshot) => {
78+
const interactions = httpInteractions(cassette)
79+
if (interactions.length === 0) return "cassette has no recorded HTTP interactions"
80+
const ranked = interactions
81+
.map((interaction, index) => ({ index, lines: requestDiff(interaction.request, incoming) }))
82+
.toSorted((a, b) => a.lines.length - b.lines.length || a.index - b.index)
83+
const best = ranked[0]
84+
return ["no recorded interaction matched", `closest interaction: #${best.index + 1}`, ...best.lines].join("\n")
85+
}
86+
87+
export const redactedErrorRequest = (request: HttpClientRequest.HttpClientRequest) =>
88+
HttpClientRequest.makeWith(
89+
request.method,
90+
redactUrl(request.url),
91+
UrlParams.empty,
92+
Option.none(),
93+
Headers.empty,
94+
HttpBody.empty,
95+
)

0 commit comments

Comments
 (0)