bindings.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
  1. /** Validate and capture the binding names available to one program. */
  2. import { DUNDER_MEMBER, PORTABLE_RESERVED_WORDS, RESERVED_BINDING_GLOBALS, RESERVED_ERROR_MEMBERS } from '@deepseek-ai/dsh-ptc-runtime'
  3. import type { PtcBindingNamespace, PtcRunRequest } from '@deepseek-ai/dsh-ptc-runtime'
  4. const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/
  5. /**
  6. * Reject unusable namespaces before starting a process.
  7. * @param request - Host-owned binding declarations.
  8. * @returns Namespaces keyed by their declared global.
  9. */
  10. export function validateBindings(request: PtcRunRequest): Map<string, PtcBindingNamespace> {
  11. const bindings = new Map<string, PtcBindingNamespace>()
  12. for (const namespace of request.bindings) {
  13. if (!IDENTIFIER.test(namespace.global) || PORTABLE_RESERVED_WORDS.has(namespace.global)) {
  14. throw new Error(`dsh-ptc-runtime-node: binding global ${JSON.stringify(namespace.global)} is not a usable identifier`)
  15. }
  16. // RESERVED_BINDING_GLOBALS is the seam's shared backend-owned set:
  17. // `console` is THIS backend's log-capture slot; the dunder entries exist
  18. // for the Python side — its seeded/wrapped slots plus the `__debug__`
  19. // compile-time constant — refused here too so the namespace list stays
  20. // portable across backends. The seam declaration is the single home for
  21. // why each entry is reserved.
  22. if (RESERVED_BINDING_GLOBALS.has(namespace.global)) {
  23. throw new Error(`dsh-ptc-runtime-node: reserved binding global ${JSON.stringify(namespace.global)}`)
  24. }
  25. if (bindings.has(namespace.global)) {
  26. throw new Error(`dsh-ptc-runtime-node: duplicate binding global ${JSON.stringify(namespace.global)}`)
  27. }
  28. bindings.set(namespace.global, namespace)
  29. }
  30. const errorClassNames = new Set<string>()
  31. for (const namespace of request.bindings) {
  32. const descriptor = namespace.errorClass
  33. if (!descriptor) continue
  34. if (!IDENTIFIER.test(descriptor.name) || PORTABLE_RESERVED_WORDS.has(descriptor.name)) {
  35. throw new Error(`dsh-ptc-runtime-node: binding error class ${JSON.stringify(descriptor.name)} is not a usable identifier`)
  36. }
  37. if (RESERVED_BINDING_GLOBALS.has(descriptor.name)) {
  38. throw new Error(`dsh-ptc-runtime-node: reserved binding global ${JSON.stringify(descriptor.name)}`)
  39. }
  40. if (bindings.has(descriptor.name) || errorClassNames.has(descriptor.name)) {
  41. throw new Error(`dsh-ptc-runtime-node: duplicate injected global ${JSON.stringify(descriptor.name)}`)
  42. }
  43. const member = descriptor.memberNameProperty
  44. if (member.length === 0 || RESERVED_ERROR_MEMBERS.has(member) || DUNDER_MEMBER.test(member)) {
  45. throw new Error(`dsh-ptc-runtime-node: binding error member property ${JSON.stringify(descriptor.memberNameProperty)} is not usable`)
  46. }
  47. errorClassNames.add(descriptor.name)
  48. }
  49. return bindings
  50. }