@opengeni/codemode 0.2.2
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/LICENSE +190 -0
- package/README.md +71 -0
- package/dist/artifacts.d.ts +99 -0
- package/dist/declarations.d.ts +12 -0
- package/dist/environment.d.ts +28 -0
- package/dist/index.d.ts +161 -0
- package/dist/index.js +1543 -0
- package/dist/index.js.map +1 -0
- package/dist/interaction.d.ts +1529 -0
- package/dist/structured.d.ts +11 -0
- package/package.json +43 -0
- package/src/artifacts.ts +313 -0
- package/src/declarations.ts +345 -0
- package/src/environment.ts +101 -0
- package/src/index.ts +736 -0
- package/src/interaction.ts +726 -0
- package/src/structured.ts +52 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import type { AttemptToolCatalog, AttemptToolCatalogEntry } from "@opengeni/contracts";
|
|
2
|
+
import { parseVerifiedAttemptToolCatalog } from "./index";
|
|
3
|
+
|
|
4
|
+
export type GenerateCodemodeDeclarationsOptions = {
|
|
5
|
+
/** Package whose augmentable CodemodeGeneratedTools interface owns the namespace. */
|
|
6
|
+
moduleSpecifier?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type NamespaceNode = {
|
|
10
|
+
children: Map<string, NamespaceNode>;
|
|
11
|
+
entry: AttemptToolCatalogEntry | null;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generate declaration merging for one exact, digest-pinned attempt catalog.
|
|
16
|
+
* Types improve authoring only; runtime catalog validation remains authoritative.
|
|
17
|
+
*/
|
|
18
|
+
export function generateCodemodeDeclarations(
|
|
19
|
+
catalog: AttemptToolCatalog,
|
|
20
|
+
options: GenerateCodemodeDeclarationsOptions = {},
|
|
21
|
+
): string {
|
|
22
|
+
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
|
+
: [],
|
|
244
|
+
);
|
|
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
|
+
}
|
|
313
|
+
|
|
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
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { CodemodeClient, type CodemodeCallOptions, type CodemodeToolFunction } from "./index";
|
|
3
|
+
|
|
4
|
+
export const CODEMODE_ENVIRONMENT = {
|
|
5
|
+
url: "OPENGENI_CODEMODE_URL",
|
|
6
|
+
tokenFile: "OPENGENI_CODEMODE_TOKEN_FILE",
|
|
7
|
+
} as const;
|
|
8
|
+
|
|
9
|
+
export type CodemodeClientProvider = () => CodemodeClient | Promise<CodemodeClient>;
|
|
10
|
+
|
|
11
|
+
/** A lazy catalog path. Every nested property remains callable at runtime. */
|
|
12
|
+
export type CodemodeDynamicTool = CodemodeToolFunction & {
|
|
13
|
+
readonly [segment: string]: CodemodeDynamicTool;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type CodemodeDynamicTools = {
|
|
17
|
+
readonly [segment: string]: CodemodeDynamicTool;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** Catalog-specific generated declarations augment this interface. */
|
|
21
|
+
export interface CodemodeGeneratedTools {}
|
|
22
|
+
|
|
23
|
+
export type CodemodeToolsNamespace = CodemodeDynamicTools & CodemodeGeneratedTools;
|
|
24
|
+
|
|
25
|
+
let cachedEnvironmentClient: { key: string; client: CodemodeClient } | null = null;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Build (or reuse) the persistent client for the exact sandbox attempt.
|
|
29
|
+
* The bearer file is reread for every HTTP request so worker renewal is live.
|
|
30
|
+
*/
|
|
31
|
+
export function environmentCodemodeClient(
|
|
32
|
+
environment: NodeJS.ProcessEnv = process.env,
|
|
33
|
+
): CodemodeClient {
|
|
34
|
+
const baseUrl = requiredEnvironment(environment, CODEMODE_ENVIRONMENT.url);
|
|
35
|
+
const tokenFile = requiredEnvironment(environment, CODEMODE_ENVIRONMENT.tokenFile);
|
|
36
|
+
const key = `${baseUrl}\u0000${tokenFile}`;
|
|
37
|
+
if (environment === process.env && cachedEnvironmentClient?.key === key) {
|
|
38
|
+
return cachedEnvironmentClient.client;
|
|
39
|
+
}
|
|
40
|
+
const client = new CodemodeClient({
|
|
41
|
+
baseUrl,
|
|
42
|
+
token: async () => await readBearerFile(tokenFile),
|
|
43
|
+
});
|
|
44
|
+
if (environment === process.env) cachedEnvironmentClient = { key, client };
|
|
45
|
+
return client;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Lazy namespace used by ordinary sandbox programs:
|
|
50
|
+
* `await tools.slack.search({ query: "..." })`.
|
|
51
|
+
*/
|
|
52
|
+
export function createCodemodeTools(
|
|
53
|
+
client: CodemodeClientProvider = () => environmentCodemodeClient(),
|
|
54
|
+
): CodemodeToolsNamespace {
|
|
55
|
+
const node = (path: readonly string[]): CodemodeDynamicTool =>
|
|
56
|
+
new Proxy(
|
|
57
|
+
(async (args: Record<string, unknown> = {}, options: CodemodeCallOptions = {}) =>
|
|
58
|
+
await (await client()).callPathValue(path, args, options)) as CodemodeDynamicTool,
|
|
59
|
+
{
|
|
60
|
+
get(_target, property) {
|
|
61
|
+
if (property === "then") return undefined;
|
|
62
|
+
if (property === Symbol.toStringTag) return "CodemodeTool";
|
|
63
|
+
if (typeof property !== "string") return undefined;
|
|
64
|
+
return node([...path, property]);
|
|
65
|
+
},
|
|
66
|
+
set() {
|
|
67
|
+
return false;
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
);
|
|
71
|
+
return new Proxy(Object.create(null) as CodemodeToolsNamespace, {
|
|
72
|
+
get(_target, property) {
|
|
73
|
+
if (property === "then") return undefined;
|
|
74
|
+
if (property === Symbol.toStringTag) return "CodemodeTools";
|
|
75
|
+
if (typeof property !== "string") return undefined;
|
|
76
|
+
return node([property]);
|
|
77
|
+
},
|
|
78
|
+
set() {
|
|
79
|
+
return false;
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const tools = createCodemodeTools();
|
|
85
|
+
|
|
86
|
+
async function readBearerFile(path: string): Promise<string> {
|
|
87
|
+
let token: string;
|
|
88
|
+
try {
|
|
89
|
+
token = (await readFile(path, "utf8")).trim();
|
|
90
|
+
} catch {
|
|
91
|
+
throw new Error(`${CODEMODE_ENVIRONMENT.tokenFile} is not readable`);
|
|
92
|
+
}
|
|
93
|
+
if (!token) throw new Error(`${CODEMODE_ENVIRONMENT.tokenFile} is empty`);
|
|
94
|
+
return token;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function requiredEnvironment(environment: NodeJS.ProcessEnv, name: string): string {
|
|
98
|
+
const value = environment[name]?.trim();
|
|
99
|
+
if (!value) throw new Error(`${name} is required`);
|
|
100
|
+
return value;
|
|
101
|
+
}
|