@pikku/core 0.12.80 → 0.12.82
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 +312 -0
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/function/function-runner.js +2 -5
- package/dist/function/index.d.ts +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/index.js +3 -3
- package/dist/pikku-state.js +4 -0
- package/dist/services/ai-agent-runner-service.d.ts +7 -0
- package/dist/services/ai-run-state-service.d.ts +10 -0
- package/dist/services/in-memory-ai-run-state-service.d.ts +5 -1
- package/dist/services/in-memory-ai-run-state-service.js +9 -0
- package/dist/services/index.d.ts +15 -15
- package/dist/services/index.js +5 -5
- package/dist/services/scoped-credential-service.d.ts +21 -0
- package/dist/services/scoped-credential-service.js +53 -0
- package/dist/testing/service-tests/ai-storage-service-tests.js +76 -0
- package/dist/types/core.types.d.ts +0 -2
- package/dist/types/state.types.d.ts +13 -0
- package/dist/wirings/actor-flow/index.d.ts +1 -1
- package/dist/wirings/ai-agent/ai-agent-finalize.d.ts +58 -0
- package/dist/wirings/ai-agent/ai-agent-finalize.js +138 -0
- package/dist/wirings/ai-agent/ai-agent-interrupt.js +1 -0
- package/dist/wirings/ai-agent/ai-agent-memory.d.ts +2 -8
- package/dist/wirings/ai-agent/ai-agent-memory.js +34 -17
- package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +7 -0
- package/dist/wirings/ai-agent/ai-agent-model-config.js +44 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.js +2 -0
- package/dist/wirings/ai-agent/ai-agent-runner.js +61 -40
- package/dist/wirings/ai-agent/ai-agent-stream.js +89 -36
- package/dist/wirings/ai-agent/ai-agent-turn.d.ts +1 -0
- package/dist/wirings/ai-agent/ai-agent-turn.js +1 -0
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +46 -1
- package/dist/wirings/ai-agent/index.d.ts +8 -7
- package/dist/wirings/ai-agent/index.js +5 -4
- package/dist/wirings/ai-scorer/ai-scorer-grade.d.ts +26 -0
- package/dist/wirings/ai-scorer/ai-scorer-grade.js +33 -0
- package/dist/wirings/ai-scorer/ai-scorer-judge.d.ts +17 -0
- package/dist/wirings/ai-scorer/ai-scorer-judge.js +92 -0
- package/dist/wirings/ai-scorer/ai-scorer-live.d.ts +15 -0
- package/dist/wirings/ai-scorer/ai-scorer-live.js +38 -0
- package/dist/wirings/ai-scorer/ai-scorer-registry.d.ts +18 -0
- package/dist/wirings/ai-scorer/ai-scorer-registry.js +46 -0
- package/dist/wirings/ai-scorer/ai-scorer-sampling.d.ts +8 -0
- package/dist/wirings/ai-scorer/ai-scorer-sampling.js +31 -0
- package/dist/wirings/ai-scorer/ai-scorer-snapshots.d.ts +10 -0
- package/dist/wirings/ai-scorer/ai-scorer-snapshots.js +40 -0
- package/dist/wirings/ai-scorer/ai-scorer-worker.d.ts +15 -0
- package/dist/wirings/ai-scorer/ai-scorer-worker.js +58 -0
- package/dist/wirings/ai-scorer/ai-scorer.d.ts +39 -0
- package/dist/wirings/ai-scorer/ai-scorer.js +40 -0
- package/dist/wirings/ai-scorer/ai-scorer.types.d.ts +90 -0
- package/dist/wirings/ai-scorer/ai-scorer.types.js +4 -0
- package/dist/wirings/ai-scorer/index.d.ts +6 -0
- package/dist/wirings/ai-scorer/index.js +5 -0
- package/dist/wirings/channel/index.d.ts +5 -6
- package/dist/wirings/channel/index.js +3 -4
- package/dist/wirings/channel/local/local-channel-runner.js +8 -1
- package/dist/wirings/cli/channel/cli-raw-channel-runner.js +9 -1
- package/dist/wirings/cli/channel/index.d.ts +1 -2
- package/dist/wirings/cli/channel/index.js +0 -1
- package/dist/wirings/cli/cli-runner.js +13 -1
- package/dist/wirings/credential/index.d.ts +1 -1
- package/dist/wirings/gateway/index.d.ts +1 -1
- package/dist/wirings/http/http-runner.js +8 -2
- package/dist/wirings/http/index.d.ts +1 -2
- package/dist/wirings/mcp/index.d.ts +1 -1
- package/dist/wirings/mcp/mcp-runner.d.ts +15 -0
- package/dist/wirings/mcp/mcp-runner.js +18 -5
- package/dist/wirings/persona/index.d.ts +3 -4
- package/dist/wirings/persona/index.js +2 -3
- package/dist/wirings/queue/index.d.ts +1 -3
- package/dist/wirings/queue/index.js +1 -3
- package/dist/wirings/rpc/addon-runner.d.ts +4 -0
- package/dist/wirings/rpc/addon-runner.js +19 -3
- package/dist/wirings/rpc/rpc-runner.js +2 -0
- package/dist/wirings/rpc/rpc-types.d.ts +4 -0
- package/dist/wirings/rpc/wire-addon.d.ts +13 -0
- package/dist/wirings/rpc/wire-addon.js +4 -0
- package/dist/wirings/scheduler/index.d.ts +1 -1
- package/dist/wirings/trigger/index.d.ts +1 -1
- package/dist/wirings/virtual-user/index.d.ts +5 -6
- package/dist/wirings/virtual-user/index.js +2 -4
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +85 -15
- package/dist/wirings/workflow/index.d.ts +6 -6
- package/dist/wirings/workflow/index.js +2 -2
- package/dist/wirings/workflow/pikku-scenario-service.d.ts +7 -7
- package/dist/wirings/workflow/pikku-scenario-service.js +39 -13
- package/dist/wirings/workflow/pikku-workflow-service.js +17 -3
- package/dist/wirings/workflow/scenario-step.types.d.ts +8 -0
- package/dist/wirings/workflow/workflow-approval-audit.d.ts +16 -0
- package/dist/wirings/workflow/workflow-approval-audit.js +40 -0
- package/dist/wirings/workflow/workflow-approval-policy.d.ts +20 -0
- package/dist/wirings/workflow/workflow-approval-policy.js +48 -0
- package/dist/wirings/workflow/workflow-approval.d.ts +29 -1
- package/dist/wirings/workflow/workflow-approval.js +65 -2
- package/dist/wirings/workflow/workflow-run-ownership.d.ts +2 -1
- package/dist/wirings/workflow/workflow-run-ownership.js +2 -1
- package/dist/wirings/workflow/workflow.types.d.ts +1 -1
- package/knowledge/decisions/internals/addon-pikku-meta-ships-at-the-package-root-or-under-dist.md +32 -0
- package/knowledge/decisions/internals/an-addon-scope-root-loses-to-a-root-the-host-already-declares.md +39 -0
- package/knowledge/decisions/internals/index.md +30 -3
- package/knowledge/decisions/internals/validate-runs-checks-by-precondition.md +115 -0
- package/knowledge/decisions/security/a-function-never-receives-the-secret-service.md +37 -0
- package/knowledge/decisions/security/a-workflow-run-is-read-and-approved-by-its-owner.md +30 -14
- package/knowledge/decisions/security/an-approval-answer-outlives-the-run-it-answered.md +59 -0
- package/knowledge/decisions/security/index.md +3 -1
- package/knowledge/questions/index.md +1 -1
- package/package.json +3 -1
- package/scripts/generate-api-report.mts +143 -18
- package/src/api-report.test.ts +2 -2
- package/src/errors/index.ts +1 -1
- package/src/function/function-runner.test.ts +52 -0
- package/src/function/function-runner.ts +5 -9
- package/src/function/index.ts +0 -2
- package/src/index.ts +0 -35
- package/src/pikku-state.ts +5 -0
- package/src/public-surface.json +70 -94
- package/src/services/ai-agent-runner-service.ts +12 -1
- package/src/services/ai-run-state-service.ts +11 -0
- package/src/services/in-memory-ai-run-state-service.ts +13 -0
- package/src/services/index.ts +3 -43
- package/src/services/scoped-credential-service.test.ts +86 -0
- package/src/services/scoped-credential-service.ts +63 -0
- package/src/testing/service-tests/ai-storage-service-tests.ts +93 -0
- package/src/types/core.types.ts +3 -6
- package/src/types/state.types.ts +16 -0
- package/src/wirings/actor-flow/index.ts +0 -3
- package/src/wirings/ai-agent/ai-agent-finalize.test.ts +186 -0
- package/src/wirings/ai-agent/ai-agent-finalize.ts +197 -0
- package/src/wirings/ai-agent/ai-agent-interrupt.ts +1 -0
- package/src/wirings/ai-agent/ai-agent-memory.ts +54 -38
- package/src/wirings/ai-agent/ai-agent-model-config.test.ts +72 -3
- package/src/wirings/ai-agent/ai-agent-model-config.ts +49 -1
- package/src/wirings/ai-agent/ai-agent-prepare.ts +2 -0
- package/src/wirings/ai-agent/ai-agent-runner.ts +71 -40
- package/src/wirings/ai-agent/ai-agent-stream-output-hooks.test.ts +353 -0
- package/src/wirings/ai-agent/ai-agent-stream.ts +116 -54
- package/src/wirings/ai-agent/ai-agent-turn.test.ts +67 -0
- package/src/wirings/ai-agent/ai-agent-turn.ts +1 -0
- package/src/wirings/ai-agent/ai-agent.types.ts +64 -4
- package/src/wirings/ai-agent/index.ts +2 -16
- package/src/wirings/ai-scorer/ai-scorer-grade.test.ts +106 -0
- package/src/wirings/ai-scorer/ai-scorer-grade.ts +55 -0
- package/src/wirings/ai-scorer/ai-scorer-judge.test.ts +143 -0
- package/src/wirings/ai-scorer/ai-scorer-judge.ts +120 -0
- package/src/wirings/ai-scorer/ai-scorer-live.test.ts +174 -0
- package/src/wirings/ai-scorer/ai-scorer-live.ts +56 -0
- package/src/wirings/ai-scorer/ai-scorer-registry.ts +63 -0
- package/src/wirings/ai-scorer/ai-scorer-sampling.test.ts +34 -0
- package/src/wirings/ai-scorer/ai-scorer-sampling.ts +36 -0
- package/src/wirings/ai-scorer/ai-scorer-snapshots.test.ts +49 -0
- package/src/wirings/ai-scorer/ai-scorer-snapshots.ts +46 -0
- package/src/wirings/ai-scorer/ai-scorer-worker.test.ts +122 -0
- package/src/wirings/ai-scorer/ai-scorer-worker.ts +69 -0
- package/src/wirings/ai-scorer/ai-scorer.ts +76 -0
- package/src/wirings/ai-scorer/ai-scorer.types.ts +107 -0
- package/src/wirings/ai-scorer/index.ts +24 -0
- package/src/wirings/channel/index.ts +1 -20
- package/src/wirings/channel/local/local-channel-runner.test.ts +68 -0
- package/src/wirings/channel/local/local-channel-runner.ts +8 -1
- package/src/wirings/cli/channel/cli-raw-channel-runner.test.ts +23 -0
- package/src/wirings/cli/channel/cli-raw-channel-runner.ts +12 -1
- package/src/wirings/cli/channel/index.ts +0 -7
- package/src/wirings/cli/cli-runner.test.ts +68 -0
- package/src/wirings/cli/cli-runner.ts +18 -1
- package/src/wirings/credential/index.ts +0 -1
- package/src/wirings/gateway/index.ts +0 -3
- package/src/wirings/http/http-runner.test.ts +66 -0
- package/src/wirings/http/http-runner.ts +10 -2
- package/src/wirings/http/index.ts +1 -1
- package/src/wirings/mcp/index.ts +0 -1
- package/src/wirings/mcp/mcp-runner.test.ts +181 -0
- package/src/wirings/mcp/mcp-runner.ts +35 -5
- package/src/wirings/persona/index.ts +0 -8
- package/src/wirings/queue/index.ts +0 -14
- package/src/wirings/rpc/addon-runner.ts +34 -3
- package/src/wirings/rpc/addon-secrets.test.ts +261 -0
- package/src/wirings/rpc/rpc-runner.test.ts +2 -0
- package/src/wirings/rpc/rpc-runner.ts +2 -0
- package/src/wirings/rpc/rpc-types.ts +4 -0
- package/src/wirings/rpc/wire-addon.ts +17 -0
- package/src/wirings/scheduler/index.ts +0 -1
- package/src/wirings/trigger/index.ts +0 -1
- package/src/wirings/virtual-user/index.ts +0 -16
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +96 -16
- package/src/wirings/workflow/graph/graph-runner.test.ts +72 -0
- package/src/wirings/workflow/index.ts +2 -20
- package/src/wirings/workflow/pikku-scenario-service.ts +60 -15
- package/src/wirings/workflow/pikku-workflow-service.test.ts +13 -12
- package/src/wirings/workflow/pikku-workflow-service.ts +28 -4
- package/src/wirings/workflow/scenario-expectations.test.ts +75 -0
- package/src/wirings/workflow/scenario-hooks.test.ts +3 -2
- package/src/wirings/workflow/scenario-step.types.ts +8 -0
- package/src/wirings/workflow/workflow-approval-audit.ts +47 -0
- package/src/wirings/workflow/workflow-approval-policy.test.ts +524 -0
- package/src/wirings/workflow/workflow-approval-policy.ts +68 -0
- package/src/wirings/workflow/workflow-approval.ts +113 -9
- package/src/wirings/workflow/workflow-run-authority.test.ts +12 -15
- package/src/wirings/workflow/workflow-run-ownership.ts +2 -1
- package/src/wirings/workflow/workflow.types.ts +0 -9
- package/src/wirings-stay-decoupled.test.ts +6 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/internal.d.ts +0 -3
- package/dist/internal.js +0 -2
- package/dist/middleware/timeout.d.ts +0 -9
- package/dist/middleware/timeout.js +0 -15
- package/dist/pikku-response.d.ts +0 -6
- package/dist/pikku-response.js +0 -6
- package/dist/services/gopass-secrets.d.ts +0 -15
- package/dist/services/gopass-secrets.js +0 -76
- package/dist/services/http-scenario-actors.d.ts +0 -75
- package/dist/services/http-scenario-actors.js +0 -195
- package/dist/services/http-user-flow-actors.d.ts +0 -67
- package/dist/services/http-user-flow-actors.js +0 -193
- package/dist/services/scenario-actors-service.d.ts +0 -127
- package/dist/services/scenario-actors-service.js +0 -40
- package/dist/services/user-flow-actors-service.d.ts +0 -39
- package/dist/services/user-flow-actors-service.js +0 -1
- package/dist/wirings/credential/wire-credential.d.ts +0 -48
- package/dist/wirings/credential/wire-credential.js +0 -47
- package/dist/wirings/oauth2/oauth2-client.d.ts +0 -47
- package/dist/wirings/oauth2/oauth2-client.js +0 -263
- package/dist/wirings/oauth2/oauth2-routes.d.ts +0 -35
- package/dist/wirings/oauth2/oauth2-routes.js +0 -146
- package/dist/wirings/scope/wire-scope.d.ts +0 -33
- package/dist/wirings/scope/wire-scope.js +0 -32
- package/dist/wirings/workflow/dsl/index.d.ts +0 -5
- package/dist/wirings/workflow/dsl/index.js +0 -4
- package/dist/wirings/workflow/graph/index.d.ts +0 -5
- package/dist/wirings/workflow/graph/index.js +0 -4
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import type { ConverseOptions, ActorFlowVerdict } from '../wirings/actor-flow/actor-flow.types.js';
|
|
2
|
-
/**
|
|
3
|
-
* What the transport answered, for a step that treats the status as data.
|
|
4
|
-
*
|
|
5
|
-
* An HTTP response with its body already drained: the stream can only be read
|
|
6
|
-
* once, and a step's return value crosses into the run record, so the response
|
|
7
|
-
* object itself cannot travel. This is the shape every caller ends up with.
|
|
8
|
-
*/
|
|
9
|
-
export interface ScenarioHttpResponse<T = unknown> {
|
|
10
|
-
status: number;
|
|
11
|
-
ok: boolean;
|
|
12
|
-
/**
|
|
13
|
-
* The parsed JSON body — or, when the body was not JSON, the raw text it was
|
|
14
|
-
* parsed from, so an HTML error page is still readable rather than lost.
|
|
15
|
-
* `undefined` for an empty response.
|
|
16
|
-
*
|
|
17
|
-
* `T` is a claim the caller makes, not one the transport checked: a step that
|
|
18
|
-
* knows the route's payload names it here instead of casting at every use.
|
|
19
|
-
*/
|
|
20
|
-
body: T;
|
|
21
|
-
/**
|
|
22
|
-
* The whole body as text, so an assertion can search it without knowing the
|
|
23
|
-
* payload's shape — and so an error body that is HTML rather than JSON still
|
|
24
|
-
* says what went wrong.
|
|
25
|
-
*/
|
|
26
|
-
serialized: string;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Drain a response into the shape a step can carry: the parsed body (an empty
|
|
30
|
-
* one counting as no body at all) alongside the text it was parsed from.
|
|
31
|
-
*
|
|
32
|
-
* `invokeRaw` returns this, and a step that has to reach past an actor — a
|
|
33
|
-
* route with no RPC, an identity no actor can hold — reaches for this rather
|
|
34
|
-
* than writing the same record by hand.
|
|
35
|
-
*/
|
|
36
|
-
export declare const readScenarioHttpResponse: <T = unknown>(res: Response) => Promise<ScenarioHttpResponse<T>>;
|
|
37
|
-
/** How to send one JSON request, for `postScenarioJson`. */
|
|
38
|
-
export interface ScenarioJsonRequest {
|
|
39
|
-
/** Serialised as the JSON body. Omit for a request that carries none. */
|
|
40
|
-
body?: unknown;
|
|
41
|
-
/** Sent alongside `content-type: application/json`, and may override it. */
|
|
42
|
-
headers?: Record<string, string>;
|
|
43
|
-
/** Defaults to `POST` — the method every scenario route here answers. */
|
|
44
|
-
method?: string;
|
|
45
|
-
/**
|
|
46
|
-
* The `fetch` to send it with. Pass a `ScenarioCookieJar`'s to keep the
|
|
47
|
-
* session; the global `fetch` otherwise, which is what a step asserting on a
|
|
48
|
-
* sessionless call wants.
|
|
49
|
-
*/
|
|
50
|
-
fetch?: typeof fetch;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* POST JSON somewhere and report what came back, without throwing on a 4xx/5xx.
|
|
54
|
-
*
|
|
55
|
-
* Every scenario that reaches past an actor was writing this by hand — the same
|
|
56
|
-
* `content-type`, the same `JSON.stringify`, the same drain — and the copies had
|
|
57
|
-
* drifted: some returned `res.json()`, which loses the status and throws
|
|
58
|
-
* outright when the target answers an empty body or an HTML error page. A
|
|
59
|
-
* refusal is the expected outcome of a permissions scenario, so it has to
|
|
60
|
-
* survive as data.
|
|
61
|
-
*/
|
|
62
|
-
export declare const postScenarioJson: <T = unknown>(url: string, { body, headers, method, fetch: send, }?: ScenarioJsonRequest) => Promise<ScenarioHttpResponse<T>>;
|
|
63
|
-
/** Per-call transport options. */
|
|
64
|
-
export interface ScenarioInvokeOptions {
|
|
65
|
-
/**
|
|
66
|
-
* Headers to send alongside the actor's own session. This is how a step
|
|
67
|
-
* expresses an identity the actor registry cannot — an impersonation header,
|
|
68
|
-
* or one of the header-shim principals a credential scenario invents.
|
|
69
|
-
*/
|
|
70
|
-
headers?: Record<string, string>;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* The RPC surface an actor can reach, as name → input/output. A project binds
|
|
74
|
-
* its generated exposed RPC map here; the default leaves every name open, which
|
|
75
|
-
* is what an actor built by hand (or by a third-party driver) gets.
|
|
76
|
-
*/
|
|
77
|
-
export type ScenarioRpcMap = Record<string, {
|
|
78
|
-
input: any;
|
|
79
|
-
output: any;
|
|
80
|
-
}>;
|
|
81
|
-
/**
|
|
82
|
-
* The actor a step wire carries, for a project whose actor registry is known.
|
|
83
|
-
* An empty registry keeps the open actor type rather than collapsing to
|
|
84
|
-
* `never` — a project may still build actors itself.
|
|
85
|
-
*/
|
|
86
|
-
export type ScenarioActorOf<TActors> = [keyof TActors] extends [never] ? ScenarioActor : TActors[keyof TActors];
|
|
87
|
-
/** A synthetic user (a user row flagged `actor`) that workflow steps run as over the real transport */
|
|
88
|
-
export interface ScenarioActor<TAgentName extends string = string, TRpcMap extends ScenarioRpcMap = ScenarioRpcMap> {
|
|
89
|
-
/** Stable actor name (the key in pikku.config.json's actor registry). */
|
|
90
|
-
readonly name: string;
|
|
91
|
-
/** The actor's user email — flows use it for invites/lookups. */
|
|
92
|
-
readonly email: string;
|
|
93
|
-
/** Invoke an exposed RPC as this actor over the real transport. */
|
|
94
|
-
invoke<TName extends keyof TRpcMap & string>(rpcName: TName, data: TRpcMap[TName]['input']): Promise<TRpcMap[TName]['output']>;
|
|
95
|
-
/**
|
|
96
|
-
* The same call, reporting what the transport answered rather than throwing.
|
|
97
|
-
* A refusal is the expected outcome of a permissions or scopes scenario, and
|
|
98
|
-
* `invoke`'s error truncates the body that names which scope was missing.
|
|
99
|
-
*/
|
|
100
|
-
invokeRaw<TName extends keyof TRpcMap & string>(rpcName: TName, data: TRpcMap[TName]['input'], options?: ScenarioInvokeOptions): Promise<ScenarioHttpResponse>;
|
|
101
|
-
/** Converse with a Pikku AI agent in this actor's persona and return its verdict */
|
|
102
|
-
converse(options: ConverseOptions<TAgentName>): Promise<ActorFlowVerdict>;
|
|
103
|
-
}
|
|
104
|
-
/** Display/config metadata for an actor (from pikku.config.json) */
|
|
105
|
-
export interface ScenarioActorConfig {
|
|
106
|
-
email: string;
|
|
107
|
-
name?: string;
|
|
108
|
-
jobTitle?: string;
|
|
109
|
-
personality?: string;
|
|
110
|
-
/**
|
|
111
|
-
* The persona this body is one of — the KIND of person, declared in
|
|
112
|
-
* `scenarios.personas`. Most personas have exactly one actor and it is
|
|
113
|
-
* materialised for them; a second body of the same persona is what tenant
|
|
114
|
-
* isolation and peer-sharing scenarios are made of.
|
|
115
|
-
*/
|
|
116
|
-
persona?: string;
|
|
117
|
-
/**
|
|
118
|
-
* Scopes this actor holds, granted directly rather than through a role, and
|
|
119
|
-
* the roles it belongs to. Pikku carries them; it never applies them — which
|
|
120
|
-
* scope store exists and which roles have been created is the app's own, so
|
|
121
|
-
* the app's seed reads these back off `scenarioActorConfigs` and grants them.
|
|
122
|
-
*/
|
|
123
|
-
scopes?: readonly string[];
|
|
124
|
-
roles?: readonly string[];
|
|
125
|
-
}
|
|
126
|
-
/** The injected `actors` service: actor name → actor. */
|
|
127
|
-
export type ScenarioActors = Record<string, ScenarioActor>;
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Drain a response into the shape a step can carry: the parsed body (an empty
|
|
3
|
-
* one counting as no body at all) alongside the text it was parsed from.
|
|
4
|
-
*
|
|
5
|
-
* `invokeRaw` returns this, and a step that has to reach past an actor — a
|
|
6
|
-
* route with no RPC, an identity no actor can hold — reaches for this rather
|
|
7
|
-
* than writing the same record by hand.
|
|
8
|
-
*/
|
|
9
|
-
export const readScenarioHttpResponse = async (res) => {
|
|
10
|
-
const text = res.status === 204 ? '' : await res.text().catch(() => '');
|
|
11
|
-
return {
|
|
12
|
-
status: res.status,
|
|
13
|
-
ok: res.ok,
|
|
14
|
-
body: (text ? parseJsonBody(text) : undefined),
|
|
15
|
-
serialized: text,
|
|
16
|
-
};
|
|
17
|
-
};
|
|
18
|
-
const parseJsonBody = (text) => {
|
|
19
|
-
try {
|
|
20
|
-
return JSON.parse(text);
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
return text;
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
/**
|
|
27
|
-
* POST JSON somewhere and report what came back, without throwing on a 4xx/5xx.
|
|
28
|
-
*
|
|
29
|
-
* Every scenario that reaches past an actor was writing this by hand — the same
|
|
30
|
-
* `content-type`, the same `JSON.stringify`, the same drain — and the copies had
|
|
31
|
-
* drifted: some returned `res.json()`, which loses the status and throws
|
|
32
|
-
* outright when the target answers an empty body or an HTML error page. A
|
|
33
|
-
* refusal is the expected outcome of a permissions scenario, so it has to
|
|
34
|
-
* survive as data.
|
|
35
|
-
*/
|
|
36
|
-
export const postScenarioJson = async (url, { body, headers, method = 'POST', fetch: send = fetch, } = {}) => readScenarioHttpResponse(await send(url, {
|
|
37
|
-
method,
|
|
38
|
-
headers: { 'content-type': 'application/json', ...headers },
|
|
39
|
-
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
40
|
-
}));
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import type { ConverseOptions, ActorFlowVerdict } from '../wirings/actor-flow/actor-flow.types.js';
|
|
2
|
-
/**
|
|
3
|
-
* A user-flow actor: a synthetic user (a normal user row flagged `actor`) that
|
|
4
|
-
* workflow steps can run as. Passed to `workflow.do(step, rpc, data, { actor })`
|
|
5
|
-
* — the step then goes through the actor's authenticated client over the REAL
|
|
6
|
-
* transport (auth middleware, permissions, serialization all exercised),
|
|
7
|
-
* never through internal dispatch. Login is lazy: the first `invoke` signs the
|
|
8
|
-
* actor in and the session is cached for the actor's lifetime.
|
|
9
|
-
*/
|
|
10
|
-
export interface UserFlowActor<TAgentName extends string = string> {
|
|
11
|
-
/** Stable actor name (the key in pikku.config.json's actor registry). */
|
|
12
|
-
readonly name: string;
|
|
13
|
-
/** The actor's user email — flows use it for invites/lookups. */
|
|
14
|
-
readonly email: string;
|
|
15
|
-
/** Invoke an exposed RPC as this actor over the real transport. */
|
|
16
|
-
invoke(rpcName: string, data: unknown): Promise<unknown>;
|
|
17
|
-
/**
|
|
18
|
-
* Hold a dynamic conversation with a target Pikku AI agent, in THIS actor's
|
|
19
|
-
* persona (personality/jobTitle). Drives the target over the real transport
|
|
20
|
-
* as the signed-in actor, answers its tool-approval requests in-persona, and
|
|
21
|
-
* returns the actor's verdict on whether the task was met. Deterministic
|
|
22
|
-
* checks are the caller's job — use `invoke` afterwards. In a typed project
|
|
23
|
-
* `agent` is constrained to the generated union of agent names.
|
|
24
|
-
*/
|
|
25
|
-
converse(options: ConverseOptions<TAgentName>): Promise<ActorFlowVerdict>;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Display/config metadata for an actor (from pikku.config.json). The email
|
|
29
|
-
* identifies the actor's user row; personality/jobTitle exist for the console
|
|
30
|
-
* screen and for agent-driven flows (the agent plays the persona).
|
|
31
|
-
*/
|
|
32
|
-
export interface UserFlowActorConfig {
|
|
33
|
-
email: string;
|
|
34
|
-
name?: string;
|
|
35
|
-
jobTitle?: string;
|
|
36
|
-
personality?: string;
|
|
37
|
-
}
|
|
38
|
-
/** The injected `actors` service: actor name → actor. */
|
|
39
|
-
export type UserFlowActors = Record<string, UserFlowActor>;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import type { CoreCredential } from './credential.types.js';
|
|
2
|
-
/**
|
|
3
|
-
* No-op function for declaring credentials.
|
|
4
|
-
* This exists purely for TypeScript type checking and will be tree-shaken.
|
|
5
|
-
* The CLI extracts metadata via AST parsing.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```typescript
|
|
9
|
-
* // Per-user API key
|
|
10
|
-
* wireCredential({
|
|
11
|
-
* name: 'stripe',
|
|
12
|
-
* displayName: 'Stripe API Key',
|
|
13
|
-
* type: 'wire',
|
|
14
|
-
* schema: z.object({ apiKey: z.string() }),
|
|
15
|
-
* })
|
|
16
|
-
*
|
|
17
|
-
* // Per-user OAuth
|
|
18
|
-
* wireCredential({
|
|
19
|
-
* name: 'google-sheets',
|
|
20
|
-
* displayName: 'Google Sheets',
|
|
21
|
-
* type: 'wire',
|
|
22
|
-
* schema: z.object({ accessToken: z.string(), refreshToken: z.string() }),
|
|
23
|
-
* oauth2: {
|
|
24
|
-
* appCredentialSecretId: 'GOOGLE_OAUTH_APP',
|
|
25
|
-
* authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
26
|
-
* tokenUrl: 'https://oauth2.googleapis.com/token',
|
|
27
|
-
* scopes: ['https://www.googleapis.com/auth/spreadsheets'],
|
|
28
|
-
* tokenSecretId: 'GOOGLE_OAUTH_TOKENS',
|
|
29
|
-
* }
|
|
30
|
-
* })
|
|
31
|
-
*
|
|
32
|
-
* // Platform-level OAuth (singleton)
|
|
33
|
-
* wireCredential({
|
|
34
|
-
* name: 'slack',
|
|
35
|
-
* displayName: 'Slack',
|
|
36
|
-
* type: 'singleton',
|
|
37
|
-
* schema: z.object({ accessToken: z.string(), refreshToken: z.string() }),
|
|
38
|
-
* oauth2: {
|
|
39
|
-
* appCredentialSecretId: 'SLACK_OAUTH_APP',
|
|
40
|
-
* authorizationUrl: 'https://slack.com/oauth/v2/authorize',
|
|
41
|
-
* tokenUrl: 'https://slack.com/api/oauth.v2.access',
|
|
42
|
-
* scopes: ['chat:write', 'channels:read'],
|
|
43
|
-
* tokenSecretId: 'SLACK_OAUTH_TOKENS',
|
|
44
|
-
* }
|
|
45
|
-
* })
|
|
46
|
-
* ```
|
|
47
|
-
*/
|
|
48
|
-
export declare const wireCredential: <T>(_config: CoreCredential<T>) => void;
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* No-op function for declaring credentials.
|
|
3
|
-
* This exists purely for TypeScript type checking and will be tree-shaken.
|
|
4
|
-
* The CLI extracts metadata via AST parsing.
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```typescript
|
|
8
|
-
* // Per-user API key
|
|
9
|
-
* wireCredential({
|
|
10
|
-
* name: 'stripe',
|
|
11
|
-
* displayName: 'Stripe API Key',
|
|
12
|
-
* type: 'wire',
|
|
13
|
-
* schema: z.object({ apiKey: z.string() }),
|
|
14
|
-
* })
|
|
15
|
-
*
|
|
16
|
-
* // Per-user OAuth
|
|
17
|
-
* wireCredential({
|
|
18
|
-
* name: 'google-sheets',
|
|
19
|
-
* displayName: 'Google Sheets',
|
|
20
|
-
* type: 'wire',
|
|
21
|
-
* schema: z.object({ accessToken: z.string(), refreshToken: z.string() }),
|
|
22
|
-
* oauth2: {
|
|
23
|
-
* appCredentialSecretId: 'GOOGLE_OAUTH_APP',
|
|
24
|
-
* authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
25
|
-
* tokenUrl: 'https://oauth2.googleapis.com/token',
|
|
26
|
-
* scopes: ['https://www.googleapis.com/auth/spreadsheets'],
|
|
27
|
-
* tokenSecretId: 'GOOGLE_OAUTH_TOKENS',
|
|
28
|
-
* }
|
|
29
|
-
* })
|
|
30
|
-
*
|
|
31
|
-
* // Platform-level OAuth (singleton)
|
|
32
|
-
* wireCredential({
|
|
33
|
-
* name: 'slack',
|
|
34
|
-
* displayName: 'Slack',
|
|
35
|
-
* type: 'singleton',
|
|
36
|
-
* schema: z.object({ accessToken: z.string(), refreshToken: z.string() }),
|
|
37
|
-
* oauth2: {
|
|
38
|
-
* appCredentialSecretId: 'SLACK_OAUTH_APP',
|
|
39
|
-
* authorizationUrl: 'https://slack.com/oauth/v2/authorize',
|
|
40
|
-
* tokenUrl: 'https://slack.com/api/oauth.v2.access',
|
|
41
|
-
* scopes: ['chat:write', 'channels:read'],
|
|
42
|
-
* tokenSecretId: 'SLACK_OAUTH_TOKENS',
|
|
43
|
-
* }
|
|
44
|
-
* })
|
|
45
|
-
* ```
|
|
46
|
-
*/
|
|
47
|
-
export const wireCredential = (_config) => { };
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import type { OAuth2Token } from './oauth2.types.js';
|
|
2
|
-
import type { OAuth2CredentialConfig } from '../secret/secret.types.js';
|
|
3
|
-
import type { SecretService } from '../../services/secret-service.js';
|
|
4
|
-
export declare class OAuth2Client {
|
|
5
|
-
private oauth2Config;
|
|
6
|
-
private appCredentialSecretId;
|
|
7
|
-
private secrets;
|
|
8
|
-
private cachedToken;
|
|
9
|
-
private cachedAppCredential;
|
|
10
|
-
private refreshPromise;
|
|
11
|
-
constructor(oauth2Config: OAuth2CredentialConfig, appCredentialSecretId: string, secrets: SecretService);
|
|
12
|
-
/**
|
|
13
|
-
* Make an authenticated request. Handles token caching and 401 refresh.
|
|
14
|
-
*/
|
|
15
|
-
request(url: string, options?: RequestInit, timeoutMs?: number): Promise<Response>;
|
|
16
|
-
/**
|
|
17
|
-
* Get cached token or load from secrets.
|
|
18
|
-
*/
|
|
19
|
-
getAccessToken(): Promise<string>;
|
|
20
|
-
/**
|
|
21
|
-
* Refresh token and update cache.
|
|
22
|
-
* Uses a promise lock to prevent concurrent refresh attempts.
|
|
23
|
-
*/
|
|
24
|
-
private refreshAndGetToken;
|
|
25
|
-
/**
|
|
26
|
-
* Perform the actual token refresh.
|
|
27
|
-
*/
|
|
28
|
-
private doRefreshToken;
|
|
29
|
-
/**
|
|
30
|
-
* Check if token is valid (not expired).
|
|
31
|
-
*/
|
|
32
|
-
private isTokenValid;
|
|
33
|
-
/**
|
|
34
|
-
* Get app credentials (cached).
|
|
35
|
-
*/
|
|
36
|
-
private getAppCredential;
|
|
37
|
-
/**
|
|
38
|
-
* Generate OAuth2 authorization URL.
|
|
39
|
-
* Used by CLI during `pikku oauth connect`.
|
|
40
|
-
*/
|
|
41
|
-
getAuthorizationUrl(state: string, redirectUri: string): Promise<string>;
|
|
42
|
-
/**
|
|
43
|
-
* Exchange authorization code for tokens.
|
|
44
|
-
* Used by CLI during `pikku oauth connect`.
|
|
45
|
-
*/
|
|
46
|
-
exchangeCode(code: string, redirectUri: string): Promise<OAuth2Token>;
|
|
47
|
-
}
|
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OAuth2 client that acts as a service.
|
|
3
|
-
* Created once in createSingletonServices, handles token caching and refresh internally.
|
|
4
|
-
* Functions use it directly via oauth.request().
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```typescript
|
|
8
|
-
* // In services.ts
|
|
9
|
-
* export const createSingletonServices = async (config, { secrets }) => {
|
|
10
|
-
* return {
|
|
11
|
-
* slackOAuth: new OAuth2Client(
|
|
12
|
-
* { tokenSecretId: 'SLACK_TOKENS', authorizationUrl: '...', tokenUrl: '...', scopes: [] },
|
|
13
|
-
* 'SLACK_APP_CREDS',
|
|
14
|
-
* secrets
|
|
15
|
-
* ),
|
|
16
|
-
* }
|
|
17
|
-
* }
|
|
18
|
-
*
|
|
19
|
-
* // In function
|
|
20
|
-
* export const postMessage = pikkuFunc(async ({ slackOAuth }, { channel, text }) => {
|
|
21
|
-
* const response = await slackOAuth.request('https://slack.com/api/chat.postMessage', {
|
|
22
|
-
* method: 'POST',
|
|
23
|
-
* body: JSON.stringify({ channel, text }),
|
|
24
|
-
* })
|
|
25
|
-
* return response.json()
|
|
26
|
-
* })
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
30
|
-
const TOKEN_EXPIRY_BUFFER_MS = 60_000;
|
|
31
|
-
/**
|
|
32
|
-
* Helper to fetch with a timeout using AbortController.
|
|
33
|
-
*/
|
|
34
|
-
async function fetchWithTimeout(url, options, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
35
|
-
const controller = new AbortController();
|
|
36
|
-
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
37
|
-
try {
|
|
38
|
-
return await fetch(url, {
|
|
39
|
-
...options,
|
|
40
|
-
signal: controller.signal,
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
finally {
|
|
44
|
-
clearTimeout(timeoutId);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
export class OAuth2Client {
|
|
48
|
-
oauth2Config;
|
|
49
|
-
appCredentialSecretId;
|
|
50
|
-
secrets;
|
|
51
|
-
cachedToken = null;
|
|
52
|
-
cachedAppCredential = null;
|
|
53
|
-
refreshPromise = null;
|
|
54
|
-
constructor(oauth2Config, appCredentialSecretId, secrets) {
|
|
55
|
-
this.oauth2Config = oauth2Config;
|
|
56
|
-
this.appCredentialSecretId = appCredentialSecretId;
|
|
57
|
-
this.secrets = secrets;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Make an authenticated request. Handles token caching and 401 refresh.
|
|
61
|
-
*/
|
|
62
|
-
async request(url, options, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
63
|
-
const token = await this.getAccessToken();
|
|
64
|
-
const response = await fetchWithTimeout(url, {
|
|
65
|
-
...options,
|
|
66
|
-
headers: {
|
|
67
|
-
...options?.headers,
|
|
68
|
-
Authorization: `Bearer ${token}`,
|
|
69
|
-
},
|
|
70
|
-
}, timeoutMs);
|
|
71
|
-
// Auto-retry on 401 after refresh
|
|
72
|
-
if (response.status === 401) {
|
|
73
|
-
const newToken = await this.refreshAndGetToken();
|
|
74
|
-
return fetchWithTimeout(url, {
|
|
75
|
-
...options,
|
|
76
|
-
headers: {
|
|
77
|
-
...options?.headers,
|
|
78
|
-
Authorization: `Bearer ${newToken}`,
|
|
79
|
-
},
|
|
80
|
-
}, timeoutMs);
|
|
81
|
-
}
|
|
82
|
-
return response;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Get cached token or load from secrets.
|
|
86
|
-
*/
|
|
87
|
-
async getAccessToken() {
|
|
88
|
-
// Return cached token if valid
|
|
89
|
-
if (this.cachedToken) {
|
|
90
|
-
if (this.isTokenValid(this.cachedToken)) {
|
|
91
|
-
return this.cachedToken.accessToken;
|
|
92
|
-
}
|
|
93
|
-
// Token expired, try to refresh
|
|
94
|
-
if (this.cachedToken.refreshToken) {
|
|
95
|
-
return this.refreshAndGetToken();
|
|
96
|
-
}
|
|
97
|
-
// Token expired and no refresh token available
|
|
98
|
-
throw new Error('OAuth2 token expired and no refresh token available');
|
|
99
|
-
}
|
|
100
|
-
// Load from secrets
|
|
101
|
-
const token = await this.secrets.getSecret(this.oauth2Config.tokenSecretId);
|
|
102
|
-
this.cachedToken = token;
|
|
103
|
-
// Check if loaded token is expired and needs refresh
|
|
104
|
-
if (!this.isTokenValid(token)) {
|
|
105
|
-
if (token.refreshToken) {
|
|
106
|
-
return this.refreshAndGetToken();
|
|
107
|
-
}
|
|
108
|
-
// Token expired and no refresh token available
|
|
109
|
-
throw new Error('OAuth2 token expired and no refresh token available');
|
|
110
|
-
}
|
|
111
|
-
return token.accessToken;
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Refresh token and update cache.
|
|
115
|
-
* Uses a promise lock to prevent concurrent refresh attempts.
|
|
116
|
-
*/
|
|
117
|
-
async refreshAndGetToken() {
|
|
118
|
-
if (this.refreshPromise) {
|
|
119
|
-
const token = await this.refreshPromise;
|
|
120
|
-
return token.accessToken;
|
|
121
|
-
}
|
|
122
|
-
this.refreshPromise = this.doRefreshToken();
|
|
123
|
-
try {
|
|
124
|
-
const token = await this.refreshPromise;
|
|
125
|
-
return token.accessToken;
|
|
126
|
-
}
|
|
127
|
-
finally {
|
|
128
|
-
this.refreshPromise = null;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Perform the actual token refresh.
|
|
133
|
-
*/
|
|
134
|
-
async doRefreshToken() {
|
|
135
|
-
if (!this.cachedToken?.refreshToken) {
|
|
136
|
-
throw new Error('No refresh token available');
|
|
137
|
-
}
|
|
138
|
-
const appCredential = await this.getAppCredential();
|
|
139
|
-
const params = new URLSearchParams({
|
|
140
|
-
grant_type: 'refresh_token',
|
|
141
|
-
refresh_token: this.cachedToken.refreshToken,
|
|
142
|
-
client_id: appCredential.clientId,
|
|
143
|
-
});
|
|
144
|
-
if (appCredential.clientSecret) {
|
|
145
|
-
params.set('client_secret', appCredential.clientSecret);
|
|
146
|
-
}
|
|
147
|
-
const response = await fetchWithTimeout(this.oauth2Config.tokenUrl, {
|
|
148
|
-
method: 'POST',
|
|
149
|
-
headers: {
|
|
150
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
151
|
-
},
|
|
152
|
-
body: params.toString(),
|
|
153
|
-
});
|
|
154
|
-
if (!response.ok) {
|
|
155
|
-
throw new Error(`Token refresh failed: ${response.status}`);
|
|
156
|
-
}
|
|
157
|
-
const data = await response.json();
|
|
158
|
-
if (!data.access_token || typeof data.access_token !== 'string') {
|
|
159
|
-
throw new Error('Invalid token response: missing access_token');
|
|
160
|
-
}
|
|
161
|
-
const token = {
|
|
162
|
-
accessToken: data.access_token,
|
|
163
|
-
refreshToken: data.refresh_token || this.cachedToken.refreshToken,
|
|
164
|
-
expiresAt: data.expires_in
|
|
165
|
-
? Date.now() + data.expires_in * 1000
|
|
166
|
-
: undefined,
|
|
167
|
-
tokenType: data.token_type || 'Bearer',
|
|
168
|
-
scope: data.scope,
|
|
169
|
-
};
|
|
170
|
-
await this.secrets.setSecret(this.oauth2Config.tokenSecretId, token);
|
|
171
|
-
this.cachedToken = token;
|
|
172
|
-
return token;
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Check if token is valid (not expired).
|
|
176
|
-
*/
|
|
177
|
-
isTokenValid(token) {
|
|
178
|
-
if (!token.expiresAt) {
|
|
179
|
-
return true; // No expiry info, assume valid
|
|
180
|
-
}
|
|
181
|
-
return token.expiresAt > Date.now() + TOKEN_EXPIRY_BUFFER_MS;
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Get app credentials (cached).
|
|
185
|
-
*/
|
|
186
|
-
async getAppCredential() {
|
|
187
|
-
if (this.cachedAppCredential) {
|
|
188
|
-
return this.cachedAppCredential;
|
|
189
|
-
}
|
|
190
|
-
this.cachedAppCredential =
|
|
191
|
-
await this.secrets.getSecret(this.appCredentialSecretId);
|
|
192
|
-
return this.cachedAppCredential;
|
|
193
|
-
}
|
|
194
|
-
// ========================================
|
|
195
|
-
// CLI-only methods (for pikku oauth connect)
|
|
196
|
-
// ========================================
|
|
197
|
-
/**
|
|
198
|
-
* Generate OAuth2 authorization URL.
|
|
199
|
-
* Used by CLI during `pikku oauth connect`.
|
|
200
|
-
*/
|
|
201
|
-
async getAuthorizationUrl(state, redirectUri) {
|
|
202
|
-
const appCredential = await this.getAppCredential();
|
|
203
|
-
const params = new URLSearchParams({
|
|
204
|
-
response_type: 'code',
|
|
205
|
-
client_id: appCredential.clientId,
|
|
206
|
-
redirect_uri: redirectUri,
|
|
207
|
-
scope: this.oauth2Config.scopes.join(' '),
|
|
208
|
-
state,
|
|
209
|
-
});
|
|
210
|
-
// Add PKCE if enabled
|
|
211
|
-
// Note: code_verifier should be stored and passed to exchangeCode
|
|
212
|
-
// For now, PKCE implementation is simplified
|
|
213
|
-
// Add any additional params
|
|
214
|
-
if (this.oauth2Config.additionalParams) {
|
|
215
|
-
for (const [key, value] of Object.entries(this.oauth2Config.additionalParams)) {
|
|
216
|
-
params.set(key, value);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return `${this.oauth2Config.authorizationUrl}?${params.toString()}`;
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Exchange authorization code for tokens.
|
|
223
|
-
* Used by CLI during `pikku oauth connect`.
|
|
224
|
-
*/
|
|
225
|
-
async exchangeCode(code, redirectUri) {
|
|
226
|
-
const appCredential = await this.getAppCredential();
|
|
227
|
-
const params = new URLSearchParams({
|
|
228
|
-
grant_type: 'authorization_code',
|
|
229
|
-
code,
|
|
230
|
-
redirect_uri: redirectUri,
|
|
231
|
-
client_id: appCredential.clientId,
|
|
232
|
-
});
|
|
233
|
-
if (appCredential.clientSecret) {
|
|
234
|
-
params.set('client_secret', appCredential.clientSecret);
|
|
235
|
-
}
|
|
236
|
-
const response = await fetchWithTimeout(this.oauth2Config.tokenUrl, {
|
|
237
|
-
method: 'POST',
|
|
238
|
-
headers: {
|
|
239
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
240
|
-
},
|
|
241
|
-
body: params.toString(),
|
|
242
|
-
});
|
|
243
|
-
if (!response.ok) {
|
|
244
|
-
throw new Error(`Token exchange failed: ${response.status}`);
|
|
245
|
-
}
|
|
246
|
-
const data = await response.json();
|
|
247
|
-
if (!data.access_token || typeof data.access_token !== 'string') {
|
|
248
|
-
throw new Error('Invalid token response: missing access_token');
|
|
249
|
-
}
|
|
250
|
-
const token = {
|
|
251
|
-
accessToken: data.access_token,
|
|
252
|
-
refreshToken: data.refresh_token,
|
|
253
|
-
expiresAt: data.expires_in
|
|
254
|
-
? Date.now() + data.expires_in * 1000
|
|
255
|
-
: undefined,
|
|
256
|
-
tokenType: data.token_type || 'Bearer',
|
|
257
|
-
scope: data.scope,
|
|
258
|
-
};
|
|
259
|
-
// Cache the token
|
|
260
|
-
this.cachedToken = token;
|
|
261
|
-
return token;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type { CredentialDefinitionsMeta } from '../credential/credential.types.js';
|
|
2
|
-
export type CreateOAuth2HandlerOptions = {
|
|
3
|
-
credentialsMeta: CredentialDefinitionsMeta;
|
|
4
|
-
basePath?: string;
|
|
5
|
-
};
|
|
6
|
-
/**
|
|
7
|
-
* Creates OAuth2 route handlers for user credential management.
|
|
8
|
-
*
|
|
9
|
-
* Returns individual handler functions for connect/callback/disconnect/status
|
|
10
|
-
* that handle the OAuth2 authorization code flow and store tokens in CredentialService.
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```typescript
|
|
14
|
-
* const oauth2 = createOAuth2Handler({ credentialsMeta })
|
|
15
|
-
*
|
|
16
|
-
* const oauth2Routes = defineHTTPRoutes({
|
|
17
|
-
* auth: true,
|
|
18
|
-
* basePath: '/credentials',
|
|
19
|
-
* routes: {
|
|
20
|
-
* connect: { method: 'get', route: '/:name/connect', func: oauth2.connect },
|
|
21
|
-
* callback: { method: 'get', route: '/:name/callback', func: oauth2.callback, auth: false },
|
|
22
|
-
* disconnect: { method: 'delete', route: '/:name', func: oauth2.disconnect },
|
|
23
|
-
* status: { method: 'get', route: '/:name/status', func: oauth2.status },
|
|
24
|
-
* },
|
|
25
|
-
* })
|
|
26
|
-
*
|
|
27
|
-
* wireHTTPRoutes({ routes: { credentials: oauth2Routes } })
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
export declare const createOAuth2Handler: (options: CreateOAuth2HandlerOptions) => {
|
|
31
|
-
connect: any;
|
|
32
|
-
callback: any;
|
|
33
|
-
disconnect: any;
|
|
34
|
-
status: any;
|
|
35
|
-
};
|