@aid-framework/generator-ts-vercel 0.1.39 → 0.1.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +122 -163
  2. package/package.json +4 -4
package/README.md CHANGED
@@ -1,164 +1,123 @@
1
- # `ts-vercel` generator
2
-
3
- The **Phase 1 second target**: turns the IR into a Next.js App Router service wired to the
4
- [`@aid-framework/runtime`](../../../runtimes/typescript/) runtime. Generated code is declarative glue — zod schemas, model
5
- aliases, prompt templates, route handlers, tool adapters, retrieval bindings, eval definitions — and every
6
- behaviour it calls already exists in the runtime ([`docs/design.md` §4](../../../docs/design.md), principle 1:
7
- *thin generated, fat runtime*).
8
-
9
- The only input is the IR. `plan` is a pure function of it: nothing here reads a spec file, a template
10
- directory, the environment or the clock, so the same IR always produces the same bytes.
11
-
12
- **Emits against** ([`§8.1`](../../../docs/design.md)):
13
-
14
- | Key | Value | Why |
15
- | --- | --- | --- |
16
- | `structuredOutput` | `zod` | Response models are zod schemas, so a structured-output call is validated by the runtime's gateway |
17
- | `tracing` | `otel-genai` | Spans follow the OTel GenAI semantic conventions |
18
- | `orchestration` | *absent* | Control flow is **plain async**. Orchestration frameworks are deferred to Phase 2 ([`§14`](../../../docs/design.md)), and declaring nothing is what keeps the choice swappable |
19
-
20
- ## Deviation: no `emitsAgainst.vectorStore`
21
-
22
- The frozen plan's L6 row asks for
23
- `emitsAgainst = { structuredOutput: 'zod', tracing: 'otel-genai', vectorStore: 'pgvector' }`. **The third key is
24
- not expressible, and this package does not declare it.** `EmitsAgainst` in
25
- [`packages/generator-sdk/src/manifest.ts:56`](../../generator-sdk/src/manifest.ts) has exactly three members —
26
- `orchestration?`, `structuredOutput?`, `tracing?` — and adding a fourth is an SDK change, which is outside this
27
- layer.
28
-
29
- The plan cites [`manifest.test.ts:135`](../../generator-sdk/test/manifest.test.ts) as evidence that
30
- `emitsAgainst.vectorStore` is supported. That line is inside the test *"rejects an unknown emitsAgainst key and
31
- an out-of-vocabulary value"*, and it asserts `manifestUnknownKey` **for** `{ vectorStore: 'pgvector' }`: the plan
32
- cited the counter-example as if it were the example.
33
-
34
- `pgvector` is nevertheless honoured — as the IR's store kind
35
- ([`packages/ir/src/shapes.ts:397-400`](../../ir/src/shapes.ts)), not as an `emitsAgainst` key. `validate.ts`
36
- accepts `memory` and `pgvector` stores and refuses any other kind, and the emitted `retrieval/bindings.ts` passes
37
- the kind through to the runtime, whose `PgVectorStore` adapter loads its driver lazily. So the **substance** of
38
- the plan's declaration is present; the declared shape is the SDK's. This is the same fail-closed resolution
39
- `py-fastapi` reached, and for the same reason: a manifest that does not parse is not a target.
40
-
41
- A test pins the key set to an exact literal — `Object.keys(manifest.emitsAgainst).sort()` equals
42
- `['structuredOutput', 'tracing']` — the same non-vacuous shape
43
- [`py-fastapi/test/target.test.ts:137`](../py-fastapi/test/target.test.ts) uses, so adding or dropping a key fails
44
- rather than silently passing.
45
-
46
- ## Capabilities: 6 declared, `streaming` not
47
-
48
- | Declared | Honoured by |
1
+ # `@aid-framework/generator-ts-vercel`
2
+
3
+ The TypeScript / Vercel target for [AID Framework](https://github.com/aid-framework/aid-framework). It compiles an
4
+ IR into a Next.js app whose behaviour lives in [`@aid-framework/runtime`](https://www.npmjs.com/package/@aid-framework/runtime),
5
+ with a `business/` tier the compiler creates once and never touches again.
6
+
7
+ ```text
8
+ app.spec.yaml ──aid gen --target ts-vercel──▶ app/generated/ (compiler)
9
+ app/business/ (you)
10
+ ```
11
+
12
+ ## Install
13
+
14
+ ```sh
15
+ npm install --save-dev @aid-framework/generator-ts-vercel
16
+ ```
17
+
18
+ Requires Node.js 24 or newer. In practice you pull it in through
19
+ [`@aid-framework/cli`](https://www.npmjs.com/package/@aid-framework/cli).
20
+
21
+ ## Use it
22
+
23
+ A spec generates one target at a time, so a project that ships both stacks declares both:
24
+
25
+ ```yaml
26
+ project:
27
+ name: support-copilot
28
+ targets:
29
+ - py-fastapi
30
+ - ts-vercel
31
+ ```
32
+
33
+ ```sh
34
+ npx aid gen --target ts-vercel
35
+ ```
36
+
37
+ ```text
38
+ ts-vercel: 20 created, 0 updated, 0 unchanged, 0 kept
39
+ ```
40
+
41
+ `--target` selects among the targets the spec declares. Naming an undeclared one fails with `target-unsupported`
42
+ and lists what is declared, because generating a stack the spec never asked for would make the spec a lie.
43
+
44
+ ## What it emits
45
+
46
+ | Path | Contents |
49
47
  | --- | --- |
50
- | `http-trigger` | `routes/<pipeline>.ts`, a Fetch-shaped handler Next serves |
51
- | `json-schema` | `schemas.ts`, zod schemas the runtime validates structured output with |
52
- | `tool-calling` | `tools/**` and `contracts/**`, adapters over handlers you own |
53
- | `otel` | `observability.ts`, the runtime tracer |
54
- | `embeddings` | `retrieval/bindings.ts`, `retrieval/index.ts`, `retrieval/ingest/**` |
55
- | `rerank` | the retriever binding's `rerank` spec, resolved by the runtime's `rerankerFor` |
56
-
57
- `streaming` is **not** declared. `ModelGateway.stream` raises `FeatureDeferredError`, so declaring the capability
58
- would be the fail-open the capability discipline forbids: a target must not advertise what it cannot emit.
59
-
60
- **Emits** (24 files per generated app — [`§8.3`](../../../docs/design.md)):
61
-
62
- - **Model gateway wiring** — `gateway/models.ts` and `gateway/index.ts`: one alias per declared model, with
63
- provider, model id and cost profile, validated against the runtime's adapter registry.
64
- - **Prompt registry wiring** — `prompts/<prompt>.ts` and `prompts/index.ts`: the template, its version, and the
65
- typed values it interpolates.
66
- - **One structured-output endpoint** — `requests.ts`, `schemas.ts`, `routes/<pipeline>.ts` and
67
- `pipelines/<pipeline>.ts`: the request shape, the response schema, the route handler, and the plain-async
68
- pipeline that renders the prompt, calls the gateway, runs the tool and shapes the response.
69
- - **One tool** — `tools/<tool>.ts` plus `contracts/<tool>.ts`, and their two indexes: a zod-validated adapter
70
- over a handler you own. The handler itself is seeded once and then yours.
71
- - **Retrieval** — `retrieval/bindings.ts`, `retrieval/index.ts` and `retrieval/ingest/**`: the embedding, store
72
- and retriever declarations, the `buildRetrievers` factory that resolves and validates the binding at startup,
73
- and one idempotent ingest entry point per retriever.
74
- - **Evals** — `evals/<eval>.ts` and `evals/index.ts`: the dataset loader, the stored baseline, the metric list and
75
- the gate, delegating every statistic to `@aid-framework/runtime`.
76
- - **`observability.ts`** and **`deps.ts`** — tracer setup and the dependency container that the route and the eval
77
- both build from, so the eval measures the prompt the app actually renders.
78
-
79
- Packaging, the Next `app/api/**` shim and the tests are **not** generated: `package.json`, `app/api/<path>/route.ts`
80
- and `tests/**` are hand-written in the example, because a plan may only write into the two managed tiers below.
81
-
82
- ## Layout and ownership ([`§8.4`](../../../docs/design.md))
83
-
84
- | Path | Owner | On regeneration |
85
- | --- | --- | --- |
86
- | `app/generated/**` | the generator | overwritten, never merged |
87
- | `app/business/**` | you | created once if missing, then **never** touched |
88
- | `app/aid.manifest.json` | the generator | rewritten from the plan; not a planned artifact itself |
89
-
90
- A generated file whose bytes no longer match the hash recorded in the manifest is **drift**: regeneration reports
91
- it as an error, leaves your edit in place, and writes the content it wanted beside it as `<path>.aid-rej`.
92
- `--force` discards the edit instead (with a warning), and `--prune` deletes generated files the plan no longer
93
- produces ([`§10`](../../../docs/design.md)).
94
-
95
- ## Accepted collision: `output.root: 'app'`
96
-
97
- Decision A7 puts `generated/` and `business/` under `output.root: 'app'` for parity with the Python target, and
98
- that root is also Next.js's own App Router directory. The plan names the collision, accepts it for parity with
99
- the SDK's `managedRoots`, and flags it for review; **this package accepts it too rather than working around it.**
100
- The consequences, stated plainly:
101
-
102
- - `app/generated/**` and `app/business/**` sit beside your own routes, layouts and pages, in the directory the
103
- framework already owns. Nothing is lost, because the two managed tiers are disjoint from Next's file
104
- conventions (`route.ts`, `layout.tsx`, `page.tsx`), but it is a naming collision a reader must know about.
105
- - A future `output.root` of `.` would remove it at the cost of parity; that is a plan decision, not this layer's.
106
-
107
- ## Deviation: the `app/api/**` shim is hand-written
108
-
109
- A7 also asks the generator to emit "a thin emitted `app/api/<path>/route.ts` shim that Next actually serves".
110
- **This package does not emit it**, for two independent mechanical reasons, either of which is sufficient:
111
-
112
- 1. **The plan validator refuses the path.** `validatePlan` accepts writes only under the manifest's managed roots
113
- (`app/generated`, `app/business`); `app/api/tickets/route.ts` is rejected with `generator/plan-owner-mismatch`.
114
- A test asserts exactly that rejection.
115
- 2. **The manifest's owner cross-check would break every later run.** `parseAidManifest` treats the first path
116
- segment as the owner, so a recorded `app/api/...` entry does not match the generator's root and the next
117
- `aid gen` fails to parse the manifest it wrote itself — a hard CLI break, not a cosmetic mismatch.
118
-
119
- So the generator emits the real handler at `app/generated/routes/<pipeline>.ts`, which exports both
120
- `handle<Pipeline>` and a `ROUTE` constant (`{ method, path }`), and the example hand-writes the three-line Next
121
- shim at `app/api/tickets/answer/route.ts`. This mirrors `py-fastapi`, where `app/main.py` is likewise hand-written
122
- because a plan may not write outside its tiers: the framework's mount point is yours, the handler is generated.
123
-
124
- ## No `next` dependency
125
-
126
- Next is the **deployment target**, not a dependency of this repository. `next` pulls `caniuse-lite`, which is
127
- CC-BY-4.0, and [`scripts/check-licenses.mjs`](../../../scripts/check-licenses.mjs) allows only
128
- 0BSD/Apache-2.0/BlueOak/BSD/CC0-1.0/ISC/MIT/MIT-0/MPL-2.0/Python-2.0/PSF-2.0/Unlicense/WTFPL. Adding Next would
129
- mean weakening the license allowlist, which this layer does not do. The example therefore ships
130
- `tests/server.ts`, a Node host that speaks the same Fetch-shaped contract as the emitted handler and looks up the
131
- same `ROUTE` constant, so the end-to-end job can boot generated code and hit a real socket without a Next install.
132
-
133
- **Determinism** ([`§8.2`](../../../docs/design.md)): output is path-sorted, LF-only, with one trailing newline, no
134
- timestamps, no absolute paths and no machine names. `checkDeterminism` in the generator SDK is the lint that holds
135
- this; a test in this package plans the example twice and compares bytes.
136
-
137
- **Prompt templates** are the one place the two runtimes disagree about syntax: the IR stores `{{ name }}` mustaches
138
- and the TypeScript runtime parses `str.format`-style `{name}` fields. `src/placeholders.ts` performs that
139
- conversion once, `validate.ts` refuses a template the runtime cannot interpolate, and the emitter never re-indents
140
- template content.
141
-
142
- **Not responsible for:** runtime behaviour — the gateway, prompt registry, tool executor, tracing, retrieval, the
143
- eval harness and the baseline statistics all live in [`runtimes/typescript/`](../../../runtimes/typescript/).
144
-
145
- **Status:** implemented for the Phase 1 scope and reachable as `aid gen --target ts-vercel` through the two-entry
146
- registry in [`packages/cli/src/targets.ts`](../../cli/src/targets.ts). `py-fastapi` remains the default target.
147
-
148
- **Scope boundary:** the generator rejects what it cannot express rather than emitting something broken
149
- ([`§4`](../../../docs/design.md), capability negotiation) — `cursor-memory`, guardrails, multi-step agent loops,
150
- streaming bodies, `llm-judge` as a required CI gate, latency and cost budgets, eval metrics selected by field or
151
- value, non-`http` triggers, retriever `filters` and non-file sources. Each refusal has a plan diagnostic code.
152
-
153
- **Tests:** `test/golden.test.ts` (the example's committed tree *is* the golden fixture), `test/drift.test.ts`
154
- (regeneration safety across both tiers), `test/target.test.ts` (manifest, capability discipline, IR-only purity,
155
- layout), `test/text.test.ts` (the byte-level helpers, including the linear edge trim every emitter shares) and
156
- [`packages/cli/test/targets.test.ts`](../../cli/test/targets.test.ts) (the registry cross-check). The
157
- example app is in [`examples/support-copilot-ts/`](../../../examples/support-copilot-ts/), and the end-to-end job
158
- runs `pnpm e2e:ts` after the Python example's `pnpm e2e` in the same `e2e` job, then the example's own suite
159
- (`pnpm --filter support-copilot-ts-example test`), which the root `vitest` run does not collect and whose 7 passing
160
- tests are pinned by `examples/support-copilot-ts/scripts/run-tests.mjs`.
161
-
162
- Reference: [`docs/design.md` §8](../../../docs/design.md) (generator plugin model),
163
- [`§9`](../../../docs/design.md) (runtime libraries), [`§6.1`](../../../docs/design.md) (example spec),
164
- [`§12`](../../../docs/design.md) (testing), [`§14`](../../../docs/design.md) (roadmap).
48
+ | `app/generated/index.ts` | The package's public surface |
49
+ | `app/generated/deps.ts` | Wiring: gateway, registry, executor, pipelines |
50
+ | `app/generated/control.ts` | The composite-step helpers the pipelines use |
51
+ | `app/generated/schemas.ts` | zod schemas derived from the IR's `types` |
52
+ | `app/generated/gateway/models.ts` | `ModelSpec` values and pinned aliases from the IR |
53
+ | `app/generated/observability.ts` | Tracer setup |
54
+ | `app/generated/guardrails.ts` | The guardrail policies the IR declares |
55
+ | `app/generated/contracts/` | The interface each business module must satisfy |
56
+ | `app/generated/pipelines/` | One module per pipeline, as ordinary async code |
57
+ | `app/generated/prompts/` | The registry the IR's prompts resolve into |
58
+ | `app/generated/tools/` | Tool definitions and dispatch |
59
+ | `app/generated/evals/` | The eval dataset and gate thresholds |
60
+ | `app/generated/retrieval/` | Retriever bindings, plus `ingest/` for corpus indexing |
61
+ | `app/generated/routes/` | Request handlers for pipelines with an `http` trigger |
62
+ | `app/business/`, `app/business/prompts/`, `app/business/tools/` | Your seam — created once, then never touched |
63
+
64
+ The exact file set follows the spec: a spec that declares no retrievers emits no `retrieval/` modules.
65
+
66
+ ### A note on `app/`
67
+
68
+ The output root is `app/`, which is also Next.js's own directory convention. That collision is deliberate and
69
+ accepted for parity with the other target's layout: the generated tree sits at the project's `app/`, alongside
70
+ whatever Next puts there. If you already have a Next project at that root, generate into a fresh directory rather
71
+ than merging the two.
72
+
73
+ ## Capabilities
74
+
75
+ ```text
76
+ http-trigger, json-schema, tool-calling, otel,
77
+ embeddings, rerank, guardrails, agents, cursor-memory
78
+ ```
79
+
80
+ It emits against **zod** for structured output and **`otel-genai`** for tracing.
81
+
82
+ Note the absence of `streaming`: this target does not declare it, so the IR's `deploy/streaming` gate rejects a
83
+ spec that asks for a streaming deployment here rather than emitting an endpoint that cannot stream.
84
+
85
+ ## The `business/` seam
86
+
87
+ Everything in `generated/` is replaced on the next `aid gen`. `business/` is created only if missing and is never
88
+ written again — that is where prompts, tool handlers and domain logic belong.
89
+
90
+ `app/generated/contracts/` carries the interfaces the business modules implement, so a missing or misnamed export
91
+ is a type error at build time instead of a runtime surprise.
92
+
93
+ ## Determinism
94
+
95
+ Given the same IR, this generator produces byte-identical output — no timestamps, no absolute paths, no host
96
+ names, and import order derived from stable ids rather than from filesystem walk order. That makes `generated/`
97
+ diffable and keeps a regeneration from producing a diff it did not mean.
98
+
99
+ ## Programmatic use
100
+
101
+ ```ts
102
+ import { manifest, plan } from '@aid-framework/generator-ts-vercel';
103
+ import type { Generator } from '@aid-framework/generator-sdk';
104
+ ```
105
+
106
+ The generator satisfies the `Generator` contract from
107
+ [`@aid-framework/generator-sdk`](https://www.npmjs.com/package/@aid-framework/generator-sdk): `plan(ir)` is pure
108
+ and `emit(plan)` returns the file set. Ownership, drift detection and manifest writing are the SDK's, so this
109
+ package contains no regeneration logic of its own.
110
+
111
+ ## Related packages
112
+
113
+ - [`@aid-framework/runtime`](https://www.npmjs.com/package/@aid-framework/runtime) — the runtime the emitted code
114
+ imports
115
+ - [`aid-runtime-python`](https://pypi.org/project/aid-runtime-python/) — the Python runtime
116
+ - [`@aid-framework/generator-py-fastapi`](https://www.npmjs.com/package/@aid-framework/generator-py-fastapi) — the
117
+ Python target
118
+ - [`@aid-framework/cli`](https://www.npmjs.com/package/@aid-framework/cli) — `aid`
119
+
120
+ ## License
121
+
122
+ [Apache-2.0](https://github.com/aid-framework/aid-framework/blob/main/LICENSE). Code generated by AID Framework is
123
+ not subject to this license.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aid-framework/generator-ts-vercel",
3
- "version": "0.1.39",
3
+ "version": "0.1.48",
4
4
  "description": "AID generator for a Next.js App Router (Vercel) TypeScript target.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -29,11 +29,11 @@
29
29
  "access": "public"
30
30
  },
31
31
  "dependencies": {
32
- "@aid-framework/generator-sdk": "0.1.39",
33
- "@aid-framework/ir": "0.1.39"
32
+ "@aid-framework/generator-sdk": "0.1.48",
33
+ "@aid-framework/ir": "0.1.48"
34
34
  },
35
35
  "devDependencies": {
36
- "@aid-framework/spec": "0.1.39",
36
+ "@aid-framework/spec": "0.1.48",
37
37
  "@types/node": "^26.6.5"
38
38
  },
39
39
  "scripts": {