@pikku/inspector 0.12.6 → 0.12.8
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/CHANGELOG.md +28 -0
- package/dist/add/add-ai-agent.js +24 -7
- package/dist/add/add-channel.js +2 -2
- package/dist/add/add-cli.js +13 -10
- package/dist/add/add-file-with-factory.js +22 -5
- package/dist/add/add-functions.js +2 -0
- package/dist/add/add-http-route.js +1 -0
- package/dist/add/add-rpc-invocations.js +2 -2
- package/dist/add/add-workflow.d.ts +5 -0
- package/dist/add/add-workflow.js +19 -1
- package/dist/inspector.js +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/utils/filter-inspector-state.js +204 -5
- package/dist/utils/load-addon-functions-meta.js +47 -0
- package/dist/utils/post-process.js +63 -0
- package/dist/utils/schema-generator.js +124 -33
- package/dist/utils/serialize-inspector-state.d.ts +1 -0
- package/dist/utils/serialize-inspector-state.js +2 -0
- package/dist/visit.js +1 -1
- package/package.json +2 -2
- package/src/add/add-ai-agent.ts +25 -10
- package/src/add/add-channel.ts +2 -2
- package/src/add/add-cli.ts +17 -16
- package/src/add/add-file-with-factory.ts +26 -7
- package/src/add/add-functions.ts +2 -0
- package/src/add/add-http-route.ts +6 -1
- package/src/add/add-queue-worker.ts +5 -1
- package/src/add/add-rpc-invocations.ts +2 -2
- package/src/add/add-workflow.ts +21 -1
- package/src/inspector.ts +1 -0
- package/src/types.ts +1 -0
- package/src/utils/filter-inspector-state.ts +239 -8
- package/src/utils/load-addon-functions-meta.ts +59 -0
- package/src/utils/post-process.ts +74 -0
- package/src/utils/schema-generator.ts +191 -41
- package/src/utils/serialize-inspector-state.ts +3 -0
- package/src/visit.ts +2 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -160,6 +160,69 @@ export function aggregateRequiredServices(state) {
|
|
|
160
160
|
}
|
|
161
161
|
});
|
|
162
162
|
}
|
|
163
|
+
// 6. Implicit platform services required by wiring types
|
|
164
|
+
// Workflows need workflowService + workflowRunService + schedulerService + queueService.
|
|
165
|
+
// Check workflow definitions, graph meta, AND helper functions (workflowStart:*, etc.)
|
|
166
|
+
// that wrap workflow operations but don't destructure the services.
|
|
167
|
+
const hasWorkflows = Object.keys(state.workflows.graphMeta).length > 0 ||
|
|
168
|
+
Object.keys(state.workflows.meta).length > 0 ||
|
|
169
|
+
Object.keys(state.functions.meta).some((id) => id.startsWith('workflowStart:') ||
|
|
170
|
+
id.startsWith('workflowStatus:') ||
|
|
171
|
+
id.startsWith('workflow:'));
|
|
172
|
+
if (hasWorkflows) {
|
|
173
|
+
requiredServices.add('workflowService');
|
|
174
|
+
requiredServices.add('workflowRunService');
|
|
175
|
+
requiredServices.add('schedulerService');
|
|
176
|
+
requiredServices.add('queueService');
|
|
177
|
+
}
|
|
178
|
+
// 6b. Inject synthetic queue workers for workflow graph steps.
|
|
179
|
+
// Each workflow gets an orchestrator queue and per-step queues.
|
|
180
|
+
// Without these, the PikkuWorkflowService constructor can't find
|
|
181
|
+
// per-workflow queue entries and falls back to shared queue names.
|
|
182
|
+
for (const [, graph] of Object.entries(state.workflows.graphMeta)) {
|
|
183
|
+
if (!graph.nodes || !graph.name)
|
|
184
|
+
continue;
|
|
185
|
+
const toKebab = (s) => s
|
|
186
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
187
|
+
.replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
|
|
188
|
+
.toLowerCase();
|
|
189
|
+
// Orchestrator queue
|
|
190
|
+
const orchQueueName = `wf-orchestrator-${toKebab(graph.name)}`;
|
|
191
|
+
if (!state.queueWorkers.meta[orchQueueName]) {
|
|
192
|
+
state.queueWorkers.meta[orchQueueName] = {
|
|
193
|
+
name: orchQueueName,
|
|
194
|
+
pikkuFuncId: `pikkuWorkflowOrchestrator:${graph.name}`,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
// Per-step queues
|
|
198
|
+
for (const node of Object.values(graph.nodes)) {
|
|
199
|
+
if (!('rpcName' in node) || !node.rpcName)
|
|
200
|
+
continue;
|
|
201
|
+
const rpcName = node.rpcName;
|
|
202
|
+
const stepQueueName = `wf-step-${toKebab(rpcName)}`;
|
|
203
|
+
if (!state.queueWorkers.meta[stepQueueName]) {
|
|
204
|
+
state.queueWorkers.meta[stepQueueName] = {
|
|
205
|
+
name: stepQueueName,
|
|
206
|
+
pikkuFuncId: `pikkuWorkflowWorker:${rpcName}`,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// AI agents need aiStorage + aiRunState + agentRunService + aiAgentRunner
|
|
212
|
+
if (Object.keys(state.agents.agentsMeta).length > 0) {
|
|
213
|
+
requiredServices.add('aiStorage');
|
|
214
|
+
requiredServices.add('aiRunState');
|
|
215
|
+
requiredServices.add('agentRunService');
|
|
216
|
+
requiredServices.add('aiAgentRunner');
|
|
217
|
+
}
|
|
218
|
+
// Channels need eventHub for pub/sub
|
|
219
|
+
if (Object.keys(state.channels.meta).length > 0) {
|
|
220
|
+
requiredServices.add('eventHub');
|
|
221
|
+
}
|
|
222
|
+
// 7. Services that addons need from the parent project
|
|
223
|
+
for (const service of state.addonRequiredParentServices ?? []) {
|
|
224
|
+
requiredServices.add(service);
|
|
225
|
+
}
|
|
163
226
|
}
|
|
164
227
|
export function validateSecretOverrides(logger, state) {
|
|
165
228
|
const { wireAddonDeclarations } = state.rpc;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
import { dirname, join, resolve } from 'path';
|
|
3
3
|
import { createGenerator, RootlessError } from 'ts-json-schema-generator';
|
|
4
|
-
import { tsImport } from 'tsx/esm/api';
|
|
4
|
+
import { register, tsImport } from 'tsx/esm/api';
|
|
5
5
|
import * as z from 'zod';
|
|
6
6
|
import { zodToTs, createAuxiliaryTypeStore } from 'zod-to-ts';
|
|
7
7
|
import { ErrorCode } from '../error-codes.js';
|
|
@@ -44,6 +44,9 @@ function primitiveTypeToSchema(typeStr) {
|
|
|
44
44
|
if (normalized === 'null') {
|
|
45
45
|
return { type: 'null' };
|
|
46
46
|
}
|
|
47
|
+
if (normalized === 'any' || normalized === 'unknown') {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
47
50
|
return null;
|
|
48
51
|
}
|
|
49
52
|
// Cached state for schema program reuse across inspect() calls
|
|
@@ -89,7 +92,7 @@ function createProgramWithVirtualFile(tsconfig, virtualFilePath, virtualFileCont
|
|
|
89
92
|
cachedSchemaProgram = program;
|
|
90
93
|
return program;
|
|
91
94
|
}
|
|
92
|
-
function generateTSSchemas(logger, tsconfig, customTypesContent, typesMap, functionMeta, httpWiringsMeta, additionalTypes, additionalProperties = false,
|
|
95
|
+
function generateTSSchemas(logger, tsconfig, customTypesContent, typesMap, functionMeta, httpWiringsMeta, additionalTypes, additionalProperties = false, generatedZodSchemas) {
|
|
93
96
|
const schemasSet = new Set(typesMap.customTypes.keys());
|
|
94
97
|
for (const { inputs, outputs } of Object.values(functionMeta)) {
|
|
95
98
|
const types = [...(inputs || []), ...(outputs || [])];
|
|
@@ -123,6 +126,14 @@ function generateTSSchemas(logger, tsconfig, customTypesContent, typesMap, funct
|
|
|
123
126
|
schemasSet.add(type);
|
|
124
127
|
}
|
|
125
128
|
}
|
|
129
|
+
// Skip ts-json-schema-generator if all schemas are already covered by Zod/primitives.
|
|
130
|
+
// Use generatedZodSchemas (actually converted) rather than schemaLookup (all attempted)
|
|
131
|
+
// so that failed Zod conversions fall through to TS schema generation.
|
|
132
|
+
const uncoveredSchemas = [...schemasSet].filter((s) => !PRIMITIVE_TYPES.has(s) && !generatedZodSchemas?.[s]);
|
|
133
|
+
if (uncoveredSchemas.length === 0) {
|
|
134
|
+
return {};
|
|
135
|
+
}
|
|
136
|
+
logger.debug(`generateTSSchemas needed for ${uncoveredSchemas.length} types: ${uncoveredSchemas.slice(0, 3).join(', ')}${uncoveredSchemas.length > 3 ? '...' : ''}`);
|
|
126
137
|
const virtualFilePath = join(dirname(resolve(tsconfig)), '__pikku_virtual_types__.ts');
|
|
127
138
|
const program = createProgramWithVirtualFile(tsconfig, virtualFilePath, customTypesContent);
|
|
128
139
|
const generator = createGenerator({
|
|
@@ -142,7 +153,7 @@ function generateTSSchemas(logger, tsconfig, customTypesContent, typesMap, funct
|
|
|
142
153
|
if (PRIMITIVE_TYPES.has(schema)) {
|
|
143
154
|
return;
|
|
144
155
|
}
|
|
145
|
-
if (
|
|
156
|
+
if (generatedZodSchemas?.[schema]) {
|
|
146
157
|
return;
|
|
147
158
|
}
|
|
148
159
|
try {
|
|
@@ -165,53 +176,133 @@ function generateTSSchemas(logger, tsconfig, customTypesContent, typesMap, funct
|
|
|
165
176
|
});
|
|
166
177
|
return schemas;
|
|
167
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Import all source files in parallel using tsx's register() API.
|
|
181
|
+
*
|
|
182
|
+
* tsx's register() sets up the TypeScript loader once, then all subsequent
|
|
183
|
+
* import() calls reuse that loader. This is dramatically faster than calling
|
|
184
|
+
* tsImport() per-file because tsImport() sets up and tears down a fresh
|
|
185
|
+
* compilation context for each call (~170ms each).
|
|
186
|
+
*
|
|
187
|
+
* With register() + parallel import():
|
|
188
|
+
* - 71 files: ~350ms total
|
|
189
|
+
* - vs tsImport loop: ~12,000ms (71 * 170ms)
|
|
190
|
+
*
|
|
191
|
+
* Falls back to serial tsImport() per-file if register() is unavailable.
|
|
192
|
+
*/
|
|
193
|
+
async function batchImportWithRegister(logger, sourceFiles) {
|
|
194
|
+
if (sourceFiles.length === 0)
|
|
195
|
+
return new Map();
|
|
196
|
+
let unregister;
|
|
197
|
+
try {
|
|
198
|
+
unregister = register();
|
|
199
|
+
const modules = new Map();
|
|
200
|
+
const results = await Promise.allSettled(sourceFiles.map(async (srcPath) => {
|
|
201
|
+
const mod = await import(srcPath);
|
|
202
|
+
modules.set(srcPath, mod);
|
|
203
|
+
}));
|
|
204
|
+
const failures = results.filter((r) => r.status === 'rejected');
|
|
205
|
+
if (failures.length > 0) {
|
|
206
|
+
logger.debug(`${failures.length}/${sourceFiles.length} files failed to import via register()`);
|
|
207
|
+
}
|
|
208
|
+
return modules;
|
|
209
|
+
}
|
|
210
|
+
catch (e) {
|
|
211
|
+
logger.debug(`tsx register() batch import failed: ${e.message}`);
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
unregister?.();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function processZodSchema(schemaName, zodSchema, schemas, typesMap, auxiliaryTypeStore, printer, fakeSourceFile, logger) {
|
|
219
|
+
const schema = z.toJSONSchema(zodSchema, {
|
|
220
|
+
unrepresentable: 'any',
|
|
221
|
+
override: ({ zodSchema, jsonSchema }) => {
|
|
222
|
+
if (zodSchema._zod?.def?.type === 'date') {
|
|
223
|
+
;
|
|
224
|
+
jsonSchema.type = 'string';
|
|
225
|
+
jsonSchema.format = 'date-time';
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
if (schema.required && schema.properties) {
|
|
230
|
+
schema.required = schema.required.filter((fieldName) => {
|
|
231
|
+
const prop = schema.properties[fieldName];
|
|
232
|
+
return prop && prop.default === undefined;
|
|
233
|
+
});
|
|
234
|
+
if (schema.required.length === 0) {
|
|
235
|
+
delete schema.required;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const { node: tsType } = zodToTs(zodSchema, { auxiliaryTypeStore });
|
|
239
|
+
const typeText = printer.printNode(ts.EmitHint.Unspecified, tsType, fakeSourceFile);
|
|
240
|
+
typesMap.addCustomType(schemaName, typeText, []);
|
|
241
|
+
schemas[schemaName] = schema;
|
|
242
|
+
logger.debug(`• Generated schema from Zod: ${schemaName}`);
|
|
243
|
+
}
|
|
168
244
|
async function generateZodSchemas(logger, schemaLookup, typesMap) {
|
|
169
245
|
const schemas = {};
|
|
170
246
|
const auxiliaryTypeStore = createAuxiliaryTypeStore();
|
|
171
247
|
const printer = ts.createPrinter();
|
|
172
248
|
const fakeSourceFile = ts.createSourceFile('zod-types.ts', '', ts.ScriptTarget.ESNext, false, ts.ScriptKind.TS);
|
|
249
|
+
// Validate all schemas are zod (or unspecified vendor)
|
|
173
250
|
for (const [schemaName, ref] of schemaLookup.entries()) {
|
|
174
251
|
if (ref.vendor && ref.vendor !== 'zod') {
|
|
175
252
|
throw new Error(`Schema '${schemaName}' uses ${ref.vendor} which is not yet supported for JSON Schema generation. ` +
|
|
176
253
|
`Currently only Zod schemas can be converted to JSON Schema. ` +
|
|
177
254
|
`Please use Zod or contribute support for ${ref.vendor}.`);
|
|
178
255
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
256
|
+
}
|
|
257
|
+
// Collect unique source files and batch-import them in parallel
|
|
258
|
+
const uniqueSourceFiles = [
|
|
259
|
+
...new Set([...schemaLookup.values()].map((ref) => ref.sourceFile)),
|
|
260
|
+
];
|
|
261
|
+
console.log(`[TIMING] Zod schemas: ${schemaLookup.size} schemas from ${uniqueSourceFiles.length} files`);
|
|
262
|
+
const importStart = performance.now();
|
|
263
|
+
const importedModules = await batchImportWithRegister(logger, uniqueSourceFiles);
|
|
264
|
+
console.log(`[TIMING] Batch import: ${(performance.now() - importStart).toFixed(0)}ms`);
|
|
265
|
+
const processStart = performance.now();
|
|
266
|
+
// Track schemas that need per-file tsImport fallback
|
|
267
|
+
const fallbackSchemas = [];
|
|
268
|
+
for (const [schemaName, ref] of schemaLookup.entries()) {
|
|
269
|
+
const mod = importedModules?.get(ref.sourceFile);
|
|
270
|
+
if (mod) {
|
|
271
|
+
const zodSchema = mod[ref.variableName];
|
|
182
272
|
if (!zodSchema) {
|
|
183
|
-
logger.warn(`Could not find exported schema '${ref.variableName}' in ${ref.sourceFile} for ${schemaName}. Available exports: ${Object.keys(
|
|
273
|
+
logger.warn(`Could not find exported schema '${ref.variableName}' in ${ref.sourceFile} for ${schemaName}. Available exports: ${Object.keys(mod).join(', ')}`);
|
|
184
274
|
continue;
|
|
185
275
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
jsonSchema.type = 'string';
|
|
192
|
-
jsonSchema.format = 'date-time';
|
|
193
|
-
}
|
|
194
|
-
},
|
|
195
|
-
});
|
|
196
|
-
if (schema.required && schema.properties) {
|
|
197
|
-
schema.required = schema.required.filter((fieldName) => {
|
|
198
|
-
const prop = schema.properties[fieldName];
|
|
199
|
-
return prop && prop.default === undefined;
|
|
200
|
-
});
|
|
201
|
-
if (schema.required.length === 0) {
|
|
202
|
-
delete schema.required;
|
|
203
|
-
}
|
|
276
|
+
try {
|
|
277
|
+
processZodSchema(schemaName, zodSchema, schemas, typesMap, auxiliaryTypeStore, printer, fakeSourceFile, logger);
|
|
278
|
+
}
|
|
279
|
+
catch (e) {
|
|
280
|
+
logger.warn(`Could not convert Zod schema '${schemaName}': ${e instanceof Error ? e.message : e}`);
|
|
204
281
|
}
|
|
205
|
-
schemas[schemaName] = schema;
|
|
206
|
-
const { node: tsType } = zodToTs(zodSchema, { auxiliaryTypeStore });
|
|
207
|
-
const typeText = printer.printNode(ts.EmitHint.Unspecified, tsType, fakeSourceFile);
|
|
208
|
-
typesMap.addCustomType(schemaName, typeText, []);
|
|
209
|
-
logger.debug(`• Generated schema from Zod: ${schemaName}`);
|
|
210
282
|
}
|
|
211
|
-
|
|
212
|
-
|
|
283
|
+
else {
|
|
284
|
+
fallbackSchemas.push([schemaName, ref]);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// Fallback: use tsImport for any schemas that batch import couldn't handle
|
|
288
|
+
if (fallbackSchemas.length > 0) {
|
|
289
|
+
logger.debug(`Falling back to tsImport for ${fallbackSchemas.length} schema(s)`);
|
|
290
|
+
for (const [schemaName, ref] of fallbackSchemas) {
|
|
291
|
+
try {
|
|
292
|
+
const module = await tsImport(ref.sourceFile, import.meta.url);
|
|
293
|
+
const zodSchema = module[ref.variableName];
|
|
294
|
+
if (!zodSchema) {
|
|
295
|
+
logger.warn(`Could not find exported schema '${ref.variableName}' in ${ref.sourceFile} for ${schemaName}. Available exports: ${Object.keys(module).join(', ')}`);
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
processZodSchema(schemaName, zodSchema, schemas, typesMap, auxiliaryTypeStore, printer, fakeSourceFile, logger);
|
|
299
|
+
}
|
|
300
|
+
catch (e) {
|
|
301
|
+
logger.warn(`Could not convert Zod schema '${schemaName}': ${e instanceof Error ? e.message : e}`);
|
|
302
|
+
}
|
|
213
303
|
}
|
|
214
304
|
}
|
|
305
|
+
console.log(`[TIMING] Process schemas: ${(performance.now() - processStart).toFixed(0)}ms (${Object.keys(schemas).length} generated)`);
|
|
215
306
|
return schemas;
|
|
216
307
|
}
|
|
217
308
|
export async function generateAllSchemas(logger, config, state) {
|
|
@@ -222,7 +313,7 @@ export async function generateAllSchemas(logger, config, state) {
|
|
|
222
313
|
logger.debug('Reusing cached TS schemas (types unchanged)');
|
|
223
314
|
return { ...cachedTSSchemas, ...zodSchemas };
|
|
224
315
|
}
|
|
225
|
-
const tsSchemas = generateTSSchemas(logger, config.tsconfig, customTypesContent, state.functions.typesMap, state.functions.meta, state.http.meta, config.schemasFromTypes, config.schema?.additionalProperties,
|
|
316
|
+
const tsSchemas = generateTSSchemas(logger, config.tsconfig, customTypesContent, state.functions.typesMap, state.functions.meta, state.http.meta, config.schemasFromTypes, config.schema?.additionalProperties, zodSchemas);
|
|
226
317
|
cachedCustomTypesContent = customTypesContent;
|
|
227
318
|
cachedTSSchemas = tsSchemas;
|
|
228
319
|
return { ...tsSchemas, ...zodSchemas };
|
|
@@ -23,6 +23,7 @@ export function serializeInspectorState(state) {
|
|
|
23
23
|
singletonServicesFactories: Array.from(state.singletonServicesFactories.entries()),
|
|
24
24
|
wireServicesFactories: Array.from(state.wireServicesFactories.entries()),
|
|
25
25
|
wireServicesMeta: Array.from(state.wireServicesMeta.entries()),
|
|
26
|
+
addonRequiredParentServices: state.addonRequiredParentServices,
|
|
26
27
|
configFactories: Array.from(state.configFactories.entries()),
|
|
27
28
|
filesAndMethods: state.filesAndMethods,
|
|
28
29
|
filesAndMethodsErrors: Array.from(state.filesAndMethodsErrors.entries()).map(([key, mapValue]) => [key, Array.from(mapValue.entries())]),
|
|
@@ -167,6 +168,7 @@ export function deserializeInspectorState(data) {
|
|
|
167
168
|
singletonServicesFactories: new Map(data.singletonServicesFactories),
|
|
168
169
|
wireServicesFactories: new Map(data.wireServicesFactories),
|
|
169
170
|
wireServicesMeta: new Map(data.wireServicesMeta),
|
|
171
|
+
addonRequiredParentServices: data.addonRequiredParentServices || [],
|
|
170
172
|
configFactories: new Map(data.configFactories),
|
|
171
173
|
filesAndMethods: data.filesAndMethods,
|
|
172
174
|
filesAndMethodsErrors: new Map(data.filesAndMethodsErrors.map(([key, entries]) => [
|
package/dist/visit.js
CHANGED
|
@@ -28,7 +28,7 @@ export const visitSetup = (logger, checker, node, state, options) => {
|
|
|
28
28
|
addFileExtendsCoreType(node, checker, state.wireServicesTypeImportMap, 'CoreServices', state);
|
|
29
29
|
addFileExtendsCoreType(node, checker, state.userSessionTypeImportMap, 'CoreUserSession', state);
|
|
30
30
|
addFileExtendsCoreType(node, checker, state.configTypeImportMap, 'CoreConfig', state);
|
|
31
|
-
addFileWithFactory(node, checker, state.singletonServicesFactories, 'CreateSingletonServices');
|
|
31
|
+
addFileWithFactory(node, checker, state.singletonServicesFactories, 'CreateSingletonServices', state);
|
|
32
32
|
addFileWithFactory(node, checker, state.wireServicesFactories, 'CreateWireServices', state);
|
|
33
33
|
addFileWithFactory(node, checker, state.configFactories, 'CreateConfig');
|
|
34
34
|
addRPCInvocations(node, state, logger);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/inspector",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.8",
|
|
4
4
|
"author": "yasser.fadl@gmail.com",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"type": "module",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@openapi-contrib/json-schema-to-openapi-schema": "^4.3.1",
|
|
38
|
-
"@pikku/core": "^0.12.
|
|
38
|
+
"@pikku/core": "^0.12.15",
|
|
39
39
|
"path-to-regexp": "^8.3.0",
|
|
40
40
|
"ts-json-schema-generator": "^2.5.0",
|
|
41
41
|
"tsx": "^4.21.0",
|
package/src/add/add-ai-agent.ts
CHANGED
|
@@ -63,7 +63,7 @@ function resolveToolReferences(
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
if (calleeName === '
|
|
66
|
+
if (calleeName === 'ref') {
|
|
67
67
|
const [firstArg] = element.arguments
|
|
68
68
|
if (firstArg && ts.isStringLiteral(firstArg)) {
|
|
69
69
|
resolved.push(firstArg.text)
|
|
@@ -258,19 +258,17 @@ export const addAIAgent: AddWiring = (
|
|
|
258
258
|
|
|
259
259
|
const modelValue = getPropertyValue(obj, 'model') as string | null
|
|
260
260
|
|
|
261
|
-
const
|
|
261
|
+
const roleValue = getPropertyValue(obj, 'role') as string | null
|
|
262
|
+
const personalityValue = getPropertyValue(obj, 'personality') as
|
|
262
263
|
| string
|
|
263
|
-
| string[]
|
|
264
264
|
| null
|
|
265
|
+
const goalValue = getPropertyValue(obj, 'goal') as string | null
|
|
265
266
|
|
|
266
267
|
const maxStepsValue = getPropertyValue(obj, 'maxSteps') as number | null
|
|
267
268
|
const temperatureValue = getPropertyValue(obj, 'temperature') as
|
|
268
269
|
| number
|
|
269
270
|
| null
|
|
270
271
|
const toolChoiceValue = getPropertyValue(obj, 'toolChoice') as string | null
|
|
271
|
-
const dynamicWorkflowsValue = getPropertyValue(obj, 'dynamicWorkflows') as
|
|
272
|
-
| string
|
|
273
|
-
| null
|
|
274
272
|
const toolsValue = resolveToolReferences(
|
|
275
273
|
obj,
|
|
276
274
|
checker,
|
|
@@ -447,10 +445,14 @@ export const addAIAgent: AddWiring = (
|
|
|
447
445
|
state.agents.agentsMeta[agentKey] = {
|
|
448
446
|
name: nameValue,
|
|
449
447
|
description,
|
|
450
|
-
|
|
448
|
+
role: roleValue || undefined,
|
|
449
|
+
personality: personalityValue || undefined,
|
|
450
|
+
goal: goalValue || '',
|
|
451
451
|
model: modelValue || '',
|
|
452
452
|
summary,
|
|
453
453
|
errors,
|
|
454
|
+
sourceFile: node.getSourceFile().fileName,
|
|
455
|
+
exportedName: exportedName || undefined,
|
|
454
456
|
...(maxStepsValue !== null && { maxSteps: maxStepsValue }),
|
|
455
457
|
...(temperatureValue !== null && { temperature: temperatureValue }),
|
|
456
458
|
...(toolChoiceValue !== null && {
|
|
@@ -458,9 +460,6 @@ export const addAIAgent: AddWiring = (
|
|
|
458
460
|
}),
|
|
459
461
|
...(toolsValue !== null && { tools: toolsValue }),
|
|
460
462
|
...(agentsValue !== null && { agents: agentsValue }),
|
|
461
|
-
...(dynamicWorkflowsValue !== null && {
|
|
462
|
-
dynamicWorkflows: dynamicWorkflowsValue as 'read' | 'always' | 'ask',
|
|
463
|
-
}),
|
|
464
463
|
tags,
|
|
465
464
|
inputSchema,
|
|
466
465
|
outputSchema,
|
|
@@ -470,5 +469,21 @@ export const addAIAgent: AddWiring = (
|
|
|
470
469
|
aiMiddleware,
|
|
471
470
|
permissions,
|
|
472
471
|
}
|
|
472
|
+
|
|
473
|
+
// AI agent functions require platform services that aren't visible
|
|
474
|
+
// through parameter destructuring
|
|
475
|
+
const funcMeta = state.functions.meta[agentKey]
|
|
476
|
+
if (funcMeta?.services) {
|
|
477
|
+
for (const svc of [
|
|
478
|
+
'aiStorage',
|
|
479
|
+
'aiRunState',
|
|
480
|
+
'agentRunService',
|
|
481
|
+
'aiAgentRunner',
|
|
482
|
+
]) {
|
|
483
|
+
if (!funcMeta.services.services.includes(svc)) {
|
|
484
|
+
funcMeta.services.services.push(svc)
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
473
488
|
}
|
|
474
489
|
}
|
package/src/add/add-channel.ts
CHANGED
|
@@ -94,8 +94,8 @@ function getHandlerNameFromExpression(
|
|
|
94
94
|
|
|
95
95
|
// Handle call expressions
|
|
96
96
|
if (ts.isCallExpression(expr)) {
|
|
97
|
-
// Handle
|
|
98
|
-
if (ts.isIdentifier(expr.expression) && expr.expression.text === '
|
|
97
|
+
// Handle ref('name') calls
|
|
98
|
+
if (ts.isIdentifier(expr.expression) && expr.expression.text === 'ref') {
|
|
99
99
|
const [firstArg] = expr.arguments
|
|
100
100
|
if (firstArg && ts.isStringLiteral(firstArg)) {
|
|
101
101
|
return firstArg.text
|
package/src/add/add-cli.ts
CHANGED
|
@@ -349,29 +349,30 @@ function processCommand(
|
|
|
349
349
|
if (
|
|
350
350
|
ts.isCallExpression(prop.initializer) &&
|
|
351
351
|
ts.isIdentifier(prop.initializer.expression) &&
|
|
352
|
-
prop.initializer.expression.text === '
|
|
352
|
+
prop.initializer.expression.text === 'ref'
|
|
353
353
|
) {
|
|
354
354
|
const [firstArg] = prop.initializer.arguments
|
|
355
355
|
if (!firstArg || !ts.isStringLiteral(firstArg)) {
|
|
356
|
-
throw new Error(
|
|
357
|
-
`addon() call requires a string literal argument in the form "namespace:funcName"`
|
|
358
|
-
)
|
|
356
|
+
throw new Error(`ref() call requires a string literal argument`)
|
|
359
357
|
}
|
|
360
358
|
pikkuFuncId = firstArg.text
|
|
361
|
-
const addonNamespace = pikkuFuncId.
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
)
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
)
|
|
359
|
+
const addonNamespace = pikkuFuncId.includes(':')
|
|
360
|
+
? pikkuFuncId.split(':')[0]
|
|
361
|
+
: null
|
|
362
|
+
if (addonNamespace) {
|
|
363
|
+
if (!inspectorState.rpc.wireAddonDeclarations.has(addonNamespace)) {
|
|
364
|
+
throw new Error(
|
|
365
|
+
`Unknown addon namespace "${addonNamespace}" in "${pikkuFuncId}": no matching wireAddonDeclarations entry found`
|
|
366
|
+
)
|
|
367
|
+
}
|
|
371
368
|
}
|
|
372
369
|
meta.pikkuFuncId = pikkuFuncId
|
|
373
|
-
|
|
374
|
-
|
|
370
|
+
if (addonNamespace) {
|
|
371
|
+
meta.packageName =
|
|
372
|
+
inspectorState.rpc.wireAddonDeclarations.get(
|
|
373
|
+
addonNamespace
|
|
374
|
+
)!.package
|
|
375
|
+
}
|
|
375
376
|
} else {
|
|
376
377
|
pikkuFuncId = extractFunctionName(
|
|
377
378
|
prop.initializer,
|
|
@@ -57,12 +57,7 @@ export const addFileWithFactory = (
|
|
|
57
57
|
})
|
|
58
58
|
methods.set(fileName, variables)
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
expectedTypeName === 'CreateWireServices' &&
|
|
63
|
-
state &&
|
|
64
|
-
callExpression.arguments.length > 0
|
|
65
|
-
) {
|
|
60
|
+
if (state && callExpression.arguments.length > 0) {
|
|
66
61
|
const firstArg = callExpression.arguments[0]
|
|
67
62
|
let functionNode:
|
|
68
63
|
| ts.ArrowFunction
|
|
@@ -75,10 +70,34 @@ export const addFileWithFactory = (
|
|
|
75
70
|
functionNode = firstArg
|
|
76
71
|
}
|
|
77
72
|
|
|
78
|
-
|
|
73
|
+
// Extract singleton services for CreateWireServices factories
|
|
74
|
+
if (expectedTypeName === 'CreateWireServices' && functionNode) {
|
|
79
75
|
const servicesMeta = extractServicesFromFunction(functionNode)
|
|
80
76
|
state.wireServicesMeta.set(variableName, servicesMeta.services)
|
|
81
77
|
}
|
|
78
|
+
|
|
79
|
+
// Extract existing services an addon needs from the parent
|
|
80
|
+
// (second parameter of pikkuAddonServices callback)
|
|
81
|
+
if (
|
|
82
|
+
wrapperFunctionName === 'pikkuAddonServices' &&
|
|
83
|
+
functionNode &&
|
|
84
|
+
functionNode.parameters.length >= 2
|
|
85
|
+
) {
|
|
86
|
+
const secondParam = functionNode.parameters[1]
|
|
87
|
+
if (secondParam && ts.isObjectBindingPattern(secondParam.name)) {
|
|
88
|
+
for (const elem of secondParam.name.elements) {
|
|
89
|
+
const name =
|
|
90
|
+
elem.propertyName && ts.isIdentifier(elem.propertyName)
|
|
91
|
+
? elem.propertyName.text
|
|
92
|
+
: ts.isIdentifier(elem.name)
|
|
93
|
+
? elem.name.text
|
|
94
|
+
: undefined
|
|
95
|
+
if (name) {
|
|
96
|
+
state.addonRequiredParentServices.push(name)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
82
101
|
}
|
|
83
102
|
|
|
84
103
|
return // Early return since we found a match
|
package/src/add/add-functions.ts
CHANGED
|
@@ -794,6 +794,8 @@ export const addFunctions: AddWiring = (
|
|
|
794
794
|
middleware,
|
|
795
795
|
permissions,
|
|
796
796
|
isDirectFunction,
|
|
797
|
+
sourceFile: node.getSourceFile().fileName,
|
|
798
|
+
exportedName: exportedName || undefined,
|
|
797
799
|
}
|
|
798
800
|
|
|
799
801
|
// Populate node metadata if node config is present
|
|
@@ -205,7 +205,11 @@ export function registerHTTPRoute({
|
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
const packageName = ts.isIdentifier(funcInitializer)
|
|
208
|
-
? resolveAddonName(
|
|
208
|
+
? resolveAddonName(
|
|
209
|
+
funcInitializer,
|
|
210
|
+
checker,
|
|
211
|
+
state.rpc.wireAddonDeclarations
|
|
212
|
+
)
|
|
209
213
|
: null
|
|
210
214
|
|
|
211
215
|
ensureFunctionMetadata(
|
|
@@ -327,6 +331,7 @@ export function registerHTTPRoute({
|
|
|
327
331
|
pikkuFuncId: funcName,
|
|
328
332
|
...(packageName && { packageName }),
|
|
329
333
|
route: fullRoute,
|
|
334
|
+
sourceFile: sourceFile.fileName,
|
|
330
335
|
method: method as HTTPMethod,
|
|
331
336
|
params: params.length > 0 ? params : undefined,
|
|
332
337
|
query: query.length > 0 ? query : undefined,
|
|
@@ -67,7 +67,11 @@ export const addQueueWorker: AddWiring = (logger, node, checker, state) => {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
const packageName = ts.isIdentifier(funcInitializer)
|
|
70
|
-
? resolveAddonName(
|
|
70
|
+
? resolveAddonName(
|
|
71
|
+
funcInitializer,
|
|
72
|
+
checker,
|
|
73
|
+
state.rpc.wireAddonDeclarations
|
|
74
|
+
)
|
|
71
75
|
: null
|
|
72
76
|
|
|
73
77
|
if (!name) {
|
|
@@ -27,8 +27,8 @@ export function addRPCInvocations(
|
|
|
27
27
|
if (ts.isCallExpression(node)) {
|
|
28
28
|
const { expression, arguments: args } = node
|
|
29
29
|
|
|
30
|
-
// Check for
|
|
31
|
-
if (ts.isIdentifier(expression) && expression.text === '
|
|
30
|
+
// Check for ref('name') calls
|
|
31
|
+
if (ts.isIdentifier(expression) && expression.text === 'ref') {
|
|
32
32
|
const [firstArg] = args
|
|
33
33
|
if (firstArg && ts.isStringLiteral(firstArg)) {
|
|
34
34
|
const functionRef = firstArg.text
|
package/src/add/add-workflow.ts
CHANGED
|
@@ -46,7 +46,7 @@ function hasInlineSteps(steps: WorkflowStepMeta[]): boolean {
|
|
|
46
46
|
/**
|
|
47
47
|
* Recursively collect all RPC names from workflow steps
|
|
48
48
|
*/
|
|
49
|
-
function collectInvokedRPCs(
|
|
49
|
+
export function collectInvokedRPCs(
|
|
50
50
|
steps: WorkflowStepMeta[],
|
|
51
51
|
rpcs: Set<string>
|
|
52
52
|
): void {
|
|
@@ -210,6 +210,7 @@ export const addWorkflow: AddWiring = (logger, node, checker, state) => {
|
|
|
210
210
|
let description: string | undefined
|
|
211
211
|
let errors: string[] | undefined
|
|
212
212
|
let inline: boolean | undefined
|
|
213
|
+
let expose: boolean | undefined
|
|
213
214
|
|
|
214
215
|
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
215
216
|
const metadata = getCommonWireMetaData(
|
|
@@ -228,6 +229,8 @@ export const addWorkflow: AddWiring = (logger, node, checker, state) => {
|
|
|
228
229
|
if (inlineProp === true) {
|
|
229
230
|
inline = true
|
|
230
231
|
}
|
|
232
|
+
|
|
233
|
+
expose = getPropertyValue(firstArg, 'expose') as boolean | undefined
|
|
231
234
|
}
|
|
232
235
|
|
|
233
236
|
// Validate that we got a valid function
|
|
@@ -334,5 +337,22 @@ export const addWorkflow: AddWiring = (logger, node, checker, state) => {
|
|
|
334
337
|
errors,
|
|
335
338
|
tags,
|
|
336
339
|
inline,
|
|
340
|
+
expose,
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Workflow functions require platform services that aren't visible
|
|
344
|
+
// through parameter destructuring (they're accessed via workflow.do/sleep)
|
|
345
|
+
const funcMeta = state.functions.meta[pikkuFuncId]
|
|
346
|
+
if (funcMeta?.services) {
|
|
347
|
+
for (const svc of [
|
|
348
|
+
'workflowService',
|
|
349
|
+
'workflowRunService',
|
|
350
|
+
'schedulerService',
|
|
351
|
+
'queueService',
|
|
352
|
+
]) {
|
|
353
|
+
if (!funcMeta.services.services.includes(svc)) {
|
|
354
|
+
funcMeta.services.services.push(svc)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
337
357
|
}
|
|
338
358
|
}
|
package/src/inspector.ts
CHANGED
|
@@ -59,6 +59,7 @@ export function getInitialInspectorState(rootDir: string): InspectorState {
|
|
|
59
59
|
singletonServicesFactories: new Map(),
|
|
60
60
|
wireServicesFactories: new Map(),
|
|
61
61
|
wireServicesMeta: new Map(),
|
|
62
|
+
addonRequiredParentServices: [],
|
|
62
63
|
configFactories: new Map(),
|
|
63
64
|
filesAndMethods: {},
|
|
64
65
|
filesAndMethodsErrors: new Map(),
|
package/src/types.ts
CHANGED
|
@@ -306,6 +306,7 @@ export interface InspectorState {
|
|
|
306
306
|
singletonServicesFactories: PathToNameAndType
|
|
307
307
|
wireServicesFactories: PathToNameAndType
|
|
308
308
|
wireServicesMeta: Map<string, string[]> // variable name -> singleton services consumed
|
|
309
|
+
addonRequiredParentServices: string[] // services an addon needs from the parent (extracted from pikkuAddonServices 2nd param)
|
|
309
310
|
configFactories: PathToNameAndType
|
|
310
311
|
filesAndMethods: InspectorFilesAndMethods
|
|
311
312
|
filesAndMethodsErrors: Map<string, PathToNameAndType>
|