@pikku/core 0.12.9 → 0.12.11
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 +13 -0
- package/dist/function/function-runner.d.ts +3 -1
- package/dist/function/function-runner.js +5 -1
- package/dist/services/credential-service.d.ts +40 -0
- package/dist/services/credential-service.js +1 -0
- package/dist/services/credential-wire-service.d.ts +13 -0
- package/dist/services/credential-wire-service.js +31 -0
- package/dist/services/index.d.ts +5 -0
- package/dist/services/index.js +3 -0
- package/dist/services/local-credential-service.d.ts +10 -0
- package/dist/services/local-credential-service.js +33 -0
- package/dist/services/typed-credential-service.d.ts +27 -0
- package/dist/services/typed-credential-service.js +40 -0
- package/dist/testing/service-tests.d.ts +8 -0
- package/dist/testing/service-tests.js +106 -0
- package/dist/types/core.types.d.ts +7 -0
- package/dist/wirings/ai-agent/agent-dynamic-workflow.js +173 -53
- package/dist/wirings/cli/channel/cli-raw-channel-runner.d.ts +15 -0
- package/dist/wirings/cli/channel/cli-raw-channel-runner.js +58 -0
- package/dist/wirings/cli/channel/index.d.ts +1 -0
- package/dist/wirings/cli/channel/index.js +1 -0
- package/dist/wirings/credential/credential.types.d.ts +24 -0
- package/dist/wirings/credential/credential.types.js +1 -0
- package/dist/wirings/credential/index.d.ts +3 -0
- package/dist/wirings/credential/index.js +2 -0
- package/dist/wirings/credential/validate-credential-definitions.d.ts +6 -0
- package/dist/wirings/credential/validate-credential-definitions.js +38 -0
- package/dist/wirings/credential/wire-credential.d.ts +48 -0
- package/dist/wirings/credential/wire-credential.js +47 -0
- package/dist/wirings/http/http-runner.js +4 -0
- package/dist/wirings/oauth2/index.d.ts +2 -1
- package/dist/wirings/oauth2/index.js +1 -1
- package/dist/wirings/oauth2/oauth2-client.js +4 -8
- package/dist/wirings/oauth2/oauth2-routes.d.ts +35 -0
- package/dist/wirings/oauth2/oauth2-routes.js +146 -0
- package/dist/wirings/oauth2/oauth2.types.d.ts +0 -26
- package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -1
- package/dist/wirings/workflow/graph/graph-runner.js +15 -0
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +2 -2
- package/dist/wirings/workflow/pikku-workflow-service.js +40 -8
- package/package.json +2 -1
- package/src/function/function-runner.ts +11 -0
- package/src/services/credential-service.ts +44 -0
- package/src/services/credential-wire-service.ts +44 -0
- package/src/services/index.ts +12 -0
- package/src/services/local-credential-service.ts +41 -0
- package/src/services/typed-credential-service.ts +75 -0
- package/src/testing/service-tests.ts +139 -0
- package/src/types/core.types.ts +7 -0
- package/src/wirings/ai-agent/agent-dynamic-workflow.ts +378 -237
- package/src/wirings/cli/channel/cli-raw-channel-runner.ts +89 -0
- package/src/wirings/cli/channel/index.ts +1 -0
- package/src/wirings/credential/credential.types.ts +28 -0
- package/src/wirings/credential/index.ts +8 -0
- package/src/wirings/credential/validate-credential-definitions.ts +64 -0
- package/src/wirings/credential/wire-credential.ts +49 -0
- package/src/wirings/http/http-runner.ts +7 -0
- package/src/wirings/oauth2/index.ts +2 -1
- package/src/wirings/oauth2/oauth2-client.ts +4 -8
- package/src/wirings/oauth2/oauth2-routes.ts +234 -0
- package/src/wirings/oauth2/oauth2.types.ts +0 -27
- package/src/wirings/workflow/graph/graph-runner.ts +33 -1
- package/src/wirings/workflow/pikku-workflow-service.ts +55 -9
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/wirings/oauth2/wire-oauth2-credential.d.ts +0 -20
- package/dist/wirings/oauth2/wire-oauth2-credential.js +0 -21
- package/src/wirings/oauth2/wire-oauth2-credential.ts +0 -23
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
## 0.12.4
|
|
2
2
|
|
|
3
|
+
## 0.12.11
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 4e52200: Add \_\_raw CLI channel handler for server-side arg parsing. Enables WebSocket CLI clients to send raw args without needing client-side command metadata.
|
|
8
|
+
|
|
9
|
+
## 0.12.10
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 0f59432: Add per-user credential system with CredentialService, OAuth2 route handlers, and KyselyCredentialService with envelope encryption
|
|
14
|
+
- 52b64d1: Provide workflow wire to graph nodes for sleep/suspend support. Graph nodes now receive a workflow wire alongside the graph wire, enabling tools like `graph:sleep` to work in graph execution context. Improves dynamic workflow system instructions with output path documentation, nested path examples, and design principles for AI agents.
|
|
15
|
+
|
|
3
16
|
## 0.12.9
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
|
@@ -2,11 +2,12 @@ import type { CoreServices, CoreUserSession, CorePikkuMiddleware, PikkuWiringTyp
|
|
|
2
2
|
import type { CorePikkuChannelMiddleware } from '../wirings/channel/channel.types.js';
|
|
3
3
|
import type { CorePermissionGroup, CorePikkuFunctionConfig, CorePikkuPermission } from './functions.types.js';
|
|
4
4
|
import type { SessionService } from '../services/user-session-service.js';
|
|
5
|
+
import type { PikkuCredentialWireService } from '../services/credential-wire-service.js';
|
|
5
6
|
export declare const addFunction: (funcName: string, funcConfig: CorePikkuFunctionConfig<any, any>, packageName?: string | null) => void;
|
|
6
7
|
export declare const getFunctionNames: (packageName?: string | null) => string[];
|
|
7
8
|
export declare const getAllFunctionNames: () => string[];
|
|
8
9
|
export declare const runPikkuFuncDirectly: <In, Out>(funcName: string, allServices: CoreServices, wire: PikkuWire, data: In, userSession?: SessionService<CoreUserSession>, packageName?: string | null) => Promise<Out>;
|
|
9
|
-
export declare const runPikkuFunc: <In = any, Out = any>(wireType: PikkuWiringTypes, wireId: string, funcName: string, { singletonServices, createWireServices, data, auth: wiringAuth, inheritedMiddleware, wireMiddleware, inheritedChannelMiddleware, wireChannelMiddleware, inheritedPermissions, wirePermissions, coerceDataFromSchema, tags, wire, sessionService, packageName, }: {
|
|
10
|
+
export declare const runPikkuFunc: <In = any, Out = any>(wireType: PikkuWiringTypes, wireId: string, funcName: string, { singletonServices, createWireServices, data, auth: wiringAuth, inheritedMiddleware, wireMiddleware, inheritedChannelMiddleware, wireChannelMiddleware, inheritedPermissions, wirePermissions, coerceDataFromSchema, tags, wire, sessionService, credentialWireService, packageName, }: {
|
|
10
11
|
singletonServices: CoreSingletonServices;
|
|
11
12
|
createWireServices?: CreateWireServices;
|
|
12
13
|
data: () => Promise<In> | In;
|
|
@@ -21,5 +22,6 @@ export declare const runPikkuFunc: <In = any, Out = any>(wireType: PikkuWiringTy
|
|
|
21
22
|
tags?: string[];
|
|
22
23
|
wire: PikkuWire;
|
|
23
24
|
sessionService?: SessionService<CoreUserSession>;
|
|
25
|
+
credentialWireService?: PikkuCredentialWireService;
|
|
24
26
|
packageName?: string | null;
|
|
25
27
|
}) => Promise<Out>;
|
|
@@ -6,6 +6,7 @@ import { coerceTopLevelDataFromSchema, validateSchema } from '../schema.js';
|
|
|
6
6
|
import { parseVersionedId } from '../version.js';
|
|
7
7
|
import { createFunctionSessionWireProps } from '../services/user-session-service.js';
|
|
8
8
|
import { ForbiddenError, ReadonlySessionError } from '../errors/errors.js';
|
|
9
|
+
import { createWireServicesCredentialWireProps } from '../services/credential-wire-service.js';
|
|
9
10
|
import { rpcService } from '../wirings/rpc/rpc-runner.js';
|
|
10
11
|
import { closeWireServices } from '../utils.js';
|
|
11
12
|
/**
|
|
@@ -70,7 +71,7 @@ export const runPikkuFuncDirectly = async (funcName, allServices, wire, data, us
|
|
|
70
71
|
};
|
|
71
72
|
return (await funcConfig.func(allServices, data, wireWithSession));
|
|
72
73
|
};
|
|
73
|
-
export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServices, createWireServices, data, auth: wiringAuth, inheritedMiddleware, wireMiddleware, inheritedChannelMiddleware, wireChannelMiddleware, inheritedPermissions, wirePermissions, coerceDataFromSchema, tags = [], wire, sessionService, packageName = null, }) => {
|
|
74
|
+
export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServices, createWireServices, data, auth: wiringAuth, inheritedMiddleware, wireMiddleware, inheritedChannelMiddleware, wireChannelMiddleware, inheritedPermissions, wirePermissions, coerceDataFromSchema, tags = [], wire, sessionService, credentialWireService, packageName = null, }) => {
|
|
74
75
|
wire.wireType ??= wireType;
|
|
75
76
|
wire.wireId ??= wireId;
|
|
76
77
|
const funcMap = pikkuState(packageName, 'function', 'functions');
|
|
@@ -188,6 +189,9 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
|
|
|
188
189
|
packageName,
|
|
189
190
|
});
|
|
190
191
|
}
|
|
192
|
+
if (credentialWireService) {
|
|
193
|
+
Object.assign(resolvedWire, createWireServicesCredentialWireProps(credentialWireService));
|
|
194
|
+
}
|
|
191
195
|
const wireServices = await resolvedCreateWireServices?.(resolvedSingletonServices, resolvedWire);
|
|
192
196
|
try {
|
|
193
197
|
const services = wireServices && Object.keys(wireServices).length > 0
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface for managing dynamic/managed credentials.
|
|
3
|
+
* Used for OAuth tokens, per-user API keys, and other credentials
|
|
4
|
+
* that change at runtime (as opposed to SecretService which is for
|
|
5
|
+
* static, developer-configured values).
|
|
6
|
+
*/
|
|
7
|
+
export interface CredentialService {
|
|
8
|
+
/**
|
|
9
|
+
* Retrieves a credential by name and optional user ID.
|
|
10
|
+
* @param name - The credential name (e.g. 'stripe', 'google-sheets').
|
|
11
|
+
* @param userId - Optional user ID for per-user credentials. Omit for platform-level.
|
|
12
|
+
* @returns The credential value, or null if not found.
|
|
13
|
+
*/
|
|
14
|
+
get<T = unknown>(name: string, userId?: string): Promise<T | null>;
|
|
15
|
+
/**
|
|
16
|
+
* Stores a credential.
|
|
17
|
+
* @param name - The credential name.
|
|
18
|
+
* @param value - The credential value to store.
|
|
19
|
+
* @param userId - Optional user ID for per-user credentials. Omit for platform-level.
|
|
20
|
+
*/
|
|
21
|
+
set(name: string, value: unknown, userId?: string): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Deletes a credential.
|
|
24
|
+
* @param name - The credential name.
|
|
25
|
+
* @param userId - Optional user ID for per-user credentials. Omit for platform-level.
|
|
26
|
+
*/
|
|
27
|
+
delete(name: string, userId?: string): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Checks if a credential exists.
|
|
30
|
+
* @param name - The credential name.
|
|
31
|
+
* @param userId - Optional user ID for per-user credentials. Omit for platform-level.
|
|
32
|
+
*/
|
|
33
|
+
has(name: string, userId?: string): Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Retrieves all credentials for a user.
|
|
36
|
+
* @param userId - The user ID.
|
|
37
|
+
* @returns A record of credential name to value.
|
|
38
|
+
*/
|
|
39
|
+
getAll(userId: string): Promise<Record<string, unknown>>;
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare class PikkuCredentialWireService {
|
|
2
|
+
private credentials;
|
|
3
|
+
set(name: string, value: unknown): void;
|
|
4
|
+
getAll(): Record<string, unknown>;
|
|
5
|
+
getScoped(allowedNames: string[]): Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
export declare function createMiddlewareCredentialWireProps(credentialWire: PikkuCredentialWireService): {
|
|
8
|
+
setCredential: (name: string, value: unknown) => void;
|
|
9
|
+
};
|
|
10
|
+
export declare function createWireServicesCredentialWireProps(credentialWire: PikkuCredentialWireService, allowedNames?: string[]): {
|
|
11
|
+
setCredential: (name: string, value: unknown) => void;
|
|
12
|
+
getCredentials: () => Record<string, unknown>;
|
|
13
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export class PikkuCredentialWireService {
|
|
2
|
+
credentials = {};
|
|
3
|
+
set(name, value) {
|
|
4
|
+
this.credentials[name] = value;
|
|
5
|
+
}
|
|
6
|
+
getAll() {
|
|
7
|
+
return this.credentials;
|
|
8
|
+
}
|
|
9
|
+
getScoped(allowedNames) {
|
|
10
|
+
const scoped = {};
|
|
11
|
+
for (const name of allowedNames) {
|
|
12
|
+
if (name in this.credentials) {
|
|
13
|
+
scoped[name] = this.credentials[name];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return scoped;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function createMiddlewareCredentialWireProps(credentialWire) {
|
|
20
|
+
return {
|
|
21
|
+
setCredential: (name, value) => credentialWire.set(name, value),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function createWireServicesCredentialWireProps(credentialWire, allowedNames) {
|
|
25
|
+
return {
|
|
26
|
+
setCredential: (name, value) => credentialWire.set(name, value),
|
|
27
|
+
getCredentials: () => allowedNames
|
|
28
|
+
? credentialWire.getScoped(allowedNames)
|
|
29
|
+
: credentialWire.getAll(),
|
|
30
|
+
};
|
|
31
|
+
}
|
package/dist/services/index.d.ts
CHANGED
|
@@ -5,8 +5,10 @@ export { LogLevel } from './logger.js';
|
|
|
5
5
|
export { ScopedSecretService } from './scoped-secret-service.js';
|
|
6
6
|
export { PikkuSessionService, createMiddlewareSessionWireProps, createFunctionSessionWireProps, } from './user-session-service.js';
|
|
7
7
|
export { TypedSecretService } from './typed-secret-service.js';
|
|
8
|
+
export { PikkuCredentialWireService, createMiddlewareCredentialWireProps, createWireServicesCredentialWireProps, } from './credential-wire-service.js';
|
|
8
9
|
export { TypedVariablesService } from './typed-variables-service.js';
|
|
9
10
|
export { LocalSecretService } from './local-secrets.js';
|
|
11
|
+
export { LocalCredentialService } from './local-credential-service.js';
|
|
10
12
|
export { LocalVariablesService } from './local-variables.js';
|
|
11
13
|
export { ConsoleLogger } from './logger-console.js';
|
|
12
14
|
export { InMemoryWorkflowService } from './in-memory-workflow-service.js';
|
|
@@ -28,4 +30,7 @@ export type { AIStorageService } from './ai-storage-service.js';
|
|
|
28
30
|
export type { AIAgentRunnerParams, AIAgentRunnerResult, AIAgentStepResult, AIAgentRunnerService, } from './ai-agent-runner-service.js';
|
|
29
31
|
export type { CreateRunInput, AIRunStateService, } from './ai-run-state-service.js';
|
|
30
32
|
export type { CredentialStatus, CredentialMeta, } from './typed-secret-service.js';
|
|
33
|
+
export type { CredentialService } from './credential-service.js';
|
|
34
|
+
export { TypedCredentialService } from './typed-credential-service.js';
|
|
35
|
+
export type { CredentialStatusInfo, CredentialMetaInfo, } from './typed-credential-service.js';
|
|
31
36
|
export type { VariableStatus, VariableMeta } from './typed-variables-service.js';
|
package/dist/services/index.js
CHANGED
|
@@ -5,11 +5,14 @@ export { LogLevel } from './logger.js';
|
|
|
5
5
|
export { ScopedSecretService } from './scoped-secret-service.js';
|
|
6
6
|
export { PikkuSessionService, createMiddlewareSessionWireProps, createFunctionSessionWireProps, } from './user-session-service.js';
|
|
7
7
|
export { TypedSecretService } from './typed-secret-service.js';
|
|
8
|
+
export { PikkuCredentialWireService, createMiddlewareCredentialWireProps, createWireServicesCredentialWireProps, } from './credential-wire-service.js';
|
|
8
9
|
export { TypedVariablesService } from './typed-variables-service.js';
|
|
9
10
|
export { LocalSecretService } from './local-secrets.js';
|
|
11
|
+
export { LocalCredentialService } from './local-credential-service.js';
|
|
10
12
|
export { LocalVariablesService } from './local-variables.js';
|
|
11
13
|
export { ConsoleLogger } from './logger-console.js';
|
|
12
14
|
export { InMemoryWorkflowService } from './in-memory-workflow-service.js';
|
|
13
15
|
export { InMemoryTriggerService } from './in-memory-trigger-service.js';
|
|
14
16
|
export { InMemoryAIRunStateService } from './in-memory-ai-run-state-service.js';
|
|
15
17
|
export { LocalGatewayService } from './local-gateway-service.js';
|
|
18
|
+
export { TypedCredentialService } from './typed-credential-service.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CredentialService } from './credential-service.js';
|
|
2
|
+
export declare class LocalCredentialService implements CredentialService {
|
|
3
|
+
private store;
|
|
4
|
+
private makeKey;
|
|
5
|
+
get<T = unknown>(name: string, userId?: string): Promise<T | null>;
|
|
6
|
+
set(name: string, value: unknown, userId?: string): Promise<void>;
|
|
7
|
+
delete(name: string, userId?: string): Promise<void>;
|
|
8
|
+
has(name: string, userId?: string): Promise<boolean>;
|
|
9
|
+
getAll(userId: string): Promise<Record<string, unknown>>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export class LocalCredentialService {
|
|
2
|
+
store = new Map();
|
|
3
|
+
makeKey(name, userId) {
|
|
4
|
+
return userId ? `${userId}:${name}` : name;
|
|
5
|
+
}
|
|
6
|
+
async get(name, userId) {
|
|
7
|
+
const key = this.makeKey(name, userId);
|
|
8
|
+
const value = this.store.get(key);
|
|
9
|
+
return value ?? null;
|
|
10
|
+
}
|
|
11
|
+
async set(name, value, userId) {
|
|
12
|
+
const key = this.makeKey(name, userId);
|
|
13
|
+
this.store.set(key, value);
|
|
14
|
+
}
|
|
15
|
+
async delete(name, userId) {
|
|
16
|
+
const key = this.makeKey(name, userId);
|
|
17
|
+
this.store.delete(key);
|
|
18
|
+
}
|
|
19
|
+
async has(name, userId) {
|
|
20
|
+
const key = this.makeKey(name, userId);
|
|
21
|
+
return this.store.has(key);
|
|
22
|
+
}
|
|
23
|
+
async getAll(userId) {
|
|
24
|
+
const prefix = `${userId}:`;
|
|
25
|
+
const result = {};
|
|
26
|
+
for (const [key, value] of this.store) {
|
|
27
|
+
if (key.startsWith(prefix)) {
|
|
28
|
+
result[key.slice(prefix.length)] = value;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CredentialService } from './credential-service.js';
|
|
2
|
+
export interface CredentialStatusInfo {
|
|
3
|
+
name: string;
|
|
4
|
+
displayName: string;
|
|
5
|
+
isConfigured: boolean;
|
|
6
|
+
type: 'singleton' | 'wire';
|
|
7
|
+
oauth2?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export type CredentialMetaInfo = {
|
|
10
|
+
name: string;
|
|
11
|
+
displayName: string;
|
|
12
|
+
type: 'singleton' | 'wire';
|
|
13
|
+
oauth2?: boolean;
|
|
14
|
+
};
|
|
15
|
+
export declare class TypedCredentialService<TMap = Record<string, unknown>> implements CredentialService {
|
|
16
|
+
private credentials;
|
|
17
|
+
private credentialsMeta;
|
|
18
|
+
constructor(credentials: CredentialService, credentialsMeta: Record<string, CredentialMetaInfo>);
|
|
19
|
+
get<K extends keyof TMap & string>(name: K, userId?: string): Promise<TMap[K] | null>;
|
|
20
|
+
get<T = unknown>(name: string, userId?: string): Promise<T | null>;
|
|
21
|
+
set<K extends string>(name: K, value: K extends keyof TMap ? TMap[K] : unknown, userId?: string): Promise<void>;
|
|
22
|
+
delete(name: string, userId?: string): Promise<void>;
|
|
23
|
+
has(name: string, userId?: string): Promise<boolean>;
|
|
24
|
+
getAll(userId: string): Promise<Record<string, unknown>>;
|
|
25
|
+
getAllStatus(userId?: string): Promise<CredentialStatusInfo[]>;
|
|
26
|
+
getMissing(userId?: string): Promise<CredentialStatusInfo[]>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export class TypedCredentialService {
|
|
2
|
+
credentials;
|
|
3
|
+
credentialsMeta;
|
|
4
|
+
constructor(credentials, credentialsMeta) {
|
|
5
|
+
this.credentials = credentials;
|
|
6
|
+
this.credentialsMeta = credentialsMeta;
|
|
7
|
+
}
|
|
8
|
+
async get(name, userId) {
|
|
9
|
+
return this.credentials.get(name, userId);
|
|
10
|
+
}
|
|
11
|
+
async set(name, value, userId) {
|
|
12
|
+
return this.credentials.set(name, value, userId);
|
|
13
|
+
}
|
|
14
|
+
async delete(name, userId) {
|
|
15
|
+
return this.credentials.delete(name, userId);
|
|
16
|
+
}
|
|
17
|
+
async has(name, userId) {
|
|
18
|
+
return this.credentials.has(name, userId);
|
|
19
|
+
}
|
|
20
|
+
async getAll(userId) {
|
|
21
|
+
return this.credentials.getAll(userId);
|
|
22
|
+
}
|
|
23
|
+
async getAllStatus(userId) {
|
|
24
|
+
const results = [];
|
|
25
|
+
for (const [name, meta] of Object.entries(this.credentialsMeta)) {
|
|
26
|
+
results.push({
|
|
27
|
+
name,
|
|
28
|
+
displayName: meta.displayName,
|
|
29
|
+
isConfigured: await this.credentials.has(name, userId),
|
|
30
|
+
type: meta.type,
|
|
31
|
+
oauth2: meta.oauth2,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return results;
|
|
35
|
+
}
|
|
36
|
+
async getMissing(userId) {
|
|
37
|
+
const all = await this.getAllStatus(userId);
|
|
38
|
+
return all.filter((c) => !c.isConfigured);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -6,6 +6,7 @@ import type { DeploymentService } from '../services/deployment-service.js';
|
|
|
6
6
|
import type { AIStorageService } from '../services/ai-storage-service.js';
|
|
7
7
|
import type { AIRunStateService } from '../services/ai-run-state-service.js';
|
|
8
8
|
import type { SecretService } from '../services/secret-service.js';
|
|
9
|
+
import type { CredentialService } from '../services/credential-service.js';
|
|
9
10
|
import type { AgentRunService } from '../wirings/ai-agent/ai-agent.types.js';
|
|
10
11
|
export interface ServiceTestConfig {
|
|
11
12
|
name: string;
|
|
@@ -26,6 +27,13 @@ export interface ServiceTestConfig {
|
|
|
26
27
|
}) => Promise<SecretService & {
|
|
27
28
|
rotateKEK?(): Promise<number>;
|
|
28
29
|
}>;
|
|
30
|
+
credentialService?: (config: {
|
|
31
|
+
key: string;
|
|
32
|
+
keyVersion?: number;
|
|
33
|
+
previousKey?: string;
|
|
34
|
+
}) => Promise<CredentialService & {
|
|
35
|
+
rotateKEK?(): Promise<number>;
|
|
36
|
+
}>;
|
|
29
37
|
};
|
|
30
38
|
}
|
|
31
39
|
export declare function defineServiceTests(config: ServiceTestConfig): void;
|
|
@@ -570,6 +570,112 @@ export function defineServiceTests(config) {
|
|
|
570
570
|
});
|
|
571
571
|
});
|
|
572
572
|
}
|
|
573
|
+
if (services.credentialService) {
|
|
574
|
+
const factory = services.credentialService;
|
|
575
|
+
const kek = 'test-key-encryption-key-32chars!';
|
|
576
|
+
describe(`CredentialService [${name}]`, () => {
|
|
577
|
+
test('set and get (global)', async () => {
|
|
578
|
+
const service = await factory({ key: kek });
|
|
579
|
+
await service.set('stripe', { apiKey: 'sk-123' });
|
|
580
|
+
const result = await service.get('stripe');
|
|
581
|
+
assert.deepEqual(result, { apiKey: 'sk-123' });
|
|
582
|
+
});
|
|
583
|
+
test('get returns null for missing', async () => {
|
|
584
|
+
const service = await factory({ key: kek });
|
|
585
|
+
const result = await service.get('nonexistent');
|
|
586
|
+
assert.equal(result, null);
|
|
587
|
+
});
|
|
588
|
+
test('has returns true/false', async () => {
|
|
589
|
+
const service = await factory({ key: kek });
|
|
590
|
+
await service.set('exists', { key: 'val' });
|
|
591
|
+
assert.strictEqual(await service.has('exists'), true);
|
|
592
|
+
assert.strictEqual(await service.has('nope'), false);
|
|
593
|
+
});
|
|
594
|
+
test('set upserts existing credential', async () => {
|
|
595
|
+
const service = await factory({ key: kek });
|
|
596
|
+
await service.set('upsert', { v: 1 });
|
|
597
|
+
await service.set('upsert', { v: 2 });
|
|
598
|
+
const result = await service.get('upsert');
|
|
599
|
+
assert.deepEqual(result, { v: 2 });
|
|
600
|
+
});
|
|
601
|
+
test('delete removes credential', async () => {
|
|
602
|
+
const service = await factory({ key: kek });
|
|
603
|
+
await service.set('to-delete', { bye: true });
|
|
604
|
+
assert.strictEqual(await service.has('to-delete'), true);
|
|
605
|
+
await service.delete('to-delete');
|
|
606
|
+
assert.strictEqual(await service.has('to-delete'), false);
|
|
607
|
+
});
|
|
608
|
+
test('per-user isolation', async () => {
|
|
609
|
+
const service = await factory({ key: kek });
|
|
610
|
+
await service.set('token', { access: 'user1' }, 'user-1');
|
|
611
|
+
await service.set('token', { access: 'user2' }, 'user-2');
|
|
612
|
+
const u1 = await service.get('token', 'user-1');
|
|
613
|
+
const u2 = await service.get('token', 'user-2');
|
|
614
|
+
assert.deepEqual(u1, { access: 'user1' });
|
|
615
|
+
assert.deepEqual(u2, { access: 'user2' });
|
|
616
|
+
});
|
|
617
|
+
test('per-user delete does not affect other users', async () => {
|
|
618
|
+
const service = await factory({ key: kek });
|
|
619
|
+
await service.set('cred', { val: 'a' }, 'user-a');
|
|
620
|
+
await service.set('cred', { val: 'b' }, 'user-b');
|
|
621
|
+
await service.delete('cred', 'user-a');
|
|
622
|
+
assert.strictEqual(await service.has('cred', 'user-a'), false);
|
|
623
|
+
assert.strictEqual(await service.has('cred', 'user-b'), true);
|
|
624
|
+
});
|
|
625
|
+
test('global and per-user are separate', async () => {
|
|
626
|
+
const service = await factory({ key: kek });
|
|
627
|
+
await service.set('shared', { scope: 'global' });
|
|
628
|
+
await service.set('shared', { scope: 'user' }, 'user-1');
|
|
629
|
+
const global = await service.get('shared');
|
|
630
|
+
const perUser = await service.get('shared', 'user-1');
|
|
631
|
+
assert.deepEqual(global, { scope: 'global' });
|
|
632
|
+
assert.deepEqual(perUser, { scope: 'user' });
|
|
633
|
+
});
|
|
634
|
+
test('getAll returns all credentials for a user', async () => {
|
|
635
|
+
const service = await factory({ key: kek });
|
|
636
|
+
await service.set('cred-a', { a: 1 }, 'user-x');
|
|
637
|
+
await service.set('cred-b', { b: 2 }, 'user-x');
|
|
638
|
+
await service.set('cred-c', { c: 3 }, 'user-y');
|
|
639
|
+
const all = await service.getAll('user-x');
|
|
640
|
+
assert.deepEqual(all['cred-a'], { a: 1 });
|
|
641
|
+
assert.deepEqual(all['cred-b'], { b: 2 });
|
|
642
|
+
assert.strictEqual(all['cred-c'], undefined);
|
|
643
|
+
});
|
|
644
|
+
test('getAll returns empty for unknown user', async () => {
|
|
645
|
+
const service = await factory({ key: kek });
|
|
646
|
+
const all = await service.getAll('nobody');
|
|
647
|
+
assert.deepEqual(all, {});
|
|
648
|
+
});
|
|
649
|
+
test('rotateKEK re-wraps all credentials', async () => {
|
|
650
|
+
const newKEK = 'new-key-encryption-key-rotated!';
|
|
651
|
+
const oldService = await factory({ key: kek });
|
|
652
|
+
await oldService.set('rotate-cred', { important: 'data' }, 'user-r');
|
|
653
|
+
const rotatedService = await factory({
|
|
654
|
+
key: newKEK,
|
|
655
|
+
keyVersion: 2,
|
|
656
|
+
previousKey: kek,
|
|
657
|
+
});
|
|
658
|
+
const before = await rotatedService.get('rotate-cred', 'user-r');
|
|
659
|
+
assert.deepEqual(before, { important: 'data' });
|
|
660
|
+
assert.ok(rotatedService.rotateKEK);
|
|
661
|
+
const count = await rotatedService.rotateKEK();
|
|
662
|
+
assert.ok(count > 0);
|
|
663
|
+
const newOnlyService = await factory({
|
|
664
|
+
key: newKEK,
|
|
665
|
+
keyVersion: 2,
|
|
666
|
+
});
|
|
667
|
+
const after = await newOnlyService.get('rotate-cred', 'user-r');
|
|
668
|
+
assert.deepEqual(after, { important: 'data' });
|
|
669
|
+
});
|
|
670
|
+
test('rotateKEK throws without previousKey', async () => {
|
|
671
|
+
const service = await factory({ key: kek });
|
|
672
|
+
assert.ok(service.rotateKEK);
|
|
673
|
+
await assert.rejects(() => service.rotateKEK(), {
|
|
674
|
+
message: 'No previousKey configured — nothing to rotate from',
|
|
675
|
+
});
|
|
676
|
+
});
|
|
677
|
+
});
|
|
678
|
+
}
|
|
573
679
|
if (services.agentRunService) {
|
|
574
680
|
const factory = services.agentRunService;
|
|
575
681
|
describe(`AgentRunService [${name}]`, () => {
|
|
@@ -23,6 +23,7 @@ import type { AIRunStateService } from '../services/ai-run-state-service.js';
|
|
|
23
23
|
import type { AgentRunService } from '../wirings/ai-agent/ai-agent.types.js';
|
|
24
24
|
import type { PikkuAIMiddlewareHooks } from '../wirings/ai-agent/ai-agent.types.js';
|
|
25
25
|
import type { WorkflowRunService } from '../wirings/workflow/workflow.types.js';
|
|
26
|
+
import type { CredentialService } from '../services/credential-service.js';
|
|
26
27
|
export type PikkuWiringTypes = 'http' | 'scheduler' | 'trigger' | 'channel' | 'rpc' | 'queue' | 'mcp' | 'cli' | 'workflow' | 'agent' | 'gateway';
|
|
27
28
|
export interface FunctionServicesMeta {
|
|
28
29
|
optimized: boolean;
|
|
@@ -172,6 +173,8 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
|
|
|
172
173
|
agentRunService?: AgentRunService;
|
|
173
174
|
/** Workflow run service (listing workflow runs) */
|
|
174
175
|
workflowRunService?: WorkflowRunService;
|
|
176
|
+
/** Credential service for dynamic/managed credentials (OAuth tokens, per-user API keys) */
|
|
177
|
+
credentialService?: CredentialService;
|
|
175
178
|
}
|
|
176
179
|
/**
|
|
177
180
|
* Represents different forms of wire within Pikku and the outside world.
|
|
@@ -200,6 +203,10 @@ export type PikkuWire<In = unknown, Out = unknown, HasInitialSession extends boo
|
|
|
200
203
|
getSession: () => Promise<UserSession> | UserSession | undefined;
|
|
201
204
|
/** Whether the session was modified during this run */
|
|
202
205
|
hasSessionChanged: () => boolean;
|
|
206
|
+
/** Set a credential value (available in middleware) */
|
|
207
|
+
setCredential: (name: string, value: unknown) => void;
|
|
208
|
+
/** Get all resolved credentials (only available in pikkuWireServices) */
|
|
209
|
+
getCredentials: () => Record<string, unknown>;
|
|
203
210
|
}>;
|
|
204
211
|
/**
|
|
205
212
|
* A function that can wrap an wire and be called before or after
|