@pikku/core 0.12.22 → 0.12.24
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 +22 -0
- package/dist/function/function-runner.js +62 -15
- package/dist/function/functions.types.d.ts +8 -5
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1 -0
- package/dist/middleware-runner.d.ts +2 -2
- package/dist/permissions.d.ts +1 -1
- package/dist/services/ai-agent-runner-service.d.ts +6 -0
- package/dist/services/audit-service.d.ts +50 -0
- package/dist/services/audit-service.js +106 -0
- package/dist/services/content-service.d.ts +1 -0
- package/dist/services/email-service.d.ts +36 -0
- package/dist/services/email-service.js +1 -0
- package/dist/services/index.d.ts +5 -1
- package/dist/services/index.js +2 -0
- package/dist/services/local-content.js +1 -0
- package/dist/services/local-email-service.d.ts +4 -0
- package/dist/services/local-email-service.js +26 -0
- package/dist/services/local-secrets.d.ts +1 -0
- package/dist/services/local-secrets.js +11 -2
- package/dist/services/meta-service.d.ts +36 -0
- package/dist/services/meta-service.js +59 -0
- package/dist/types/core.types.d.ts +15 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +2 -0
- package/dist/wirings/ai-agent/ai-agent-prepare.js +8 -2
- package/dist/wirings/ai-agent/ai-agent-runner.js +3 -1
- package/dist/wirings/ai-agent/ai-agent-stream.js +2 -1
- package/dist/wirings/ai-agent/ai-agent-utils.d.ts +1 -0
- package/dist/wirings/ai-agent/ai-agent-utils.js +1 -0
- package/dist/wirings/rpc/rpc-runner.js +9 -2
- package/package.json +1 -1
- package/src/function/function-runner.test.ts +255 -0
- package/src/function/function-runner.ts +66 -20
- package/src/function/functions.types.ts +26 -11
- package/src/index.ts +35 -0
- package/src/middleware-runner.ts +10 -10
- package/src/permissions.ts +4 -4
- package/src/services/ai-agent-runner-service.ts +6 -0
- package/src/services/audit-service.test.ts +44 -0
- package/src/services/audit-service.ts +198 -0
- package/src/services/content-service.ts +1 -0
- package/src/services/email-service.ts +46 -0
- package/src/services/index.ts +33 -0
- package/src/services/local-content.ts +1 -0
- package/src/services/local-email-service.test.ts +108 -0
- package/src/services/local-email-service.ts +30 -0
- package/src/services/local-secrets.ts +11 -2
- package/src/services/meta-service.ts +115 -0
- package/src/types/core.types.ts +20 -2
- package/src/wirings/ai-agent/ai-agent-prepare.ts +11 -2
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +105 -0
- package/src/wirings/ai-agent/ai-agent-runner.ts +4 -1
- package/src/wirings/ai-agent/ai-agent-stream.ts +3 -1
- package/src/wirings/ai-agent/ai-agent-utils.ts +1 -0
- package/src/wirings/rpc/rpc-runner.ts +9 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -86,6 +86,34 @@ export interface PermissionsGroupsMeta {
|
|
|
86
86
|
httpGroups: Record<string, GroupMeta>;
|
|
87
87
|
tagGroups: Record<string, GroupMeta>;
|
|
88
88
|
}
|
|
89
|
+
export interface EmailTemplateLocaleMeta {
|
|
90
|
+
contentHash: string;
|
|
91
|
+
htmlHash: string;
|
|
92
|
+
subjectHash: string;
|
|
93
|
+
textHash: string;
|
|
94
|
+
}
|
|
95
|
+
export interface EmailTemplateMeta {
|
|
96
|
+
variables: string[];
|
|
97
|
+
hasHtml: boolean;
|
|
98
|
+
hasSubject: boolean;
|
|
99
|
+
hasText: boolean;
|
|
100
|
+
locales: Record<string, EmailTemplateLocaleMeta>;
|
|
101
|
+
}
|
|
102
|
+
export interface EmailsMeta {
|
|
103
|
+
src: string;
|
|
104
|
+
themeHash: string;
|
|
105
|
+
templates: Record<string, EmailTemplateMeta>;
|
|
106
|
+
}
|
|
107
|
+
export interface EmailTemplateAssets {
|
|
108
|
+
theme: Record<string, unknown>;
|
|
109
|
+
strings: Record<string, unknown>;
|
|
110
|
+
layout: string;
|
|
111
|
+
partials: Record<string, string>;
|
|
112
|
+
html: string;
|
|
113
|
+
subject: string;
|
|
114
|
+
text: string;
|
|
115
|
+
missing: string[];
|
|
116
|
+
}
|
|
89
117
|
/**
|
|
90
118
|
* Abstraction over .pikku metadata file access.
|
|
91
119
|
* All paths are relative to the .pikku root directory.
|
|
@@ -100,6 +128,8 @@ export interface MetaService {
|
|
|
100
128
|
readFile(relativePath: string): Promise<string | null>;
|
|
101
129
|
/** List files in a directory by relative path. Returns empty array if not found. */
|
|
102
130
|
readDir(relativePath: string): Promise<string[]>;
|
|
131
|
+
/** Read a project source file relative to the project root (one level above .pikku). */
|
|
132
|
+
readProjectFile(relativePath: string): Promise<string | null>;
|
|
103
133
|
getHttpMeta(): Promise<HTTPWiringsMeta>;
|
|
104
134
|
getChannelsMeta(): Promise<ChannelsMeta>;
|
|
105
135
|
getSchedulerMeta(): Promise<ScheduledTasksMeta>;
|
|
@@ -117,6 +147,8 @@ export interface MetaService {
|
|
|
117
147
|
getSecretsMeta(): Promise<SecretDefinitionsMeta>;
|
|
118
148
|
getCredentialsMeta(): Promise<CredentialDefinitionsMeta>;
|
|
119
149
|
getVariablesMeta(): Promise<VariableDefinitionsMeta>;
|
|
150
|
+
getEmailMeta(): Promise<EmailsMeta>;
|
|
151
|
+
getEmailTemplateAssets(templateName: string, locale: string): Promise<EmailTemplateAssets>;
|
|
120
152
|
getServicesMeta(): Promise<ServicesMetaRecord>;
|
|
121
153
|
getSchema(schemaName: string): Promise<JSONSchema7 | null>;
|
|
122
154
|
getSchemas(schemaNames: string[]): Promise<Record<string, JSONSchema7 | null>>;
|
|
@@ -143,6 +175,7 @@ export declare class LocalMetaService implements MetaService {
|
|
|
143
175
|
private secretsMetaCache;
|
|
144
176
|
private credentialsMetaCache;
|
|
145
177
|
private variablesMetaCache;
|
|
178
|
+
private emailMetaCache;
|
|
146
179
|
private middlewareGroupsMetaCache;
|
|
147
180
|
private permissionsGroupsMetaCache;
|
|
148
181
|
private agentsMetaCache;
|
|
@@ -150,6 +183,7 @@ export declare class LocalMetaService implements MetaService {
|
|
|
150
183
|
constructor(basePath: string);
|
|
151
184
|
readFile(relativePath: string): Promise<string | null>;
|
|
152
185
|
readDir(relativePath: string): Promise<string[]>;
|
|
186
|
+
readProjectFile(relativePath: string): Promise<string | null>;
|
|
153
187
|
clearCache(): void;
|
|
154
188
|
private readMetaJson;
|
|
155
189
|
getHttpMeta(): Promise<HTTPWiringsMeta>;
|
|
@@ -169,6 +203,8 @@ export declare class LocalMetaService implements MetaService {
|
|
|
169
203
|
getSecretsMeta(): Promise<SecretDefinitionsMeta>;
|
|
170
204
|
getCredentialsMeta(): Promise<CredentialDefinitionsMeta>;
|
|
171
205
|
getVariablesMeta(): Promise<VariableDefinitionsMeta>;
|
|
206
|
+
getEmailMeta(): Promise<EmailsMeta>;
|
|
207
|
+
getEmailTemplateAssets(templateName: string, locale: string): Promise<EmailTemplateAssets>;
|
|
172
208
|
getServicesMeta(): Promise<ServicesMetaRecord>;
|
|
173
209
|
getSchema(schemaName: string): Promise<JSONSchema7 | null>;
|
|
174
210
|
getSchemas(schemaNames: string[]): Promise<Record<string, JSONSchema7 | null>>;
|
|
@@ -21,6 +21,7 @@ export class LocalMetaService {
|
|
|
21
21
|
secretsMetaCache = null;
|
|
22
22
|
credentialsMetaCache = null;
|
|
23
23
|
variablesMetaCache = null;
|
|
24
|
+
emailMetaCache = null;
|
|
24
25
|
middlewareGroupsMetaCache = null;
|
|
25
26
|
permissionsGroupsMetaCache = null;
|
|
26
27
|
agentsMetaCache = null;
|
|
@@ -44,6 +45,14 @@ export class LocalMetaService {
|
|
|
44
45
|
return [];
|
|
45
46
|
}
|
|
46
47
|
}
|
|
48
|
+
async readProjectFile(relativePath) {
|
|
49
|
+
try {
|
|
50
|
+
return await readFile(join(this.basePath, '..', relativePath), 'utf-8');
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
47
56
|
clearCache() {
|
|
48
57
|
this.httpMetaCache = null;
|
|
49
58
|
this.channelsMetaCache = null;
|
|
@@ -60,6 +69,7 @@ export class LocalMetaService {
|
|
|
60
69
|
this.secretsMetaCache = null;
|
|
61
70
|
this.credentialsMetaCache = null;
|
|
62
71
|
this.variablesMetaCache = null;
|
|
72
|
+
this.emailMetaCache = null;
|
|
63
73
|
this.middlewareGroupsMetaCache = null;
|
|
64
74
|
this.permissionsGroupsMetaCache = null;
|
|
65
75
|
this.agentsMetaCache = null;
|
|
@@ -257,6 +267,55 @@ export class LocalMetaService {
|
|
|
257
267
|
this.variablesMetaCache = content ? JSON.parse(content) : {};
|
|
258
268
|
return this.variablesMetaCache;
|
|
259
269
|
}
|
|
270
|
+
async getEmailMeta() {
|
|
271
|
+
if (this.emailMetaCache)
|
|
272
|
+
return this.emailMetaCache;
|
|
273
|
+
const content = await this.readFile('email/pikku-emails-meta.gen.json');
|
|
274
|
+
this.emailMetaCache = content
|
|
275
|
+
? JSON.parse(content)
|
|
276
|
+
: {
|
|
277
|
+
src: '',
|
|
278
|
+
themeHash: '',
|
|
279
|
+
templates: {},
|
|
280
|
+
};
|
|
281
|
+
return this.emailMetaCache;
|
|
282
|
+
}
|
|
283
|
+
async getEmailTemplateAssets(templateName, locale) {
|
|
284
|
+
const emailsMeta = await this.getEmailMeta();
|
|
285
|
+
if (!emailsMeta.src) {
|
|
286
|
+
throw new Error('No generated email metadata found. Run `pikku emails generate`.');
|
|
287
|
+
}
|
|
288
|
+
const baseDir = emailsMeta.src;
|
|
289
|
+
const [themeRaw, localeRaw, layoutRaw, footerRaw, templateHtml, templateSubject, templateText,] = await Promise.all([
|
|
290
|
+
this.readProjectFile(`${baseDir}/theme.json`),
|
|
291
|
+
this.readProjectFile(`${baseDir}/locales/${locale}.json`),
|
|
292
|
+
this.readProjectFile(`${baseDir}/partials/layout.html`),
|
|
293
|
+
this.readProjectFile(`${baseDir}/partials/footer.html`),
|
|
294
|
+
this.readProjectFile(`${baseDir}/templates/${templateName}.html`),
|
|
295
|
+
this.readProjectFile(`${baseDir}/templates/${templateName}.subject.txt`),
|
|
296
|
+
this.readProjectFile(`${baseDir}/templates/${templateName}.text.txt`),
|
|
297
|
+
]);
|
|
298
|
+
return {
|
|
299
|
+
theme: themeRaw ? JSON.parse(themeRaw) : {},
|
|
300
|
+
strings: localeRaw
|
|
301
|
+
? JSON.parse(localeRaw)
|
|
302
|
+
: {},
|
|
303
|
+
layout: layoutRaw ?? '',
|
|
304
|
+
partials: {
|
|
305
|
+
footer: footerRaw ?? '',
|
|
306
|
+
},
|
|
307
|
+
html: templateHtml ?? '',
|
|
308
|
+
subject: templateSubject ?? '',
|
|
309
|
+
text: templateText ?? '',
|
|
310
|
+
missing: [
|
|
311
|
+
...(themeRaw ? [] : ['theme']),
|
|
312
|
+
...(localeRaw ? [] : ['locale']),
|
|
313
|
+
...(templateHtml ? [] : ['html']),
|
|
314
|
+
...(templateSubject ? [] : ['subject']),
|
|
315
|
+
...(templateText ? [] : ['text']),
|
|
316
|
+
],
|
|
317
|
+
};
|
|
318
|
+
}
|
|
260
319
|
async getServicesMeta() {
|
|
261
320
|
if (this.servicesMetaCache)
|
|
262
321
|
return this.servicesMetaCache;
|
|
@@ -25,8 +25,10 @@ import type { AgentRunService } from '../wirings/ai-agent/ai-agent.types.js';
|
|
|
25
25
|
import type { PikkuAIMiddlewareHooks } from '../wirings/ai-agent/ai-agent.types.js';
|
|
26
26
|
import type { WorkflowRunService } from '../wirings/workflow/workflow.types.js';
|
|
27
27
|
import type { CredentialService } from '../services/credential-service.js';
|
|
28
|
+
import type { EmailService } from '../services/email-service.js';
|
|
28
29
|
import type { MetaService } from '../services/meta-service.js';
|
|
29
30
|
import type { SessionStore } from '../services/session-store.js';
|
|
31
|
+
import type { AuditDurability, AuditService } from '../services/audit-service.js';
|
|
30
32
|
export type PikkuWiringTypes = 'http' | 'scheduler' | 'trigger' | 'channel' | 'rpc' | 'queue' | 'mcp' | 'cli' | 'workflow' | 'agent' | 'gateway';
|
|
31
33
|
export interface FunctionServicesMeta {
|
|
32
34
|
optimized: boolean;
|
|
@@ -143,6 +145,8 @@ export type CoreConfig<Config extends Record<string, unknown> = {}> = {
|
|
|
143
145
|
* Represents a core user session, which can be extended for more specific session information.
|
|
144
146
|
*/
|
|
145
147
|
export interface CoreUserSession {
|
|
148
|
+
userId?: string;
|
|
149
|
+
orgId?: string;
|
|
146
150
|
}
|
|
147
151
|
/**
|
|
148
152
|
* Interface for core singleton services provided by Pikku.
|
|
@@ -184,8 +188,12 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
|
|
|
184
188
|
workflowRunService?: WorkflowRunService;
|
|
185
189
|
/** Credential service for dynamic/managed credentials (OAuth tokens, per-user API keys) */
|
|
186
190
|
credentialService?: CredentialService;
|
|
191
|
+
/** Email service for outbound messages and template-backed delivery */
|
|
192
|
+
emailService?: EmailService;
|
|
187
193
|
/** Meta service for reading .pikku metadata files (filesystem on Node, R2/KV on CF) */
|
|
188
194
|
metaService?: MetaService;
|
|
195
|
+
/** Audit service for durable or staged audit event capture */
|
|
196
|
+
audit?: AuditService;
|
|
189
197
|
/** Session store for persisting user sessions keyed by pikkuUserId */
|
|
190
198
|
sessionStore?: SessionStore;
|
|
191
199
|
}
|
|
@@ -197,6 +205,8 @@ export type PikkuWire<In = unknown, Out = unknown, HasInitialSession extends boo
|
|
|
197
205
|
wireId: string;
|
|
198
206
|
/** Trace ID for distributed tracing — propagated across remote RPC calls via x-trace-id header */
|
|
199
207
|
traceId: string;
|
|
208
|
+
/** Function id for the current invocation */
|
|
209
|
+
functionId: string;
|
|
200
210
|
http: PikkuHTTP<In>;
|
|
201
211
|
mcp: PikkuMCP<MCPTools>;
|
|
202
212
|
rpc: TypedRPC;
|
|
@@ -211,7 +221,7 @@ export type PikkuWire<In = unknown, Out = unknown, HasInitialSession extends boo
|
|
|
211
221
|
gateway: PikkuGateway;
|
|
212
222
|
session: HasInitialSession extends true ? UserSession : UserSession | undefined;
|
|
213
223
|
/** Update and persist the current session */
|
|
214
|
-
setSession: (session:
|
|
224
|
+
setSession: (session: UserSession) => Promise<void> | void;
|
|
215
225
|
/** Clear and persist the current session */
|
|
216
226
|
clearSession: () => Promise<void> | void;
|
|
217
227
|
/** Fetch the latest session (may read from backing store) */
|
|
@@ -226,6 +236,10 @@ export type PikkuWire<In = unknown, Out = unknown, HasInitialSession extends boo
|
|
|
226
236
|
getCredential: <T = unknown>(name: string) => T | null | Promise<T | null>;
|
|
227
237
|
/** Get all resolved credentials — lazy-loads from CredentialService on first call, sync thereafter */
|
|
228
238
|
getCredentials: () => Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
239
|
+
/** Resolved function audit metadata for this invocation, if enabled */
|
|
240
|
+
audit: {
|
|
241
|
+
durability: AuditDurability;
|
|
242
|
+
};
|
|
229
243
|
}>;
|
|
230
244
|
/**
|
|
231
245
|
* A function that can wrap an wire and be called before or after
|
|
@@ -5,6 +5,8 @@ import { PikkuError } from '../../errors/error-handler.js';
|
|
|
5
5
|
import type { SessionService } from '../../services/user-session-service.js';
|
|
6
6
|
export type RunAIAgentParams = {
|
|
7
7
|
sessionService?: SessionService<CoreUserSession>;
|
|
8
|
+
/** Credential accessor for the current request — used to read per-user overrides (e.g. AI_API_KEY). */
|
|
9
|
+
getCredential?: <T = unknown>(name: string) => T | null | Promise<T | null>;
|
|
8
10
|
};
|
|
9
11
|
export type StreamAIAgentOptions = {
|
|
10
12
|
requiresToolApproval?: 'all' | 'explicit' | false;
|
|
@@ -3,7 +3,7 @@ import { checkAuthPermissions } from '../../permissions.js';
|
|
|
3
3
|
import { AIProviderNotConfiguredError } from '../../errors/errors.js';
|
|
4
4
|
import { pikkuState, getSingletonServices } from '../../pikku-state.js';
|
|
5
5
|
import { createMiddlewareSessionWireProps } from '../../services/user-session-service.js';
|
|
6
|
-
import { randomUUID } from '
|
|
6
|
+
import { randomUUID } from './ai-agent-utils.js';
|
|
7
7
|
import { streamAIAgent } from './ai-agent-stream.js';
|
|
8
8
|
import { runAIAgent } from './ai-agent-runner.js';
|
|
9
9
|
import { resolveNamespace, ContextAwareRPCService, } from '../../wirings/rpc/rpc-runner.js';
|
|
@@ -434,10 +434,16 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
|
|
|
434
434
|
export async function prepareAgentRun(agentName, input, params, agentSessionMap, streamContext) {
|
|
435
435
|
const singletonServices = getSingletonServices();
|
|
436
436
|
const { agent, packageName, resolvedName } = resolveAgent(agentName);
|
|
437
|
-
|
|
437
|
+
let agentRunner = singletonServices.aiAgentRunner;
|
|
438
438
|
if (!agentRunner) {
|
|
439
439
|
throw new AIProviderNotConfiguredError();
|
|
440
440
|
}
|
|
441
|
+
if (params.getCredential && agentRunner.withApiKey) {
|
|
442
|
+
const aiCredential = await params.getCredential('AI_API_KEY');
|
|
443
|
+
if (aiCredential?.apiKey?.trim()) {
|
|
444
|
+
agentRunner = agentRunner.withApiKey(aiCredential.apiKey);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
441
447
|
const { storage } = resolveMemoryServices(agent, singletonServices);
|
|
442
448
|
const memoryConfig = agent.memory;
|
|
443
449
|
const threadId = input.threadId;
|
|
@@ -4,6 +4,7 @@ import { checkForApprovals, appendStepMessages } from './ai-agent-stream.js';
|
|
|
4
4
|
import { pikkuState, getSingletonServices } from '../../pikku-state.js';
|
|
5
5
|
import { resolveModelConfig } from './ai-agent-model-config.js';
|
|
6
6
|
import { AIProviderNotConfiguredError } from '../../errors/errors.js';
|
|
7
|
+
import { randomUUID } from './ai-agent-utils.js';
|
|
7
8
|
function stripNulls(obj) {
|
|
8
9
|
if (obj === null)
|
|
9
10
|
return undefined;
|
|
@@ -19,7 +20,6 @@ function stripNulls(obj) {
|
|
|
19
20
|
}
|
|
20
21
|
return result;
|
|
21
22
|
}
|
|
22
|
-
import { randomUUID } from 'crypto';
|
|
23
23
|
function extractStructuredAssistantOutput(object) {
|
|
24
24
|
if (!object || typeof object !== 'object') {
|
|
25
25
|
return { text: null, uiSpec: null };
|
|
@@ -33,6 +33,7 @@ function extractStructuredAssistantOutput(object) {
|
|
|
33
33
|
export async function runAIAgent(agentName, input, params, agentSessionMap) {
|
|
34
34
|
const sessionMap = agentSessionMap ?? new Map();
|
|
35
35
|
const { agent, agentRunner, storage, memoryConfig, threadId, userMessage, runnerParams, maxSteps, missingRpcs, workingMemorySchemaName, } = await prepareAgentRun(agentName, input, params, sessionMap);
|
|
36
|
+
runnerParams.agentId = agentName;
|
|
36
37
|
const singletonServices = getSingletonServices();
|
|
37
38
|
const { aiRunState } = singletonServices;
|
|
38
39
|
if (!aiRunState) {
|
|
@@ -417,6 +418,7 @@ async function continueAfterToolResultSync(run, agent, packageName, resolvedName
|
|
|
417
418
|
outputSchema: meta?.outputSchema
|
|
418
419
|
? pikkuState(packageName, 'misc', 'schemas').get(meta.outputSchema)
|
|
419
420
|
: undefined,
|
|
421
|
+
agentId: run.agentName,
|
|
420
422
|
};
|
|
421
423
|
try {
|
|
422
424
|
const accumulatedSteps = [];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { pikkuState, getSingletonServices } from '../../pikku-state.js';
|
|
2
2
|
import { AIProviderNotConfiguredError } from '../../errors/errors.js';
|
|
3
|
+
import { randomUUID } from './ai-agent-utils.js';
|
|
3
4
|
import { combineChannelMiddleware, wrapChannelWithMiddleware, } from '../channel/channel-middleware-runner.js';
|
|
4
|
-
import { randomUUID } from 'crypto';
|
|
5
5
|
import { resolveMemoryServices, loadContextMessages, trimMessages, getWorkingMemoryMiddleware, } from './ai-agent-memory.js';
|
|
6
6
|
import { prepareAgentRun, resolveAgent, buildInstructions, buildToolDefs, createScopedChannel, ToolApprovalRequired, ToolCredentialRequired, } from './ai-agent-prepare.js';
|
|
7
7
|
import { resolveModelConfig } from './ai-agent-model-config.js';
|
|
@@ -354,6 +354,7 @@ export async function streamAIAgent(agentName, input, channel, params, agentSess
|
|
|
354
354
|
const streamContext = { channel, options };
|
|
355
355
|
// delegateState is attached after prepareAgentRun resolves the agent config
|
|
356
356
|
const { agent, packageName, resolvedName, agentRunner, storage, memoryConfig, threadId, userMessage, runnerParams, maxSteps, missingRpcs, workingMemorySchemaName, } = await prepareAgentRun(agentName, normalizedInput, params, sessionMap, streamContext);
|
|
357
|
+
runnerParams.agentId = agentName;
|
|
357
358
|
const singletonServices = getSingletonServices();
|
|
358
359
|
const { aiRunState } = singletonServices;
|
|
359
360
|
if (!aiRunState) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const randomUUID: () => string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const randomUUID = () => globalThis.crypto.randomUUID();
|
|
@@ -250,6 +250,7 @@ export class ContextAwareRPCService {
|
|
|
250
250
|
run: async (agentName, input) => {
|
|
251
251
|
const result = await runAIAgent(agentName, input, {
|
|
252
252
|
sessionService: this.options.sessionService,
|
|
253
|
+
getCredential: this.wire.getCredential?.bind(this.wire),
|
|
253
254
|
});
|
|
254
255
|
return {
|
|
255
256
|
runId: result.runId,
|
|
@@ -265,13 +266,19 @@ export class ContextAwareRPCService {
|
|
|
265
266
|
const channel = this.wire.channel;
|
|
266
267
|
if (!channel)
|
|
267
268
|
throw new Error('No channel available for streaming');
|
|
268
|
-
await streamAIAgent(agentName, input, channel, {
|
|
269
|
+
await streamAIAgent(agentName, input, channel, {
|
|
270
|
+
sessionService: this.options.sessionService,
|
|
271
|
+
getCredential: this.wire.getCredential?.bind(this.wire),
|
|
272
|
+
}, undefined, options);
|
|
269
273
|
},
|
|
270
274
|
resume: async (runId, input, options) => {
|
|
271
275
|
const channel = this.wire.channel;
|
|
272
276
|
if (!channel)
|
|
273
277
|
throw new Error('No channel available for streaming');
|
|
274
|
-
await resumeAIAgent({ runId, ...input }, channel, {
|
|
278
|
+
await resumeAIAgent({ runId, ...input }, channel, {
|
|
279
|
+
sessionService: this.options.sessionService,
|
|
280
|
+
getCredential: this.wire.getCredential?.bind(this.wire),
|
|
281
|
+
}, options);
|
|
275
282
|
},
|
|
276
283
|
approve: async (runId, approvals, expectedAgentName) => {
|
|
277
284
|
const result = await resumeAIAgentSync(runId, approvals, {
|
package/package.json
CHANGED
|
@@ -785,6 +785,55 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
785
785
|
assert.deepEqual(receivedSession, { userId: 'user-1', loaded: true })
|
|
786
786
|
})
|
|
787
787
|
|
|
788
|
+
test('should expose middleware-set session to createWireServices credential lookups', async () => {
|
|
789
|
+
let credentialLookups = 0
|
|
790
|
+
let receivedCredential: any
|
|
791
|
+
const sessionService = new PikkuSessionService<any>()
|
|
792
|
+
|
|
793
|
+
addTestFunction('credentialWire', {
|
|
794
|
+
middleware: [
|
|
795
|
+
async (_services: any, wire: any, next: any) => {
|
|
796
|
+
wire.setSession({ userId: 'user-1' })
|
|
797
|
+
await next()
|
|
798
|
+
},
|
|
799
|
+
],
|
|
800
|
+
func: async (_services: any, _data: any, wire: any) => {
|
|
801
|
+
return wire.getSession()
|
|
802
|
+
},
|
|
803
|
+
})
|
|
804
|
+
|
|
805
|
+
await runPikkuFunc('rpc', Math.random().toString(), 'credentialWire', {
|
|
806
|
+
singletonServices: {
|
|
807
|
+
...mockSingletonServices,
|
|
808
|
+
credentialService: {
|
|
809
|
+
getAll: async (userId: string) => {
|
|
810
|
+
credentialLookups++
|
|
811
|
+
assert.equal(userId, 'user-1')
|
|
812
|
+
return {
|
|
813
|
+
'user-oauth': { accessToken: 'token-1' },
|
|
814
|
+
}
|
|
815
|
+
},
|
|
816
|
+
},
|
|
817
|
+
} as any,
|
|
818
|
+
createWireServices: async (_services: any, wire: any) => {
|
|
819
|
+
receivedCredential = await wire.getCredential('user-oauth')
|
|
820
|
+
return {}
|
|
821
|
+
},
|
|
822
|
+
data: () => ({}),
|
|
823
|
+
auth: false,
|
|
824
|
+
wire: {
|
|
825
|
+
session: undefined,
|
|
826
|
+
setSession: (session: any) => sessionService.setInitial(session),
|
|
827
|
+
getSession: () => sessionService.get(),
|
|
828
|
+
hasSessionChanged: () => sessionService.sessionChanged,
|
|
829
|
+
} as any,
|
|
830
|
+
sessionService,
|
|
831
|
+
})
|
|
832
|
+
|
|
833
|
+
assert.equal(credentialLookups, 1)
|
|
834
|
+
assert.deepEqual(receivedCredential, { accessToken: 'token-1' })
|
|
835
|
+
})
|
|
836
|
+
|
|
788
837
|
test('should pass addon-scoped singleton services and wrap bare workflow names', async () => {
|
|
789
838
|
pikkuState(null, 'addons', 'packages').set('stripe', {
|
|
790
839
|
package: '@addon/stripe',
|
|
@@ -969,6 +1018,212 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
969
1018
|
assert.equal(result, true)
|
|
970
1019
|
assert.ok(receivedWire.rpc)
|
|
971
1020
|
})
|
|
1021
|
+
|
|
1022
|
+
test('should expose resolved audit metadata on the wire for audited functions', async () => {
|
|
1023
|
+
let receivedAudit: any
|
|
1024
|
+
|
|
1025
|
+
addTestFunction('auditedWire', {
|
|
1026
|
+
audit: { durability: 'transactional' },
|
|
1027
|
+
func: async (_services: any, _data: any, wire: any) => {
|
|
1028
|
+
receivedAudit = wire.audit
|
|
1029
|
+
return 'ok'
|
|
1030
|
+
},
|
|
1031
|
+
})
|
|
1032
|
+
|
|
1033
|
+
const result = await runPikkuFunc('rpc', 'wire-audit-on', 'auditedWire', {
|
|
1034
|
+
singletonServices: mockSingletonServices,
|
|
1035
|
+
data: () => ({}),
|
|
1036
|
+
auth: false,
|
|
1037
|
+
wire: {},
|
|
1038
|
+
})
|
|
1039
|
+
|
|
1040
|
+
assert.equal(result, 'ok')
|
|
1041
|
+
assert.deepEqual(receivedAudit, { durability: 'transactional' })
|
|
1042
|
+
})
|
|
1043
|
+
|
|
1044
|
+
test('should leave wire audit metadata undefined when audit is disabled', async () => {
|
|
1045
|
+
let receivedWire: any
|
|
1046
|
+
|
|
1047
|
+
addTestFunction('plainWire', {
|
|
1048
|
+
func: async (_services: any, _data: any, wire: any) => {
|
|
1049
|
+
receivedWire = wire
|
|
1050
|
+
return 'ok'
|
|
1051
|
+
},
|
|
1052
|
+
})
|
|
1053
|
+
|
|
1054
|
+
const result = await runPikkuFunc('rpc', 'wire-audit-off', 'plainWire', {
|
|
1055
|
+
singletonServices: mockSingletonServices,
|
|
1056
|
+
data: () => ({}),
|
|
1057
|
+
auth: false,
|
|
1058
|
+
wire: {},
|
|
1059
|
+
})
|
|
1060
|
+
|
|
1061
|
+
assert.equal(result, 'ok')
|
|
1062
|
+
assert.equal(receivedWire.audit, undefined)
|
|
1063
|
+
})
|
|
1064
|
+
|
|
1065
|
+
test('should restore parent audit metadata after nested non-audited calls', async () => {
|
|
1066
|
+
let auditBeforeChild: any
|
|
1067
|
+
let auditAfterChild: any
|
|
1068
|
+
|
|
1069
|
+
addTestFunction('childPlain', {
|
|
1070
|
+
func: async () => 'child-ok',
|
|
1071
|
+
})
|
|
1072
|
+
|
|
1073
|
+
addTestFunction('parentAudited', {
|
|
1074
|
+
audit: true,
|
|
1075
|
+
func: async (_services: any, _data: any, wire: any) => {
|
|
1076
|
+
auditBeforeChild = wire.audit
|
|
1077
|
+
await runPikkuFunc('rpc', 'nested-child-wire', 'childPlain', {
|
|
1078
|
+
singletonServices: mockSingletonServices,
|
|
1079
|
+
data: () => ({}),
|
|
1080
|
+
auth: false,
|
|
1081
|
+
wire,
|
|
1082
|
+
})
|
|
1083
|
+
auditAfterChild = wire.audit
|
|
1084
|
+
return 'parent-ok'
|
|
1085
|
+
},
|
|
1086
|
+
})
|
|
1087
|
+
|
|
1088
|
+
const result = await runPikkuFunc(
|
|
1089
|
+
'rpc',
|
|
1090
|
+
'nested-parent-wire',
|
|
1091
|
+
'parentAudited',
|
|
1092
|
+
{
|
|
1093
|
+
singletonServices: mockSingletonServices,
|
|
1094
|
+
data: () => ({}),
|
|
1095
|
+
auth: false,
|
|
1096
|
+
wire: {},
|
|
1097
|
+
}
|
|
1098
|
+
)
|
|
1099
|
+
|
|
1100
|
+
assert.equal(result, 'parent-ok')
|
|
1101
|
+
assert.deepEqual(auditBeforeChild, { durability: 'best-effort' })
|
|
1102
|
+
assert.deepEqual(auditAfterChild, { durability: 'best-effort' })
|
|
1103
|
+
})
|
|
1104
|
+
|
|
1105
|
+
test('should preserve parent audit metadata across rpc sub-calls to non-audited functions', async () => {
|
|
1106
|
+
let parentAuditBeforeChild: any
|
|
1107
|
+
let parentAuditAfterChild: any
|
|
1108
|
+
let parentFunctionIdBeforeChild: any
|
|
1109
|
+
let parentFunctionIdAfterChild: any
|
|
1110
|
+
let childAudit: any
|
|
1111
|
+
let childFunctionId: any
|
|
1112
|
+
|
|
1113
|
+
addTestFunction('rpcChildPlain', {
|
|
1114
|
+
func: async (_services: any, _data: any, wire: any) => {
|
|
1115
|
+
childAudit = wire.audit
|
|
1116
|
+
childFunctionId = wire.functionId
|
|
1117
|
+
return 'child-ok'
|
|
1118
|
+
},
|
|
1119
|
+
})
|
|
1120
|
+
|
|
1121
|
+
addTestFunction('rpcParentAudited', {
|
|
1122
|
+
audit: true,
|
|
1123
|
+
func: async (_services: any, _data: any, wire: any) => {
|
|
1124
|
+
parentAuditBeforeChild = wire.audit
|
|
1125
|
+
parentFunctionIdBeforeChild = wire.functionId
|
|
1126
|
+
const childResult = await wire.rpc.invoke('rpcChildPlain', {})
|
|
1127
|
+
parentAuditAfterChild = wire.audit
|
|
1128
|
+
parentFunctionIdAfterChild = wire.functionId
|
|
1129
|
+
return childResult
|
|
1130
|
+
},
|
|
1131
|
+
})
|
|
1132
|
+
|
|
1133
|
+
const result = await runPikkuFunc(
|
|
1134
|
+
'rpc',
|
|
1135
|
+
'rpc-parent-wire',
|
|
1136
|
+
'rpcParentAudited',
|
|
1137
|
+
{
|
|
1138
|
+
singletonServices: mockSingletonServices,
|
|
1139
|
+
data: () => ({}),
|
|
1140
|
+
auth: false,
|
|
1141
|
+
wire: {},
|
|
1142
|
+
}
|
|
1143
|
+
)
|
|
1144
|
+
|
|
1145
|
+
assert.equal(result, 'child-ok')
|
|
1146
|
+
assert.deepEqual(parentAuditBeforeChild, { durability: 'best-effort' })
|
|
1147
|
+
assert.deepEqual(parentAuditAfterChild, { durability: 'best-effort' })
|
|
1148
|
+
assert.equal(parentFunctionIdBeforeChild, 'rpcParentAudited')
|
|
1149
|
+
assert.equal(parentFunctionIdAfterChild, 'rpcParentAudited')
|
|
1150
|
+
assert.equal(childAudit, undefined)
|
|
1151
|
+
assert.equal(childFunctionId, 'rpcChildPlain')
|
|
1152
|
+
})
|
|
1153
|
+
|
|
1154
|
+
test('should bind audited rpc sub-calls to the child invocation context', async () => {
|
|
1155
|
+
let parentAuditBeforeChild: any
|
|
1156
|
+
let parentAuditAfterChild: any
|
|
1157
|
+
let parentFunctionIdAfterChild: any
|
|
1158
|
+
let childAudit: any
|
|
1159
|
+
let childFunctionId: any
|
|
1160
|
+
|
|
1161
|
+
addTestFunction('rpcChildAudited', {
|
|
1162
|
+
audit: { durability: 'transactional' },
|
|
1163
|
+
func: async (_services: any, _data: any, wire: any) => {
|
|
1164
|
+
childAudit = wire.audit
|
|
1165
|
+
childFunctionId = wire.functionId
|
|
1166
|
+
return 'child-audited-ok'
|
|
1167
|
+
},
|
|
1168
|
+
})
|
|
1169
|
+
|
|
1170
|
+
addTestFunction('rpcParentAuditedChild', {
|
|
1171
|
+
audit: true,
|
|
1172
|
+
func: async (_services: any, _data: any, wire: any) => {
|
|
1173
|
+
parentAuditBeforeChild = wire.audit
|
|
1174
|
+
const childResult = await wire.rpc.invoke('rpcChildAudited', {})
|
|
1175
|
+
parentAuditAfterChild = wire.audit
|
|
1176
|
+
parentFunctionIdAfterChild = wire.functionId
|
|
1177
|
+
return childResult
|
|
1178
|
+
},
|
|
1179
|
+
})
|
|
1180
|
+
|
|
1181
|
+
const result = await runPikkuFunc(
|
|
1182
|
+
'rpc',
|
|
1183
|
+
'rpc-parent-child-audit-wire',
|
|
1184
|
+
'rpcParentAuditedChild',
|
|
1185
|
+
{
|
|
1186
|
+
singletonServices: mockSingletonServices,
|
|
1187
|
+
data: () => ({}),
|
|
1188
|
+
auth: false,
|
|
1189
|
+
wire: {},
|
|
1190
|
+
}
|
|
1191
|
+
)
|
|
1192
|
+
|
|
1193
|
+
assert.equal(result, 'child-audited-ok')
|
|
1194
|
+
assert.deepEqual(parentAuditBeforeChild, { durability: 'best-effort' })
|
|
1195
|
+
assert.deepEqual(parentAuditAfterChild, { durability: 'best-effort' })
|
|
1196
|
+
assert.equal(parentFunctionIdAfterChild, 'rpcParentAuditedChild')
|
|
1197
|
+
assert.deepEqual(childAudit, { durability: 'transactional' })
|
|
1198
|
+
assert.equal(childFunctionId, 'rpcChildAudited')
|
|
1199
|
+
})
|
|
1200
|
+
|
|
1201
|
+
test('should expose the resolved base function id after version fallback', async () => {
|
|
1202
|
+
let receivedFunctionId: any
|
|
1203
|
+
|
|
1204
|
+
addTestFunction('versionedBase', {
|
|
1205
|
+
audit: true,
|
|
1206
|
+
func: async (_services: any, _data: any, wire: any) => {
|
|
1207
|
+
receivedFunctionId = wire.functionId
|
|
1208
|
+
return 'ok'
|
|
1209
|
+
},
|
|
1210
|
+
})
|
|
1211
|
+
|
|
1212
|
+
const result = await runPikkuFunc(
|
|
1213
|
+
'rpc',
|
|
1214
|
+
'wire-version-fallback',
|
|
1215
|
+
'versionedBase@v2',
|
|
1216
|
+
{
|
|
1217
|
+
singletonServices: mockSingletonServices,
|
|
1218
|
+
data: () => ({}),
|
|
1219
|
+
auth: false,
|
|
1220
|
+
wire: {},
|
|
1221
|
+
}
|
|
1222
|
+
)
|
|
1223
|
+
|
|
1224
|
+
assert.equal(result, 'ok')
|
|
1225
|
+
assert.equal(receivedFunctionId, 'versionedBase')
|
|
1226
|
+
})
|
|
972
1227
|
})
|
|
973
1228
|
|
|
974
1229
|
describe('function-runner helpers', () => {
|