@opengeni/codemode 0.4.22 → 0.4.27-canary.0

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.
package/dist/site.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { CodemodeClient } from "./index.js";
2
+ import { type CodemodeClientProvider } from "./environment.js";
3
+ export declare const CODEMODE_SITE_LOCAL_PATH: "/__opengeni/site-tools";
4
+ export type CodemodeSiteRequestHandler = (request: Request) => Promise<Response>;
5
+ /**
6
+ * Same-origin local Site preview adapter. The Bun host retains the exact
7
+ * attempt bearer; browser code receives only the ordinary Site tool protocol.
8
+ */
9
+ export declare function createCodemodeSiteRequestHandler(client?: CodemodeClient | CodemodeClientProvider): CodemodeSiteRequestHandler;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/codemode",
3
- "version": "0.4.22",
3
+ "version": "0.4.27-canary.0",
4
4
  "description": "Attempt-frozen programmatic tool catalog and execution authority for OpenGeni.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -34,8 +34,8 @@
34
34
  "prepublishOnly": "bash ../../scripts/prepublish-guard"
35
35
  },
36
36
  "dependencies": {
37
- "@opengeni/contracts": "^2.9.2",
38
- "ajv": "^8.20.0"
37
+ "@opengeni/contracts": "^2.13.0-canary.0",
38
+ "@opengeni/tool-gateway": "^0.1.0-canary.0"
39
39
  },
40
40
  "engines": {
41
41
  "node": ">=18"
@@ -1,4 +1,5 @@
1
- import type { AttemptToolCatalog, AttemptToolCatalogEntry } from "@opengeni/contracts";
1
+ import type { AttemptToolCatalog } from "@opengeni/contracts";
2
+ import { generateToolDeclarations, jsonSchemaToTypeScript } from "@opengeni/tool-gateway";
2
3
  import { parseVerifiedAttemptToolCatalog } from "./index";
3
4
 
4
5
  export type GenerateCodemodeDeclarationsOptions = {
@@ -6,11 +7,6 @@ export type GenerateCodemodeDeclarationsOptions = {
6
7
  moduleSpecifier?: string;
7
8
  };
8
9
 
9
- type NamespaceNode = {
10
- children: Map<string, NamespaceNode>;
11
- entry: AttemptToolCatalogEntry | null;
12
- };
13
-
14
10
  /**
15
11
  * Generate declaration merging for one exact, digest-pinned attempt catalog.
16
12
  * Types improve authoring only; runtime catalog validation remains authoritative.
@@ -20,326 +16,17 @@ export function generateCodemodeDeclarations(
20
16
  options: GenerateCodemodeDeclarationsOptions = {},
21
17
  ): string {
22
18
  const verified = parseVerifiedAttemptToolCatalog(catalog);
23
- const moduleSpecifier = options.moduleSpecifier ?? "@opengeni/codemode";
24
- const root = namespaceNode();
25
- for (const entry of verified.entries) insertEntry(root, entry);
26
-
27
- return [
28
- "// Generated by @opengeni/codemode. Do not edit.",
29
- `// Attempt catalog digest: ${verified.digest}`,
30
- `import type { CodemodeCallOptions, CodemodeToolResult } from ${JSON.stringify(moduleSpecifier)};`,
31
- "",
32
- `declare module ${JSON.stringify(moduleSpecifier)} {`,
33
- " interface CodemodeGeneratedTools {",
34
- ...renderChildren(root, 4),
35
- " }",
36
- "}",
37
- "",
38
- "export {};",
39
- "",
40
- ].join("\n");
41
- }
42
-
43
- /** Honest JSON-Schema-to-TypeScript projection used by declaration generation. */
44
- export function jsonSchemaToTypeScript(schema: unknown): string {
45
- return schemaType(schema, schema, new Set<string>(), 0);
46
- }
47
-
48
- function namespaceNode(): NamespaceNode {
49
- return { children: new Map(), entry: null };
50
- }
51
-
52
- function insertEntry(root: NamespaceNode, entry: AttemptToolCatalogEntry): void {
53
- let node = root;
54
- for (const [index, segment] of entry.codemodePath.entries()) {
55
- if (node.entry) {
56
- throw new Error(
57
- `Codemode declaration path ${entry.codemodePath.join(".")} extends a tool leaf`,
58
- );
59
- }
60
- let child = node.children.get(segment);
61
- if (!child) {
62
- child = namespaceNode();
63
- node.children.set(segment, child);
64
- }
65
- node = child;
66
- if (index === entry.codemodePath.length - 1) {
67
- if (node.entry || node.children.size > 0) {
68
- throw new Error(`Codemode declaration path ${entry.codemodePath.join(".")} collides`);
69
- }
70
- node.entry = entry;
71
- }
72
- }
73
- }
74
-
75
- function renderChildren(node: NamespaceNode, indent: number): string[] {
76
- const lines: string[] = [];
77
- for (const [name, child] of [...node.children].sort(([left], [right]) =>
78
- left.localeCompare(right),
79
- )) {
80
- if (child.entry) {
81
- lines.push(...renderTool(name, child.entry, indent));
82
- continue;
83
- }
84
- lines.push(`${spaces(indent)}readonly ${name}: {`);
85
- lines.push(...renderChildren(child, indent + 2));
86
- lines.push(`${spaces(indent)}};`);
87
- }
88
- return lines;
89
- }
90
-
91
- function renderTool(name: string, entry: AttemptToolCatalogEntry, indent: number): string[] {
92
- const input = schemaType(entry.inputSchema, entry.inputSchema, new Set<string>(), 0);
93
- const output = entry.outputSchema
94
- ? schemaType(entry.outputSchema, entry.outputSchema, new Set<string>(), 0)
95
- : "CodemodeToolResult";
96
- const optionalArguments = rootObjectArgumentsAreOptional(entry.inputSchema);
97
- const description = boundedDoc(entry.description ?? entry.title);
98
- return [
99
- ...(description ? renderDoc(description, indent) : []),
100
- `${spaces(indent)}readonly ${name}: (`,
101
- `${spaces(indent + 2)}argumentsValue${optionalArguments ? "?" : ""}: ${input},`,
102
- `${spaces(indent + 2)}options?: CodemodeCallOptions,`,
103
- `${spaces(indent)}) => Promise<${output}>;`,
104
- ];
105
- }
106
-
107
- function rootObjectArgumentsAreOptional(schema: unknown): boolean {
108
- if (!isSchemaObject(schema)) return false;
109
- const required = Array.isArray(schema.required)
110
- ? schema.required.filter((value): value is string => typeof value === "string")
111
- : [];
112
- return required.length === 0 && (schema.type === "object" || isSchemaObject(schema.properties));
113
- }
114
-
115
- function schemaType(
116
- schema: unknown,
117
- rootSchema: unknown,
118
- resolvingRefs: Set<string>,
119
- depth: number,
120
- ): string {
121
- if (depth > 48 || schema === true) return "unknown";
122
- if (schema === false) return "never";
123
- if (!isSchemaObject(schema)) return "unknown";
124
-
125
- if (typeof schema.$ref === "string") {
126
- const reference = schema.$ref;
127
- if (!reference.startsWith("#/") || resolvingRefs.has(reference)) return "unknown";
128
- const resolved = resolveLocalReference(rootSchema, reference);
129
- if (resolved === undefined) return "unknown";
130
- const next = new Set(resolvingRefs);
131
- next.add(reference);
132
- return schemaType(resolved, rootSchema, next, depth + 1);
133
- }
134
-
135
- if (Object.hasOwn(schema, "const")) return literalType(schema.const);
136
- if (Array.isArray(schema.enum)) {
137
- return union(schema.enum.map(literalType));
138
- }
139
-
140
- const composites: string[] = [];
141
- if (Array.isArray(schema.oneOf)) {
142
- composites.push(
143
- union(schema.oneOf.map((part) => schemaType(part, rootSchema, resolvingRefs, depth + 1))),
144
- );
145
- }
146
- if (Array.isArray(schema.anyOf)) {
147
- composites.push(
148
- union(schema.anyOf.map((part) => schemaType(part, rootSchema, resolvingRefs, depth + 1))),
149
- );
150
- }
151
- if (Array.isArray(schema.allOf)) {
152
- composites.push(
153
- intersection(
154
- schema.allOf.map((part) => schemaType(part, rootSchema, resolvingRefs, depth + 1)),
155
- ),
156
- );
157
- }
158
- if (composites.length > 0) {
159
- const composed = intersection(composites);
160
- return schema.nullable === true ? union([composed, "null"]) : composed;
161
- }
162
-
163
- const declaredTypes = Array.isArray(schema.type)
164
- ? schema.type.filter((value): value is string => typeof value === "string")
165
- : typeof schema.type === "string"
166
- ? [schema.type]
167
- : inferredSchemaTypes(schema);
168
- const rendered = declaredTypes.map((type) =>
169
- typeType(type, schema, rootSchema, resolvingRefs, depth + 1),
170
- );
171
- if (schema.nullable === true) rendered.push("null");
172
- return union(rendered.length > 0 ? rendered : ["unknown"]);
173
- }
174
-
175
- function inferredSchemaTypes(schema: Record<string, unknown>): string[] {
176
- if (isSchemaObject(schema.properties) || Object.hasOwn(schema, "additionalProperties")) {
177
- return ["object"];
178
- }
179
- if (Object.hasOwn(schema, "items") || Array.isArray(schema.prefixItems)) return ["array"];
180
- return [];
181
- }
182
-
183
- function typeType(
184
- type: string,
185
- schema: Record<string, unknown>,
186
- rootSchema: unknown,
187
- resolvingRefs: Set<string>,
188
- depth: number,
189
- ): string {
190
- switch (type) {
191
- case "null":
192
- return "null";
193
- case "boolean":
194
- return "boolean";
195
- case "integer":
196
- case "number":
197
- return "number";
198
- case "string":
199
- return "string";
200
- case "array":
201
- return arrayType(schema, rootSchema, resolvingRefs, depth);
202
- case "object":
203
- return objectType(schema, rootSchema, resolvingRefs, depth);
204
- default:
205
- return "unknown";
206
- }
207
- }
208
-
209
- function arrayType(
210
- schema: Record<string, unknown>,
211
- rootSchema: unknown,
212
- resolvingRefs: Set<string>,
213
- depth: number,
214
- ): string {
215
- if (Array.isArray(schema.prefixItems)) {
216
- const tuple = schema.prefixItems.map((item) =>
217
- schemaType(item, rootSchema, resolvingRefs, depth + 1),
218
- );
219
- if (schema.items === false) return `readonly [${tuple.join(", ")}]`;
220
- const rest =
221
- schema.items === undefined || schema.items === true
222
- ? "unknown"
223
- : schemaType(schema.items, rootSchema, resolvingRefs, depth + 1);
224
- return `readonly [${tuple.join(", ")}${tuple.length > 0 ? ", " : ""}...${rest}[]]`;
225
- }
226
- const item =
227
- schema.items === undefined || schema.items === true
228
- ? "unknown"
229
- : schemaType(schema.items, rootSchema, resolvingRefs, depth + 1);
230
- return `readonly (${item})[]`;
231
- }
232
-
233
- function objectType(
234
- schema: Record<string, unknown>,
235
- rootSchema: unknown,
236
- resolvingRefs: Set<string>,
237
- depth: number,
238
- ): string {
239
- const properties = isSchemaObject(schema.properties) ? schema.properties : {};
240
- const required = new Set(
241
- Array.isArray(schema.required)
242
- ? schema.required.filter((value): value is string => typeof value === "string")
243
- : [],
19
+ return generateToolDeclarations(
20
+ { digest: verified.digest, entries: verified.entries },
21
+ {
22
+ moduleSpecifier: options.moduleSpecifier ?? "@opengeni/codemode",
23
+ interfaceName: "CodemodeGeneratedTools",
24
+ callOptionsType: "CodemodeCallOptions",
25
+ fallbackResultType: "CodemodeToolResult",
26
+ generatedBy: "@opengeni/codemode via @opengeni/tool-gateway",
27
+ catalogDigestLabel: "Attempt catalog digest",
28
+ },
244
29
  );
245
- const entries = Object.entries(properties).sort(([left], [right]) => left.localeCompare(right));
246
- const fields = entries.map(([name, propertySchema]) => {
247
- const key = identifierOrQuoted(name);
248
- const optional = required.has(name) ? "" : "?";
249
- return `readonly ${key}${optional}: ${schemaType(
250
- propertySchema,
251
- rootSchema,
252
- resolvingRefs,
253
- depth + 1,
254
- )}`;
255
- });
256
- for (const missing of [...required].filter((name) => !Object.hasOwn(properties, name)).sort()) {
257
- fields.push(`readonly ${identifierOrQuoted(missing)}: unknown`);
258
- }
259
-
260
- const additional = schema.additionalProperties;
261
- if (additional !== false) {
262
- if (entries.length === 0 && additional !== undefined && additional !== true) {
263
- return `Readonly<Record<string, ${schemaType(
264
- additional,
265
- rootSchema,
266
- resolvingRefs,
267
- depth + 1,
268
- )}>>`;
269
- }
270
- // Known properties and typed additional properties can have incompatible
271
- // value types. `unknown` preserves legal values without fabricating a lie.
272
- fields.push("readonly [key: string]: unknown");
273
- }
274
- return fields.length === 0 ? "Record<string, never>" : `{ ${fields.join("; ")} }`;
275
- }
276
-
277
- function resolveLocalReference(rootSchema: unknown, reference: string): unknown {
278
- let current = rootSchema;
279
- for (const encoded of reference.slice(2).split("/")) {
280
- if (!isSchemaObject(current)) return undefined;
281
- const segment = encoded.replace(/~1/gu, "/").replace(/~0/gu, "~");
282
- if (!Object.hasOwn(current, segment)) return undefined;
283
- current = current[segment];
284
- }
285
- return current;
286
- }
287
-
288
- function literalType(value: unknown): string {
289
- if (
290
- value === null ||
291
- typeof value === "string" ||
292
- typeof value === "number" ||
293
- typeof value === "boolean"
294
- ) {
295
- return JSON.stringify(value);
296
- }
297
- if (Array.isArray(value)) return `readonly [${value.map(literalType).join(", ")}]`;
298
- if (isSchemaObject(value)) {
299
- return `{ ${Object.entries(value)
300
- .sort(([left], [right]) => left.localeCompare(right))
301
- .map(([key, child]) => `readonly ${identifierOrQuoted(key)}: ${literalType(child)}`)
302
- .join("; ")} }`;
303
- }
304
- return "unknown";
305
- }
306
-
307
- function union(types: string[]): string {
308
- const unique = [...new Set(types)];
309
- if (unique.includes("unknown")) return "unknown";
310
- if (unique.length === 0) return "never";
311
- return unique.length === 1 ? unique[0]! : unique.map(parenthesizeComposite).join(" | ");
312
30
  }
313
31
 
314
- function intersection(types: string[]): string {
315
- const unique = [...new Set(types.filter((type) => type !== "unknown"))];
316
- if (unique.length === 0) return "unknown";
317
- return unique.length === 1 ? unique[0]! : unique.map(parenthesizeComposite).join(" & ");
318
- }
319
-
320
- function parenthesizeComposite(type: string): string {
321
- return /[|&]/u.test(type) ? `(${type})` : type;
322
- }
323
-
324
- function identifierOrQuoted(value: string): string {
325
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/u.test(value) ? value : JSON.stringify(value);
326
- }
327
-
328
- function boundedDoc(value: string | undefined): string | null {
329
- if (!value) return null;
330
- const normalized = value.replace(/\s+/gu, " ").trim().replace(/\*\//gu, "*\\/");
331
- if (!normalized) return null;
332
- return normalized.length <= 512 ? normalized : `${normalized.slice(0, 509)}...`;
333
- }
334
-
335
- function renderDoc(value: string, indent: number): string[] {
336
- return [`${spaces(indent)}/** ${value} */`];
337
- }
338
-
339
- function spaces(count: number): string {
340
- return " ".repeat(count);
341
- }
342
-
343
- function isSchemaObject(value: unknown): value is Record<string, unknown> {
344
- return value !== null && typeof value === "object" && !Array.isArray(value);
345
- }
32
+ export { jsonSchemaToTypeScript };