@sokel-dev/plugin-sdk 0.3.0 → 0.5.3
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/README.md +62 -47
- package/README.zh-CN.md +95 -0
- package/dist/src/auth.d.ts +12 -8
- package/dist/src/auth.js +2 -9
- package/dist/src/auth.js.map +1 -1
- package/dist/src/contract.d.ts +27 -9
- package/dist/src/contract.js +10 -9
- package/dist/src/contract.js.map +1 -1
- package/dist/src/env.d.ts +1 -6
- package/dist/src/env.js +6 -3
- package/dist/src/env.js.map +1 -1
- package/dist/src/events.d.ts +32 -23
- package/dist/src/events.js +32 -31
- package/dist/src/events.js.map +1 -1
- package/dist/src/index.d.ts +4 -2
- package/dist/src/index.js +6 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/nats.d.ts +22 -16
- package/dist/src/nats.js +83 -64
- package/dist/src/nats.js.map +1 -1
- package/dist/src/plugin.d.ts +24 -19
- package/dist/src/plugin.js +42 -29
- package/dist/src/plugin.js.map +1 -1
- package/dist/src/runtime.d.ts +30 -23
- package/dist/src/runtime.js +28 -25
- package/dist/src/runtime.js.map +1 -1
- package/dist/src/webhook.d.ts +15 -10
- package/dist/src/webhook.js +8 -12
- package/dist/src/webhook.js.map +1 -1
- package/package.json +1 -1
package/dist/src/plugin.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
// Copyright 2026 The Sokel Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
3
|
/**
|
|
2
|
-
* Plugin
|
|
4
|
+
* Plugin: the registry plus dispatch. Transport-agnostic — the NATS layer only moves bytes.
|
|
3
5
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
+
* Dispatch is the part **most worth unit-testing** (unknown operations, the reply shape for
|
|
7
|
+
* streaming versus non-streaming, intercepting the webhook frame), so it touches no NATS at all: a
|
|
8
|
+
* fake sink is enough to exercise the whole path.
|
|
6
9
|
*/
|
|
7
10
|
import { CAP_WEBHOOK, Contract, OP_AUTH_POLL, OP_AUTH_START, OP_AUTH_SUBMIT } from "./contract.js";
|
|
8
11
|
import { CONFIRMED, PENDING } from "./auth.js";
|
|
@@ -26,44 +29,49 @@ export class Plugin {
|
|
|
26
29
|
this.name = cfg.name || cfg.contract.name || "sokel-plugin";
|
|
27
30
|
this.endpoint = cfg.endpoint || envOr("ENDPOINT", "http://localhost:8088");
|
|
28
31
|
this.token = cfg.token || env("TOKEN");
|
|
29
|
-
//
|
|
32
|
+
// Version, in order of precedence: explicit argument > the contract's plugin.version >
|
|
33
|
+
// environment variable > fallback
|
|
30
34
|
this.version = cfg.version || cfg.contract.version || env("VERSION") || "sdk-node";
|
|
31
35
|
}
|
|
32
|
-
/**
|
|
36
|
+
/** Low-level registration: fn(ctx, input, emitter). The generated onXxx is its typed shell. */
|
|
33
37
|
register(opId, fn) {
|
|
34
38
|
if (!opId.includes(".") && !this.contract.operation(opId)) {
|
|
35
|
-
throw new Error(
|
|
39
|
+
throw new Error(`operation "${opId}" is not in the contract — declare it under operations in manifest.yml and regenerate`);
|
|
36
40
|
}
|
|
37
41
|
if (this.ops.has(opId))
|
|
38
|
-
throw new Error(
|
|
42
|
+
throw new Error(`operation "${opId}" registered twice`);
|
|
39
43
|
this.ops.set(opId, fn);
|
|
40
44
|
}
|
|
41
|
-
/**
|
|
45
|
+
/** Register a long-running event source; run() starts one task per source × credential. */
|
|
42
46
|
registerSource(id, label, fn) {
|
|
43
47
|
this.sources.push({ id, label, fn });
|
|
44
48
|
}
|
|
45
|
-
/**
|
|
49
|
+
/** Register the webhook handler (one per plugin: route upstream event types yourself by header
|
|
50
|
+
* or path). */
|
|
46
51
|
registerWebhook(fn) {
|
|
47
52
|
this.webhookFn = fn;
|
|
48
|
-
//
|
|
53
|
+
// The capability follows the fact, not a declaration: registering a handler *is* support.
|
|
54
|
+
// Forgetting to declare it should never make the entry-point button disappear.
|
|
49
55
|
this.contract.data.capabilities = { ...(this.contract.data.capabilities ?? {}), [CAP_WEBHOOK]: true };
|
|
50
56
|
}
|
|
51
57
|
get hasWebhook() {
|
|
52
58
|
return this.webhookFn !== undefined;
|
|
53
59
|
}
|
|
54
60
|
/**
|
|
55
|
-
*
|
|
61
|
+
* Attach the auth flow's implementation. The shape (qr / input / oauth) is declared in manifest.yml;
|
|
62
|
+
* only the implementation goes here.
|
|
56
63
|
*
|
|
57
|
-
* kind=oauth
|
|
58
|
-
*
|
|
64
|
+
* For kind=oauth the platform answers start/poll itself — the client secret lives there and a
|
|
65
|
+
* plugin cannot build the consent URL — so such a plugin writes no handler at all.
|
|
59
66
|
*/
|
|
60
67
|
registerAuth(h) {
|
|
61
68
|
const declared = this.contract.data.auth_flow;
|
|
62
69
|
const steps = declared?.steps ?? [];
|
|
63
70
|
const requireStep = (step) => {
|
|
64
71
|
if (!steps.includes(step)) {
|
|
65
|
-
throw new Error(
|
|
66
|
-
"
|
|
72
|
+
throw new Error(`the contract's auth flow has no "${step}" step (it has ${steps.join("/") || "none"}) — ` +
|
|
73
|
+
"the steps follow from credential.auth.kind, and implementing more than was declared " +
|
|
74
|
+
"means writing code that will never be called");
|
|
67
75
|
}
|
|
68
76
|
};
|
|
69
77
|
if (h.start) {
|
|
@@ -71,7 +79,7 @@ export class Plugin {
|
|
|
71
79
|
this.ops.set(OP_AUTH_START, async (ctx, _in, out) => {
|
|
72
80
|
const ch = await h.start(ctx);
|
|
73
81
|
if (!ch)
|
|
74
|
-
throw new Error("
|
|
82
|
+
throw new Error("the auth flow's start returned no challenge");
|
|
75
83
|
out.vars({
|
|
76
84
|
auth_id: ch.authId || `auth_${Date.now()}`,
|
|
77
85
|
challenge: { kind: ch.kind ?? declared?.kind ?? "", qr_image: ch.qrImage ?? "", prompt: ch.prompt ?? "" },
|
|
@@ -84,7 +92,8 @@ export class Plugin {
|
|
|
84
92
|
this.ops.set(OP_AUTH_POLL, async (ctx, input, out) => {
|
|
85
93
|
const st = (await h.poll(ctx, String(input.auth_id ?? ""))) ?? { status: PENDING };
|
|
86
94
|
const vars = { status: st.status };
|
|
87
|
-
// session
|
|
95
|
+
// Only carry the session once confirmed: handing back a null while pending makes the
|
|
96
|
+
// platform rewrite the credential row over and over.
|
|
88
97
|
if (st.status === CONFIRMED && st.session)
|
|
89
98
|
vars.session = st.session;
|
|
90
99
|
out.vars(vars);
|
|
@@ -98,22 +107,24 @@ export class Plugin {
|
|
|
98
107
|
});
|
|
99
108
|
}
|
|
100
109
|
}
|
|
101
|
-
/**
|
|
110
|
+
/** Declare which **optional** capabilities this plugin has — how far a given operation goes. */
|
|
102
111
|
setCapabilities(caps) {
|
|
103
112
|
this.contract.data.capabilities = { ...(this.contract.data.capabilities ?? {}), ...caps };
|
|
104
113
|
}
|
|
105
114
|
/**
|
|
106
|
-
*
|
|
115
|
+
* The registration / heartbeat payload (protocol §3).
|
|
107
116
|
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
117
|
+
* It is a separate method so it can be tested: **declared but never reported** is the classic
|
|
118
|
+
* silent failure of a self-reporting mechanism. Everything looks fine on the plugin side, nothing
|
|
119
|
+
* happens on the platform side, and the author is left staring at an inert UI.
|
|
110
120
|
*/
|
|
111
121
|
registerPayload(instanceId, host, startedAt) {
|
|
112
122
|
const body = {
|
|
113
123
|
token: this.token,
|
|
114
124
|
instance_id: instanceId,
|
|
115
125
|
host,
|
|
116
|
-
//
|
|
126
|
+
// Process start time: registration and every heartbeat resend the same value, which is how
|
|
127
|
+
// the platform tells "a new replica came up" from "the old one is still alive".
|
|
117
128
|
started_at: startedAt,
|
|
118
129
|
version: this.version,
|
|
119
130
|
transport: "nats",
|
|
@@ -131,11 +142,13 @@ export class Plugin {
|
|
|
131
142
|
const fn = this.ops.get(opId);
|
|
132
143
|
if (fn)
|
|
133
144
|
return fn;
|
|
134
|
-
//
|
|
145
|
+
// Single-operation plugin: when `operation` is missing (or unknown), fall back to the only one
|
|
146
|
+
// there is — the same fallback the Go SDK has.
|
|
135
147
|
const business = [...this.ops.keys()].filter((k) => !k.includes("."));
|
|
136
148
|
return business.length === 1 ? this.ops.get(business[0]) : undefined;
|
|
137
149
|
}
|
|
138
|
-
/**
|
|
150
|
+
/** Run one call, handing frames to the sink. Exceptions propagate; the transport turns them into
|
|
151
|
+
* an error frame or an error reply. */
|
|
139
152
|
async dispatch(call, sink, files) {
|
|
140
153
|
const opId = call.operation ?? "";
|
|
141
154
|
const fn = this.find(opId);
|
|
@@ -144,19 +157,19 @@ export class Plugin {
|
|
|
144
157
|
const ctx = new Ctx({ credential: call.credential, trace: call.trace, files });
|
|
145
158
|
await fn(ctx, call.input ?? {}, new Emitter(sink));
|
|
146
159
|
}
|
|
147
|
-
/**
|
|
160
|
+
/** Non-streaming: buffer the frames and merge the variables into a single reply. */
|
|
148
161
|
async dispatchBuffered(call, files) {
|
|
149
162
|
const buf = new BufferSink();
|
|
150
163
|
await this.dispatch(call, buf.sink, files);
|
|
151
164
|
return buf.vars;
|
|
152
165
|
}
|
|
153
166
|
/**
|
|
154
|
-
*
|
|
155
|
-
*
|
|
167
|
+
* Handle one __webhook__ frame. The reply carries an events count: that is how the platform's
|
|
168
|
+
* webhook panel answers "the request arrived, so why did no workflow start?".
|
|
156
169
|
*/
|
|
157
170
|
async handleWebhook(sctx, frame) {
|
|
158
171
|
if (!this.webhookFn)
|
|
159
|
-
return { status: 0, error: "
|
|
172
|
+
return { status: 0, error: "the plugin registered no webhook handler" };
|
|
160
173
|
const req = new WebhookRequest(frame ?? {});
|
|
161
174
|
let events = 0;
|
|
162
175
|
const counted = new Proxy(sctx, {
|
|
@@ -179,7 +192,7 @@ export class Plugin {
|
|
|
179
192
|
return { status: 0, error: `${e instanceof Error ? e.message : e}` };
|
|
180
193
|
}
|
|
181
194
|
}
|
|
182
|
-
/**
|
|
195
|
+
/** Connect, register, heartbeat and dispatch calls. The promise settles on SIGINT/SIGTERM. */
|
|
183
196
|
async run() {
|
|
184
197
|
const { NatsTransport } = await import("./nats.js");
|
|
185
198
|
await new NatsTransport().run(this);
|
package/dist/src/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"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"}
|
package/dist/src/runtime.d.ts
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Runtime shapes: file references, output frames, the call context.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Transport-agnostic — NATS supplies only three callbacks (fetch bytes, store bytes, emit a frame);
|
|
5
|
+
* every other semantic lives here. That is why tests need no broker: a fake file runtime and a fake
|
|
6
|
+
* sink are enough to exercise the whole dispatch path.
|
|
6
7
|
*/
|
|
7
|
-
/**
|
|
8
|
+
/** A file reference. Only the reference travels through the canvas; bytes never inline. */
|
|
8
9
|
export interface SokelFile {
|
|
9
10
|
id?: string;
|
|
10
11
|
url?: string;
|
|
11
12
|
name?: string;
|
|
12
13
|
mime?: string;
|
|
13
14
|
size?: number;
|
|
14
|
-
/**
|
|
15
|
+
/** Bytes carried directly when there is no platform file layer (unit tests). Not serialized. */
|
|
15
16
|
data?: Uint8Array;
|
|
16
17
|
}
|
|
17
|
-
/**
|
|
18
|
+
/** The fetch/store backend for file bytes, injected by the transport. */
|
|
18
19
|
export interface FileRuntime {
|
|
19
20
|
fetch(f: SokelFile): Promise<Uint8Array>;
|
|
20
21
|
store(name: string, mime: string, data: Uint8Array): Promise<SokelFile>;
|
|
21
|
-
/**
|
|
22
|
+
/** Stream while reading: memory stays at one chunk regardless of file size. */
|
|
22
23
|
storeStream(name: string, mime: string, src: AsyncIterable<Uint8Array>): Promise<SokelFile>;
|
|
23
24
|
}
|
|
24
25
|
export declare const FRAME_TEXT = "text";
|
|
@@ -31,24 +32,27 @@ export interface Frame {
|
|
|
31
32
|
vars?: Record<string, unknown>;
|
|
32
33
|
}
|
|
33
34
|
export type Sink = (f: Frame) => void;
|
|
34
|
-
/**
|
|
35
|
+
/** Typed emitter. Each call is one frame (streaming); for non-streaming operations the SDK buffers
|
|
36
|
+
* the frames and merges them into a single reply. */
|
|
35
37
|
export declare class Emitter<Out> {
|
|
36
38
|
private readonly sink;
|
|
37
39
|
constructor(sink: Sink);
|
|
38
|
-
/**
|
|
40
|
+
/** Human-readable text (display / tracing). */
|
|
39
41
|
text(s: string): void;
|
|
40
|
-
/**
|
|
42
|
+
/** Structured JSON (display / tracing). */
|
|
41
43
|
json(v: unknown): void;
|
|
42
|
-
/**
|
|
44
|
+
/** Typed output variables (they flow downstream). May be called repeatedly; a later frame
|
|
45
|
+
* overwrites same-named fields. */
|
|
43
46
|
vars(out: Out): void;
|
|
44
47
|
}
|
|
45
48
|
export declare function toVars(value: unknown): Record<string, unknown>;
|
|
46
|
-
/**
|
|
49
|
+
/** Non-streaming sink: keeps only variables frames and merges them into one output object.
|
|
50
|
+
* text/json frames exist for streaming display only. */
|
|
47
51
|
export declare class BufferSink {
|
|
48
52
|
readonly vars: Record<string, unknown>;
|
|
49
53
|
readonly sink: Sink;
|
|
50
54
|
}
|
|
51
|
-
/**
|
|
55
|
+
/** The context handed to an operation handler: credentials, tracing, file fetch/store. */
|
|
52
56
|
export declare class Ctx {
|
|
53
57
|
readonly credential: Record<string, string>;
|
|
54
58
|
private readonly traceMap;
|
|
@@ -59,26 +63,29 @@ export declare class Ctx {
|
|
|
59
63
|
files?: FileRuntime;
|
|
60
64
|
});
|
|
61
65
|
/**
|
|
62
|
-
*
|
|
66
|
+
* Tracing context supplied by the platform (run_id / workflow_id / node_id).
|
|
63
67
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
68
|
+
* Calls outside a workflow (console tests, health checks) have none of these and get "" back.
|
|
69
|
+
* **Treat "" as "no retry semantics"**, never as a constant key — doing the latter would
|
|
70
|
+
* deduplicate two independent calls into one.
|
|
66
71
|
*/
|
|
67
72
|
trace(key: string): string;
|
|
68
73
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
74
|
+
* Read the credential into a typed shape. It returns Partial<T> rather than T: the platform sends
|
|
75
|
+
* whichever fields that credential row has, and a field the generated Credential marks required
|
|
76
|
+
* can still be missing at runtime (a freshly created credential, one that has not logged in yet).
|
|
77
|
+
* Pretending otherwise only moves the undefined further from where it will explode.
|
|
72
78
|
*/
|
|
73
79
|
credentialAs<T extends object>(): Partial<T>;
|
|
74
80
|
fetch(f: SokelFile): Promise<Uint8Array>;
|
|
75
|
-
/**
|
|
81
|
+
/** Produce a file: hand the bytes back to the platform and get a reference for the output. */
|
|
76
82
|
upload(name: string, mime: string, data: Uint8Array): Promise<SokelFile>;
|
|
77
83
|
/**
|
|
78
|
-
*
|
|
84
|
+
* Stream a local file: memory stays at one chunk (1 MiB) regardless of file size.
|
|
79
85
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
86
|
+
* Anything above a few hundred megabytes (video, archives, datasets) belongs here. upload() reads
|
|
87
|
+
* the whole file into memory first, and the symptom of that is a container mysteriously killed by
|
|
88
|
+
* the OOM reaper on large inputs.
|
|
82
89
|
*/
|
|
83
90
|
uploadFile(path: string, name?: string, mime?: string): Promise<SokelFile>;
|
|
84
91
|
}
|
package/dist/src/runtime.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* 测试因此不需要 broker:塞一个假的文件运行时与假的 sink 就能跑完整条分发路径。
|
|
6
|
-
*/
|
|
7
|
-
// 够用就行:猜不出来的落 application/octet-stream,平台不会因此少存一个字节。
|
|
1
|
+
// Copyright 2026 The Sokel Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
// Good enough: anything unrecognised becomes application/octet-stream, and the platform stores
|
|
4
|
+
// exactly the same bytes either way.
|
|
8
5
|
const MIME_BY_EXT = {
|
|
9
6
|
".mp4": "video/mp4", ".webm": "video/webm", ".mkv": "video/x-matroska", ".mov": "video/quicktime",
|
|
10
7
|
".mp3": "audio/mpeg", ".m4a": "audio/mp4", ".opus": "audio/opus", ".wav": "audio/wav",
|
|
@@ -15,21 +12,23 @@ const MIME_BY_EXT = {
|
|
|
15
12
|
export const FRAME_TEXT = "text";
|
|
16
13
|
export const FRAME_JSON = "json";
|
|
17
14
|
export const FRAME_VARS = "variables";
|
|
18
|
-
/**
|
|
15
|
+
/** Typed emitter. Each call is one frame (streaming); for non-streaming operations the SDK buffers
|
|
16
|
+
* the frames and merges them into a single reply. */
|
|
19
17
|
export class Emitter {
|
|
20
18
|
sink;
|
|
21
19
|
constructor(sink) {
|
|
22
20
|
this.sink = sink;
|
|
23
21
|
}
|
|
24
|
-
/**
|
|
22
|
+
/** Human-readable text (display / tracing). */
|
|
25
23
|
text(s) {
|
|
26
24
|
this.sink({ kind: FRAME_TEXT, text: s });
|
|
27
25
|
}
|
|
28
|
-
/**
|
|
26
|
+
/** Structured JSON (display / tracing). */
|
|
29
27
|
json(v) {
|
|
30
28
|
this.sink({ kind: FRAME_JSON, json: v });
|
|
31
29
|
}
|
|
32
|
-
/**
|
|
30
|
+
/** Typed output variables (they flow downstream). May be called repeatedly; a later frame
|
|
31
|
+
* overwrites same-named fields. */
|
|
33
32
|
vars(out) {
|
|
34
33
|
const m = toVars(out);
|
|
35
34
|
if (Object.keys(m).length > 0)
|
|
@@ -40,7 +39,7 @@ export function toVars(value) {
|
|
|
40
39
|
if (value === null || value === undefined)
|
|
41
40
|
return {};
|
|
42
41
|
if (typeof value !== "object" || Array.isArray(value)) {
|
|
43
|
-
throw new TypeError(
|
|
42
|
+
throw new TypeError(`output must be an object, got ${typeof value}`);
|
|
44
43
|
}
|
|
45
44
|
const out = {};
|
|
46
45
|
for (const [k, v] of Object.entries(value)) {
|
|
@@ -49,7 +48,8 @@ export function toVars(value) {
|
|
|
49
48
|
}
|
|
50
49
|
return out;
|
|
51
50
|
}
|
|
52
|
-
/**
|
|
51
|
+
/** Non-streaming sink: keeps only variables frames and merges them into one output object.
|
|
52
|
+
* text/json frames exist for streaming display only. */
|
|
53
53
|
export class BufferSink {
|
|
54
54
|
vars = {};
|
|
55
55
|
sink = (f) => {
|
|
@@ -58,7 +58,7 @@ export class BufferSink {
|
|
|
58
58
|
Object.assign(this.vars, f.vars ?? {});
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
|
-
/**
|
|
61
|
+
/** The context handed to an operation handler: credentials, tracing, file fetch/store. */
|
|
62
62
|
export class Ctx {
|
|
63
63
|
credential;
|
|
64
64
|
traceMap;
|
|
@@ -69,18 +69,20 @@ export class Ctx {
|
|
|
69
69
|
this.files = opts.files;
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
72
|
-
*
|
|
72
|
+
* Tracing context supplied by the platform (run_id / workflow_id / node_id).
|
|
73
73
|
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
74
|
+
* Calls outside a workflow (console tests, health checks) have none of these and get "" back.
|
|
75
|
+
* **Treat "" as "no retry semantics"**, never as a constant key — doing the latter would
|
|
76
|
+
* deduplicate two independent calls into one.
|
|
76
77
|
*/
|
|
77
78
|
trace(key) {
|
|
78
79
|
return this.traceMap[key] ?? "";
|
|
79
80
|
}
|
|
80
81
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
82
|
+
* Read the credential into a typed shape. It returns Partial<T> rather than T: the platform sends
|
|
83
|
+
* whichever fields that credential row has, and a field the generated Credential marks required
|
|
84
|
+
* can still be missing at runtime (a freshly created credential, one that has not logged in yet).
|
|
85
|
+
* Pretending otherwise only moves the undefined further from where it will explode.
|
|
84
86
|
*/
|
|
85
87
|
credentialAs() {
|
|
86
88
|
return this.credential;
|
|
@@ -89,20 +91,21 @@ export class Ctx {
|
|
|
89
91
|
if (f.data)
|
|
90
92
|
return f.data;
|
|
91
93
|
if (!this.files)
|
|
92
|
-
throw new Error("
|
|
94
|
+
throw new Error("file runtime not ready");
|
|
93
95
|
return this.files.fetch(f);
|
|
94
96
|
}
|
|
95
|
-
/**
|
|
97
|
+
/** Produce a file: hand the bytes back to the platform and get a reference for the output. */
|
|
96
98
|
async upload(name, mime, data) {
|
|
97
99
|
if (!this.files)
|
|
98
100
|
return { name, mime, size: data.length, data };
|
|
99
101
|
return this.files.store(name, mime, data);
|
|
100
102
|
}
|
|
101
103
|
/**
|
|
102
|
-
*
|
|
104
|
+
* Stream a local file: memory stays at one chunk (1 MiB) regardless of file size.
|
|
103
105
|
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
+
* Anything above a few hundred megabytes (video, archives, datasets) belongs here. upload() reads
|
|
107
|
+
* the whole file into memory first, and the symptom of that is a container mysteriously killed by
|
|
108
|
+
* the OOM reaper on large inputs.
|
|
106
109
|
*/
|
|
107
110
|
async uploadFile(path, name, mime) {
|
|
108
111
|
const { createReadStream, readFileSync } = await import("node:fs");
|
package/dist/src/runtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/runtime.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/runtime.ts"],"names":[],"mappings":"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"}
|
package/dist/src/webhook.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Webhooks relayed by the platform: upstream system -> platform /hooks/{token} -> a __webhook__
|
|
3
|
+
* frame lands here (protocol §7b).
|
|
3
4
|
*
|
|
4
|
-
* handler
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* What the handler is responsible for: verifying the upstream signature using the secret in the
|
|
6
|
+
* credential (every vendor signs differently — the platform does not know the upstream, the plugin
|
|
7
|
+
* does), parsing the body, pushing typed events with ctx.trigger (which reuses the declared-event
|
|
8
|
+
* check and the platform's deduplication), and deciding the response (GitLab wants a 2xx, Feishu's
|
|
9
|
+
* URL verification wants the challenge echoed back).
|
|
7
10
|
*/
|
|
8
11
|
export interface WebhookFrame {
|
|
9
12
|
method?: string;
|
|
@@ -12,27 +15,29 @@ export interface WebhookFrame {
|
|
|
12
15
|
headers?: Record<string, string>;
|
|
13
16
|
body_b64?: string;
|
|
14
17
|
}
|
|
15
|
-
/**
|
|
18
|
+
/** One inbound webhook (the platform has already stripped Cookie and other platform-side headers). */
|
|
16
19
|
export declare class WebhookRequest {
|
|
17
20
|
readonly method: string;
|
|
18
21
|
readonly path: string;
|
|
19
22
|
readonly query: string;
|
|
20
23
|
readonly headers: Record<string, string>;
|
|
21
|
-
/** body
|
|
24
|
+
/** The body travels as base64 to keep the exact bytes: HMAC-style verification has to see them
|
|
25
|
+
* byte for byte, and re-encoding the JSON would break the signature. */
|
|
22
26
|
readonly body: Buffer;
|
|
23
27
|
constructor(frame: WebhookFrame);
|
|
24
|
-
/**
|
|
28
|
+
/** Case-insensitive header lookup (HTTP semantics: X-Gitlab-Event and x-gitlab-event both hit). */
|
|
25
29
|
header(name: string): string;
|
|
26
|
-
/** body
|
|
30
|
+
/** Parse the body as JSON. Throws on bad input: malformed JSON upstream should fail visibly. */
|
|
27
31
|
json<T = unknown>(): T;
|
|
28
32
|
}
|
|
29
|
-
/**
|
|
33
|
+
/** The reply sent back upstream. status=0 plus an error means the plugin failed to handle it (the
|
|
34
|
+
* platform translates that into a 5xx). */
|
|
30
35
|
export interface WebhookResponse {
|
|
31
36
|
status: number;
|
|
32
37
|
headers?: Record<string, string>;
|
|
33
38
|
body?: Buffer | string;
|
|
34
39
|
}
|
|
35
40
|
export declare function ok(): WebhookResponse;
|
|
36
|
-
/**
|
|
41
|
+
/** For the cases that must return a body (Feishu's URL-verification challenge, say). */
|
|
37
42
|
export declare function text(status: number, body: string): WebhookResponse;
|
|
38
43
|
export declare function responseFrame(resp: WebhookResponse, events: number): Record<string, unknown>;
|
package/dist/src/webhook.js
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* handler 的职责:用凭证里的 secret 验上游签名(各家算法不同,平台不懂上游、插件懂)→
|
|
5
|
-
* 解析 body → ctx.trigger 推 typed 事件(走既有声明校验与平台去重)→ 返回响应
|
|
6
|
-
* (GitLab 要 2xx、飞书 URL 校验要回 challenge,由 handler 决定)。
|
|
7
|
-
*/
|
|
8
|
-
/** 一次入站 webhook(平台已剥掉 Cookie 等平台侧头)。 */
|
|
1
|
+
// Copyright 2026 The Sokel Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/** One inbound webhook (the platform has already stripped Cookie and other platform-side headers). */
|
|
9
4
|
export class WebhookRequest {
|
|
10
5
|
method;
|
|
11
6
|
path;
|
|
12
7
|
query;
|
|
13
8
|
headers;
|
|
14
|
-
/** body
|
|
9
|
+
/** The body travels as base64 to keep the exact bytes: HMAC-style verification has to see them
|
|
10
|
+
* byte for byte, and re-encoding the JSON would break the signature. */
|
|
15
11
|
body;
|
|
16
12
|
constructor(frame) {
|
|
17
13
|
this.method = frame.method ?? "POST";
|
|
@@ -20,7 +16,7 @@ export class WebhookRequest {
|
|
|
20
16
|
this.headers = frame.headers ?? {};
|
|
21
17
|
this.body = Buffer.from(frame.body_b64 ?? "", "base64");
|
|
22
18
|
}
|
|
23
|
-
/**
|
|
19
|
+
/** Case-insensitive header lookup (HTTP semantics: X-Gitlab-Event and x-gitlab-event both hit). */
|
|
24
20
|
header(name) {
|
|
25
21
|
const lowered = name.toLowerCase();
|
|
26
22
|
for (const [k, v] of Object.entries(this.headers)) {
|
|
@@ -29,7 +25,7 @@ export class WebhookRequest {
|
|
|
29
25
|
}
|
|
30
26
|
return "";
|
|
31
27
|
}
|
|
32
|
-
/** body
|
|
28
|
+
/** Parse the body as JSON. Throws on bad input: malformed JSON upstream should fail visibly. */
|
|
33
29
|
json() {
|
|
34
30
|
return JSON.parse(this.body.toString("utf8"));
|
|
35
31
|
}
|
|
@@ -37,7 +33,7 @@ export class WebhookRequest {
|
|
|
37
33
|
export function ok() {
|
|
38
34
|
return { status: 200 };
|
|
39
35
|
}
|
|
40
|
-
/**
|
|
36
|
+
/** For the cases that must return a body (Feishu's URL-verification challenge, say). */
|
|
41
37
|
export function text(status, body) {
|
|
42
38
|
return { status, body };
|
|
43
39
|
}
|
package/dist/src/webhook.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/webhook.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/webhook.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,sCAAsC;AAqBtC,sGAAsG;AACtG,MAAM,OAAO,cAAc;IAChB,MAAM,CAAS;IACf,IAAI,CAAS;IACb,KAAK,CAAS;IACd,OAAO,CAAyB;IACzC;4EACwE;IAC/D,IAAI,CAAS;IAEtB,YAAY,KAAmB;QAC7B,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,MAAM,CAAC;QACrC,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC;QACnC,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC1D,CAAC;IAED,mGAAmG;IACnG,MAAM,CAAC,IAAY;QACjB,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QACnC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAClD,IAAI,CAAC,CAAC,WAAW,EAAE,KAAK,OAAO;gBAAE,OAAO,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,gGAAgG;IAChG,IAAI;QACF,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAM,CAAC;IACrD,CAAC;CACF;AAUD,MAAM,UAAU,EAAE;IAChB,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;AACzB,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,IAAI,CAAC,MAAc,EAAE,IAAY;IAC/C,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAqB,EAAE,MAAc;IACjE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAa,CAAC,CAAC;IACzF,OAAO;QACL,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,GAAG;QAC1B,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI;QAC7B,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACjC,MAAM;KACP,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sokel-dev/plugin-sdk",
|
|
3
|
-
"version": "0.3
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Sokel plugin SDK for Node.js — declare the contract in sokel.yaml, implement typed handlers.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/sokel-dev/sokel-plugin-sdk/tree/main/sdk-node",
|