@wenathlan/getry 2.0.165 → 2.0.167

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 (66) hide show
  1. package/README.md +1 -1
  2. package/accounts.ts +2087 -0
  3. package/antigravity.ts +2171 -0
  4. package/auth.ts +265 -0
  5. package/cli.ts +3 -3
  6. package/constants.ts +950 -0
  7. package/db.ts +810 -0
  8. package/docs/distributionlanes.md +1 -1
  9. package/docs/features.md +1 -2
  10. package/docs/gateway.md +2 -3
  11. package/docs/getry.md +33 -0
  12. package/engine.ts +1535 -0
  13. package/fingerprint.ts +1461 -0
  14. package/gateway.ts +1306 -0
  15. package/gatewaycheck.ts +142 -0
  16. package/intelligence.ts +169 -0
  17. package/json.ts +932 -0
  18. package/labels.ts +262 -0
  19. package/llm.ts +2709 -0
  20. package/models.ts +4017 -0
  21. package/nodeshim.ts +265 -0
  22. package/oauth.ts +2201 -0
  23. package/package.json +580 -225
  24. package/project.ts +1293 -0
  25. package/providers.ts +3238 -0
  26. package/quota.ts +1891 -0
  27. package/recovery.ts +2434 -0
  28. package/request.ts +4496 -0
  29. package/search.ts +1761 -0
  30. package/server.ts +766 -0
  31. package/streaming.ts +4398 -0
  32. package/{tests → test}/cli.test.ts +1 -1
  33. package/{tests → test}/gateway.test.ts +2 -2
  34. package/{tests → test}/gatewayversions.test.ts +1 -1
  35. package/{tests → test}/intelligence.test.ts +1 -1
  36. package/{tests → test}/keyrotation.test.ts +1 -1
  37. package/{tests → test}/onboardingstate.test.ts +1 -1
  38. package/{tests → test}/providerfixtures.ts +12 -12
  39. package/{tests → test}/researchfixtures.ts +1 -1
  40. package/{tests → test}/sandboxmodality.test.ts +1 -1
  41. package/thinking.ts +416 -0
  42. package/types.ts +809 -0
  43. package/utils.ts +263 -0
  44. package/validate.ts +61 -0
  45. package/versionregistry.ts +1325 -0
  46. /package/{tests → test}/debonairlane.test.ts +0 -0
  47. /package/{tests → test}/design.test.ts +0 -0
  48. /package/{tests → test}/family.test.ts +0 -0
  49. /package/{tests → test}/gatewayserver.ts +0 -0
  50. /package/{tests → test}/jsonpolicy.test.ts +0 -0
  51. /package/{tests → test}/llm.test.ts +0 -0
  52. /package/{tests → test}/modelroute.test.ts +0 -0
  53. /package/{tests → test}/promptlibrary.test.ts +0 -0
  54. /package/{tests → test}/providercli.test.ts +0 -0
  55. /package/{tests → test}/providerconstants.test.ts +0 -0
  56. /package/{tests → test}/providerfingerprint.test.ts +0 -0
  57. /package/{tests → test}/providermodels.test.ts +0 -0
  58. /package/{tests → test}/provideroauth.test.ts +0 -0
  59. /package/{tests → test}/routerotation.test.ts +0 -0
  60. /package/{tests → test}/serverauth.test.ts +0 -0
  61. /package/{tests → test}/serverengine.test.ts +0 -0
  62. /package/{tests → test}/serverproperty.test.ts +0 -0
  63. /package/{tests → test}/serverutils.test.ts +0 -0
  64. /package/{tests → test}/streamfallback.test.ts +0 -0
  65. /package/{tests → test}/thinkingladder.test.ts +0 -0
  66. /package/{tests → test}/torturellm.test.ts +0 -0
@@ -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
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * intelligence.ts — the AI planning, provenance, retrieval and metrics
3
+ * domain of the devthink family (the saddle ownership law, wave a,
4
+ * actions 108-112: the intelligence domain moved out of the saddle
5
+ * engine — the master virtualization engine keeps only the
6
+ * virtualization modalities, and every workload feature that is not
7
+ * sandbox/vm/runner/vgpu/vcpu/ports/hardware-virtual rides its new
8
+ * owner; devthink answers the intelligence lane).
9
+ *
10
+ * This file is the v2.0.0 grand-merge consolidation of the former nested
11
+ * folders of the repository (ai) into the single
12
+ * root-level domain file: one optimized TypeScript file per correlated
13
+ * domain, ordinal sections, JSDoc on every block, intra-domain imports
14
+ * dissolved. Every capability of every source file is preserved here;
15
+ * the pure re-export barrels (none in this domain) folded their surface
16
+ * into this file directly. Zero runtime imports: the domain stays
17
+ * browser-pure (the crypto.subtle digest the rag manifest hashes ride
18
+ * degrades to the deterministic byte fallback under hosts without it).
19
+ */
20
+
21
+ /* ════════════════════════════════════════════════════════════════════ */
22
+ /* Section 1: ai/provenance.ts — context provenance links retrieved chunks to source, query and transformation metadata. */
23
+ /* ════════════════════════════════════════════════════════════════════ */
24
+ /**
25
+ * context provenance links retrieved chunks to source, query and transformation metadata.
26
+ */
27
+
28
+ /** one retrieved chunk a provenance record carries (the loose input form:
29
+ * every field answers a sensible default the record normalizes). */
30
+ export interface provenancechunkinput {
31
+ id?: string | number;
32
+ contenthash?: string;
33
+ score?: number;
34
+ headingpath?: string;
35
+ tokencount?: number;
36
+ citation?: string;
37
+ }
38
+
39
+ /** the provenance call shape: a source or a source url names the record,
40
+ * the chunks and the metadata ride beside them. */
41
+ export interface provenanceinput {
42
+ source?: string;
43
+ sourceurl?: string;
44
+ documentid?: string;
45
+ query?: string;
46
+ retrievedat?: number;
47
+ chunks?: provenancechunkinput[];
48
+ metadata?: Record<string, unknown>;
49
+ }
50
+
51
+ /** the serializable retrieval record provenance answers (the normalized
52
+ * form the merge lane and the context readers consume). */
53
+ export interface provenancerecord {
54
+ version: number;
55
+ source: string;
56
+ sourceurl?: string;
57
+ documentid?: string;
58
+ query?: string;
59
+ retrievedat: number;
60
+ chunks: Array<{ id: string; contenthash?: string; score?: number; headingpath?: string; tokencount?: number; citation?: string }>;
61
+ metadata: Record<string, unknown>;
62
+ }
63
+
64
+ /** Creates a serializable retrieval record for an agent context result. */
65
+ export function provenance(input: provenanceinput = {}) {
66
+ if (!input.source && !input.sourceurl) throw new TypeError("provenance requires a source");
67
+ return { version: 1, source: (input.source ?? input.sourceurl) as string, sourceurl: input.sourceurl, documentid: input.documentid, query: input.query, retrievedat: Number(input.retrievedat ?? Date.now()), chunks: Array.isArray(input.chunks) ? input.chunks.map((chunk: any, index: any) => ({ id: String(chunk.id ?? index), contenthash: chunk.contenthash, score: chunk.score === undefined ? undefined : Number(chunk.score), headingpath: chunk.headingpath, tokencount: chunk.tokencount, citation: chunk.citation ?? input.sourceurl })) : [], metadata: { ...(input.metadata ?? {}) } } satisfies provenancerecord;
68
+ }
69
+
70
+ /** Merges provenance records while deduplicating chunk identifiers. */
71
+ export function mergeprovenance(records: provenancerecord[] = []) {
72
+ const valid = records.filter(Boolean);
73
+ const chunks: any[] = [];
74
+ const seen = new Set();
75
+ for (const record of valid) for (const chunk of record.chunks ?? []) { const key = `${record.documentid ?? record.source}:${chunk.id}`; if (seen.has(key)) continue; seen.add(key); chunks.push({ ...chunk, source: record.source, documentid: record.documentid }); }
76
+ return { version: 1, sources: [...new Set(valid.map((record: any) => record.source))], chunks, mergedat: Date.now() };
77
+ }
78
+
79
+ /* ════════════════════════════════════════════════════════════════════ */
80
+ /* Section 2: ai/tokens.ts — token helpers use configurable model ratios and never require a provider tokenizer. */
81
+ /* ════════════════════════════════════════════════════════════════════ */
82
+ /**
83
+ * token helpers use configurable model ratios and never require a provider tokenizer.
84
+ */
85
+ const ratios = Object.freeze({ default: 4, gpt: 3.5, claude: 3.2, gemini: 3.5 });
86
+
87
+ export function estimatetokens(text: any, model = "default") { const ratio = (ratios as Record<string, number>)[model] ?? ratios.default; return Math.ceil(String(text ?? "").length / ratio); }
88
+ export function fitscontext(text: any, context: any, model = "default") { return estimatetokens(text, model) <= context; }
89
+ export function tokenbudget(text: any, options: Record<string, any> = {}) { const tokens = estimatetokens(text, options.model); return { tokens, context: options.context ?? null, fits: options.context == null ? true : tokens <= options.context, remaining: options.context == null ? null : Math.max(0, options.context - tokens) }; }
90
+ export function settokenratios(values: Record<string, any> = {}) { Object.assign(ratios, values); return { ...ratios }; }
91
+
92
+ /* ════════════════════════════════════════════════════════════════════ */
93
+ /* Section 3: ai/chunk.ts — markdown chunking preserves heading paths and uses paragraph boundaries before hard cuts. */
94
+ /* ════════════════════════════════════════════════════════════════════ */
95
+ /**
96
+ * markdown chunking preserves heading paths and uses paragraph boundaries before hard cuts.
97
+ */
98
+
99
+ export function chunkmarkdown(markdown: any, options: Record<string, any> = {}) {
100
+ const maxtokens = options.maxtokens ?? 512;
101
+ const overlap = options.overlaptokens ?? 50;
102
+ const lines = String(markdown ?? "").split(/\r?\n/);
103
+ const chunks: any[] = [];
104
+ let headingpath: any[] = [];
105
+ let buffer: string[] = [];
106
+ function flush() { if (!buffer.length) return; const content = buffer.join("\n").trim(); if (content) chunks.push({ content, headingpath: [...headingpath], tokencount: estimatetokens(content, options.model) }); buffer = []; }
107
+ for (const line of lines) {
108
+ const heading = line.match(/^(#{1,6})[ \t]+(\S.*)$/);
109
+ if (heading) { flush(); const level = heading[1].length; headingpath = headingpath.slice(0, level - 1); headingpath[level - 1] = heading[2].trim(); buffer.push(line); continue; }
110
+ buffer.push(line);
111
+ if (estimatetokens(buffer.join("\n"), options.model) > maxtokens) { const last: any = buffer.pop(); flush(); const overlaptext = buffer.slice(-overlap).join("\n"); buffer = overlaptext ? [overlaptext, last] : [last]; }
112
+ }
113
+ flush();
114
+ return chunks.map((chunk: any, index: any) => ({ ...chunk, id: `chunk${index}` }));
115
+ }
116
+
117
+ /* ════════════════════════════════════════════════════════════════════ */
118
+ /* Section 4: ai/rag.ts — rag manifests connect chunks to embedding stores without forcing a vendor client. */
119
+ /* ════════════════════════════════════════════════════════════════════ */
120
+ /**
121
+ * rag manifests connect chunks to embedding stores without forcing a vendor client.
122
+ */
123
+ export async function ragmanifest(input: Record<string, any> = {}) {
124
+ const chunks = input.chunks ?? [];
125
+ const unique = [];
126
+ const hashes = new Set();
127
+ for (const chunk of chunks) { const hash = await hashtext(chunk.content); if (hashes.has(hash)) continue; hashes.add(hash); unique.push({ ...chunk, contenthash: hash, documentid: hash.slice(0, 16), metadata: { ...(input.metadata ?? {}), ...(chunk.metadata ?? {}) } }); }
128
+ return { documentid: (await hashtext(input.source ?? unique.map((chunk: any) => chunk.content).join("\n"))).slice(0, 16), source: input.source, chunks: unique, embeddingmodel: input.embeddingmodel, embeddingdimensions: input.embeddingdimensions, createdat: Date.now() };
129
+ }
130
+
131
+ export function vectorrecord(chunk: any, vector: any, options: Record<string, any> = {}) { return { id: `${chunk.documentid}-${chunk.id}`, vector, metadata: { headingpath: chunk.headingpath, contenthash: chunk.contenthash, sourceurl: options.sourceurl, tokencount: chunk.tokencount, contenttype: options.contenttype ?? "text", language: options.language ?? "en", embeddingmodel: options.embeddingmodel, embeddingdimensions: vector?.length } }; }
132
+
133
+ async function hashtext(text: any) { const bytes = new TextEncoder().encode(String(text)); if (globalThis.crypto?.subtle) { const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes); return [...new Uint8Array(digest)].map((value: any) => value.toString(16).padStart(2, "0")).join(""); } return Array.from(bytes).map((value: any) => value.toString(16).padStart(2, "0")).join("").slice(0, 64).padEnd(64, "0"); }
134
+
135
+ /* ════════════════════════════════════════════════════════════════════ */
136
+ /* Section 5: ai/llmstxt.ts — llms text generation creates compact absolute links for agent consumption. */
137
+ /* ════════════════════════════════════════════════════════════════════ */
138
+ /**
139
+ * llms text generation creates compact absolute links for agent consumption.
140
+ */
141
+ export function llmstxt(options: Record<string, any> = {}) {
142
+ const title = options.title ?? "devthink";
143
+ const description = options.description ?? "the reasoning workspace of the devthink family";
144
+ const pages = (options.pages ?? []).filter((page: any) => page?.url && /^https:\/\//.test(page.url)).slice(0, options.limit ?? 100);
145
+ const lines = [`# ${title}`, `> ${description}`, "", "## pages", "", ...pages.map((page: any) => `- [${page.title ?? page.url}](${page.url}): ${(page.description ?? "").slice(0, 100)}`)];
146
+ return `${lines.join("\n")}\n`;
147
+ }
148
+
149
+ export function llmsfull(options: Record<string, any> = {}) { return (options.pages ?? []).filter((page: any) => page?.url && /^https:\/\//.test(page.url)).map((page: any) => `# ${page.title ?? page.url}\n\nsource: ${page.url}\n\n${page.content ?? ""}`).join("\n\n---\n\n"); }
150
+
151
+ /* ════════════════════════════════════════════════════════════════════ */
152
+ /* Section 6: ai/metrics.ts — metrics keeps low-cardinality counters and durations without requiring a telemetry vendor. */
153
+ /* ════════════════════════════════════════════════════════════════════ */
154
+ /**
155
+ * metrics keeps low-cardinality counters and durations without requiring a telemetry vendor.
156
+ */
157
+
158
+ /** Creates an in-memory metric collector with bounded names and labels. */
159
+ export function metricstore(options: Record<string, any> = {}) {
160
+ const maxnames = Number(options.maxnames ?? 256);
161
+ const values = new Map();
162
+ const durations = new Map();
163
+ function key(name: any, labels: Record<string, any> = {}) { const sorted = Object.entries(labels).slice(0, 8).sort(([left], [right]) => left.localeCompare(right)); return `${String(name)}|${JSON.stringify(sorted)}`; }
164
+ function count(name: any, amount = 1, labels: Record<string, any> = {}) { if (values.size >= maxnames && !values.has(key(name, labels))) throw new Error("metric cardinality limit reached"); const metric = key(name, labels); values.set(metric, (values.get(metric) ?? 0) + Number(amount)); return values.get(metric); }
165
+ function observe(name: any, milliseconds: any, labels: Record<string, any> = {}) { const metric = key(name, labels); const list = durations.get(metric) ?? []; if (list.length < 1000) list.push(Number(milliseconds)); durations.set(metric, list); return Number(milliseconds); }
166
+ function snapshot() { return { counters: Object.fromEntries(values), durations: Object.fromEntries([...durations].map(([name, list]) => [name, { count: list.length, total: list.reduce((sum: any, value: any) => sum + value, 0), max: Math.max(0, ...list) }])) }; }
167
+ function reset() { values.clear(); durations.clear(); }
168
+ return { count, observe, snapshot, reset };
169
+ }