mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Restore explicit .ts relative specifiers in source and enable rewriteRelativeImportExtensions so emitted JS uses .js while declarations keep explicit .ts specifiers. Add a NodeNext declaration-consumer gate to prevent extensionless declaration regressions.
288 lines
10 KiB
TypeScript
288 lines
10 KiB
TypeScript
import { defineProperty } from 'cosmokit'
|
|
import type { Context, Service } from './index.ts'
|
|
|
|
/** Ordered collection of disposable values with O(1) deletion by value. */
|
|
export class DisposableList<T extends WeakKey> {
|
|
private sn = 0
|
|
private map = new Map<number, T>()
|
|
private weak = new WeakMap<T, number>()
|
|
|
|
get length() {
|
|
return this.map.size
|
|
}
|
|
|
|
push(value: T) {
|
|
const sn = ++this.sn
|
|
this.map.set(sn, value)
|
|
this.weak.set(value, sn)
|
|
return () => this.map.delete(sn)
|
|
}
|
|
|
|
delete(value: T) {
|
|
const sn = this.weak.get(value)
|
|
if (!sn) return false
|
|
return this.map.delete(sn)
|
|
}
|
|
|
|
clear() {
|
|
const values = [...this.map.values()]
|
|
this.map.clear()
|
|
return values.reverse()
|
|
}
|
|
|
|
[Symbol.iterator]() {
|
|
return this.map.values()
|
|
}
|
|
|
|
[Symbol.for('nodejs.util.inspect.custom')]() {
|
|
return [...this]
|
|
}
|
|
}
|
|
|
|
/** Metadata used by traceable proxies to rebind `ctx` and associated services. */
|
|
export interface Tracker {
|
|
associate?: string
|
|
property?: string
|
|
noShadow?: boolean
|
|
}
|
|
|
|
/** Shared symbols used to avoid public property-name collisions. */
|
|
export const symbols = {
|
|
// internal symbols
|
|
shadow: Symbol.for('cordis.shadow'),
|
|
receiver: Symbol.for('cordis.receiver'),
|
|
original: Symbol.for('cordis.original'),
|
|
metadata: Symbol.for('cordis.metadata'),
|
|
initHooks: Symbol.for('cordis.initHooks'),
|
|
checkProto: Symbol.for('cordis.checkProto'),
|
|
|
|
// context symbols
|
|
effect: Symbol.for('cordis.effect') as typeof Context.effect,
|
|
filter: Symbol.for('cordis.filter') as typeof Context.filter,
|
|
isolate: Symbol.for('cordis.isolate') as typeof Context.isolate,
|
|
intercept: Symbol.for('cordis.intercept') as typeof Context.intercept,
|
|
|
|
// service symbols
|
|
init: Symbol.for('cordis.init') as typeof Service.init,
|
|
check: Symbol.for('cordis.check') as typeof Service.check,
|
|
config: Symbol.for('cordis.config') as typeof Service.config,
|
|
invoke: Symbol.for('cordis.invoke') as typeof Service.invoke,
|
|
extend: Symbol.for('cordis.extend') as typeof Service.extend,
|
|
tracker: Symbol.for('cordis.tracker') as typeof Service.tracker,
|
|
resolveConfig: Symbol.for('cordis.resolveConfig') as typeof Service.resolveConfig,
|
|
}
|
|
|
|
const GeneratorFunction = function* () {}.constructor
|
|
const AsyncGeneratorFunction = async function* () {}.constructor
|
|
|
|
/** Return true when a plugin callback should be constructed with `new`. */
|
|
export function isConstructor(func: any): func is new (...args: any) => any {
|
|
// async function or arrow function
|
|
if (!func.prototype) return false
|
|
// generator function or malformed definition
|
|
// we cannot use below check because `mock.fn()` is proxied
|
|
// if (func.prototype.constructor !== func) return false
|
|
if (func instanceof GeneratorFunction) return false
|
|
// polyfilled AsyncGeneratorFunction === Function
|
|
if (AsyncGeneratorFunction !== Function && func instanceof AsyncGeneratorFunction) return false
|
|
return true
|
|
}
|
|
|
|
/** Merge two prototype chains while preserving descriptors from `proto1`. */
|
|
export function joinPrototype(proto1: {}, proto2: {}) {
|
|
if (proto1 === Object.prototype) return proto2
|
|
const result = Object.create(joinPrototype(Object.getPrototypeOf(proto1), proto2))
|
|
for (const key of Reflect.ownKeys(proto1)) {
|
|
Object.defineProperty(result, key, Object.getOwnPropertyDescriptor(proto1, key)!)
|
|
}
|
|
return result
|
|
}
|
|
|
|
/** Return true for non-null objects and functions. */
|
|
export function isObject(value: any): value is {} {
|
|
return value && (typeof value === 'object' || typeof value === 'function')
|
|
}
|
|
|
|
/** Find a property descriptor by walking an object's prototype chain. */
|
|
export function getPropertyDescriptor(target: any, prop: string | symbol) {
|
|
let proto = target
|
|
while (proto) {
|
|
const desc = Reflect.getOwnPropertyDescriptor(proto, prop)
|
|
if (desc) return desc
|
|
proto = Object.getPrototypeOf(proto)
|
|
}
|
|
}
|
|
|
|
/** Wrap services/functions so method calls see the caller's active context. */
|
|
export function getTraceable<T>(ctx: Context, value: T): T {
|
|
if (!isObject(value)) return value
|
|
if (Object.hasOwn(value, symbols.shadow)) {
|
|
return Object.getPrototypeOf(value)
|
|
}
|
|
const tracker = value[symbols.tracker]
|
|
if (!tracker) return value
|
|
return createTraceable(ctx, value, tracker)
|
|
}
|
|
|
|
/** Return a proxy that overlays readonly or writable properties onto a target. */
|
|
export function withProps(target: any, props?: {}) {
|
|
if (!props) return target
|
|
return new Proxy(target, {
|
|
get: (target, prop, receiver) => {
|
|
if (prop in props && prop !== 'constructor') return Reflect.get(props, prop, receiver)
|
|
return Reflect.get(target, prop, receiver)
|
|
},
|
|
set: (target, prop, value, receiver) => {
|
|
if (prop in props && prop !== 'constructor') return Reflect.set(props, prop, value, receiver)
|
|
return Reflect.set(target, prop, value, receiver)
|
|
},
|
|
})
|
|
}
|
|
|
|
function withProp(target: any, prop: string | symbol, value: any) {
|
|
return withProps(target, Object.defineProperty(Object.create(null), prop, {
|
|
value,
|
|
writable: false,
|
|
}))
|
|
}
|
|
|
|
function createShadow(ctx: Context, target: any, property: string | undefined, receiver: any) {
|
|
if (!property) return receiver
|
|
const origin = Reflect.getOwnPropertyDescriptor(target, property)?.value
|
|
if (!origin) return receiver
|
|
return withProp(receiver, property, ctx.extend({ [symbols.shadow]: origin }))
|
|
}
|
|
|
|
function createShadowMethod(ctx: Context, value: any, outer: any, shadow: {}) {
|
|
return new Proxy(value, {
|
|
apply: (target, thisArg, args) => {
|
|
if (thisArg === outer) thisArg = shadow
|
|
return getTraceable(ctx, Reflect.apply(target, thisArg, args))
|
|
},
|
|
})
|
|
}
|
|
|
|
function createTraceable(ctx: Context, value: any, tracker: Tracker) {
|
|
// noShadow services are identity-aware (e.g. logger uses the origin fiber to
|
|
// derive its name): keep the shadow ctx so they can read [symbols.shadow]
|
|
// and resolve the origin. Non-noShadow services strip — their side effects
|
|
// bind to caller, not origin.
|
|
if (ctx[symbols.shadow] && !tracker.noShadow) {
|
|
ctx = Object.getPrototypeOf(ctx)
|
|
}
|
|
const proxy = new Proxy(value, {
|
|
get: (target, prop, receiver) => {
|
|
if (prop === symbols.original) return target
|
|
if (prop === tracker.property) return ctx
|
|
if (typeof prop === 'symbol') {
|
|
return Reflect.get(target, prop, receiver)
|
|
}
|
|
if (tracker.associate && ctx.reflect.props[`${tracker.associate}.${prop}`]) {
|
|
return Reflect.get(ctx, `${tracker.associate}.${prop}`, withProp(ctx, symbols.receiver, receiver))
|
|
}
|
|
let shadow: any, innerValue: any
|
|
const desc = getPropertyDescriptor(target, prop)
|
|
if (desc && 'value' in desc) {
|
|
innerValue = desc.value
|
|
} else {
|
|
shadow = createShadow(ctx, target, tracker.property, receiver)
|
|
innerValue = Reflect.get(target, prop, shadow)
|
|
}
|
|
const innerTracker = innerValue?.[symbols.tracker]
|
|
if (innerTracker) {
|
|
return createTraceable(ctx, innerValue, innerTracker)
|
|
} else if (!tracker.noShadow && typeof innerValue === 'function') {
|
|
shadow ??= createShadow(ctx, target, tracker.property, receiver)
|
|
return createShadowMethod(ctx, innerValue, receiver, shadow)
|
|
} else {
|
|
return innerValue
|
|
}
|
|
},
|
|
set: (target, prop, value, receiver) => {
|
|
if (prop === symbols.original) return false
|
|
if (prop === tracker.property) return false
|
|
if (typeof prop === 'symbol') {
|
|
return Reflect.set(target, prop, value, receiver)
|
|
}
|
|
if (tracker.associate && ctx.reflect.props[`${tracker.associate}.${prop}`]) {
|
|
return Reflect.set(ctx, `${tracker.associate}.${prop}`, value, withProp(ctx, symbols.receiver, receiver))
|
|
}
|
|
const shadow = createShadow(ctx, target, tracker.property, receiver)
|
|
return Reflect.set(target, prop, value, shadow)
|
|
},
|
|
apply: (target, thisArg, args) => {
|
|
return applyTraceable(proxy, target, thisArg, args)
|
|
},
|
|
})
|
|
return proxy
|
|
}
|
|
|
|
function applyTraceable(proxy: any, value: any, thisArg: any, args: any[]) {
|
|
if (!value[symbols.invoke]) return Reflect.apply(value, thisArg, args)
|
|
return value[symbols.invoke].apply(proxy, args)
|
|
}
|
|
|
|
/** Create a callable service object that dispatches through `symbols.invoke`. */
|
|
export function createCallable(name: string, proto: {}, tracker: Tracker) {
|
|
const self = function (...args: any[]) {
|
|
const proxy = createTraceable(self['ctx'], self, tracker)
|
|
return applyTraceable(proxy, self, this, args)
|
|
}
|
|
defineProperty(self, 'name', name)
|
|
return Object.setPrototypeOf(self, proto)
|
|
}
|
|
|
|
interface StackInfo {
|
|
offset: number
|
|
error: Error
|
|
}
|
|
|
|
function handleError(info: StackInfo, reason: any, getOuterStack: () => string[]): never {
|
|
const innerLines = info.error.stack!.split('\n')
|
|
|
|
// malformed error
|
|
if (typeof reason?.stack !== 'string') {
|
|
const outerError = new Error(reason)
|
|
const lines = outerError.stack!.split('\n')
|
|
lines.splice(1, Infinity, ...getOuterStack())
|
|
outerError.stack = lines.join('\n')
|
|
throw outerError
|
|
}
|
|
|
|
// long stack trace
|
|
const lines: string[] = reason.stack.split('\n')
|
|
let index = lines.indexOf(innerLines[2])
|
|
if (index === -1) throw reason
|
|
|
|
index -= info.offset
|
|
while (index > 0) {
|
|
if (!lines[index - 1].endsWith(' (<anonymous>)')) break
|
|
index -= 1
|
|
}
|
|
lines.splice(index, Infinity, ...getOuterStack())
|
|
reason.stack = lines.join('\n')
|
|
throw reason
|
|
}
|
|
|
|
/** Run a callback and splice outer call-site frames into thrown async errors. */
|
|
export function composeError<T>(callback: (info: StackInfo) => T, getOuterStack = buildOuterStack()): T {
|
|
const info: StackInfo = { offset: 1, error: new Error() }
|
|
|
|
try {
|
|
const result: any = callback(info)
|
|
if (isObject(result) && 'then' in result) {
|
|
return (result as any).then(undefined, (reason) => handleError(info, reason, getOuterStack)) as T
|
|
} else {
|
|
return result
|
|
}
|
|
} catch (reason: any) {
|
|
handleError(info, reason, getOuterStack)
|
|
}
|
|
}
|
|
|
|
/** Capture a lazy stack-frame supplier for later error composition. */
|
|
export function buildOuterStack(offset = 0) {
|
|
const outerError = new Error()
|
|
return () => outerError.stack!.split('\n').slice(3 + offset)
|
|
}
|