@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.
Files changed (90) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/data-classification.d.ts +16 -0
  3. package/dist/data-classification.js +1 -0
  4. package/dist/function/function-runner.js +62 -15
  5. package/dist/function/functions.types.d.ts +8 -5
  6. package/dist/index.d.ts +5 -0
  7. package/dist/index.js +1 -0
  8. package/dist/middleware-runner.d.ts +2 -2
  9. package/dist/permissions.d.ts +1 -1
  10. package/dist/services/ai-agent-runner-service.d.ts +3 -0
  11. package/dist/services/audit-service.d.ts +50 -0
  12. package/dist/services/audit-service.js +106 -0
  13. package/dist/services/content-service.d.ts +1 -0
  14. package/dist/services/email-service.d.ts +36 -0
  15. package/dist/services/email-service.js +1 -0
  16. package/dist/services/gopass-secrets.d.ts +2 -35
  17. package/dist/services/gopass-secrets.js +10 -40
  18. package/dist/services/index.d.ts +5 -1
  19. package/dist/services/index.js +2 -0
  20. package/dist/services/local-content.js +1 -0
  21. package/dist/services/local-email-service.d.ts +4 -0
  22. package/dist/services/local-email-service.js +26 -0
  23. package/dist/services/local-secrets.d.ts +3 -36
  24. package/dist/services/local-secrets.js +12 -52
  25. package/dist/services/local-variables.d.ts +3 -5
  26. package/dist/services/local-variables.js +10 -10
  27. package/dist/services/meta-service.d.ts +36 -0
  28. package/dist/services/meta-service.js +59 -0
  29. package/dist/services/scoped-secret-service.d.ts +2 -3
  30. package/dist/services/scoped-secret-service.js +2 -6
  31. package/dist/services/secret-service.d.ts +5 -12
  32. package/dist/services/typed-secret-service.d.ts +3 -4
  33. package/dist/services/typed-secret-service.js +2 -5
  34. package/dist/services/typed-variables-service.d.ts +3 -6
  35. package/dist/services/typed-variables-service.js +0 -6
  36. package/dist/services/variables-service.d.ts +2 -4
  37. package/dist/testing/service-tests.js +13 -13
  38. package/dist/types/core.types.d.ts +15 -1
  39. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +2 -0
  40. package/dist/wirings/ai-agent/ai-agent-prepare.js +12 -3
  41. package/dist/wirings/ai-agent/ai-agent-runner.js +1 -1
  42. package/dist/wirings/ai-agent/ai-agent-stream.js +1 -1
  43. package/dist/wirings/ai-agent/ai-agent-utils.d.ts +1 -0
  44. package/dist/wirings/ai-agent/ai-agent-utils.js +1 -0
  45. package/dist/wirings/ai-agent/ai-agent.types.d.ts +4 -0
  46. package/dist/wirings/oauth2/oauth2-client.js +3 -3
  47. package/dist/wirings/rpc/rpc-runner.js +9 -2
  48. package/package.json +1 -1
  49. package/src/data-classification.ts +15 -0
  50. package/src/function/function-runner.test.ts +255 -0
  51. package/src/function/function-runner.ts +66 -20
  52. package/src/function/functions.types.ts +26 -11
  53. package/src/index.ts +44 -0
  54. package/src/middleware-runner.ts +10 -10
  55. package/src/permissions.ts +4 -4
  56. package/src/services/ai-agent-runner-service.ts +3 -0
  57. package/src/services/audit-service.test.ts +44 -0
  58. package/src/services/audit-service.ts +198 -0
  59. package/src/services/content-service.ts +1 -0
  60. package/src/services/email-service.ts +46 -0
  61. package/src/services/gopass-secrets.ts +10 -42
  62. package/src/services/index.ts +33 -0
  63. package/src/services/local-content.ts +1 -0
  64. package/src/services/local-email-service.test.ts +108 -0
  65. package/src/services/local-email-service.ts +30 -0
  66. package/src/services/local-secrets.test.ts +10 -10
  67. package/src/services/local-secrets.ts +16 -55
  68. package/src/services/local-variables.test.ts +4 -4
  69. package/src/services/local-variables.ts +12 -17
  70. package/src/services/meta-service.ts +115 -0
  71. package/src/services/scoped-secret-service.test.ts +10 -11
  72. package/src/services/scoped-secret-service.ts +4 -9
  73. package/src/services/secret-service.ts +5 -12
  74. package/src/services/typed-secret-service.test.ts +16 -17
  75. package/src/services/typed-secret-service.ts +5 -9
  76. package/src/services/typed-variables-service.test.ts +5 -5
  77. package/src/services/typed-variables-service.ts +5 -17
  78. package/src/services/variables-service.ts +2 -4
  79. package/src/testing/service-tests.ts +13 -13
  80. package/src/types/core.types.ts +20 -2
  81. package/src/wirings/ai-agent/ai-agent-prepare.ts +17 -3
  82. package/src/wirings/ai-agent/ai-agent-runner.test.ts +105 -0
  83. package/src/wirings/ai-agent/ai-agent-runner.ts +1 -1
  84. package/src/wirings/ai-agent/ai-agent-stream.ts +1 -1
  85. package/src/wirings/ai-agent/ai-agent-utils.ts +1 -0
  86. package/src/wirings/ai-agent/ai-agent.types.ts +4 -0
  87. package/src/wirings/oauth2/oauth2-client.test.ts +2 -3
  88. package/src/wirings/oauth2/oauth2-client.ts +3 -3
  89. package/src/wirings/rpc/rpc-runner.ts +9 -2
  90. 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 'crypto'
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
- const agentRunner = singletonServices.aiAgentRunner
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
- const instructions = await buildInstructions(resolvedName, packageName)
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 string,
18
- getSecretJSON: async <T>(key: string) => secrets.get(key) as T,
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.getSecretJSON<OAuth2Token>(
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.setSecretJSON(this.oauth2Config.tokenSecretId, token)
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.getSecretJSON<OAuth2AppCredential>(
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
- { sessionService: this.options.sessionService },
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
- { sessionService: this.options.sessionService },
375
+ {
376
+ sessionService: this.options.sessionService,
377
+ getCredential: this.wire.getCredential?.bind(this.wire),
378
+ },
372
379
  options
373
380
  )
374
381
  },