@pikku/core 0.12.63 → 0.12.66
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 +368 -0
- package/dist/dev/hot-reload.js +13 -55
- package/dist/dev/module-runner.d.ts +14 -0
- package/dist/dev/module-runner.js +59 -0
- package/dist/dev/reload-meta.d.ts +13 -0
- package/dist/dev/reload-meta.js +22 -0
- package/dist/errors/errors.d.ts +12 -0
- package/dist/errors/errors.js +19 -0
- package/dist/function/function-runner.d.ts +5 -5
- package/dist/function/function-runner.js +39 -111
- package/dist/function/functions.types.d.ts +17 -7
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3 -1
- package/dist/middleware/auth-apikey.d.ts +1 -0
- package/dist/middleware/auth-bearer.d.ts +1 -0
- package/dist/middleware/auth-cookie.d.ts +1 -0
- package/dist/middleware/cors.d.ts +1 -0
- package/dist/middleware/index.d.ts +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/middleware/remote-auth.d.ts +1 -0
- package/dist/middleware/telemetry.d.ts +2 -0
- package/dist/middleware/timeout.d.ts +1 -0
- package/dist/permissions.d.ts +42 -27
- package/dist/permissions.js +101 -203
- package/dist/pikku-state.js +0 -2
- package/dist/scopes.d.ts +17 -0
- package/dist/scopes.js +59 -0
- package/dist/services/ai-embedding-service.d.ts +31 -0
- package/dist/services/ai-embedding-service.js +1 -0
- package/dist/services/credential-wire-service.d.ts +3 -1
- package/dist/services/credential-wire-service.js +10 -4
- package/dist/services/index.d.ts +4 -0
- package/dist/services/index.js +2 -0
- package/dist/services/meta-service.d.ts +15 -2
- package/dist/services/meta-service.js +51 -1
- package/dist/services/queue-webhook-service.d.ts +55 -0
- package/dist/services/queue-webhook-service.js +136 -0
- package/dist/services/scope-service.d.ts +67 -0
- package/dist/services/scope-service.js +1 -0
- package/dist/services/typed-secret-service.d.ts +8 -0
- package/dist/services/typed-secret-service.js +39 -4
- package/dist/services/webhook-service.d.ts +140 -0
- package/dist/services/webhook-service.js +44 -0
- package/dist/services/workflow-service.d.ts +7 -0
- package/dist/testing/service-tests.js +37 -0
- package/dist/types/core.types.d.ts +34 -6
- package/dist/types/state.types.d.ts +20 -2
- package/dist/utils/hmac.d.ts +16 -0
- package/dist/utils/hmac.js +26 -0
- package/dist/utils/safe-fetch.d.ts +51 -0
- package/dist/utils/safe-fetch.js +192 -0
- package/dist/wirings/ai-agent/ai-agent-agui.d.ts +84 -0
- package/dist/wirings/ai-agent/ai-agent-agui.js +305 -0
- package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +105 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.js +249 -11
- package/dist/wirings/ai-agent/ai-agent-runner.js +10 -1
- package/dist/wirings/ai-agent/ai-agent-stream.js +88 -16
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +56 -2
- package/dist/wirings/ai-agent/index.d.ts +2 -1
- package/dist/wirings/ai-agent/index.js +2 -1
- package/dist/wirings/ai-agent/voice-input.d.ts +1 -0
- package/dist/wirings/ai-agent/voice-input.js +5 -41
- package/dist/wirings/ai-agent/voice-output.d.ts +1 -0
- package/dist/wirings/channel/channel-common.js +0 -1
- package/dist/wirings/channel/channel-handler.js +1 -4
- package/dist/wirings/channel/channel-middleware-runner.d.ts +12 -0
- package/dist/wirings/channel/channel-middleware-runner.js +34 -19
- package/dist/wirings/channel/channel.types.d.ts +2 -6
- package/dist/wirings/cli/cli-runner.js +3 -2
- package/dist/wirings/cli/cli.types.d.ts +1 -2
- package/dist/wirings/cli/command-parser.d.ts +2 -0
- package/dist/wirings/cli/command-parser.js +59 -2
- package/dist/wirings/credential/credential.types.d.ts +14 -0
- package/dist/wirings/credential/validate-credential-definitions.js +1 -0
- package/dist/wirings/gateway/gateway-runner.js +100 -50
- package/dist/wirings/gateway/gateway.types.d.ts +8 -5
- package/dist/wirings/http/http-routes.js +0 -3
- package/dist/wirings/http/http-runner.d.ts +9 -29
- package/dist/wirings/http/http-runner.js +9 -34
- package/dist/wirings/http/http.types.d.ts +4 -12
- package/dist/wirings/mcp/mcp-runner.js +0 -2
- package/dist/wirings/mcp/mcp.types.d.ts +5 -11
- package/dist/wirings/oauth2/index.d.ts +0 -3
- package/dist/wirings/oauth2/index.js +1 -2
- package/dist/wirings/rpc/addon-runner.d.ts +28 -0
- package/dist/wirings/rpc/addon-runner.js +173 -0
- package/dist/wirings/rpc/index.d.ts +5 -1
- package/dist/wirings/rpc/index.js +3 -1
- package/dist/wirings/rpc/remote-addon-auth.d.ts +26 -0
- package/dist/wirings/rpc/remote-addon-auth.js +43 -0
- package/dist/wirings/rpc/rpc-runner.d.ts +19 -0
- package/dist/wirings/rpc/rpc-runner.js +109 -3
- package/dist/wirings/rpc/rpc-types.d.ts +15 -1
- package/dist/wirings/rpc/wire-addon.js +9 -0
- package/dist/wirings/rpc/wire-remote-addon.d.ts +47 -0
- package/dist/wirings/rpc/wire-remote-addon.js +19 -0
- package/dist/wirings/scope/index.d.ts +3 -0
- package/dist/wirings/scope/index.js +2 -0
- package/dist/wirings/scope/scope.types.d.ts +39 -0
- package/dist/wirings/scope/scope.types.js +1 -0
- package/dist/wirings/scope/validate-scope-definitions.d.ts +16 -0
- package/dist/wirings/scope/validate-scope-definitions.js +76 -0
- package/dist/wirings/scope/wire-scope.d.ts +33 -0
- package/dist/wirings/scope/wire-scope.js +32 -0
- package/dist/wirings/secret/secret.types.d.ts +14 -0
- package/dist/wirings/secret/validate-secret-definitions.js +2 -0
- package/dist/wirings/variable/validate-variable-definitions.js +2 -0
- package/dist/wirings/variable/variable.types.d.ts +14 -0
- package/dist/wirings/workflow/dsl/index.d.ts +1 -1
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +89 -7
- package/dist/wirings/workflow/graph/graph-node.d.ts +2 -0
- package/dist/wirings/workflow/graph/graph-runner.js +3 -0
- package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +6 -0
- package/dist/wirings/workflow/graph/wire-workflow-graph.js +1 -0
- package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +5 -0
- package/dist/wirings/workflow/index.d.ts +2 -2
- package/dist/wirings/workflow/index.js +1 -1
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +59 -1
- package/dist/wirings/workflow/pikku-workflow-service.js +187 -0
- package/dist/wirings/workflow/workflow.types.d.ts +1 -3
- package/package.json +6 -1
- package/run-tests.sh +1 -0
- package/src/dev/hot-reload.ts +13 -68
- package/src/dev/module-runner.test.ts +169 -0
- package/src/dev/module-runner.ts +103 -0
- package/src/dev/reload-meta.test.ts +31 -2
- package/src/dev/reload-meta.ts +26 -0
- package/src/errors/errors.ts +24 -0
- package/src/function/function-runner.test.ts +276 -142
- package/src/function/function-runner.ts +52 -145
- package/src/function/functions.types.ts +28 -38
- package/src/index.ts +8 -6
- package/src/middleware/index.ts +1 -5
- package/src/permissions.test.ts +160 -373
- package/src/permissions.ts +137 -279
- package/src/pikku-state.ts +0 -2
- package/src/scopes.test.ts +167 -0
- package/src/scopes.ts +69 -0
- package/src/services/ai-embedding-service.ts +31 -0
- package/src/services/credential-wire-service.ts +10 -4
- package/src/services/index.ts +20 -0
- package/src/services/meta-service.ts +68 -3
- package/src/services/queue-webhook-service.test.ts +408 -0
- package/src/services/queue-webhook-service.ts +182 -0
- package/src/services/scope-service.ts +75 -0
- package/src/services/typed-secret-service.test.ts +35 -0
- package/src/services/typed-secret-service.ts +39 -4
- package/src/services/webhook-service.ts +180 -0
- package/src/services/workflow-service.ts +7 -0
- package/src/testing/service-tests.ts +49 -0
- package/src/types/core.types.ts +42 -14
- package/src/types/state.types.ts +23 -3
- package/src/utils/hmac.ts +27 -0
- package/src/utils/safe-fetch.test.ts +373 -0
- package/src/utils/safe-fetch.ts +213 -0
- package/src/wirings/ai-agent/ai-agent-agui.test.ts +1127 -0
- package/src/wirings/ai-agent/ai-agent-agui.ts +386 -0
- package/src/wirings/ai-agent/ai-agent-authorization.test.ts +204 -0
- package/src/wirings/ai-agent/ai-agent-prepare.test.ts +483 -0
- package/src/wirings/ai-agent/ai-agent-prepare.ts +324 -13
- package/src/wirings/ai-agent/ai-agent-resume-authorization.test.ts +207 -0
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +48 -1
- package/src/wirings/ai-agent/ai-agent-runner.ts +28 -0
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +301 -10
- package/src/wirings/ai-agent/ai-agent-stream.ts +122 -16
- package/src/wirings/ai-agent/ai-agent.types.ts +57 -0
- package/src/wirings/ai-agent/index.ts +5 -0
- package/src/wirings/ai-agent/voice-input.test.ts +90 -0
- package/src/wirings/ai-agent/voice-input.ts +9 -40
- package/src/wirings/channel/channel-common.ts +0 -1
- package/src/wirings/channel/channel-handler.ts +1 -9
- package/src/wirings/channel/channel-middleware-runner.test.ts +28 -2
- package/src/wirings/channel/channel-middleware-runner.ts +39 -27
- package/src/wirings/channel/channel.types.ts +0 -6
- package/src/wirings/cli/cli-runner.ts +4 -2
- package/src/wirings/cli/cli.types.ts +0 -2
- package/src/wirings/cli/command-parser.test.ts +130 -0
- package/src/wirings/cli/command-parser.ts +80 -2
- package/src/wirings/credential/credential.types.ts +14 -0
- package/src/wirings/credential/validate-credential-definitions.ts +1 -0
- package/src/wirings/gateway/gateway-authorization.test.ts +444 -0
- package/src/wirings/gateway/gateway-runner.ts +114 -68
- package/src/wirings/gateway/gateway.types.ts +7 -9
- package/src/wirings/http/http-routes.ts +0 -3
- package/src/wirings/http/http-runner.test.ts +1 -11
- package/src/wirings/http/http-runner.ts +16 -39
- package/src/wirings/http/http.types.ts +7 -14
- package/src/wirings/mcp/mcp-runner.ts +0 -2
- package/src/wirings/mcp/mcp.types.ts +3 -11
- package/src/wirings/oauth2/index.ts +0 -3
- package/src/wirings/rpc/addon-runner.ts +233 -0
- package/src/wirings/rpc/index.ts +17 -1
- package/src/wirings/rpc/remote-addon-auth.ts +69 -0
- package/src/wirings/rpc/rpc-runner.test.ts +319 -16
- package/src/wirings/rpc/rpc-runner.ts +155 -3
- package/src/wirings/rpc/rpc-types.ts +16 -5
- package/src/wirings/rpc/wire-addon.test.ts +6 -3
- package/src/wirings/rpc/wire-addon.ts +9 -0
- package/src/wirings/rpc/wire-remote-addon.ts +57 -0
- package/src/wirings/scope/index.ts +14 -0
- package/src/wirings/scope/scope.test.ts +135 -0
- package/src/wirings/scope/scope.types.ts +44 -0
- package/src/wirings/scope/validate-scope-definitions.ts +110 -0
- package/src/wirings/scope/wire-scope.ts +34 -0
- package/src/wirings/secret/secret.types.ts +14 -0
- package/src/wirings/secret/validate-secret-definitions.ts +2 -0
- package/src/wirings/variable/validate-variable-definitions.ts +2 -0
- package/src/wirings/variable/variable.types.ts +14 -0
- package/src/wirings/workflow/dsl/index.ts +4 -0
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +97 -6
- package/src/wirings/workflow/graph/graph-node.ts +2 -0
- package/src/wirings/workflow/graph/graph-runner.test.ts +58 -0
- package/src/wirings/workflow/graph/graph-runner.ts +3 -0
- package/src/wirings/workflow/graph/wire-workflow-graph.ts +7 -0
- package/src/wirings/workflow/graph/workflow-graph.types.ts +5 -0
- package/src/wirings/workflow/index.ts +5 -0
- package/src/wirings/workflow/pikku-workflow-service.test.ts +341 -0
- package/src/wirings/workflow/pikku-workflow-service.ts +272 -0
- package/src/wirings/workflow/workflow-on-error.test.ts +154 -0
- package/src/wirings/workflow/workflow-step-session.test.ts +21 -5
- package/src/wirings/workflow/workflow.types.ts +4 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/src/wirings/oauth2/oauth2-client.test.ts +0 -930
- package/src/wirings/oauth2/oauth2-client.ts +0 -331
- package/src/wirings/oauth2/oauth2-routes.ts +0 -234
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { CoreServices, PikkuRawWire } from '../../types/core.types.js'
|
|
2
|
+
import { PikkuError, addError } from '../../errors/error-handler.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* How a `wireRemoteAddon` consumer supplies the token the hosted addon expects.
|
|
6
|
+
* This is a CLIENT authenticating to a hosted library — not the trusted mesh.
|
|
7
|
+
*/
|
|
8
|
+
export type RemoteAddonAuthBinding =
|
|
9
|
+
| { credentialId: string }
|
|
10
|
+
| { secretId: string }
|
|
11
|
+
| {
|
|
12
|
+
resolve: (
|
|
13
|
+
services: CoreServices,
|
|
14
|
+
wire: PikkuRawWire
|
|
15
|
+
) => string | Promise<string>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The addon's auth was bound but the source produced no token — fail closed. */
|
|
19
|
+
export class RemoteAddonAuthError extends PikkuError {
|
|
20
|
+
public readonly namespace: string
|
|
21
|
+
constructor(namespace: string, detail: string) {
|
|
22
|
+
super(`Remote addon '${namespace}' auth could not be resolved: ${detail}`)
|
|
23
|
+
this.namespace = namespace
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
addError(RemoteAddonAuthError, {
|
|
27
|
+
status: 401,
|
|
28
|
+
message: 'Remote addon authentication could not be resolved.',
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the bearer token for a remote addon call from the consumer's bound
|
|
33
|
+
* source. Returns `null` only when no auth is bound (a public surface).
|
|
34
|
+
*
|
|
35
|
+
* Security: the resolved token is never logged or traced; per-user credentials
|
|
36
|
+
* are read via the wire (scoped to `pikkuUserId`); an empty result fails closed.
|
|
37
|
+
*/
|
|
38
|
+
export async function resolveRemoteAddonToken(
|
|
39
|
+
auth: RemoteAddonAuthBinding | undefined,
|
|
40
|
+
services: CoreServices,
|
|
41
|
+
wire: PikkuRawWire,
|
|
42
|
+
namespace: string
|
|
43
|
+
): Promise<string | null> {
|
|
44
|
+
if (!auth) {
|
|
45
|
+
// No auth bound → the addon declares its remote surface public.
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let token: unknown
|
|
50
|
+
if ('credentialId' in auth) {
|
|
51
|
+
if (typeof wire.getCredential !== 'function') {
|
|
52
|
+
throw new RemoteAddonAuthError(
|
|
53
|
+
namespace,
|
|
54
|
+
`credentialId '${auth.credentialId}' requires a wire with credential access`
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
token = await wire.getCredential(auth.credentialId)
|
|
58
|
+
} else if ('secretId' in auth) {
|
|
59
|
+
token = await services.secrets.getSecret(auth.secretId)
|
|
60
|
+
} else if (typeof auth.resolve === 'function') {
|
|
61
|
+
token = await auth.resolve(services, wire)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (token === null || token === undefined || token === '') {
|
|
65
|
+
throw new RemoteAddonAuthError(namespace, 'resolved token was empty')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return String(token)
|
|
69
|
+
}
|
|
@@ -6,9 +6,13 @@ import { pikkuState, resetPikkuState } from '../../pikku-state.js'
|
|
|
6
6
|
import {
|
|
7
7
|
ContextAwareRPCService,
|
|
8
8
|
RPCNotFoundError,
|
|
9
|
+
RemoteAddonRequestError,
|
|
9
10
|
resolveNamespace,
|
|
10
11
|
rpcService,
|
|
11
12
|
} from './rpc-runner.js'
|
|
13
|
+
import { RemoteAddonAuthError } from './remote-addon-auth.js'
|
|
14
|
+
import { wireAddon } from './wire-addon.js'
|
|
15
|
+
import { wireRemoteAddon } from './wire-remote-addon.js'
|
|
12
16
|
|
|
13
17
|
const createLogger = () => ({
|
|
14
18
|
debug: () => {},
|
|
@@ -207,28 +211,12 @@ describe('ContextAwareRPCService.rpc', () => {
|
|
|
207
211
|
{ requiresAuth: true }
|
|
208
212
|
)
|
|
209
213
|
|
|
210
|
-
const seenTags: string[][] = []
|
|
211
|
-
const addonTagGroup = pikkuState('@addon/stripe', 'permissions', 'tagGroup')
|
|
212
|
-
addonTagGroup['addon-tag'] = [
|
|
213
|
-
async (_services: any, _data: any, wire: any) => {
|
|
214
|
-
seenTags.push(['addon-tag', wire.wireType])
|
|
215
|
-
return true
|
|
216
|
-
},
|
|
217
|
-
] as never
|
|
218
|
-
addonTagGroup['function-tag'] = [
|
|
219
|
-
async (_services: any, _data: any, wire: any) => {
|
|
220
|
-
seenTags.push(['function-tag', wire.wireType])
|
|
221
|
-
return true
|
|
222
|
-
},
|
|
223
|
-
] as never
|
|
224
|
-
|
|
225
214
|
const result = await service.rpc('stripe:createCharge', { amount: 10 })
|
|
226
215
|
|
|
227
216
|
assert.deepEqual(result, {
|
|
228
217
|
data: { amount: 10 },
|
|
229
218
|
auth: 'http',
|
|
230
219
|
})
|
|
231
|
-
assert.deepEqual(seenTags, [['addon-tag', 'http']])
|
|
232
220
|
})
|
|
233
221
|
|
|
234
222
|
test('falls back to deploymentService when rpc meta is missing', async () => {
|
|
@@ -492,6 +480,321 @@ describe('ContextAwareRPCService.remote', () => {
|
|
|
492
480
|
})
|
|
493
481
|
})
|
|
494
482
|
|
|
483
|
+
describe('multi-instance addons', () => {
|
|
484
|
+
const createSecretService = () => ({
|
|
485
|
+
getSecret: async (key: string) => `secret:${key}`,
|
|
486
|
+
hasSecret: async () => true,
|
|
487
|
+
setSecret: async () => {},
|
|
488
|
+
deleteSecret: async () => {},
|
|
489
|
+
getSecrets: async () => ({}),
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
test('resolves per-instance secretOverrides and caches singletons per namespace', async () => {
|
|
493
|
+
const createdServices: any[] = []
|
|
494
|
+
pikkuState('@addon/slack', 'package', 'factories', {
|
|
495
|
+
createSingletonServices: async (_config: any, parent: any) => {
|
|
496
|
+
const token = await parent.secrets.getSecret('slackToken')
|
|
497
|
+
const svc = { logger: createLogger(), token }
|
|
498
|
+
createdServices.push(svc)
|
|
499
|
+
return svc
|
|
500
|
+
},
|
|
501
|
+
} as never)
|
|
502
|
+
registerFunction(
|
|
503
|
+
'send',
|
|
504
|
+
async (services: any) => ({ token: services.token }),
|
|
505
|
+
{
|
|
506
|
+
packageName: '@addon/slack',
|
|
507
|
+
}
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
wireAddon({
|
|
511
|
+
name: 'slack-marketing',
|
|
512
|
+
package: '@addon/slack',
|
|
513
|
+
secretOverrides: { slackToken: 'marketing_secret' },
|
|
514
|
+
})
|
|
515
|
+
wireAddon({
|
|
516
|
+
name: 'slack-support',
|
|
517
|
+
package: '@addon/slack',
|
|
518
|
+
secretOverrides: { slackToken: 'support_secret' },
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
const service = new ContextAwareRPCService(
|
|
522
|
+
createServices({ secrets: createSecretService() }),
|
|
523
|
+
{} as never,
|
|
524
|
+
{ requiresAuth: false }
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
const marketing = await service.rpc('slack-marketing:send', {})
|
|
528
|
+
const support = await service.rpc('slack-support:send', {})
|
|
529
|
+
|
|
530
|
+
assert.deepEqual(marketing, { token: 'secret:marketing_secret' })
|
|
531
|
+
assert.deepEqual(support, { token: 'secret:support_secret' })
|
|
532
|
+
assert.equal(createdServices.length, 2)
|
|
533
|
+
assert.notEqual(createdServices[0], createdServices[1])
|
|
534
|
+
})
|
|
535
|
+
|
|
536
|
+
test('reuses the same per-namespace singleton across intra-addon sibling calls', async () => {
|
|
537
|
+
let factoryCalls = 0
|
|
538
|
+
pikkuState('@addon/slack', 'package', 'factories', {
|
|
539
|
+
createSingletonServices: async (_config: any, parent: any) => {
|
|
540
|
+
factoryCalls++
|
|
541
|
+
return {
|
|
542
|
+
logger: createLogger(),
|
|
543
|
+
token: await parent.secrets.getSecret('slackToken'),
|
|
544
|
+
}
|
|
545
|
+
},
|
|
546
|
+
} as never)
|
|
547
|
+
|
|
548
|
+
registerFunction(
|
|
549
|
+
'leaf',
|
|
550
|
+
async (services: any) => ({ token: services.token }),
|
|
551
|
+
{
|
|
552
|
+
packageName: '@addon/slack',
|
|
553
|
+
}
|
|
554
|
+
)
|
|
555
|
+
registerFunction(
|
|
556
|
+
'root',
|
|
557
|
+
async (_services: any, _data: any, wire: any) =>
|
|
558
|
+
wire.rpc.invoke('leaf', {}),
|
|
559
|
+
{ packageName: '@addon/slack' }
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
wireAddon({
|
|
563
|
+
name: 'slack-marketing',
|
|
564
|
+
package: '@addon/slack',
|
|
565
|
+
secretOverrides: { slackToken: 'marketing_secret' },
|
|
566
|
+
})
|
|
567
|
+
|
|
568
|
+
const service = new ContextAwareRPCService(
|
|
569
|
+
createServices({ secrets: createSecretService() }),
|
|
570
|
+
{} as never,
|
|
571
|
+
{ requiresAuth: false }
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
const result = await service.rpc('slack-marketing:root', {})
|
|
575
|
+
|
|
576
|
+
assert.deepEqual(result, { token: 'secret:marketing_secret' })
|
|
577
|
+
assert.equal(factoryCalls, 1)
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
test('resolves per-instance credentialOverrides via wire.getCredential', async () => {
|
|
581
|
+
pikkuState('@addon/slack', 'package', 'factories', {
|
|
582
|
+
createSingletonServices: async () => ({ logger: createLogger() }),
|
|
583
|
+
} as never)
|
|
584
|
+
registerFunction(
|
|
585
|
+
'whoami',
|
|
586
|
+
async (_services: any, _data: any, wire: any) => ({
|
|
587
|
+
cred: await wire.getCredential('slack'),
|
|
588
|
+
}),
|
|
589
|
+
{ packageName: '@addon/slack' }
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
wireAddon({
|
|
593
|
+
name: 'slack-marketing',
|
|
594
|
+
package: '@addon/slack',
|
|
595
|
+
credentialOverrides: { slack: 'marketing_cred' },
|
|
596
|
+
})
|
|
597
|
+
wireAddon({
|
|
598
|
+
name: 'slack-support',
|
|
599
|
+
package: '@addon/slack',
|
|
600
|
+
credentialOverrides: { slack: 'support_cred' },
|
|
601
|
+
})
|
|
602
|
+
|
|
603
|
+
const credentialService = {
|
|
604
|
+
getAll: async (_userId: string) => ({
|
|
605
|
+
marketing_cred: { token: 'm' },
|
|
606
|
+
support_cred: { token: 's' },
|
|
607
|
+
}),
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const service = new ContextAwareRPCService(
|
|
611
|
+
createServices({ credentialService }),
|
|
612
|
+
{ pikkuUserId: 'user-1' } as never,
|
|
613
|
+
{ requiresAuth: false }
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
const marketing = await service.rpc('slack-marketing:whoami', {})
|
|
617
|
+
const support = await service.rpc('slack-support:whoami', {})
|
|
618
|
+
|
|
619
|
+
assert.deepEqual(marketing, { cred: { token: 'm' } })
|
|
620
|
+
assert.deepEqual(support, { cred: { token: 's' } })
|
|
621
|
+
})
|
|
622
|
+
})
|
|
623
|
+
|
|
624
|
+
describe('wireRemoteAddon dispatch', () => {
|
|
625
|
+
const realFetch = globalThis.fetch
|
|
626
|
+
let calls: Array<{ url: string; init: any }> = []
|
|
627
|
+
|
|
628
|
+
const stubFetch = (
|
|
629
|
+
response: { status?: number; body?: unknown } = {}
|
|
630
|
+
): void => {
|
|
631
|
+
const { status = 200, body = { ok: true } } = response
|
|
632
|
+
calls = []
|
|
633
|
+
globalThis.fetch = (async (url: any, init: any) => {
|
|
634
|
+
calls.push({ url: String(url), init })
|
|
635
|
+
return {
|
|
636
|
+
ok: status >= 200 && status < 300,
|
|
637
|
+
status,
|
|
638
|
+
text: async () => (body === undefined ? '' : JSON.stringify(body)),
|
|
639
|
+
}
|
|
640
|
+
}) as any
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const restoreFetch = () => {
|
|
644
|
+
globalThis.fetch = realFetch
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const secretService = () =>
|
|
648
|
+
({
|
|
649
|
+
getSecret: async (key: string) => `secret-value-for-${key}`,
|
|
650
|
+
getSecrets: async () => ({}),
|
|
651
|
+
hasSecret: async () => true,
|
|
652
|
+
setSecret: async () => {},
|
|
653
|
+
deleteSecret: async () => {},
|
|
654
|
+
}) as never
|
|
655
|
+
|
|
656
|
+
test('POSTs to /remote/rpc/<fn> with { rpcName, data } and a platform bearer token', async () => {
|
|
657
|
+
stubFetch({ body: { echoed: 42 } })
|
|
658
|
+
try {
|
|
659
|
+
wireRemoteAddon({
|
|
660
|
+
name: 'registry',
|
|
661
|
+
package: '@pikkufabric/addon-registry',
|
|
662
|
+
serverUrl: 'https://api.example.com/',
|
|
663
|
+
auth: { secretId: 'REGISTRY_TOKEN' },
|
|
664
|
+
})
|
|
665
|
+
|
|
666
|
+
const service = new ContextAwareRPCService(
|
|
667
|
+
createServices({ secrets: secretService() }),
|
|
668
|
+
{ traceId: 'trace-r' } as never,
|
|
669
|
+
{}
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
const result = await service.rpc('registry:getOpenApi', { name: 'stripe' })
|
|
673
|
+
|
|
674
|
+
assert.deepEqual(result, { echoed: 42 })
|
|
675
|
+
assert.equal(calls.length, 1)
|
|
676
|
+
assert.equal(
|
|
677
|
+
calls[0]!.url,
|
|
678
|
+
'https://api.example.com/remote/rpc/getOpenApi'
|
|
679
|
+
)
|
|
680
|
+
assert.equal(calls[0]!.init.method, 'POST')
|
|
681
|
+
assert.deepEqual(JSON.parse(calls[0]!.init.body), {
|
|
682
|
+
rpcName: 'getOpenApi',
|
|
683
|
+
data: { name: 'stripe' },
|
|
684
|
+
})
|
|
685
|
+
assert.equal(
|
|
686
|
+
calls[0]!.init.headers.authorization,
|
|
687
|
+
'Bearer secret-value-for-REGISTRY_TOKEN'
|
|
688
|
+
)
|
|
689
|
+
assert.equal(calls[0]!.init.headers['x-trace-id'], 'trace-r')
|
|
690
|
+
} finally {
|
|
691
|
+
restoreFetch()
|
|
692
|
+
}
|
|
693
|
+
})
|
|
694
|
+
|
|
695
|
+
test('resolves a per-user credential token via wire.getCredential', async () => {
|
|
696
|
+
stubFetch()
|
|
697
|
+
try {
|
|
698
|
+
wireRemoteAddon({
|
|
699
|
+
name: 'registry',
|
|
700
|
+
package: '@pikkufabric/addon-registry',
|
|
701
|
+
serverUrl: () => 'https://api.example.com',
|
|
702
|
+
auth: { credentialId: 'fabricRegistryToken' },
|
|
703
|
+
})
|
|
704
|
+
|
|
705
|
+
const service = new ContextAwareRPCService(
|
|
706
|
+
createServices(),
|
|
707
|
+
{
|
|
708
|
+
pikkuUserId: 'user-9',
|
|
709
|
+
getCredential: async (name: string) =>
|
|
710
|
+
name === 'fabricRegistryToken' ? 'user-token-9' : null,
|
|
711
|
+
} as never,
|
|
712
|
+
{}
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
await service.rpc('registry:getOpenApi', {})
|
|
716
|
+
assert.equal(
|
|
717
|
+
calls[0]!.init.headers.authorization,
|
|
718
|
+
'Bearer user-token-9'
|
|
719
|
+
)
|
|
720
|
+
} finally {
|
|
721
|
+
restoreFetch()
|
|
722
|
+
}
|
|
723
|
+
})
|
|
724
|
+
|
|
725
|
+
test('omits the Authorization header for a public (no auth) surface', async () => {
|
|
726
|
+
stubFetch()
|
|
727
|
+
try {
|
|
728
|
+
wireRemoteAddon({
|
|
729
|
+
name: 'registry',
|
|
730
|
+
package: '@pikkufabric/addon-registry',
|
|
731
|
+
serverUrl: 'https://api.example.com',
|
|
732
|
+
})
|
|
733
|
+
|
|
734
|
+
const service = new ContextAwareRPCService(
|
|
735
|
+
createServices(),
|
|
736
|
+
{} as never,
|
|
737
|
+
{}
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
await service.rpc('registry:listOpenApis', {})
|
|
741
|
+
assert.equal(calls[0]!.init.headers.authorization, undefined)
|
|
742
|
+
} finally {
|
|
743
|
+
restoreFetch()
|
|
744
|
+
}
|
|
745
|
+
})
|
|
746
|
+
|
|
747
|
+
test('fails closed when a bound credential resolves empty', async () => {
|
|
748
|
+
stubFetch()
|
|
749
|
+
try {
|
|
750
|
+
wireRemoteAddon({
|
|
751
|
+
name: 'registry',
|
|
752
|
+
package: '@pikkufabric/addon-registry',
|
|
753
|
+
serverUrl: 'https://api.example.com',
|
|
754
|
+
auth: { credentialId: 'fabricRegistryToken' },
|
|
755
|
+
})
|
|
756
|
+
|
|
757
|
+
const service = new ContextAwareRPCService(
|
|
758
|
+
createServices(),
|
|
759
|
+
{ getCredential: async () => null } as never,
|
|
760
|
+
{}
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
await assert.rejects(
|
|
764
|
+
() => service.rpc('registry:getOpenApi', {}),
|
|
765
|
+
RemoteAddonAuthError
|
|
766
|
+
)
|
|
767
|
+
assert.equal(calls.length, 0)
|
|
768
|
+
} finally {
|
|
769
|
+
restoreFetch()
|
|
770
|
+
}
|
|
771
|
+
})
|
|
772
|
+
|
|
773
|
+
test('throws RemoteAddonRequestError on a non-2xx response', async () => {
|
|
774
|
+
stubFetch({ status: 503, body: { error: 'down' } })
|
|
775
|
+
try {
|
|
776
|
+
wireRemoteAddon({
|
|
777
|
+
name: 'registry',
|
|
778
|
+
package: '@pikkufabric/addon-registry',
|
|
779
|
+
serverUrl: 'https://api.example.com',
|
|
780
|
+
})
|
|
781
|
+
|
|
782
|
+
const service = new ContextAwareRPCService(
|
|
783
|
+
createServices(),
|
|
784
|
+
{} as never,
|
|
785
|
+
{}
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
await assert.rejects(
|
|
789
|
+
() => service.rpc('registry:getOpenApi', {}),
|
|
790
|
+
RemoteAddonRequestError
|
|
791
|
+
)
|
|
792
|
+
} finally {
|
|
793
|
+
restoreFetch()
|
|
794
|
+
}
|
|
795
|
+
})
|
|
796
|
+
})
|
|
797
|
+
|
|
495
798
|
describe('rpcService.getContextRPCService', () => {
|
|
496
799
|
test('builds a typed rpc facade with depth and bound methods', async () => {
|
|
497
800
|
pikkuState(null, 'rpc', 'meta').echo = 'echoFunc'
|
|
@@ -6,10 +6,13 @@ import type {
|
|
|
6
6
|
import type { SessionService } from '../../services/user-session-service.js'
|
|
7
7
|
import type { CoreUserSession } from '../../types/core.types.js'
|
|
8
8
|
import { runPikkuFunc } from '../../function/function-runner.js'
|
|
9
|
+
import type { AddonInstance } from './addon-runner.js'
|
|
10
|
+
import { addonInstanceForNamespace } from './addon-runner.js'
|
|
9
11
|
import { pikkuState } from '../../pikku-state.js'
|
|
10
12
|
import { PikkuError, addError } from '../../errors/error-handler.js'
|
|
11
13
|
import type { PikkuRPC, ResolvedFunction } from './rpc-types.js'
|
|
12
14
|
import { parseVersionedId } from '../../version.js'
|
|
15
|
+
import { resolveRemoteAddonToken } from './remote-addon-auth.js'
|
|
13
16
|
|
|
14
17
|
export class RPCNotFoundError extends PikkuError {
|
|
15
18
|
public readonly rpcName: string
|
|
@@ -23,11 +26,43 @@ addError(RPCNotFoundError, {
|
|
|
23
26
|
mcpCode: -32601,
|
|
24
27
|
message: 'RPC function not found.',
|
|
25
28
|
})
|
|
29
|
+
|
|
30
|
+
/** A `wireRemoteAddon` namespace is missing a usable `serverUrl`. */
|
|
31
|
+
export class RemoteAddonConfigError extends PikkuError {
|
|
32
|
+
constructor(namespace: string, detail: string) {
|
|
33
|
+
super(`Remote addon '${namespace}' is misconfigured: ${detail}`)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
addError(RemoteAddonConfigError, {
|
|
37
|
+
status: 500,
|
|
38
|
+
message: 'Remote addon is misconfigured.',
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
/** The hosted addon returned a non-2xx response for a remote RPC. */
|
|
42
|
+
export class RemoteAddonRequestError extends PikkuError {
|
|
43
|
+
public readonly httpStatus: number
|
|
44
|
+
constructor(
|
|
45
|
+
namespace: string,
|
|
46
|
+
fnName: string,
|
|
47
|
+
status: number,
|
|
48
|
+
detail: string
|
|
49
|
+
) {
|
|
50
|
+
super(
|
|
51
|
+
`Remote addon '${namespace}:${fnName}' returned ${status}${detail ? `: ${detail}` : ''}`
|
|
52
|
+
)
|
|
53
|
+
this.httpStatus = status
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
addError(RemoteAddonRequestError, {
|
|
57
|
+
status: 502,
|
|
58
|
+
message: 'Remote addon request failed.',
|
|
59
|
+
})
|
|
26
60
|
import type { AIAgentInput } from '../ai-agent/ai-agent.types.js'
|
|
27
61
|
import type { AIStreamChannel } from '../ai-agent/ai-agent.types.js'
|
|
28
62
|
import type { StreamAIAgentOptions } from '../ai-agent/ai-agent-prepare.js'
|
|
29
63
|
import { runAIAgent, resumeAIAgentSync } from '../ai-agent/ai-agent-runner.js'
|
|
30
64
|
import { streamAIAgent, resumeAIAgent } from '../ai-agent/ai-agent-stream.js'
|
|
65
|
+
import { wrapChannelWithAGUI } from '../ai-agent/ai-agent-agui.js'
|
|
31
66
|
|
|
32
67
|
/**
|
|
33
68
|
* Resolve a namespaced function reference to package and function names
|
|
@@ -164,6 +199,12 @@ export class ContextAwareRPCService {
|
|
|
164
199
|
// 'namespace:func' boundary via invokeAddonFunction.
|
|
165
200
|
try {
|
|
166
201
|
const resolved = resolvePikkuFunction(funcName, this.packageName)
|
|
202
|
+
const addonInstance = resolved.packageName
|
|
203
|
+
? addonInstanceForNamespace(
|
|
204
|
+
this.wire.addonNamespace,
|
|
205
|
+
resolved.packageName
|
|
206
|
+
)
|
|
207
|
+
: undefined
|
|
167
208
|
return await runPikkuFunc<In, Out>(
|
|
168
209
|
'rpc',
|
|
169
210
|
funcName,
|
|
@@ -174,6 +215,7 @@ export class ContextAwareRPCService {
|
|
|
174
215
|
data: () => data,
|
|
175
216
|
wire: updatedWire,
|
|
176
217
|
packageName: resolved.packageName,
|
|
218
|
+
addonInstance,
|
|
177
219
|
}
|
|
178
220
|
)
|
|
179
221
|
} catch (e) {
|
|
@@ -213,6 +255,21 @@ export class ContextAwareRPCService {
|
|
|
213
255
|
throw new RPCNotFoundError(namespacedFunction)
|
|
214
256
|
}
|
|
215
257
|
|
|
258
|
+
const namespace = namespacedFunction.slice(
|
|
259
|
+
0,
|
|
260
|
+
namespacedFunction.indexOf(':')
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
// wireRemoteAddon: the addon ships as a devDependency (types only) and its
|
|
264
|
+
// handlers run on the host — dispatch over HTTP, not through local meta.
|
|
265
|
+
if (resolved.addonConfig?.remote) {
|
|
266
|
+
return this.invokeRemoteAddonFunction<In, Out>(
|
|
267
|
+
namespace,
|
|
268
|
+
resolved.function,
|
|
269
|
+
data
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
216
273
|
// Get the function meta from the addon package
|
|
217
274
|
// Addon packages use function meta, not RPC meta
|
|
218
275
|
const addonFunctionMeta = pikkuState(resolved.package, 'function', 'meta')
|
|
@@ -228,6 +285,15 @@ export class ContextAwareRPCService {
|
|
|
228
285
|
...(funcMeta.tags ?? []),
|
|
229
286
|
]
|
|
230
287
|
|
|
288
|
+
// The namespace is the consumer-facing wireAddon name; it selects the
|
|
289
|
+
// per-instance singleton services and secret/variable/credential overrides.
|
|
290
|
+
const addonInstance: AddonInstance = {
|
|
291
|
+
namespace,
|
|
292
|
+
secretOverrides: resolved.addonConfig?.secretOverrides,
|
|
293
|
+
variableOverrides: resolved.addonConfig?.variableOverrides,
|
|
294
|
+
credentialOverrides: resolved.addonConfig?.credentialOverrides,
|
|
295
|
+
}
|
|
296
|
+
|
|
231
297
|
// Execute the function using runPikkuFunc with the addon package's state
|
|
232
298
|
// We use the parent services (this.services) since addon packages share services
|
|
233
299
|
// Pass the function's tags so tag-based middleware/permissions are applied
|
|
@@ -238,9 +304,78 @@ export class ContextAwareRPCService {
|
|
|
238
304
|
wire,
|
|
239
305
|
packageName: resolved.package,
|
|
240
306
|
tags,
|
|
307
|
+
addonInstance,
|
|
241
308
|
})
|
|
242
309
|
}
|
|
243
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Dispatch a `wireRemoteAddon` RPC over HTTP to the hosting service.
|
|
313
|
+
*
|
|
314
|
+
* The consumer sends the addon's own function name (not the namespaced form)
|
|
315
|
+
* to the host's `/remote/rpc/:rpcName` endpoint, authenticating as a client
|
|
316
|
+
* with the token bound in `wireRemoteAddon({ auth })`. The addon's handlers
|
|
317
|
+
* live on the host, so there is no local function meta to resolve.
|
|
318
|
+
*/
|
|
319
|
+
private async invokeRemoteAddonFunction<In = any, Out = any>(
|
|
320
|
+
namespace: string,
|
|
321
|
+
fnName: string,
|
|
322
|
+
data: In
|
|
323
|
+
): Promise<Out> {
|
|
324
|
+
const cfg = pikkuState(null, 'addons', 'packages').get(namespace)
|
|
325
|
+
if (!cfg?.remote) {
|
|
326
|
+
throw new RPCNotFoundError(`${namespace}:${fnName}`)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const serverUrl =
|
|
330
|
+
typeof cfg.serverUrl === 'function'
|
|
331
|
+
? await cfg.serverUrl(this.services)
|
|
332
|
+
: cfg.serverUrl
|
|
333
|
+
if (!serverUrl) {
|
|
334
|
+
throw new RemoteAddonConfigError(namespace, 'serverUrl resolved empty')
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const remoteFn = cfg.remoteName ? cfg.remoteName(fnName) : fnName
|
|
338
|
+
const token = await resolveRemoteAddonToken(
|
|
339
|
+
cfg.remoteAuth,
|
|
340
|
+
this.services,
|
|
341
|
+
this.wire,
|
|
342
|
+
namespace
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
const headers: Record<string, string> = {
|
|
346
|
+
'content-type': 'application/json',
|
|
347
|
+
accept: 'application/json',
|
|
348
|
+
}
|
|
349
|
+
if (token) {
|
|
350
|
+
headers.authorization = `Bearer ${token}`
|
|
351
|
+
}
|
|
352
|
+
if (this.wire.traceId) {
|
|
353
|
+
headers['x-trace-id'] = this.wire.traceId
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const base = serverUrl.replace(/\/+$/, '')
|
|
357
|
+
const res = await fetch(
|
|
358
|
+
`${base}/remote/rpc/${encodeURIComponent(remoteFn)}`,
|
|
359
|
+
{
|
|
360
|
+
method: 'POST',
|
|
361
|
+
headers,
|
|
362
|
+
body: JSON.stringify({ rpcName: remoteFn, data }),
|
|
363
|
+
}
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
if (!res.ok) {
|
|
367
|
+
// Best-effort body read to enrich the thrown error (mirrors postRpc).
|
|
368
|
+
const detail = (await res.text().catch(() => '')).slice(0, 300)
|
|
369
|
+
throw new RemoteAddonRequestError(namespace, remoteFn, res.status, detail)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (res.status === 204) {
|
|
373
|
+
return undefined as Out
|
|
374
|
+
}
|
|
375
|
+
const text = await res.text()
|
|
376
|
+
return (text ? JSON.parse(text) : undefined) as Out
|
|
377
|
+
}
|
|
378
|
+
|
|
244
379
|
public async rpcWithWire<In = any, Out = any>(
|
|
245
380
|
rpcName: string,
|
|
246
381
|
data: In,
|
|
@@ -257,12 +392,19 @@ export class ContextAwareRPCService {
|
|
|
257
392
|
|
|
258
393
|
try {
|
|
259
394
|
const resolved = resolvePikkuFunction(rpcName, this.packageName)
|
|
395
|
+
const addonInstance = resolved.packageName
|
|
396
|
+
? addonInstanceForNamespace(
|
|
397
|
+
this.wire.addonNamespace,
|
|
398
|
+
resolved.packageName
|
|
399
|
+
)
|
|
400
|
+
: undefined
|
|
260
401
|
return await runPikkuFunc<In, Out>('rpc', rpcName, resolved.pikkuFuncId, {
|
|
261
402
|
auth: this.options.requiresAuth,
|
|
262
403
|
singletonServices: this.services,
|
|
263
404
|
data: () => data,
|
|
264
405
|
wire: mergedWire,
|
|
265
406
|
packageName: resolved.packageName,
|
|
407
|
+
addonInstance,
|
|
266
408
|
})
|
|
267
409
|
} catch (e) {
|
|
268
410
|
if (e instanceof RPCNotFoundError && this.services.deploymentService) {
|
|
@@ -338,16 +480,26 @@ export class ContextAwareRPCService {
|
|
|
338
480
|
) => {
|
|
339
481
|
const channel = this.wire.channel as unknown as AIStreamChannel
|
|
340
482
|
if (!channel) throw new Error('No channel available for streaming')
|
|
483
|
+
let currentRunId: string | undefined
|
|
341
484
|
await streamAIAgent(
|
|
342
485
|
agentName,
|
|
343
486
|
input,
|
|
344
|
-
channel,
|
|
487
|
+
wrapChannelWithAGUI(channel, {
|
|
488
|
+
threadId: input.threadId,
|
|
489
|
+
getRunId: () => currentRunId,
|
|
490
|
+
}),
|
|
345
491
|
{
|
|
346
492
|
sessionService: this.options.sessionService,
|
|
347
493
|
getCredential: this.wire.getCredential?.bind(this.wire),
|
|
348
494
|
},
|
|
349
495
|
undefined,
|
|
350
|
-
|
|
496
|
+
{
|
|
497
|
+
...options,
|
|
498
|
+
onRunCreated: (runId) => {
|
|
499
|
+
currentRunId = runId
|
|
500
|
+
options?.onRunCreated?.(runId)
|
|
501
|
+
},
|
|
502
|
+
}
|
|
351
503
|
)
|
|
352
504
|
},
|
|
353
505
|
resume: async (
|
|
@@ -359,7 +511,7 @@ export class ContextAwareRPCService {
|
|
|
359
511
|
if (!channel) throw new Error('No channel available for streaming')
|
|
360
512
|
await resumeAIAgent(
|
|
361
513
|
{ runId, ...input },
|
|
362
|
-
channel,
|
|
514
|
+
wrapChannelWithAGUI(channel, { runId }),
|
|
363
515
|
{
|
|
364
516
|
sessionService: this.options.sessionService,
|
|
365
517
|
getCredential: this.wire.getCredential?.bind(this.wire),
|