@pikku/core 0.12.23 → 0.12.25
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 +30 -0
- package/dist/data-classification.d.ts +16 -0
- package/dist/data-classification.js +1 -0
- package/dist/function/function-runner.js +62 -15
- package/dist/function/functions.types.d.ts +8 -5
- package/dist/index.d.ts +5 -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 +3 -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/gopass-secrets.d.ts +2 -35
- package/dist/services/gopass-secrets.js +10 -40
- 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 +3 -36
- package/dist/services/local-secrets.js +12 -52
- package/dist/services/local-variables.d.ts +3 -5
- package/dist/services/local-variables.js +10 -10
- package/dist/services/meta-service.d.ts +36 -0
- package/dist/services/meta-service.js +59 -0
- package/dist/services/scoped-secret-service.d.ts +2 -3
- package/dist/services/scoped-secret-service.js +2 -6
- package/dist/services/secret-service.d.ts +5 -12
- package/dist/services/typed-secret-service.d.ts +3 -4
- package/dist/services/typed-secret-service.js +2 -5
- package/dist/services/typed-variables-service.d.ts +3 -6
- package/dist/services/typed-variables-service.js +0 -6
- package/dist/services/variables-service.d.ts +2 -4
- package/dist/testing/service-tests.js +13 -13
- 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 +12 -3
- package/dist/wirings/ai-agent/ai-agent-runner.js +1 -1
- package/dist/wirings/ai-agent/ai-agent-stream.js +1 -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/ai-agent/ai-agent.types.d.ts +4 -0
- package/dist/wirings/oauth2/oauth2-client.js +3 -3
- package/dist/wirings/rpc/rpc-runner.js +9 -2
- package/package.json +1 -1
- package/src/data-classification.ts +15 -0
- 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 +44 -0
- package/src/middleware-runner.ts +10 -10
- package/src/permissions.ts +4 -4
- package/src/services/ai-agent-runner-service.ts +3 -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/gopass-secrets.ts +10 -42
- 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.test.ts +10 -10
- package/src/services/local-secrets.ts +16 -55
- package/src/services/local-variables.test.ts +4 -4
- package/src/services/local-variables.ts +12 -17
- package/src/services/meta-service.ts +115 -0
- package/src/services/scoped-secret-service.test.ts +10 -11
- package/src/services/scoped-secret-service.ts +4 -9
- package/src/services/secret-service.ts +5 -12
- package/src/services/typed-secret-service.test.ts +16 -17
- package/src/services/typed-secret-service.ts +5 -9
- package/src/services/typed-variables-service.test.ts +5 -5
- package/src/services/typed-variables-service.ts +5 -17
- package/src/services/variables-service.ts +2 -4
- package/src/testing/service-tests.ts +13 -13
- package/src/types/core.types.ts +20 -2
- package/src/wirings/ai-agent/ai-agent-prepare.ts +17 -3
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +105 -0
- package/src/wirings/ai-agent/ai-agent-runner.ts +1 -1
- package/src/wirings/ai-agent/ai-agent-stream.ts +1 -1
- package/src/wirings/ai-agent/ai-agent-utils.ts +1 -0
- package/src/wirings/ai-agent/ai-agent.types.ts +4 -0
- package/src/wirings/oauth2/oauth2-client.test.ts +2 -3
- package/src/wirings/oauth2/oauth2-client.ts +3 -3
- package/src/wirings/rpc/rpc-runner.ts +9 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -16,7 +16,7 @@ import { AIProviderNotConfiguredError } from '../../errors/errors.js'
|
|
|
16
16
|
import { pikkuState, getSingletonServices } from '../../pikku-state.js'
|
|
17
17
|
import { createMiddlewareSessionWireProps } from '../../services/user-session-service.js'
|
|
18
18
|
import type { SessionService } from '../../services/user-session-service.js'
|
|
19
|
-
import { randomUUID } from '
|
|
19
|
+
import { randomUUID } from './ai-agent-utils.js'
|
|
20
20
|
import { streamAIAgent } from './ai-agent-stream.js'
|
|
21
21
|
import { runAIAgent } from './ai-agent-runner.js'
|
|
22
22
|
import {
|
|
@@ -32,6 +32,8 @@ import { resolveModelConfig } from './ai-agent-model-config.js'
|
|
|
32
32
|
|
|
33
33
|
export type RunAIAgentParams = {
|
|
34
34
|
sessionService?: SessionService<CoreUserSession>
|
|
35
|
+
/** Credential accessor for the current request — used to read per-user overrides (e.g. AI_API_KEY). */
|
|
36
|
+
getCredential?: <T = unknown>(name: string) => T | null | Promise<T | null>
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
export type StreamAIAgentOptions = {
|
|
@@ -616,11 +618,20 @@ export async function prepareAgentRun(
|
|
|
616
618
|
const singletonServices = getSingletonServices()
|
|
617
619
|
const { agent, packageName, resolvedName } = resolveAgent(agentName)
|
|
618
620
|
|
|
619
|
-
|
|
621
|
+
let agentRunner = singletonServices.aiAgentRunner
|
|
620
622
|
if (!agentRunner) {
|
|
621
623
|
throw new AIProviderNotConfiguredError()
|
|
622
624
|
}
|
|
623
625
|
|
|
626
|
+
if (params.getCredential && agentRunner.withApiKey) {
|
|
627
|
+
const aiCredential = await params.getCredential<{ apiKey: string }>(
|
|
628
|
+
'AI_API_KEY'
|
|
629
|
+
)
|
|
630
|
+
if (aiCredential?.apiKey?.trim()) {
|
|
631
|
+
agentRunner = agentRunner.withApiKey(aiCredential.apiKey)
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
624
635
|
const { storage } = resolveMemoryServices(agent, singletonServices)
|
|
625
636
|
const memoryConfig = agent.memory
|
|
626
637
|
const threadId = input.threadId
|
|
@@ -698,7 +709,10 @@ export async function prepareAgentRun(
|
|
|
698
709
|
agent.agentMode
|
|
699
710
|
)
|
|
700
711
|
|
|
701
|
-
|
|
712
|
+
let instructions = await buildInstructions(resolvedName, packageName)
|
|
713
|
+
if (input.context) {
|
|
714
|
+
instructions = `${instructions}\n\nCurrent context (use these identifiers directly in tool calls — do not ask the user for them):\n${input.context}`
|
|
715
|
+
}
|
|
702
716
|
|
|
703
717
|
const resolved = resolveModelConfig(resolvedName, agent)
|
|
704
718
|
|
|
@@ -970,3 +970,108 @@ describe('resumeAIAgentSync', () => {
|
|
|
970
970
|
assert.equal(result.text, 'Recovered')
|
|
971
971
|
})
|
|
972
972
|
})
|
|
973
|
+
|
|
974
|
+
describe('getCredential API key override', () => {
|
|
975
|
+
test('uses withApiKey runner when getCredential returns apiKey', async () => {
|
|
976
|
+
addTestAgent('api-key-agent')
|
|
977
|
+
|
|
978
|
+
const originalRunCalls: string[] = []
|
|
979
|
+
const overrideRunCalls: string[] = []
|
|
980
|
+
|
|
981
|
+
const overrideRunner = {
|
|
982
|
+
run: async (): Promise<AIAgentStepResult> => {
|
|
983
|
+
overrideRunCalls.push('override')
|
|
984
|
+
return makeStepResult({ text: 'from-override', finishReason: 'stop' })
|
|
985
|
+
},
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const mockServices = {
|
|
989
|
+
logger: { info: () => {}, warn: () => {}, error: () => {}, debug: () => {} },
|
|
990
|
+
aiAgentRunner: {
|
|
991
|
+
run: async (): Promise<AIAgentStepResult> => {
|
|
992
|
+
originalRunCalls.push('original')
|
|
993
|
+
return makeStepResult({ text: 'from-original', finishReason: 'stop' })
|
|
994
|
+
},
|
|
995
|
+
withApiKey: (_key: string) => overrideRunner,
|
|
996
|
+
},
|
|
997
|
+
aiRunState: {
|
|
998
|
+
createRun: async () => 'run-key-override',
|
|
999
|
+
updateRun: async () => {},
|
|
1000
|
+
},
|
|
1001
|
+
} as any
|
|
1002
|
+
|
|
1003
|
+
pikkuState(null, 'package', 'singletonServices', mockServices)
|
|
1004
|
+
|
|
1005
|
+
const result = await runAIAgent(
|
|
1006
|
+
'api-key-agent',
|
|
1007
|
+
{ message: 'hi', threadId: 't-key', resourceId: 'r-key' },
|
|
1008
|
+
{ getCredential: async () => ({ apiKey: 'my-secret-key' }) }
|
|
1009
|
+
)
|
|
1010
|
+
|
|
1011
|
+
assert.equal(result.text, 'from-override')
|
|
1012
|
+
assert.equal(overrideRunCalls.length, 1)
|
|
1013
|
+
assert.equal(originalRunCalls.length, 0)
|
|
1014
|
+
})
|
|
1015
|
+
|
|
1016
|
+
test('uses original runner when getCredential returns null', async () => {
|
|
1017
|
+
addTestAgent('no-cred-agent')
|
|
1018
|
+
|
|
1019
|
+
const originalRunCalls: string[] = []
|
|
1020
|
+
|
|
1021
|
+
const mockServices = {
|
|
1022
|
+
logger: { info: () => {}, warn: () => {}, error: () => {}, debug: () => {} },
|
|
1023
|
+
aiAgentRunner: {
|
|
1024
|
+
run: async (): Promise<AIAgentStepResult> => {
|
|
1025
|
+
originalRunCalls.push('original')
|
|
1026
|
+
return makeStepResult({ text: 'from-original', finishReason: 'stop' })
|
|
1027
|
+
},
|
|
1028
|
+
withApiKey: (_key: string) => ({ run: async () => makeStepResult({ text: 'should-not-be-called', finishReason: 'stop' }) }),
|
|
1029
|
+
},
|
|
1030
|
+
aiRunState: {
|
|
1031
|
+
createRun: async () => 'run-no-cred',
|
|
1032
|
+
updateRun: async () => {},
|
|
1033
|
+
},
|
|
1034
|
+
} as any
|
|
1035
|
+
|
|
1036
|
+
pikkuState(null, 'package', 'singletonServices', mockServices)
|
|
1037
|
+
|
|
1038
|
+
const result = await runAIAgent(
|
|
1039
|
+
'no-cred-agent',
|
|
1040
|
+
{ message: 'hi', threadId: 't-no-cred', resourceId: 'r-no-cred' },
|
|
1041
|
+
{ getCredential: async () => null }
|
|
1042
|
+
)
|
|
1043
|
+
|
|
1044
|
+
assert.equal(result.text, 'from-original')
|
|
1045
|
+
assert.equal(originalRunCalls.length, 1)
|
|
1046
|
+
})
|
|
1047
|
+
|
|
1048
|
+
test('uses original runner when runner does not implement withApiKey', async () => {
|
|
1049
|
+
addTestAgent('no-withkey-agent')
|
|
1050
|
+
|
|
1051
|
+
const originalRunCalls: string[] = []
|
|
1052
|
+
|
|
1053
|
+
const mockServices = {
|
|
1054
|
+
logger: { info: () => {}, warn: () => {}, error: () => {}, debug: () => {} },
|
|
1055
|
+
aiAgentRunner: {
|
|
1056
|
+
run: async (): Promise<AIAgentStepResult> => {
|
|
1057
|
+
originalRunCalls.push('original')
|
|
1058
|
+
return makeStepResult({ text: 'from-original', finishReason: 'stop' })
|
|
1059
|
+
},
|
|
1060
|
+
},
|
|
1061
|
+
aiRunState: {
|
|
1062
|
+
createRun: async () => 'run-no-withkey',
|
|
1063
|
+
updateRun: async () => {},
|
|
1064
|
+
},
|
|
1065
|
+
} as any
|
|
1066
|
+
|
|
1067
|
+
pikkuState(null, 'package', 'singletonServices', mockServices)
|
|
1068
|
+
|
|
1069
|
+
await runAIAgent(
|
|
1070
|
+
'no-withkey-agent',
|
|
1071
|
+
{ message: 'hi', threadId: 't-no-withkey', resourceId: 'r-no-withkey' },
|
|
1072
|
+
{ getCredential: async () => ({ apiKey: 'ignored-key' }) }
|
|
1073
|
+
)
|
|
1074
|
+
|
|
1075
|
+
assert.equal(originalRunCalls.length, 1)
|
|
1076
|
+
})
|
|
1077
|
+
})
|
|
@@ -33,6 +33,7 @@ import { checkForApprovals, appendStepMessages } from './ai-agent-stream.js'
|
|
|
33
33
|
import { pikkuState, getSingletonServices } from '../../pikku-state.js'
|
|
34
34
|
import { resolveModelConfig } from './ai-agent-model-config.js'
|
|
35
35
|
import { AIProviderNotConfiguredError } from '../../errors/errors.js'
|
|
36
|
+
import { randomUUID } from './ai-agent-utils.js'
|
|
36
37
|
|
|
37
38
|
function stripNulls(obj: unknown): unknown {
|
|
38
39
|
if (obj === null) return undefined
|
|
@@ -46,7 +47,6 @@ function stripNulls(obj: unknown): unknown {
|
|
|
46
47
|
}
|
|
47
48
|
return result
|
|
48
49
|
}
|
|
49
|
-
import { randomUUID } from 'crypto'
|
|
50
50
|
|
|
51
51
|
function extractStructuredAssistantOutput(object: unknown): {
|
|
52
52
|
text: string | null
|
|
@@ -11,11 +11,11 @@ import type {
|
|
|
11
11
|
} from './ai-agent.types.js'
|
|
12
12
|
import { pikkuState, getSingletonServices } from '../../pikku-state.js'
|
|
13
13
|
import { AIProviderNotConfiguredError } from '../../errors/errors.js'
|
|
14
|
+
import { randomUUID } from './ai-agent-utils.js'
|
|
14
15
|
import {
|
|
15
16
|
combineChannelMiddleware,
|
|
16
17
|
wrapChannelWithMiddleware,
|
|
17
18
|
} from '../channel/channel-middleware-runner.js'
|
|
18
|
-
import { randomUUID } from 'crypto'
|
|
19
19
|
import type { AIStorageService } from '../../services/ai-storage-service.js'
|
|
20
20
|
import type {
|
|
21
21
|
AIAgentRunnerParams,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const randomUUID = (): string => globalThis.crypto.randomUUID()
|
|
@@ -81,6 +81,10 @@ export interface AIAgentInput {
|
|
|
81
81
|
attachments?: AIAgentInputAttachment[]
|
|
82
82
|
model?: string
|
|
83
83
|
temperature?: number
|
|
84
|
+
/** Structured context injected into the system instructions for this request.
|
|
85
|
+
* Use to provide upfront state (e.g. current org/project/branch/deployment)
|
|
86
|
+
* so the agent can call tools without asking the user for identifiers. */
|
|
87
|
+
context?: string
|
|
84
88
|
}
|
|
85
89
|
|
|
86
90
|
export interface AIAgentOutput {
|
|
@@ -14,9 +14,8 @@ function createMockSecretService(
|
|
|
14
14
|
): SecretService & { getStoredSecrets: () => Map<string, unknown> } {
|
|
15
15
|
const secrets = new Map<string, unknown>(Object.entries(initialSecrets))
|
|
16
16
|
return {
|
|
17
|
-
getSecret: async (key: string) => secrets.get(key) as
|
|
18
|
-
|
|
19
|
-
setSecretJSON: async (key: string, value: unknown) => {
|
|
17
|
+
getSecret: async <T = string>(key: string) => secrets.get(key) as T,
|
|
18
|
+
setSecret: async (key: string, value: unknown) => {
|
|
20
19
|
secrets.set(key, value)
|
|
21
20
|
},
|
|
22
21
|
deleteSecret: async (_key: string) => {
|
|
@@ -124,7 +124,7 @@ export class OAuth2Client {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
// Load from secrets
|
|
127
|
-
const token = await this.secrets.
|
|
127
|
+
const token = await this.secrets.getSecret<OAuth2Token>(
|
|
128
128
|
this.oauth2Config.tokenSecretId
|
|
129
129
|
)
|
|
130
130
|
this.cachedToken = token
|
|
@@ -209,7 +209,7 @@ export class OAuth2Client {
|
|
|
209
209
|
scope: data.scope,
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
await this.secrets.
|
|
212
|
+
await this.secrets.setSecret(this.oauth2Config.tokenSecretId, token)
|
|
213
213
|
this.cachedToken = token
|
|
214
214
|
|
|
215
215
|
return token
|
|
@@ -233,7 +233,7 @@ export class OAuth2Client {
|
|
|
233
233
|
return this.cachedAppCredential
|
|
234
234
|
}
|
|
235
235
|
this.cachedAppCredential =
|
|
236
|
-
await this.secrets.
|
|
236
|
+
await this.secrets.getSecret<OAuth2AppCredential>(
|
|
237
237
|
this.appCredentialSecretId
|
|
238
238
|
)
|
|
239
239
|
return this.cachedAppCredential
|
|
@@ -325,6 +325,7 @@ export class ContextAwareRPCService {
|
|
|
325
325
|
run: async (agentName: string, input: AIAgentInput) => {
|
|
326
326
|
const result = await runAIAgent(agentName, input, {
|
|
327
327
|
sessionService: this.options.sessionService,
|
|
328
|
+
getCredential: this.wire.getCredential?.bind(this.wire),
|
|
328
329
|
})
|
|
329
330
|
return {
|
|
330
331
|
runId: result.runId,
|
|
@@ -353,7 +354,10 @@ export class ContextAwareRPCService {
|
|
|
353
354
|
agentName,
|
|
354
355
|
input,
|
|
355
356
|
channel,
|
|
356
|
-
{
|
|
357
|
+
{
|
|
358
|
+
sessionService: this.options.sessionService,
|
|
359
|
+
getCredential: this.wire.getCredential?.bind(this.wire),
|
|
360
|
+
},
|
|
357
361
|
undefined,
|
|
358
362
|
options
|
|
359
363
|
)
|
|
@@ -368,7 +372,10 @@ export class ContextAwareRPCService {
|
|
|
368
372
|
await resumeAIAgent(
|
|
369
373
|
{ runId, ...input },
|
|
370
374
|
channel,
|
|
371
|
-
{
|
|
375
|
+
{
|
|
376
|
+
sessionService: this.options.sessionService,
|
|
377
|
+
getCredential: this.wire.getCredential?.bind(this.wire),
|
|
378
|
+
},
|
|
372
379
|
options
|
|
373
380
|
)
|
|
374
381
|
},
|