@pikku/core 0.12.14 → 0.12.16
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 +36 -0
- package/dist/errors/errors.d.ts +4 -0
- package/dist/errors/errors.js +12 -0
- package/dist/function/function-runner.js +2 -1
- package/dist/function/functions.types.js +1 -0
- package/dist/function/index.d.ts +2 -1
- package/dist/function/index.js +1 -0
- package/dist/handle-error.d.ts +2 -2
- package/dist/handle-error.js +8 -8
- package/dist/index.d.ts +2 -3
- package/dist/index.js +1 -2
- package/dist/middleware/index.d.ts +3 -0
- package/dist/middleware/index.js +3 -0
- package/dist/middleware/remote-auth.js +5 -2
- package/dist/middleware/telemetry.d.ts +47 -0
- package/dist/middleware/telemetry.js +106 -0
- package/dist/middleware-runner.js +31 -4
- package/dist/permissions.d.ts +13 -1
- package/dist/permissions.js +51 -0
- package/dist/pikku-state.d.ts +0 -1
- package/dist/pikku-state.js +1 -4
- package/dist/remote.d.ts +10 -0
- package/dist/remote.js +32 -0
- package/dist/schema.js +2 -0
- package/dist/services/deployment-service.d.ts +12 -1
- package/dist/services/in-memory-queue-service.d.ts +7 -0
- package/dist/services/in-memory-queue-service.js +28 -0
- package/dist/services/index.d.ts +4 -1
- package/dist/services/index.js +2 -1
- package/dist/services/logger-console.d.ts +26 -40
- package/dist/services/logger-console.js +102 -46
- package/dist/services/logger.d.ts +5 -0
- package/dist/services/meta-service.d.ts +175 -0
- package/dist/services/meta-service.js +310 -0
- package/dist/services/workflow-service.d.ts +6 -1
- package/dist/testing/service-tests.js +0 -8
- package/dist/types/core.types.d.ts +21 -0
- package/dist/types/core.types.js +7 -1
- package/dist/types/state.types.d.ts +2 -2
- package/dist/wirings/ai-agent/ai-agent-prepare.js +42 -21
- package/dist/wirings/ai-agent/ai-agent-registry.js +2 -2
- package/dist/wirings/ai-agent/ai-agent-runner.js +18 -1
- package/dist/wirings/ai-agent/ai-agent-stream.js +1 -0
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +5 -3
- package/dist/wirings/channel/channel-runner.js +3 -3
- package/dist/wirings/cli/cli-runner.js +5 -3
- package/dist/wirings/cli/command-parser.js +7 -3
- package/dist/wirings/http/http-runner.d.ts +1 -1
- package/dist/wirings/http/http-runner.js +23 -11
- package/dist/wirings/http/http.types.d.ts +3 -0
- package/dist/wirings/http/pikku-fetch-http-response.d.ts +1 -0
- package/dist/wirings/http/pikku-fetch-http-response.js +3 -0
- package/dist/wirings/mcp/mcp-runner.js +5 -3
- package/dist/wirings/mcp/mcp.types.d.ts +1 -1
- package/dist/wirings/queue/queue-runner.d.ts +3 -1
- package/dist/wirings/queue/queue-runner.js +7 -3
- package/dist/wirings/rpc/rpc-runner.js +56 -90
- package/dist/wirings/scheduler/scheduler-runner.d.ts +3 -1
- package/dist/wirings/scheduler/scheduler-runner.js +9 -5
- package/dist/wirings/trigger/trigger-runner.js +4 -2
- package/dist/wirings/workflow/dsl/workflow-runner.d.ts +1 -1
- package/dist/wirings/workflow/dsl/workflow-runner.js +6 -9
- package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -1
- package/dist/wirings/workflow/graph/graph-runner.js +18 -4
- package/dist/wirings/workflow/index.d.ts +4 -2
- package/dist/wirings/workflow/index.js +4 -2
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +16 -4
- package/dist/wirings/workflow/pikku-workflow-service.js +216 -24
- package/dist/wirings/workflow/workflow-queue-workers.d.ts +40 -0
- package/dist/wirings/workflow/workflow-queue-workers.js +41 -0
- package/dist/wirings/workflow/workflow.types.d.ts +17 -1
- package/package.json +4 -1
- package/src/errors/errors.ts +12 -0
- package/src/function/function-runner.ts +5 -4
- package/src/function/functions.types.ts +1 -0
- package/src/function/index.ts +10 -0
- package/src/handle-error.test.ts +2 -2
- package/src/handle-error.ts +8 -8
- package/src/index.ts +2 -2
- package/src/middleware/index.ts +3 -0
- package/src/middleware/remote-auth.test.ts +4 -4
- package/src/middleware/remote-auth.ts +7 -2
- package/src/middleware/telemetry.ts +110 -0
- package/src/middleware-runner.test.ts +149 -1
- package/src/middleware-runner.ts +48 -4
- package/src/permissions.ts +70 -0
- package/src/pikku-state.test.ts +0 -26
- package/src/pikku-state.ts +1 -5
- package/src/remote.ts +46 -0
- package/src/schema.ts +1 -0
- package/src/services/deployment-service.ts +17 -1
- package/src/services/in-memory-queue-service.ts +46 -0
- package/src/services/index.ts +21 -1
- package/src/services/logger-console.ts +127 -47
- package/src/services/logger.ts +6 -0
- package/src/services/meta-service.ts +523 -0
- package/src/services/workflow-service.ts +8 -0
- package/src/testing/service-tests.ts +0 -10
- package/src/types/core.types.ts +36 -1
- package/src/types/state.types.ts +2 -2
- package/src/wirings/ai-agent/ai-agent-prepare.ts +51 -40
- package/src/wirings/ai-agent/ai-agent-registry.ts +3 -3
- package/src/wirings/ai-agent/ai-agent-runner.ts +16 -1
- package/src/wirings/ai-agent/ai-agent-stream.ts +8 -0
- package/src/wirings/ai-agent/ai-agent.types.ts +5 -3
- package/src/wirings/channel/channel-runner.ts +4 -5
- package/src/wirings/channel/local/local-channel-runner.test.ts +5 -1
- package/src/wirings/cli/cli-runner.test.ts +8 -7
- package/src/wirings/cli/cli-runner.ts +6 -4
- package/src/wirings/cli/command-parser.ts +8 -3
- package/src/wirings/http/http-runner.ts +27 -11
- package/src/wirings/http/http.types.ts +3 -0
- package/src/wirings/http/pikku-fetch-http-response.ts +4 -0
- package/src/wirings/mcp/mcp-runner.ts +7 -9
- package/src/wirings/mcp/mcp.types.ts +1 -1
- package/src/wirings/queue/queue-runner.test.ts +5 -11
- package/src/wirings/queue/queue-runner.ts +11 -3
- package/src/wirings/rpc/rpc-runner.ts +82 -115
- package/src/wirings/scheduler/scheduler-runner.test.ts +5 -11
- package/src/wirings/scheduler/scheduler-runner.ts +13 -5
- package/src/wirings/trigger/trigger-runner.test.ts +10 -11
- package/src/wirings/trigger/trigger-runner.ts +6 -4
- package/src/wirings/workflow/dsl/workflow-runner.ts +11 -10
- package/src/wirings/workflow/graph/graph-runner.ts +19 -4
- package/src/wirings/workflow/index.ts +17 -6
- package/src/wirings/workflow/pikku-workflow-service.ts +284 -24
- package/src/wirings/workflow/workflow-queue-workers.ts +85 -0
- package/src/wirings/workflow/workflow.types.ts +16 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/wirings/ai-agent/agent-dynamic-workflow.d.ts +0 -6
- package/dist/wirings/ai-agent/agent-dynamic-workflow.js +0 -468
- package/dist/wirings/workflow/workflow-helpers.d.ts +0 -36
- package/dist/wirings/workflow/workflow-helpers.js +0 -38
- package/src/wirings/ai-agent/agent-dynamic-workflow.ts +0 -610
- package/src/wirings/workflow/workflow-helpers.test.ts +0 -129
- package/src/wirings/workflow/workflow-helpers.ts +0 -79
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
} from './ai-agent.types.js'
|
|
12
12
|
import type { AIAgentRunnerParams } from '../../services/ai-agent-runner-service.js'
|
|
13
13
|
import { PikkuError } from '../../errors/error-handler.js'
|
|
14
|
+
import { checkAuthPermissions } from '../../permissions.js'
|
|
14
15
|
import { AIProviderNotConfiguredError } from '../../errors/errors.js'
|
|
15
16
|
import { pikkuState, getSingletonServices } from '../../pikku-state.js'
|
|
16
17
|
import { createMiddlewareSessionWireProps } from '../../services/user-session-service.js'
|
|
@@ -22,10 +23,6 @@ import {
|
|
|
22
23
|
resolveNamespace,
|
|
23
24
|
ContextAwareRPCService,
|
|
24
25
|
} from '../../wirings/rpc/rpc-runner.js'
|
|
25
|
-
import {
|
|
26
|
-
buildDynamicWorkflowInstructions,
|
|
27
|
-
buildWorkflowTools,
|
|
28
|
-
} from './agent-dynamic-workflow.js'
|
|
29
26
|
import {
|
|
30
27
|
resolveMemoryServices,
|
|
31
28
|
loadContextMessages,
|
|
@@ -148,6 +145,13 @@ export type StreamContext = {
|
|
|
148
145
|
export const resolveAgent = (
|
|
149
146
|
agentName: string
|
|
150
147
|
): { agent: CoreAIAgent; packageName: string | null; resolvedName: string } => {
|
|
148
|
+
if (!agentName) {
|
|
149
|
+
console.error(
|
|
150
|
+
'[resolveAgent] agentName is undefined/null! Stack:',
|
|
151
|
+
new Error().stack
|
|
152
|
+
)
|
|
153
|
+
throw new Error('resolveAgent called with undefined agentName')
|
|
154
|
+
}
|
|
151
155
|
const mainAgent = pikkuState(null, 'agent', 'agents').get(agentName)
|
|
152
156
|
if (mainAgent) {
|
|
153
157
|
return { agent: mainAgent, packageName: null, resolvedName: agentName }
|
|
@@ -181,10 +185,21 @@ export async function buildInstructions(
|
|
|
181
185
|
packageName: string | null
|
|
182
186
|
): Promise<string> {
|
|
183
187
|
const meta = pikkuState(packageName, 'agent', 'agentsMeta')[agentName]
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
const parts: string[] = []
|
|
189
|
+
if (meta?.role) parts.push(meta.role)
|
|
190
|
+
if (meta?.personality) parts.push(meta.personality)
|
|
191
|
+
if (meta?.goal) parts.push(meta.goal)
|
|
192
|
+
let instructions = parts.join('\n\n')
|
|
193
|
+
|
|
194
|
+
if (meta?.tools?.length) {
|
|
195
|
+
instructions +=
|
|
196
|
+
'\n\nTool usage rules:\n' +
|
|
197
|
+
'- Act immediately with the information given. Do NOT ask clarifying questions unless a required field is truly missing.\n' +
|
|
198
|
+
'- Only use fields defined in your tool schemas. Never mention or ask for fields that do not exist.\n' +
|
|
199
|
+
'- Never fill optional fields with placeholder or zero values. Omit them entirely unless the user provides a real value.\n' +
|
|
200
|
+
'- Never stuff unrelated information into the wrong field.\n' +
|
|
201
|
+
'- Keep responses concise.'
|
|
202
|
+
}
|
|
188
203
|
|
|
189
204
|
if (meta?.agents?.length) {
|
|
190
205
|
instructions +=
|
|
@@ -194,13 +209,6 @@ export async function buildInstructions(
|
|
|
194
209
|
'When a request involves multiple actions for the same domain, combine them into a single sub-agent call rather than making separate calls.'
|
|
195
210
|
}
|
|
196
211
|
|
|
197
|
-
if (meta?.dynamicWorkflows && meta.tools?.length) {
|
|
198
|
-
instructions += buildDynamicWorkflowInstructions(
|
|
199
|
-
meta.tools,
|
|
200
|
-
meta.dynamicWorkflows
|
|
201
|
-
)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
212
|
return instructions
|
|
205
213
|
}
|
|
206
214
|
|
|
@@ -279,6 +287,11 @@ export async function buildToolDefs(
|
|
|
279
287
|
const meta = pikkuState(packageName, 'agent', 'agentsMeta')[agentName]
|
|
280
288
|
if (!meta) return { tools, missingRpcs }
|
|
281
289
|
|
|
290
|
+
// Get session for permission filtering
|
|
291
|
+
const session = params.sessionService
|
|
292
|
+
? await params.sessionService.get()
|
|
293
|
+
: null
|
|
294
|
+
|
|
282
295
|
const metaTools = meta.tools
|
|
283
296
|
const metaAgents = meta.agents
|
|
284
297
|
|
|
@@ -315,6 +328,18 @@ export async function buildToolDefs(
|
|
|
315
328
|
continue
|
|
316
329
|
}
|
|
317
330
|
|
|
331
|
+
// Filter out tools the user doesn't have auth for
|
|
332
|
+
if (fnMeta.permissions?.length) {
|
|
333
|
+
if (!session) continue
|
|
334
|
+
const allowed = await checkAuthPermissions(
|
|
335
|
+
fnMeta.permissions,
|
|
336
|
+
session,
|
|
337
|
+
singletonServices,
|
|
338
|
+
resolvedPkg
|
|
339
|
+
)
|
|
340
|
+
if (!allowed) continue
|
|
341
|
+
}
|
|
342
|
+
|
|
318
343
|
const inputSchemaName = fnMeta?.inputSchemaName
|
|
319
344
|
let inputSchema = inputSchemaName
|
|
320
345
|
? schemas.get(inputSchemaName)
|
|
@@ -393,6 +418,17 @@ export async function buildToolDefs(
|
|
|
393
418
|
continue
|
|
394
419
|
}
|
|
395
420
|
|
|
421
|
+
// Filter out sub-agents the user doesn't have auth for
|
|
422
|
+
if (subMeta.permissions?.length) {
|
|
423
|
+
if (!session) continue
|
|
424
|
+
const allowed = await checkAuthPermissions(
|
|
425
|
+
subMeta.permissions,
|
|
426
|
+
session,
|
|
427
|
+
singletonServices
|
|
428
|
+
)
|
|
429
|
+
if (!allowed) continue
|
|
430
|
+
}
|
|
431
|
+
|
|
396
432
|
tools.push({
|
|
397
433
|
name: subAgentName,
|
|
398
434
|
description: subMeta.description,
|
|
@@ -507,18 +543,6 @@ export async function buildToolDefs(
|
|
|
507
543
|
}
|
|
508
544
|
}
|
|
509
545
|
|
|
510
|
-
if (meta.dynamicWorkflows) {
|
|
511
|
-
const workflowTools = buildWorkflowTools(
|
|
512
|
-
agentName,
|
|
513
|
-
packageName,
|
|
514
|
-
meta.tools ?? [],
|
|
515
|
-
meta.dynamicWorkflows,
|
|
516
|
-
streamContext,
|
|
517
|
-
params.sessionService
|
|
518
|
-
)
|
|
519
|
-
tools.push(...workflowTools)
|
|
520
|
-
}
|
|
521
|
-
|
|
522
546
|
const hasToolHooks = aiMiddlewares?.some(
|
|
523
547
|
(mw) => mw.beforeToolCall || mw.afterToolCall
|
|
524
548
|
)
|
|
@@ -594,19 +618,6 @@ export async function prepareAgentRun(
|
|
|
594
618
|
throw new AIProviderNotConfiguredError()
|
|
595
619
|
}
|
|
596
620
|
|
|
597
|
-
if (agent.dynamicWorkflows && singletonServices.workflowService) {
|
|
598
|
-
const persisted =
|
|
599
|
-
await singletonServices.workflowService.getAIGeneratedWorkflows(
|
|
600
|
-
resolvedName
|
|
601
|
-
)
|
|
602
|
-
const allMeta = pikkuState(null, 'workflows', 'meta')
|
|
603
|
-
for (const wf of persisted) {
|
|
604
|
-
if (!allMeta[wf.workflowName]) {
|
|
605
|
-
allMeta[wf.workflowName] = wf.graph
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
|
|
610
621
|
const { storage } = resolveMemoryServices(agent, singletonServices)
|
|
611
622
|
const memoryConfig = agent.memory
|
|
612
623
|
const threadId = input.threadId
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { CoreAIAgent, AgentRunState } from './ai-agent.types.js'
|
|
2
2
|
import { pikkuState } from '../../pikku-state.js'
|
|
3
3
|
import type { AIRunStateService } from '../../services/ai-run-state-service.js'
|
|
4
|
-
import { PikkuMissingMetaError } from '../../errors/errors.js'
|
|
5
4
|
|
|
6
5
|
export const addAIAgent = (
|
|
7
6
|
agentName: string,
|
|
@@ -11,9 +10,10 @@ export const addAIAgent = (
|
|
|
11
10
|
const agentsMeta = pikkuState(packageName, 'agent', 'agentsMeta')
|
|
12
11
|
const agentMeta = agentsMeta[agentName]
|
|
13
12
|
if (!agentMeta) {
|
|
14
|
-
|
|
15
|
-
`
|
|
13
|
+
console.warn(
|
|
14
|
+
`[pikku] Skipping AI agent '${agentName}' — metadata not found. Consider moving this wiring to its own file.`
|
|
16
15
|
)
|
|
16
|
+
return
|
|
17
17
|
}
|
|
18
18
|
const agents = pikkuState(packageName, 'agent', 'agents')
|
|
19
19
|
if (agents.has(agentName)) {
|
|
@@ -32,6 +32,19 @@ import { checkForApprovals, appendStepMessages } from './ai-agent-stream.js'
|
|
|
32
32
|
import { pikkuState, getSingletonServices } from '../../pikku-state.js'
|
|
33
33
|
import { resolveModelConfig } from './ai-agent-model-config.js'
|
|
34
34
|
import { AIProviderNotConfiguredError } from '../../errors/errors.js'
|
|
35
|
+
|
|
36
|
+
function stripNulls(obj: unknown): unknown {
|
|
37
|
+
if (obj === null) return undefined
|
|
38
|
+
if (Array.isArray(obj)) return obj.map(stripNulls)
|
|
39
|
+
if (typeof obj !== 'object') return obj
|
|
40
|
+
const result: Record<string, unknown> = {}
|
|
41
|
+
for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {
|
|
42
|
+
if (value !== null) {
|
|
43
|
+
result[key] = stripNulls(value)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return result
|
|
47
|
+
}
|
|
35
48
|
import { randomUUID } from 'crypto'
|
|
36
49
|
|
|
37
50
|
export async function runAIAgent(
|
|
@@ -420,10 +433,12 @@ export async function resumeAIAgentSync(
|
|
|
420
433
|
`Tool "${pending.toolName}" not found in agent definition`
|
|
421
434
|
)
|
|
422
435
|
}
|
|
423
|
-
const
|
|
436
|
+
const rawArgs =
|
|
424
437
|
typeof pending.args === 'string'
|
|
425
438
|
? JSON.parse(pending.args)
|
|
426
439
|
: pending.args
|
|
440
|
+
// Strip null values recursively — LLMs send null for optional fields but Zod expects undefined
|
|
441
|
+
const toolArgs = stripNulls(rawArgs) ?? {}
|
|
427
442
|
try {
|
|
428
443
|
const toolResult = await matchingTool.execute(toolArgs)
|
|
429
444
|
resultStr =
|
|
@@ -1166,6 +1166,14 @@ async function continueAfterToolResult(
|
|
|
1166
1166
|
).tools
|
|
1167
1167
|
|
|
1168
1168
|
const resolved = resolveModelConfig(resolvedName, agent)
|
|
1169
|
+
console.log(
|
|
1170
|
+
'[DEBUG resume] resolvedName:',
|
|
1171
|
+
resolvedName,
|
|
1172
|
+
'agent.model:',
|
|
1173
|
+
agent.model,
|
|
1174
|
+
'resolved.model:',
|
|
1175
|
+
resolved.model
|
|
1176
|
+
)
|
|
1169
1177
|
const maxSteps = resolved.maxSteps ?? 10
|
|
1170
1178
|
|
|
1171
1179
|
const runnerParams: AIAgentRunnerParams = {
|
|
@@ -195,7 +195,9 @@ export type CoreAIAgent<
|
|
|
195
195
|
description: string
|
|
196
196
|
summary?: string
|
|
197
197
|
errors?: string[]
|
|
198
|
-
|
|
198
|
+
role?: string
|
|
199
|
+
personality?: string
|
|
200
|
+
goal: string
|
|
199
201
|
model: string
|
|
200
202
|
temperature?: number
|
|
201
203
|
tools?: unknown[]
|
|
@@ -204,7 +206,6 @@ export type CoreAIAgent<
|
|
|
204
206
|
memory?: AIAgentMemoryConfig
|
|
205
207
|
maxSteps?: number
|
|
206
208
|
toolChoice?: 'auto' | 'required' | 'none'
|
|
207
|
-
dynamicWorkflows?: 'read' | 'always' | 'ask'
|
|
208
209
|
input?: unknown
|
|
209
210
|
output?: unknown
|
|
210
211
|
tags?: string[]
|
|
@@ -398,6 +399,7 @@ export type AIAgentMeta = Record<
|
|
|
398
399
|
channelMiddleware?: MiddlewareMetadata[]
|
|
399
400
|
aiMiddleware?: MiddlewareMetadata[]
|
|
400
401
|
permissions?: PermissionMetadata[]
|
|
401
|
-
|
|
402
|
+
sourceFile?: string
|
|
403
|
+
exportedName?: string
|
|
402
404
|
}
|
|
403
405
|
>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NotFoundError
|
|
1
|
+
import { NotFoundError } from '../../errors/errors.js'
|
|
2
2
|
import { addFunction } from '../../function/function-runner.js'
|
|
3
3
|
import type { CorePikkuPermission } from '../../function/functions.types.js'
|
|
4
4
|
import { pikkuState, getSingletonServices } from '../../pikku-state.js'
|
|
@@ -36,13 +36,12 @@ export const wireChannel = <
|
|
|
36
36
|
const channelsMeta = pikkuState(null, 'channel', 'meta')
|
|
37
37
|
const channelMeta = channelsMeta[channel.name]
|
|
38
38
|
if (!channelMeta) {
|
|
39
|
-
|
|
40
|
-
`
|
|
39
|
+
console.warn(
|
|
40
|
+
`[pikku] Skipping channel '${channel.name}' — metadata not found. Consider moving this wiring to its own file.`
|
|
41
41
|
)
|
|
42
|
+
return
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
pikkuState(null, 'channel', 'channels').set(channel.name, channel as any)
|
|
45
|
-
|
|
46
45
|
// Register onConnect function if provided
|
|
47
46
|
if (channel.onConnect && channelMeta.connect) {
|
|
48
47
|
addFunction(channelMeta.connect.pikkuFuncId, channel.onConnect as any)
|
|
@@ -70,7 +70,11 @@ export class PikkuMockRequest implements PikkuHTTPRequest {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
export class PikkuMockResponse implements PikkuHTTPResponse {
|
|
73
|
-
public _status: number
|
|
73
|
+
public _status: number = 200
|
|
74
|
+
|
|
75
|
+
public get statusCode(): number {
|
|
76
|
+
return this._status
|
|
77
|
+
}
|
|
74
78
|
|
|
75
79
|
status(code: number): this {
|
|
76
80
|
this._status = code
|
|
@@ -369,15 +369,16 @@ describe('CLI Runner', () => {
|
|
|
369
369
|
assert.strictEqual(programs['my-cli'].middleware[0], middleware)
|
|
370
370
|
})
|
|
371
371
|
|
|
372
|
-
test('should
|
|
372
|
+
test('should skip when CLI metadata not found', () => {
|
|
373
373
|
pikkuState(null, 'cli', 'meta', { programs: {}, renderers: {} })
|
|
374
374
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
375
|
+
wireCLI({
|
|
376
|
+
program: 'nonexistent',
|
|
377
|
+
commands: {},
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
const programs = pikkuState(null, 'cli', 'programs')
|
|
381
|
+
assert.strictEqual(programs['nonexistent'], undefined)
|
|
381
382
|
})
|
|
382
383
|
})
|
|
383
384
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NotFoundError
|
|
1
|
+
import { NotFoundError } from '../../errors/errors.js'
|
|
2
2
|
import { addFunction, runPikkuFunc } from '../../function/function-runner.js'
|
|
3
3
|
import { pikkuState } from '../../pikku-state.js'
|
|
4
4
|
import type {
|
|
@@ -66,9 +66,10 @@ export const wireCLI = <
|
|
|
66
66
|
const cliMeta = pikkuState(null, 'cli', 'meta') || {}
|
|
67
67
|
|
|
68
68
|
if (!cliMeta.programs?.[cli.program]) {
|
|
69
|
-
|
|
70
|
-
`
|
|
69
|
+
console.warn(
|
|
70
|
+
`[pikku] Skipping CLI program '${cli.program}' — metadata not found. Consider moving this wiring to its own file.`
|
|
71
71
|
)
|
|
72
|
+
return
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
// Get existing programs state and add this program
|
|
@@ -499,8 +500,9 @@ export async function executeCLI({
|
|
|
499
500
|
? await createConfig(new LocalVariablesService(), data)
|
|
500
501
|
: ({} as any)
|
|
501
502
|
|
|
502
|
-
// Create services with config
|
|
503
|
+
// Create services with config and register in global state
|
|
503
504
|
const singletonServices = await createSingletonServices(config)
|
|
505
|
+
pikkuState(null, 'package', 'singletonServices', singletonServices)
|
|
504
506
|
|
|
505
507
|
// Execute the command
|
|
506
508
|
await runCLICommand({
|
|
@@ -6,6 +6,11 @@ import type {
|
|
|
6
6
|
CLIOption,
|
|
7
7
|
} from './cli.types.js'
|
|
8
8
|
|
|
9
|
+
/** Convert kebab-case to camelCase: "from-plan" → "fromPlan" */
|
|
10
|
+
function toCamelCase(str: string): string {
|
|
11
|
+
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase())
|
|
12
|
+
}
|
|
13
|
+
|
|
9
14
|
/**
|
|
10
15
|
* Result of parsing CLI arguments
|
|
11
16
|
*/
|
|
@@ -117,11 +122,11 @@ export function parseCLIArguments(
|
|
|
117
122
|
const arg = args[currentIndex]
|
|
118
123
|
|
|
119
124
|
if (arg.startsWith('--')) {
|
|
120
|
-
// Long option
|
|
125
|
+
// Long option (--from-plan → fromPlan)
|
|
121
126
|
const equalIndex = arg.indexOf('=')
|
|
122
127
|
if (equalIndex > 0) {
|
|
123
128
|
// --option=value format
|
|
124
|
-
const key = arg.slice(2, equalIndex)
|
|
129
|
+
const key = toCamelCase(arg.slice(2, equalIndex))
|
|
125
130
|
const optionDef = availableOptions[key]
|
|
126
131
|
|
|
127
132
|
// Unknown options are allowed for forward compatibility
|
|
@@ -129,7 +134,7 @@ export function parseCLIArguments(
|
|
|
129
134
|
optionArgs[key] = parseOptionValue(value, optionDef)
|
|
130
135
|
} else {
|
|
131
136
|
// --option value format
|
|
132
|
-
const key = arg.slice(2)
|
|
137
|
+
const key = toCamelCase(arg.slice(2))
|
|
133
138
|
const optionDef = availableOptions[key]
|
|
134
139
|
|
|
135
140
|
// Unknown options are allowed for forward compatibility
|
|
@@ -21,7 +21,7 @@ import type {
|
|
|
21
21
|
PikkuWire,
|
|
22
22
|
PikkuWiringTypes,
|
|
23
23
|
} from '../../types/core.types.js'
|
|
24
|
-
import { NotFoundError
|
|
24
|
+
import { NotFoundError } from '../../errors/errors.js'
|
|
25
25
|
import {
|
|
26
26
|
closeWireServices,
|
|
27
27
|
createWeakUID,
|
|
@@ -180,9 +180,14 @@ export const wireHTTP = <
|
|
|
180
180
|
const httpMeta = pikkuState(null, 'http', 'meta')
|
|
181
181
|
const routeMeta = httpMeta[httpWiring.method][httpWiring.route]
|
|
182
182
|
if (!routeMeta) {
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
// In deploy units with filtered metadata, wiring files may include
|
|
184
|
+
// routes for functions not in this unit. This happens when multiple
|
|
185
|
+
// wirings share a file — split them into separate files for better
|
|
186
|
+
// tree-shaking.
|
|
187
|
+
console.warn(
|
|
188
|
+
`[pikku] Skipping HTTP route '${httpWiring.method.toUpperCase()} ${httpWiring.route}' — metadata not found. Consider moving this wiring to its own file.`
|
|
185
189
|
)
|
|
190
|
+
return
|
|
186
191
|
}
|
|
187
192
|
if (httpWiring.func) {
|
|
188
193
|
addFunction(routeMeta.pikkuFuncId, httpWiring.func)
|
|
@@ -371,6 +376,7 @@ const executeRoute = async (
|
|
|
371
376
|
}
|
|
372
377
|
|
|
373
378
|
const wire: PikkuWire = {
|
|
379
|
+
traceId: requestId,
|
|
374
380
|
http,
|
|
375
381
|
channel,
|
|
376
382
|
session: userSession.get() as CoreUserSession | undefined,
|
|
@@ -489,6 +495,7 @@ export const fetchData = async <In, Out>(
|
|
|
489
495
|
bubbleErrors = false,
|
|
490
496
|
exposeErrors = false,
|
|
491
497
|
generateRequestId,
|
|
498
|
+
traceId: externalTraceId,
|
|
492
499
|
}: RunHTTPWiringOptions = {}
|
|
493
500
|
): Promise<Out | void> => {
|
|
494
501
|
const singletonServices = getSingletonServices()
|
|
@@ -499,11 +506,20 @@ export const fetchData = async <In, Out>(
|
|
|
499
506
|
// Combine the request and response into one wire object
|
|
500
507
|
const pikkuRequest =
|
|
501
508
|
request instanceof Request ? new PikkuFetchHTTPRequest(request) : request
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
509
|
+
|
|
510
|
+
// Resolve traceId: external (e.g. CF-Ray) > x-request-id header > generated
|
|
511
|
+
let requestId: string | null = externalTraceId ?? null
|
|
512
|
+
if (!requestId) {
|
|
513
|
+
try {
|
|
514
|
+
requestId = pikkuRequest.header('x-request-id')
|
|
515
|
+
} catch {}
|
|
516
|
+
}
|
|
506
517
|
requestId = requestId || generateRequestId?.() || createWeakUID()
|
|
518
|
+
|
|
519
|
+
// Scoped logger for HTTP runner internal logging (error handling, route matching)
|
|
520
|
+
// Functions/middleware receive singletonServices.logger directly for compatibility
|
|
521
|
+
const scopedLogger =
|
|
522
|
+
singletonServices.logger.scope?.(requestId) ?? singletonServices.logger
|
|
507
523
|
const http = createHTTPWire(pikkuRequest, response)
|
|
508
524
|
const apiType = http!.request!.method()
|
|
509
525
|
const apiRoute = http!.request!.path()
|
|
@@ -526,7 +542,7 @@ export const fetchData = async <In, Out>(
|
|
|
526
542
|
response.status(204).json(undefined as any)
|
|
527
543
|
return
|
|
528
544
|
}
|
|
529
|
-
|
|
545
|
+
scopedLogger.info({
|
|
530
546
|
message: 'Route not found',
|
|
531
547
|
apiRoute,
|
|
532
548
|
apiType,
|
|
@@ -551,7 +567,7 @@ export const fetchData = async <In, Out>(
|
|
|
551
567
|
} catch (e: any) {
|
|
552
568
|
if (matchedRoute?.route.sse) {
|
|
553
569
|
// For SSE routes, send error through the stream since the response is already in stream mode
|
|
554
|
-
|
|
570
|
+
scopedLogger.error(e instanceof Error ? e.message : e)
|
|
555
571
|
try {
|
|
556
572
|
const errorResponse = getErrorResponse(e)
|
|
557
573
|
response.arrayBuffer(
|
|
@@ -562,7 +578,7 @@ export const fetchData = async <In, Out>(
|
|
|
562
578
|
)
|
|
563
579
|
response.arrayBuffer(JSON.stringify({ type: 'done' }))
|
|
564
580
|
} catch (streamErr: any) {
|
|
565
|
-
|
|
581
|
+
scopedLogger.error(
|
|
566
582
|
`SSE error while sending error payload: ${streamErr instanceof Error ? streamErr.message : String(streamErr)}`
|
|
567
583
|
)
|
|
568
584
|
}
|
|
@@ -572,7 +588,7 @@ export const fetchData = async <In, Out>(
|
|
|
572
588
|
e,
|
|
573
589
|
http,
|
|
574
590
|
requestId,
|
|
575
|
-
|
|
591
|
+
scopedLogger,
|
|
576
592
|
logWarningsForStatusCodes,
|
|
577
593
|
respondWith404,
|
|
578
594
|
bubbleErrors,
|
|
@@ -37,6 +37,8 @@ export type RunHTTPWiringOptions = Partial<{
|
|
|
37
37
|
bubbleErrors: boolean
|
|
38
38
|
exposeErrors: boolean
|
|
39
39
|
generateRequestId: () => string
|
|
40
|
+
/** Pre-resolved trace ID (e.g. CF-Ray). Falls back to x-request-id header or generated ID. */
|
|
41
|
+
traceId: string
|
|
40
42
|
}>
|
|
41
43
|
|
|
42
44
|
/**
|
|
@@ -269,6 +271,7 @@ export interface PikkuHTTPRequest<In = unknown> {
|
|
|
269
271
|
}
|
|
270
272
|
|
|
271
273
|
export interface PikkuHTTPResponse<Out = unknown> {
|
|
274
|
+
readonly statusCode: number
|
|
272
275
|
status(code: number): this
|
|
273
276
|
cookie(name: string, value: string | null, options: SerializeOptions): this
|
|
274
277
|
header(name: string, value: string | string[]): this
|
|
@@ -20,11 +20,7 @@ import {
|
|
|
20
20
|
} from '../../pikku-state.js'
|
|
21
21
|
import { addFunction, runPikkuFunc } from '../../function/function-runner.js'
|
|
22
22
|
import { resolveNamespace } from '../rpc/rpc-runner.js'
|
|
23
|
-
import {
|
|
24
|
-
BadRequestError,
|
|
25
|
-
NotFoundError,
|
|
26
|
-
PikkuMissingMetaError,
|
|
27
|
-
} from '../../errors/errors.js'
|
|
23
|
+
import { BadRequestError, NotFoundError } from '../../errors/errors.js'
|
|
28
24
|
import {
|
|
29
25
|
PikkuSessionService,
|
|
30
26
|
createMiddlewareSessionWireProps,
|
|
@@ -57,9 +53,10 @@ export const wireMCPResource = <
|
|
|
57
53
|
const resourcesMeta = pikkuState(null, 'mcp', 'resourcesMeta')
|
|
58
54
|
const mcpResourceMeta = resourcesMeta[mcpResource.uri]
|
|
59
55
|
if (!mcpResourceMeta) {
|
|
60
|
-
|
|
61
|
-
`
|
|
56
|
+
console.warn(
|
|
57
|
+
`[pikku] Skipping MCP resource '${mcpResource.uri}' — metadata not found. Consider moving this wiring to its own file.`
|
|
62
58
|
)
|
|
59
|
+
return
|
|
63
60
|
}
|
|
64
61
|
addFunction(mcpResourceMeta.pikkuFuncId, mcpResource.func as any)
|
|
65
62
|
const resources = pikkuState(null, 'mcp', 'resources')
|
|
@@ -79,9 +76,10 @@ export const wireMCPPrompt = <
|
|
|
79
76
|
const promptsMeta = pikkuState(null, 'mcp', 'promptsMeta')
|
|
80
77
|
const mcpPromptMeta = promptsMeta[mcpPrompt.name]
|
|
81
78
|
if (!mcpPromptMeta) {
|
|
82
|
-
|
|
83
|
-
`
|
|
79
|
+
console.warn(
|
|
80
|
+
`[pikku] Skipping MCP prompt '${mcpPrompt.name}' — metadata not found. Consider moving this wiring to its own file.`
|
|
84
81
|
)
|
|
82
|
+
return
|
|
85
83
|
}
|
|
86
84
|
addFunction(mcpPromptMeta.pikkuFuncId, mcpPrompt.func as any)
|
|
87
85
|
const prompts = pikkuState(null, 'mcp', 'prompts')
|
|
@@ -72,7 +72,7 @@ describe('wireQueueWorker', () => {
|
|
|
72
72
|
assert.equal(registrations.get('test-queue'), mockWorker)
|
|
73
73
|
})
|
|
74
74
|
|
|
75
|
-
test('should
|
|
75
|
+
test('should skip when queue worker metadata not found', () => {
|
|
76
76
|
const mockWorker: CoreQueueWorker = {
|
|
77
77
|
name: 'missing-meta-queue',
|
|
78
78
|
func: {
|
|
@@ -81,16 +81,10 @@ describe('wireQueueWorker', () => {
|
|
|
81
81
|
},
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
error.message.includes('Missing generated metadata for queue worker')
|
|
89
|
-
)
|
|
90
|
-
assert(error.message.includes('missing-meta-queue'))
|
|
91
|
-
return true
|
|
92
|
-
}
|
|
93
|
-
)
|
|
84
|
+
wireQueueWorker(mockWorker)
|
|
85
|
+
|
|
86
|
+
const registrations = pikkuState(null, 'queue', 'registrations')
|
|
87
|
+
assert.equal(registrations.has('missing-meta-queue'), false)
|
|
94
88
|
})
|
|
95
89
|
|
|
96
90
|
test('should wire worker with middleware and tags', () => {
|
|
@@ -58,9 +58,10 @@ export const wireQueueWorker = <
|
|
|
58
58
|
const meta = pikkuState(null, 'queue', 'meta')
|
|
59
59
|
const processorMeta = meta[queueWorker.name]
|
|
60
60
|
if (!processorMeta) {
|
|
61
|
-
|
|
62
|
-
`
|
|
61
|
+
console.warn(
|
|
62
|
+
`[pikku] Skipping queue worker '${queueWorker.name}' — metadata not found. Consider moving this wiring to its own file.`
|
|
63
63
|
)
|
|
64
|
+
return
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
// Register the function with pikku
|
|
@@ -104,13 +105,19 @@ export async function removeQueueWorker(name: string): Promise<void> {
|
|
|
104
105
|
export async function runQueueJob({
|
|
105
106
|
job,
|
|
106
107
|
updateProgress,
|
|
108
|
+
traceId,
|
|
107
109
|
}: {
|
|
108
110
|
job: QueueJob
|
|
109
111
|
updateProgress?: (progress: number | string | object) => Promise<void>
|
|
112
|
+
/** Pre-resolved trace ID (e.g. from queue message metadata) */
|
|
113
|
+
traceId?: string
|
|
110
114
|
}): Promise<void> {
|
|
111
115
|
const singletonServices = getSingletonServices()
|
|
112
116
|
const createWireServices = getCreateWireServices()
|
|
113
|
-
const
|
|
117
|
+
const resolvedTraceId = traceId ?? `q-${job.id}`
|
|
118
|
+
const logger =
|
|
119
|
+
singletonServices.logger.scope?.(resolvedTraceId) ??
|
|
120
|
+
singletonServices.logger
|
|
114
121
|
|
|
115
122
|
const meta = pikkuState(null, 'queue', 'meta')
|
|
116
123
|
const processorMeta = meta[job.queueName]
|
|
@@ -150,6 +157,7 @@ export async function runQueueJob({
|
|
|
150
157
|
logger.info(`Processing job ${job.id} in queue ${job.queueName}`)
|
|
151
158
|
|
|
152
159
|
const wire: PikkuWire = {
|
|
160
|
+
traceId: resolvedTraceId,
|
|
153
161
|
queue,
|
|
154
162
|
}
|
|
155
163
|
|