{"_id":"@aihubmix/codegen","_rev":"2-6e9c2fe5e841eac44203f829393ca526","name":"@aihubmix/codegen","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@aihubmix/codegen","version":"0.1.0","license":"MIT","_id":"@aihubmix/codegen@0.1.0","maintainers":[{"name":"chenxue","email":"450907240@qq.com"}],"homepage":"https://github.com/AIhubmix/aihubmix-codegen#readme","bugs":{"url":"https://github.com/AIhubmix/aihubmix-codegen/issues"},"dist":{"shasum":"81cc5a7d6aa69064105fe90a33e90e88abde4ce5","tarball":"https://registry.npmjs.org/@aihubmix/codegen/-/codegen-0.1.0.tgz","fileCount":9,"integrity":"sha512-z18rcBrjzCvRcQxYu7417F9AYTVttClBqgPcnbWwPdsfH+dM+j7crFGC9ZqcNuXd2Qi+bh+B54MQWSFn+/zFrg==","signatures":[{"sig":"MEUCIQDv6mqSfW6rbEXeZObBFXyu3lYCNDQ11NvPS4hbaiu89wIgIwUo1ipljqbTeKGY7PQS8xCDW+HZfpepZ6w23vojcK4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":679100},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"gitHead":"d4125470f80bca916b11eeddaa49339a2f8e3544","scripts":{"test":"vitest run","build":"tsup","smoke":"tsup && node scripts/smoke-node.cjs","prepare":"tsup","snapshot":"node scripts/snapshot.mjs","test:all":"pnpm typecheck && pnpm test && pnpm test:verify-classify && pnpm test:codegen-escaping && pnpm test:cjs-types","typecheck":"tsc --noEmit","snapshot:diff":"diff -r .snapshots/before .snapshots/after && echo '零字节差异 OK'","snapshot:after":"node scripts/snapshot.mjs --src src/index.ts --out .snapshots/after","test:cjs-types":"node scripts/test-cjs-types.mjs","verify:codegen":"node scripts/verify-codegen.mjs","snapshot:before":"node scripts/snapshot.mjs --src ~/code/playground/src/lib/codegen.ts --alias ~/code/playground/src --out .snapshots/before","test:verify-classify":"node scripts/test-verify-classify.mjs","test:codegen-escaping":"node scripts/test-codegen-escaping.mjs"},"_npmUser":{"name":"chenxue","email":"450907240@qq.com"},"repository":{"url":"git+https://github.com/AIhubmix/aihubmix-codegen.git","type":"git"},"_npmVersion":"10.9.8","description":"Pure, isomorphic request/code generator for the AIHubMix gateway: 4 protocols x 7 languages. One wire body feeds both the generated snippet and the real request. No transport, no DOM — capabilities and base URL are injected.","directories":{},"sideEffects":false,"_nodeVersion":"22.22.3","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","vitest":"^2.1.8","esbuild":"^0.24.0","typescript":"^5.6.3","@types/node":"^22.20.1"},"_npmOperationalInternal":{"tmp":"tmp/codegen_0.1.0_1785482694307_0.7593617971895121","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@aihubmix/codegen","version":"0.1.1","description":"Pure, isomorphic request/code generator for the AIHubMix gateway: 4 protocols x 7 languages. One wire body feeds both the generated snippet and the real request. No transport, no DOM — capabilities and base URL are injected.","type":"module","license":"MIT","repository":{"type":"git","url":"git+https://github.com/AIhubmix/aihubmix-codegen.git"},"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"sideEffects":false,"publishConfig":{"access":"public"},"scripts":{"build":"tsup","prepare":"tsup","test":"vitest run","typecheck":"tsc --noEmit","smoke":"tsup && node scripts/smoke-node.cjs","snapshot":"node scripts/snapshot.mjs","snapshot:after":"node scripts/snapshot.mjs --src src/index.ts --out .snapshots/after","snapshot:diff":"diff -r .snapshots/before .snapshots/after && echo '零字节差异 OK'","verify:codegen":"node scripts/verify-codegen.mjs","test:codegen-escaping":"node scripts/test-codegen-escaping.mjs","test:verify-classify":"node scripts/test-verify-classify.mjs","test:cjs-types":"node scripts/test-cjs-types.mjs","test:all":"pnpm typecheck && pnpm test && pnpm test:verify-classify && pnpm test:codegen-escaping && pnpm test:cjs-types"},"devDependencies":{"@types/node":"^22.20.1","esbuild":"^0.28.1","tsup":"^8.3.5","typescript":"^5.6.3","vitest":"^3.2.7","vite":"^6.4.3"},"pnpm":{"overrides":{"esbuild":">=0.28.1"}},"_id":"@aihubmix/codegen@0.1.1","gitHead":"7f26e5724fb688bcda505e304e9027696a6e867c","bugs":{"url":"https://github.com/AIhubmix/aihubmix-codegen/issues"},"homepage":"https://github.com/AIhubmix/aihubmix-codegen#readme","_nodeVersion":"22.22.3","_npmVersion":"10.9.8","dist":{"integrity":"sha512-yrk7QvS6FyBB5/ipTNBqCJKt9X6f8QKrm3SZdMU+5UbOo+63puQkO6WtW2MSC148bd+7RRfMs+bt0GkUTvHJtQ==","shasum":"6414f5076399fedd7aa9d928991f80fe894f4532","tarball":"https://registry.npmjs.org/@aihubmix/codegen/-/codegen-0.1.1.tgz","fileCount":9,"unpackedSize":682421,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDl+scqoeRoFs+9h3HFkW/oyCVKwc2O3xl+C4FpxdnXDQIhAKA+tli4slIfHO467ONaVtSfj/zdoy0pQEuSNNnos+Uj"}]},"_npmUser":{"name":"chenxue","email":"450907240@qq.com"},"directories":{},"maintainers":[{"name":"chenxue","email":"450907240@qq.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/codegen_0.1.1_1785758126619_0.6207859385217582"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-31T07:24:54.148Z","modified":"2026-08-03T11:55:26.966Z","0.1.0":"2026-07-31T07:24:54.484Z","0.1.1":"2026-08-03T11:55:26.804Z"},"bugs":{"url":"https://github.com/AIhubmix/aihubmix-codegen/issues"},"license":"MIT","homepage":"https://github.com/AIhubmix/aihubmix-codegen#readme","repository":{"type":"git","url":"git+https://github.com/AIhubmix/aihubmix-codegen.git"},"description":"Pure, isomorphic request/code generator for the AIHubMix gateway: 4 protocols x 7 languages. One wire body feeds both the generated snippet and the real request. No transport, no DOM — capabilities and base URL are injected.","maintainers":[{"name":"chenxue","email":"450907240@qq.com"}],"readme":"# @aihubmix/codegen\n\nPure, isomorphic request/code generator for the AIHubMix gateway — **4 protocols × 7 languages**.\n\nThe same `buildBody()` feeds both the generated snippet and the real request, so \"what the panel shows\" == \"what gets sent\" == \"what the code example prints\". No transport, no DOM, no network, no env reads.\n\n```bash\npnpm add @aihubmix/codegen\n```\n\nZero runtime dependencies. Ships ESM + CJS + `.d.ts` + `.d.cts`.\n\n## Quick start\n\n```ts\nimport { generateCode } from '@aihubmix/codegen';\n\nconst code = generateCode('messages', 'python', {\n  baseUrl: 'https://aihubmix.com',   // required — see below\n  model: { id: 'claude-opus-5' },\n  sys: 'You are a helpful assistant.',\n  user: 'Hello, how are you?',\n  // `max_tokens` / `temperature` / `top_p` are required fields of `p`; `paramKeys`\n  // decides which of them actually reach the wire.\n  p: { max_tokens: 1024, temperature: 0.7, top_p: 0.9 },\n  paramKeys: ['max_tokens'],\n  stream: false,\n});\n```\n\n## Support matrix\n\n| `CodeLang` | label | `chat` | `messages` | `responses` | `gemini` |\n|------------|-------|:------:|:----------:|:-----------:|:--------:|\n| `python`     | Python     | SDK  | SDK  | SDK  | SDK  |\n| `javascript` | TypeScript | SDK  | SDK  | SDK  | SDK  |\n| `ruby`       | Ruby       | SDK  | SDK  | SDK  | SDK  |\n| `go`         | Go         | SDK  | REST | REST | REST |\n| `java`       | Java       | REST | REST | REST | REST |\n| `csharp`     | C#         | REST | REST | REST | REST |\n| `curl`       | cURL       | REST | REST | REST | REST |\n\nNote the id is `javascript` even though the emitted code (and the display label) is TypeScript.\n\n`SDK` = renders the vendor SDK (`openai`, `anthropic`, `google-genai`). `REST` = renders a raw HTTP call against the gateway. Both go through the same `buildBody()`.\n\nProtocol → route and auth header are the gateway contract, and live in exactly one place (`src/config/protocols.ts`):\n\n| protocol | route | auth header |\n|---|---|---|\n| `chat` | `/v1/chat/completions` | `Authorization: Bearer` |\n| `responses` | `/v1/responses` | `Authorization: Bearer` |\n| `messages` | `/v1/messages` | `x-api-key` (+ `anthropic-version`) |\n| `gemini` | `/gemini/v1beta/models/{model}:generateContent` | `x-goog-api-key` |\n\nMedia generation (image / video × 7 languages) is available through `generateMediaCode(opts)`.\n\n## `baseUrl` is required, and there is no setter\n\n`CodeGenCtx.baseUrl` is a required field. This is deliberate:\n\n- Consumers are **dual-domain builds** (`aihubmix.com` and `api.inferera.com`). A hard-coded base would make one domain emit code pointing at the other.\n- Making it required means every call site fails to compile until it passes one explicitly.\n- There is **no `setBaseUrl()`** or any module-level mutable state. Consumers plan to fork on the `Host` header at request time inside one process; module-level state would leak across concurrent requests.\n\n## `paramKeys` is the only gate\n\n`buildBody()` has three generic fall-through channels that claim new keys by type:\n\n| value type | channel | behaviour |\n|---|---|---|\n| number | `emitExtraNumbers` | skips keys already handled specially |\n| enum / string | `emitEnums` | sends only when non-empty and ≠ schema default |\n| object / array / boolean | `emitObjects` | skips capability-gated keys and empty containers |\n\n**All three are gated by `inSchema()`, i.e. `ctx.paramKeys`.** A key that the model's schema does not declare is never sent, even if a stale value for it is still sitting in `ctx`.\n\nConsequence: **adding a parameter needs no change to this package.** Declare it in the schema, put a value in `ctx`, and all 28 cells pick it up.\n\nOmitting `paramKeys` disables gating entirely (backwards compatibility). Prefer passing it.\n\nTwo lists still need manual upkeep, both covered by tests:\n\n- `PY_OPENAI_NATIVE` — whether a key renders as a native kwarg or lands in `extra_body`. Fail-safe either way: the snippet still runs.\n- `GO_CHAT_KEYS` / `GO_OBJ_FIELDS` — `go-openai`'s `ChatCompletionRequest` is a closed struct: no map fallback, no `ExtraBody` (checked against v1.41.2). So the `go` + `chat` cell is the one place where a body key can fail to reach the wire; the other six languages render whatever `buildBody()` produced.\n\n  Every key that *does* have a struct field is mapped. Anything left over is **named in a comment in the emitted code** rather than silently dropped, so the snippet tells you what it isn't sending and points at `net/http` as the way out. `tests/extensibility.test.ts` asserts both halves — the leftover key appears in a comment and does *not* appear in the request struct — and `scripts/test-codegen-escaping.mjs` runs a real `go build` over the result. When you add a parameter, check whether `go-openai` has a field for it: if yes, map it and add the key here; if no, do nothing and the comment picks it up.\n\n- `GO_REASONING_PREFIXES` — `go-openai` ships a **client-side** validator (`reasoning_validator.go`) that rejects a set of parameters for models whose id starts with `o1` / `o3` / `o4` / `gpt-5`. It fires *before the request is sent*, so a snippet that trips it prints a panic and never reaches the gateway. This is an SDK rule, not a gateway one — the identical body sent with `curl` returns 200.\n\n  Consequently the `go` + `chat` cell renders `MaxCompletionTokens` instead of `MaxTokens` for those models, and omits `temperature`, `top_p`, `n`, the two penalties, and `logprobs` — again **naming them in a comment**, worded to distinguish \"this SDK won't send it\" from \"no struct field exists\". The trigger is the model id, not `paramKeys`: most models today carry no schema at all, and keying off one would put every `gpt-5` request on the panicking path. Mirror upstream when it changes; `tests/go-reasoning-validator.test.ts` covers both branches and the escaping harness `go build`s each.\n\n## One wire-level correction\n\n`buildBody()` passes values through; it does not second-guess them. The single exception is the `messages` protocol, where Anthropic requires `max_tokens` to be **strictly greater** than `thinking.budget_tokens` — violating it is a hard 400, not a degradation. When extended thinking is on and `max_tokens` is not above the budget, `buildBody()` raises it to `budget_tokens + 1024`.\n\nIt lives here rather than in a caller because the correction has to apply to the real request, not only to the printed snippet. A parameter panel that lets the user set an 8192 budget against a 1024 limit would otherwise show working code next to a request that 400s. `tests/thinking-budget.test.ts` calls `buildBody()` directly, without going through any capability layer, for exactly that reason.\n\n## Node / CommonJS\n\nThe package must be `require()`-able from bare Node — `inferera-web`'s prerender step is a consumer:\n\n```js\nconst { generateCode } = require('@aihubmix/codegen');\n```\n\nTypeScript projects on `moduleResolution: node16 | nodenext` resolve `./dist/index.d.cts` for `require` and `./dist/index.d.ts` for `import`.\n\n## Scope: wire vocabulary only\n\nThis package speaks two things and nothing else: what the gateway's HTTP body looks like (four protocols — a closed set that only moves when an upstream API changes), and how to write that body as code in seven languages.\n\nIt deliberately does **not** speak knowledge-base vocabulary — capability keys (`reasoning-effort`, `vision`), verdicts (`tested-effective`, `silent-degrade`), or the knowledge base's own protocol identifiers. That vocabulary is an open set that grows on its own schedule; if it lived here, adding one capability upstream would mean a release of this package and an upgrade in every consumer. It lives in **`@aihubmix/model-schema`**, which depends on this package and translates knowledge-base records into the wire-only `CodeGenCtx` below. The dependency is one-way: nothing here imports that package.\n\n`tests/vocabulary-isolation.test.ts` enforces this by scanning `src/` for those literals, so it is a build-time fact rather than a convention.\n\nThe package also ships **no UI**: no chips, no strikethrough styling, no syntax highlighting.\n\n## Invariants\n\nEnforced by tests in `tests/`, not by convention:\n\n1. **Isomorphic** — no DOM, `window`, network, or env reads anywhere in `src/`.\n2. **Single wire source** — every renderer's body comes from `buildBody()`; no bypass path.\n3. **Facts live once** — auth headers, `anthropic-version`, the four routes, the API-key placeholder, SDK package names and response accessors appear only in `src/config/**`. `tests/fact-localization.test.ts` fails if one leaks into `src/renderers/**` or `scripts/**`.\n4. **`CAP_GATED_WIRE_KEYS` is derived**, never hand-written twice.\n5. **Wire vocabulary only** — no capability key, verdict, or knowledge-base protocol id appears in `src/`. `tests/vocabulary-isolation.test.ts` fails if one is added back.\n6. **SDK records are self-consistent** — an `Anthropic` client's response accessor may not contain `choices[`, and an OpenAI client's may not contain `content[0].text`.\n\n## Development\n\n```bash\npnpm build          # tsup → ESM + CJS + d.ts + d.cts\npnpm test           # vitest\npnpm typecheck      # tsc --noEmit\npnpm test:all       # typecheck + vitest + classifier + escaping\npnpm smoke          # build, then require() from bare Node\npnpm verify:codegen # live verification — really runs the generated snippets\n```\n\n`verify:codegen` is the real acceptance criterion: it executes the generated code against a gateway and classifies the result. Pass the base URL in; never hard-code a domain or a key.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}