@wenathlan/getry 2.0.166 → 2.0.168

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 (41) hide show
  1. package/README.md +1 -1
  2. package/cli.ts +15 -6
  3. package/db.ts +33 -29
  4. package/docs/features.md +1 -2
  5. package/docs/gateway.md +2 -3
  6. package/gatewaycheck.ts +142 -0
  7. package/package.json +81 -89
  8. package/server.ts +9 -5
  9. package/{tests → test}/cli.test.ts +1 -1
  10. package/{tests → test}/design.test.ts +3 -3
  11. package/test/familyseats.test.ts +220 -0
  12. package/{tests → test}/gateway.test.ts +2 -2
  13. package/{tests → test}/gatewayversions.test.ts +1 -1
  14. package/{tests → test}/intelligence.test.ts +1 -1
  15. package/{tests → test}/keyrotation.test.ts +1 -1
  16. package/{tests → test}/onboardingstate.test.ts +1 -1
  17. package/{tests → test}/providerfixtures.ts +12 -12
  18. package/{tests → test}/researchfixtures.ts +1 -1
  19. package/{tests → test}/sandboxmodality.test.ts +1 -1
  20. package/gatewayprobe.ts +0 -61
  21. package/gatewaysmoke.ts +0 -64
  22. /package/{tests → test}/debonairlane.test.ts +0 -0
  23. /package/{tests → test}/family.test.ts +0 -0
  24. /package/{tests → test}/gatewayserver.ts +0 -0
  25. /package/{tests → test}/jsonpolicy.test.ts +0 -0
  26. /package/{tests → test}/llm.test.ts +0 -0
  27. /package/{tests → test}/modelroute.test.ts +0 -0
  28. /package/{tests → test}/promptlibrary.test.ts +0 -0
  29. /package/{tests → test}/providercli.test.ts +0 -0
  30. /package/{tests → test}/providerconstants.test.ts +0 -0
  31. /package/{tests → test}/providerfingerprint.test.ts +0 -0
  32. /package/{tests → test}/providermodels.test.ts +0 -0
  33. /package/{tests → test}/provideroauth.test.ts +0 -0
  34. /package/{tests → test}/routerotation.test.ts +0 -0
  35. /package/{tests → test}/serverauth.test.ts +0 -0
  36. /package/{tests → test}/serverengine.test.ts +0 -0
  37. /package/{tests → test}/serverproperty.test.ts +0 -0
  38. /package/{tests → test}/serverutils.test.ts +0 -0
  39. /package/{tests → test}/streamfallback.test.ts +0 -0
  40. /package/{tests → test}/thinkingladder.test.ts +0 -0
  41. /package/{tests → test}/torturellm.test.ts +0 -0
package/README.md CHANGED
@@ -20,7 +20,7 @@ pnpm dev
20
20
  # The family contract (wave 6-a)
21
21
 
22
22
  - `family.ts` — the single table of the eight siblings beside getry with the
23
- spec §12 accent of each one; the shell rail foot and `tests/family.test.ts`
23
+ spec §12 accent of each one; the shell rail foot and `test/family.test.ts`
24
24
  both consume this one table.
25
25
  - `familyurl.ts` — the verbatim relative-url contract (`"../" + slug + "/"`,
26
26
  consolidated to the family variant) that keeps every cross-app link
package/cli.ts CHANGED
@@ -727,7 +727,7 @@ async function cmdkeys(versionarg: string | undefined): Promise<void> {
727
727
  if (key) keys.push(key);
728
728
  }
729
729
  // write to a seed script the user can run
730
- const seedpath = resolve(process.cwd(), "tests", "seedkeys.mjs");
730
+ const seedpath = resolve(process.cwd(), "test", "seedkeys.mjs");
731
731
  const seedcontent = [
732
732
  "/** seed keys — generated by gateway cli */",
733
733
  'import { PrismaClient } from "@prisma/client"',
@@ -748,9 +748,9 @@ async function cmdkeys(versionarg: string | undefined): Promise<void> {
748
748
  `console.log(\`registered \${keys.length} keys\`)`,
749
749
  "",
750
750
  ].join("\n");
751
- mkdirSync(resolve(process.cwd(), "tests"), { recursive: true });
751
+ mkdirSync(resolve(process.cwd(), "test"), { recursive: true });
752
752
  writeFileSync(seedpath, seedcontent);
753
- console.log(`\n${colors.green}created${colors.reset} tests/seedkeys.mjs — run: node tests/seedkeys.mjs`);
753
+ console.log(`\n${colors.green}created${colors.reset} test/seedkeys.mjs — run: node test/seedkeys.mjs`);
754
754
  }
755
755
  }
756
756
  console.log("");
@@ -791,9 +791,18 @@ async function cmdmodels(versionarg: string | undefined): Promise<void> {
791
791
  // ---------------------------------------------------------------------------
792
792
 
793
793
  async function cmdserve(portarg: string | undefined): Promise<void> {
794
- const port = Number(portarg) || Number(process.env.PORT) || 3001;
795
- process.env.PORT = String(port);
796
- console.log(`${colors.cyan}starting gateway on port ${port}${colors.reset}`);
794
+ /* the gateway ports law (host/porta sorteados, never localhost literal):
795
+ * the caller pin rides when it carries a positive port (the arg lands in
796
+ * the env the transport main reads), the env pin rides when the env
797
+ * carries one, and when both stay silent the transport main draws the
798
+ * host and the port (binddraw.ts) — never a literal fallback */
799
+ const argport = Number(portarg);
800
+ if (portarg !== undefined && Number.isFinite(argport) && argport > 0) process.env.PORT = String(argport);
801
+ const pinned = Number(process.env.PORT);
802
+ const pinnedport = Number.isFinite(pinned) && pinned > 0 ? pinned : undefined;
803
+ console.log(
804
+ `${colors.cyan}starting gateway${pinnedport !== undefined ? ` on port ${pinnedport}` : " — the bind draws (host and port) since no env pin rides"}${colors.reset}`,
805
+ );
797
806
  /* the transport main loads lazily (the published-envelope law): the
798
807
  * console family — init through export, chat, help — runs on node
799
808
  * builtins alone inside the published envelope, and the serve seat
package/db.ts CHANGED
@@ -20,6 +20,7 @@
20
20
 
21
21
  /// <reference types="better-sqlite3" />
22
22
 
23
+ import { createOnboardingStore, type OnboardingLane, type OnboardingStore } from "@wenathlan/vault/onboardingstore";
23
24
  import { GATEWAYROUTES, GATEWAYVERSIONS, type GatewayRoute, type GatewayVersion } from "./gateway";
24
25
  import { LABEL_MANIFEST, LABEL_REVISION } from "./labels";
25
26
 
@@ -399,12 +400,6 @@ CREATE TABLE IF NOT EXISTS gateway_versions (
399
400
  );
400
401
  `;
401
402
 
402
- /** the prepared statements of the mirror, created once per process. */
403
- type OnboardingStatements = {
404
- select: import("better-sqlite3").Statement;
405
- upsert: import("better-sqlite3").Statement;
406
- };
407
-
408
403
  /** the prepared label statements of the mirror, created once per process. */
409
404
  type LabelStatements = {
410
405
  select: import("better-sqlite3").Statement;
@@ -421,7 +416,7 @@ type VersionStatements = {
421
416
  /** the cached driver bundle (null until the first node call). */
422
417
  let mirror: {
423
418
  db: import("better-sqlite3").Database;
424
- statements: OnboardingStatements;
419
+ onboarding: OnboardingStore;
425
420
  labels: LabelStatements;
426
421
  versions: VersionStatements;
427
422
  } | null = null;
@@ -437,7 +432,7 @@ let mirror: {
437
432
  */
438
433
  async function openMirror(): Promise<{
439
434
  db: import("better-sqlite3").Database;
440
- statements: OnboardingStatements;
435
+ onboarding: OnboardingStore;
441
436
  labels: LabelStatements;
442
437
  versions: VersionStatements;
443
438
  }> {
@@ -454,21 +449,31 @@ async function openMirror(): Promise<{
454
449
  db.exec(ONBOARDING_SCHEMA);
455
450
  db.exec(LABELS_SCHEMA);
456
451
  db.exec(GATEWAYVERSIONS_SCHEMA);
452
+ /* the onboarding store rides the vault factory over the mirror's own
453
+ * connection (the structural lane — execute, queryOne, exec — answered
454
+ * through one memoized prepare cache, so every statement stays prepared
455
+ * once per process): the seen-wins upsert —
456
+ * ON CONFLICT(visitor) DO UPDATE SET introseen = MAX(introseen, excluded.introseen),
457
+ * onboardingseen = MAX(onboardingseen, excluded.onboardingseen) — rides
458
+ * the shared mechanism, never a local copy of the sql, and the browser
459
+ * graph never gains a storage import — the factory module owns zero
460
+ * imports (the labelstore precedent). */
461
+ const prepared = new Map<string, import("better-sqlite3").Statement>();
462
+ const prepareCached = (sql: string): import("better-sqlite3").Statement => {
463
+ const cached = prepared.get(sql);
464
+ if (cached) return cached;
465
+ const statement = db.prepare(sql);
466
+ prepared.set(sql, statement);
467
+ return statement;
468
+ };
469
+ const onboardinglane: OnboardingLane = {
470
+ execute: (sql, params = []) => prepareCached(sql).run(...params),
471
+ queryOne: (sql, params = []) => prepareCached(sql).get(...params),
472
+ exec: (ddl) => db.exec(ddl),
473
+ };
457
474
  mirror = {
458
475
  db,
459
- statements: {
460
- select: db.prepare(
461
- "SELECT id, visitor, introseen, onboardingseen, updatedAt FROM onboarding_states WHERE visitor = ?",
462
- ),
463
- upsert: db.prepare(
464
- `INSERT INTO onboarding_states (visitor, introseen, onboardingseen, updatedAt)
465
- VALUES (?, ?, ?, datetime('now'))
466
- ON CONFLICT(visitor) DO UPDATE SET
467
- introseen = MAX(introseen, excluded.introseen),
468
- onboardingseen = MAX(onboardingseen, excluded.onboardingseen),
469
- updatedAt = datetime('now')`,
470
- ),
471
- },
476
+ onboarding: createOnboardingStore(onboardinglane),
472
477
  labels: {
473
478
  select: db.prepare("SELECT id, text, revision, updatedAt, translated FROM labels WHERE id = ?"),
474
479
  selectAll: db.prepare("SELECT id, text, revision, updatedAt, translated FROM labels ORDER BY id ASC"),
@@ -514,15 +519,14 @@ export type OnboardingFlags = { introseen: boolean; onboardingseen: boolean };
514
519
  */
515
520
  export async function getonboardingstate(visitor: string): Promise<OnboardingFlags | undefined> {
516
521
  if (!isnode()) return undefined;
517
- const { statements } = await openMirror();
518
- const row = statements.select.get(visitor) as { introseen: number; onboardingseen: number } | undefined;
519
- if (!row) return undefined;
520
- return { introseen: row.introseen === 1, onboardingseen: row.onboardingseen === 1 };
522
+ const { onboarding } = await openMirror();
523
+ return onboarding.getOnboardingState(visitor);
521
524
  }
522
525
 
523
526
  /**
524
- * Marks one onboarding stage seen for a visitor — idempotent seen-wins
525
- * upsert (a stage seen anywhere stays seen), prepared and bound, never
527
+ * Marks one onboarding stage seen for a visitor — the idempotent seen-wins
528
+ * upsert of the vault store (a stage seen anywhere stays seen; the later
529
+ * stage implies the earlier — the flow order), prepared and bound, never
526
530
  * concatenated SQL.
527
531
  *
528
532
  * @param visitor the visitor key of the row.
@@ -531,8 +535,8 @@ export async function getonboardingstate(visitor: string): Promise<OnboardingFla
531
535
  */
532
536
  export async function setonboardingstage(visitor: string, stage: "intro" | "onboarding"): Promise<OnboardingFlags> {
533
537
  if (!isnode()) return { introseen: false, onboardingseen: false };
534
- const { statements } = await openMirror();
535
- statements.upsert.run(visitor, stage === "intro" ? 1 : 0, stage === "onboarding" ? 1 : 0);
538
+ const { onboarding } = await openMirror();
539
+ onboarding.setOnboardingStage(visitor, stage);
536
540
  return (await getonboardingstate(visitor)) ?? { introseen: false, onboardingseen: false };
537
541
  }
538
542
 
package/docs/features.md CHANGED
@@ -16,8 +16,7 @@ every feature below is read from the code of the getry app root and the Sol them
16
16
  - cors handling — preflight and cors headers on every route.
17
17
  - response formats — chat completion, anthropic message and response shapes built from one pipeline.
18
18
  - gateway cli — `cli.ts` scaffolds and inspects gateway configs.
19
- - gateway probe — `gatewayprobe.ts` runs a manual chat round trip against a deployed host.
20
- - container smoke — `gatewaysmoke.ts` runs one full offline request cycle against the recorded openaicompat fixture.
19
+ - gateway check — `gatewaycheck.ts` folds both check seats: the offline smoke (one full request cycle against the recorded openaicompat fixture) and the manual probe (a chat round trip against a deployed host).
21
20
 
22
21
  ## providers
23
22
 
package/docs/gateway.md CHANGED
@@ -29,8 +29,7 @@
29
29
  - `oauth.ts` / `accounts.ts` / `recovery.ts` / `project.ts` — the provider oauth family, multi-account manager, recovery and project id discovery.
30
30
  - `db.ts` — the database contract: typed rows, in-memory seeds, HTTPS accessors, the health-aware round-robin key picker (`nextkeyrow`) and the better-sqlite3 mirror (onboarding states, labels and the `gateway_versions` route table).
31
31
  - `prismaconfig.ts` / `prismashim.ts` / `nodeshim.ts` — prisma schema path resolution and runtime shims.
32
- - `gatewayprobe.ts` — manual chat round trip against a deployed gateway (`GET /v1/models`, `POST /v1/chat/completions`).
33
- - `gatewaysmoke.ts` — offline container smoke: one full request cycle against the recorded openaicompat fixture.
32
+ - `gatewaycheck.ts` — the two gateway check seats in one file: the offline container smoke (one full request cycle against the recorded openaicompat fixture) and the manual probe (chat round trip against a deployed gateway, `GET /v1/models`, `POST /v1/chat/completions`).
34
33
  - `registernvidiakeys.ts` — registers the 22 nvidia api keys into the `nvidiaKey` table.
35
34
  - `cli.ts` / `providercli.ts` — the gateway cli and the provider cli.
36
35
  - `versionregistry.ts`, `request.ts`, `search.ts`, `fingerprint.ts`, `constants.ts`, `antigravity.ts` — the antigravity/gemini-cli provider lineage: version resolution, request building, search, fingerprinting, endpoint constants.
@@ -122,4 +121,4 @@ the 7-level thinking ladder (`thinking.ts`), each level bound to a token budget:
122
121
 
123
122
  ## tests
124
123
 
125
- - 29 test files under `tests/` cover the gateway surface: gateway, gatewayversions (the 5/35 route table law, the sqlite mirror rows, the thinking chain), server (auth, engine, property, utils), llm, modelroute, promptlibrary, keyrotation (the health-aware key round-robin and the masking discipline), routerotation, thinkingladder (the registerable ladder and the tier/budget/chain/lane routing), streamfallback, intelligence, jsonpolicy, family, design, cli, providercli, onboardingstate, sandboxmodality, debonairlane, torture.llm, plus the provider lineage (constants, fingerprint, models, oauth) and the shared fixtures (`gatewayserver.ts`, `providerfixtures.ts`, `researchfixtures.ts`).
124
+ - 29 test files under `test/` cover the gateway surface: gateway, gatewayversions (the 5/35 route table law, the sqlite mirror rows, the thinking chain), server (auth, engine, property, utils), llm, modelroute, promptlibrary, keyrotation (the health-aware key round-robin and the masking discipline), routerotation, thinkingladder (the registerable ladder and the tier/budget/chain/lane routing), streamfallback, intelligence, jsonpolicy, family, design, cli, providercli, onboardingstate, sandboxmodality, debonairlane, torture.llm, plus the provider lineage (constants, fingerprint, models, oauth) and the shared fixtures (`gatewayserver.ts`, `providerfixtures.ts`, `researchfixtures.ts`).
@@ -0,0 +1,142 @@
1
+ /**
2
+ * gatewaycheck — consolidated gateway check lane of the getry family
3
+ * @module gatewaycheck
4
+ * @family wave/gatewaycheck
5
+ * One keyed file per family: the former variant arms (gatewayprobe, gatewaysmoke)
6
+ * retired into this seat with every behavior preserved under the same name.
7
+ */
8
+
9
+ import { startgatewayfixtureserver } from "./test/gatewayserver.ts";
10
+ import type { baseurlconfig } from "./types.ts";
11
+
12
+ /**
13
+ * the offline smoke seat of the 1.1.83 family: it starts the localhost fixture
14
+ * server, loads the built gateway module and runs one full request cycle
15
+ * against the recorded openaicompat fixture offline — no provider endpoint,
16
+ * no key, no external network — so the container image proves the gateway
17
+ * call path before it ships.
18
+ */
19
+ export async function gatewaysmokeseat(): Promise<void> {
20
+ const server = await startgatewayfixtureserver();
21
+ try {
22
+ /* the smoke loads the BUILT gateway module — build/llm.js is a container
23
+ * build artifact a fresh checkout never carries, so the specifier rides a
24
+ * variable (tsc never fails on the absent artifact while the runtime
25
+ * keeps loading the built face) and the cast pins the module to the
26
+ * source contract that sits beside it. */
27
+ const builtpath = "./build/llm.js";
28
+ const gateway = (await import(builtpath)) as typeof import("./llm.ts");
29
+ const config: baseurlconfig = {
30
+ providerid: "smoke",
31
+ kind: "openaicompat",
32
+ baseurl: `http://127.0.0.1:${server.port}/openaicompat`,
33
+ enabled: true,
34
+ consented: true,
35
+ consentedat: Date.now(),
36
+ updatedat: Date.now(),
37
+ };
38
+ const outcome = await gateway.gatewaycall({
39
+ config,
40
+ model: "fixture-model-a",
41
+ messages: [{ role: "user", content: "draft one step" }],
42
+ requestid: "smoke-1",
43
+ transport: async (url: string, init: { method: string; headers: Record<string, string>; body?: string }) => {
44
+ const response = await fetch(url, { method: init.method, headers: init.headers, body: init.body });
45
+ return {
46
+ status: response.status,
47
+ headers: Object.fromEntries(response.headers.entries()),
48
+ body: await response.text(),
49
+ };
50
+ },
51
+ });
52
+ if (!outcome.text.includes("goal")) throw new Error("The gateway smoke answer carries no goal field.");
53
+ if (outcome.usage?.totaltokens !== 55) throw new Error("The gateway smoke usage did not parse.");
54
+ const streamed = await gateway.gatewaystream({
55
+ config: { ...config, providerid: "smoke-stream" },
56
+ model: "fixture-model-a",
57
+ messages: [{ role: "user", content: "stream" }],
58
+ transport: async (url: string, init: { method: string; headers: Record<string, string>; body?: string }) => {
59
+ const response = await fetch(url, { method: init.method, headers: init.headers, body: init.body });
60
+ return {
61
+ status: response.status,
62
+ headers: Object.fromEntries(response.headers.entries()),
63
+ body: await response.text(),
64
+ };
65
+ },
66
+ });
67
+ if (streamed.tokens.length !== 3)
68
+ throw new Error("The gateway smoke stream did not carry the three recorded deltas.");
69
+ console.log(
70
+ JSON.stringify({
71
+ valid: true,
72
+ requestid: outcome.requestid,
73
+ tokens: streamed.tokens.length,
74
+ usage: outcome.usage.totaltokens,
75
+ }),
76
+ );
77
+ } finally {
78
+ await server.close();
79
+ }
80
+ }
81
+
82
+ /**
83
+ * the live probe seat: a manual chat round trip against a deployed DevThink
84
+ * gateway (the ZAI sandbox deploy of the getry template). this seat is NOT
85
+ * wired into the deterministic gates: it needs the live network and the
86
+ * deployed host is a runtime setting, never a tracked constant of a gate.
87
+ * it exercises the exact surface the Sol chat client consumes: GET
88
+ * /v1/models (the picker) and POST /v1/chat/completions with the model
89
+ * "devthink" (content + reasoning_content on the answer turn).
90
+ */
91
+ export async function gatewayprobeseat(base: string): Promise<void> {
92
+ /* GET /v1/models — the chat picker surface. */
93
+ const models = await fetch(`${base}/v1/models`);
94
+ if (!models.ok) {
95
+ console.error(`models probe failed: ${models.status}`);
96
+ process.exit(1);
97
+ }
98
+ const modellist = (await models.json()) as { data?: Array<{ id?: string }> };
99
+ const ids = (modellist.data ?? []).map((m) => m.id);
100
+ console.log(`models: ${ids.join(", ") || "(none)"}`);
101
+
102
+ /* POST /v1/chat/completions — one round trip on the chat surface. */
103
+ const chat = await fetch(`${base}/v1/chat/completions`, {
104
+ method: "POST",
105
+ headers: { "content-type": "application/json" },
106
+ body: JSON.stringify({
107
+ model: "devthink",
108
+ messages: [{ role: "user", content: "Reply with exactly: gateway ok" }],
109
+ max_tokens: 20,
110
+ }),
111
+ });
112
+ if (!chat.ok) {
113
+ console.error(`chat probe failed: ${chat.status}`);
114
+ process.exit(1);
115
+ }
116
+ const answer = (await chat.json()) as {
117
+ choices?: Array<{ message?: { content?: string; reasoning_content?: string } }>;
118
+ usage?: unknown;
119
+ };
120
+ const turn = answer.choices?.[0]?.message;
121
+ console.log(`chat: ${JSON.stringify(turn?.content ?? "")}`);
122
+ console.log(`reasoning present: ${Boolean(turn?.reasoning_content)}`);
123
+ console.log(`usage: ${JSON.stringify(answer.usage ?? {})}`);
124
+
125
+ if (turn?.content !== "gateway ok") {
126
+ console.error("the gateway answered, but the round trip content drifted");
127
+ process.exit(1);
128
+ }
129
+ console.log("gateway probe: ok");
130
+ }
131
+
132
+ /* the dispatched entry the two seats ride: an http(s) URL argument answers
133
+ * the live probe seat (`node gatewaycheck.ts https://<deployed-gateway-host>`,
134
+ * the documented probe call), every other call answers the offline smoke
135
+ * seat (`node gatewaycheck.ts`, the container call). */
136
+ const firstarg = (process.argv[2] ?? "").replace(/\/+$/, "");
137
+
138
+ if (/^https?:\/\//.test(firstarg)) {
139
+ await gatewayprobeseat(firstarg);
140
+ } else {
141
+ await gatewaysmokeseat();
142
+ }