@glubean/port 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +203 -0
  2. package/dist/async-queue.d.ts +7 -0
  3. package/dist/async-queue.d.ts.map +1 -0
  4. package/dist/async-queue.js +37 -0
  5. package/dist/index.d.ts +14 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +22 -0
  8. package/dist/json-rpc.d.ts +31 -0
  9. package/dist/json-rpc.d.ts.map +1 -0
  10. package/dist/json-rpc.js +122 -0
  11. package/dist/lifecycle.d.ts +4 -0
  12. package/dist/lifecycle.d.ts.map +1 -0
  13. package/dist/lifecycle.js +517 -0
  14. package/dist/options.d.ts +12 -0
  15. package/dist/options.d.ts.map +1 -0
  16. package/dist/options.js +32 -0
  17. package/dist/port.d.ts +4 -0
  18. package/dist/port.d.ts.map +1 -0
  19. package/dist/port.js +4 -0
  20. package/dist/provider-options.typecheck.d.ts +2 -0
  21. package/dist/provider-options.typecheck.d.ts.map +1 -0
  22. package/dist/provider-options.typecheck.js +29 -0
  23. package/dist/providers/adapter.d.ts +14 -0
  24. package/dist/providers/adapter.d.ts.map +1 -0
  25. package/dist/providers/adapter.js +1 -0
  26. package/dist/providers/claude.d.ts +9 -0
  27. package/dist/providers/claude.d.ts.map +1 -0
  28. package/dist/providers/claude.js +300 -0
  29. package/dist/providers/codex-normalizer.d.ts +7 -0
  30. package/dist/providers/codex-normalizer.d.ts.map +1 -0
  31. package/dist/providers/codex-normalizer.js +287 -0
  32. package/dist/providers/codex.d.ts +19 -0
  33. package/dist/providers/codex.d.ts.map +1 -0
  34. package/dist/providers/codex.js +300 -0
  35. package/dist/providers/common.d.ts +6 -0
  36. package/dist/providers/common.d.ts.map +1 -0
  37. package/dist/providers/common.js +67 -0
  38. package/dist/providers/gemini.d.ts +9 -0
  39. package/dist/providers/gemini.d.ts.map +1 -0
  40. package/dist/providers/gemini.js +378 -0
  41. package/dist/structured.d.ts +8 -0
  42. package/dist/structured.d.ts.map +1 -0
  43. package/dist/structured.js +127 -0
  44. package/dist/token-usage.d.ts +4 -0
  45. package/dist/token-usage.d.ts.map +1 -0
  46. package/dist/token-usage.js +59 -0
  47. package/dist/types.d.ts +407 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +1 -0
  50. package/package.json +35 -0
package/README.md ADDED
@@ -0,0 +1,203 @@
1
+ # Glubean Port
2
+
3
+ Glubean Port is a small runtime adapter layer for local coding agents.
4
+
5
+ The MVP exposes a neutral `session + turn + event + capability` API and includes Codex app-server, Claude Code, and Gemini ACP providers. Provider-specific runtime details are kept behind a typed adapter boundary, so callers can build their own orchestration on top without inheriting another caller's workflow concepts.
6
+
7
+ ## Scope
8
+
9
+ - `@glubean/port` core types and `createPort()`.
10
+ - Codex app-server over line-delimited JSON-RPC on stdio.
11
+ - Claude Code over `--output-format stream-json`.
12
+ - Gemini over ACP JSON-RPC on stdio.
13
+ - Event normalization from provider notifications into `AgentEvent`.
14
+ - Token usage telemetry via `token.usage` events when the provider reports it.
15
+ - Structured JSON turns with optional Zod 4 validation and retry.
16
+ - Local lifecycle state for sessions, managed turns, cancellation, wait results,
17
+ and in-memory event replay with sequence cursors.
18
+ - Fixture tests that do not require a real Codex login or model call.
19
+
20
+ Not in this MVP:
21
+
22
+ - Caller orchestration state.
23
+ - Domain-specific high-level APIs.
24
+
25
+ ## API Shape
26
+
27
+ ```ts
28
+ import { createPort } from "@glubean/port"
29
+
30
+ const port = await createPort({
31
+ provider: "codex",
32
+ cwd: process.cwd(),
33
+ })
34
+
35
+ const session = await port.sessions.create({
36
+ approvalPolicy: "on-request",
37
+ sandbox: "workspace-write",
38
+ })
39
+
40
+ for await (const event of port.turns.start({
41
+ sessionId: session.id,
42
+ input: [{ type: "text", text: "Review the current diff." }],
43
+ })) {
44
+ console.log(event)
45
+ }
46
+
47
+ await port.close()
48
+ ```
49
+
50
+ Managed lifecycle:
51
+
52
+ ```ts
53
+ const session = await port.sessions.create()
54
+
55
+ const turn = await port.turns.submit({
56
+ sessionId: session.id,
57
+ input: [{ type: "text", text: "Work on the leased task." }],
58
+ timeoutMs: 5 * 60_000,
59
+ })
60
+
61
+ for await (const event of port.turns.stream({ ...turn, afterSeq: 0 })) {
62
+ console.log(event.seq, event.type)
63
+ if (event.type === "turn.completed") break
64
+ }
65
+
66
+ const result = await port.turns.wait(turn)
67
+ console.log(result.status, result.text)
68
+ ```
69
+
70
+ Structured output:
71
+
72
+ ```ts
73
+ import { z } from "zod"
74
+ import { createPort } from "@glubean/port"
75
+
76
+ const port = await createPort({ provider: "claude", cwd: process.cwd() })
77
+ const session = await port.sessions.create()
78
+
79
+ const result = await port.turns.startStructured({
80
+ sessionId: session.id,
81
+ input: [{ type: "text", text: "Return { ok: true }." }],
82
+ schema: z.object({ ok: z.literal(true) }),
83
+ maxRetries: 2,
84
+ })
85
+
86
+ console.log(result.data)
87
+ ```
88
+
89
+ ## Local Development
90
+
91
+ Node 24 can run the TypeScript sources directly.
92
+
93
+ ```sh
94
+ npm test
95
+ npm run typecheck
96
+ ```
97
+
98
+ To run live provider integrations, make sure `codex`, `claude`, and `gemini` are installed and authenticated:
99
+
100
+ ```sh
101
+ npm run test:integration
102
+ ```
103
+
104
+ To run a live Codex smoke:
105
+
106
+ ```sh
107
+ npm run smoke:codex -- "Say ok and stop."
108
+ ```
109
+
110
+ ## Design Notes
111
+
112
+ The public Port API is intentionally not a mirror of any provider:
113
+
114
+ - Codex uses `thread` and `turn`.
115
+ - ACP providers use `session` and `prompt`.
116
+ - Claude Code currently exposes a stream-json CLI shape rather than a public JSON-RPC appserver.
117
+
118
+ Port keeps provider-specific names in the adapter and exposes stable terms upward:
119
+
120
+ - `sessions`
121
+ - `turns`
122
+ - `AgentEvent`
123
+ - `capabilities`
124
+
125
+ Token usage is reported as a neutral `token.usage` event with normalized
126
+ `inputTokens`, `outputTokens`, `cachedInputTokens`, `reasoningOutputTokens`,
127
+ and `totalTokens` fields when available. Managed turn status and wait results
128
+ also expose the latest `usage` snapshot for the turn.
129
+
130
+ Structured output is implemented above the provider adapters. Codex and Claude Code use native JSON Schema support; Gemini currently uses prompt constraints plus local validation and retry.
131
+
132
+ Runtime options are split into provider-neutral fields and provider-specific fields. The selected provider binds the options type:
133
+
134
+ ```ts
135
+ await createPort({
136
+ provider: "codex",
137
+ options: {
138
+ model: "gpt-5.5",
139
+ effort: "high",
140
+ sandbox: "workspace-write",
141
+ },
142
+ })
143
+
144
+ await createPort({
145
+ provider: "claude",
146
+ options: {
147
+ model: "sonnet",
148
+ permissionMode: "plan",
149
+ thinking: { type: "adaptive" },
150
+ },
151
+ })
152
+
153
+ await createPort({
154
+ provider: "gemini",
155
+ options: {
156
+ model: "gemini-2.5-pro",
157
+ approvalMode: "plan",
158
+ settingsPath: "/path/to/gemini-settings.json",
159
+ },
160
+ })
161
+ ```
162
+
163
+ Lifecycle state is also implemented above the provider adapters. Providers only
164
+ need to expose create/resume/start/cancel primitives; Port records local
165
+ session and turn status, stores an in-memory event log, and exposes replay via
166
+ `events({ afterSeq })` or `turns.stream({ sessionId, turnId, afterSeq })`.
167
+ This is intentionally runtime-level only: workflow envelopes, policy labels,
168
+ ack/result protocols, and durable controller state stay above Port.
169
+
170
+ ## Type sync with consumers
171
+
172
+ Port now ships a built artefact set: `dist/index.js` (JS) and
173
+ `dist/index.d.ts` (full type declarations). `package.json#exports`
174
+ points consumers at those, not at `src/`. Most consumers can therefore
175
+ import Port's types directly — no shim needed — once they install the
176
+ current packed tgz.
177
+
178
+ For consumers still pinned to an older tgz (or `file:` link from
179
+ before the dist build), a compile-time shim of the export surface
180
+ still exists. The current shim of record:
181
+
182
+ - `gloop` → `gloop/src/types/glubean-port.d.ts` (narrow ambient
183
+ module declaring only the symbols `gloop`'s reviewer adapters
184
+ consume: `createPort`, `Port`, `RuntimeOptions`,
185
+ `RuntimeOptionsFor<P>`, `StartStructuredTurnInput`,
186
+ `StructuredTurnResult`, `StructuredTurnError`, the per-provider
187
+ enums like `ClaudePermissionMode` / `GeminiApprovalMode`, etc.)
188
+
189
+ While the shim is in use, when you change the exported type surface
190
+ in `src/index.ts` / `src/types.ts` — adding a field to
191
+ `RuntimeOptions`, a new `StructuredTurnError` subclass, an additional
192
+ provider id, anything that affects what `import type` consumers see —
193
+ update the consumer's shim in the same change so the shim stays a
194
+ strict subset of the dist declaration. TypeScript merges the two; the
195
+ shim must NEVER tighten a field declared in the dist (only widen or
196
+ add). Skipping the update leaves the consumer's `tsc` green against a
197
+ stale view of Port's types and the divergence surfaces only at
198
+ runtime.
199
+
200
+ Removing a consumer's shim is a separate change: rebuild the
201
+ consumer's lockfile against a tgz that ships dist, verify `tsc`
202
+ greenness directly against `dist/index.d.ts`, then delete the shim
203
+ file in a follow-up commit.
@@ -0,0 +1,7 @@
1
+ export declare class AsyncQueue<T> implements AsyncIterable<T> {
2
+ #private;
3
+ push(item: T): void;
4
+ close(): void;
5
+ [Symbol.asyncIterator](): AsyncIterator<T>;
6
+ }
7
+ //# sourceMappingURL=async-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-queue.d.ts","sourceRoot":"","sources":["../src/async-queue.ts"],"names":[],"mappings":"AAAA,qBAAa,UAAU,CAAC,CAAC,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;;IAKpD,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI;IAUnB,KAAK,IAAI,IAAI;IAQb,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC;CAc3C"}
@@ -0,0 +1,37 @@
1
+ export class AsyncQueue {
2
+ #items = [];
3
+ #waiters = [];
4
+ #closed = false;
5
+ push(item) {
6
+ if (this.#closed)
7
+ return;
8
+ const waiter = this.#waiters.shift();
9
+ if (waiter) {
10
+ waiter({ value: item, done: false });
11
+ return;
12
+ }
13
+ this.#items.push(item);
14
+ }
15
+ close() {
16
+ if (this.#closed)
17
+ return;
18
+ this.#closed = true;
19
+ for (const waiter of this.#waiters.splice(0)) {
20
+ waiter({ value: undefined, done: true });
21
+ }
22
+ }
23
+ [Symbol.asyncIterator]() {
24
+ return {
25
+ next: () => {
26
+ const item = this.#items.shift();
27
+ if (item !== undefined) {
28
+ return Promise.resolve({ value: item, done: false });
29
+ }
30
+ if (this.#closed) {
31
+ return Promise.resolve({ value: undefined, done: true });
32
+ }
33
+ return new Promise((resolve) => this.#waiters.push(resolve));
34
+ },
35
+ };
36
+ }
37
+ }
@@ -0,0 +1,14 @@
1
+ import type { CreatePortInput, CreatePortInputFor, Port } from "./types.ts";
2
+ export * from "./types.ts";
3
+ export { CodexEventNormalizer } from "./providers/codex-normalizer.ts";
4
+ export { codexCapabilities, createCodexAdapter, createCodexPort, createCodexThreadParams } from "./providers/codex.ts";
5
+ export { claudeCapabilities, createClaudeAdapter, createClaudePort } from "./providers/claude.ts";
6
+ export { geminiCapabilities, createGeminiAdapter, createGeminiPort } from "./providers/gemini.ts";
7
+ export type { ProviderAdapter } from "./providers/adapter.ts";
8
+ export { JsonRpcLineConnection } from "./json-rpc.ts";
9
+ export { StructuredTurnError } from "./structured.ts";
10
+ export declare function createPort(input: CreatePortInputFor<"codex">): Promise<Port<"codex">>;
11
+ export declare function createPort(input: CreatePortInputFor<"claude">): Promise<Port<"claude">>;
12
+ export declare function createPort(input: CreatePortInputFor<"gemini">): Promise<Port<"gemini">>;
13
+ export declare function createPort(input: CreatePortInput): Promise<Port>;
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAK5E,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACvH,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAClG,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAClG,YAAY,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEtD,wBAAsB,UAAU,CAAC,KAAK,EAAE,kBAAkB,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;AAC7F,wBAAsB,UAAU,CAAC,KAAK,EAAE,kBAAkB,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC/F,wBAAsB,UAAU,CAAC,KAAK,EAAE,kBAAkB,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC/F,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ import { createCodexPort } from "./providers/codex.js";
2
+ import { createClaudePort } from "./providers/claude.js";
3
+ import { createGeminiPort } from "./providers/gemini.js";
4
+ export * from "./types.js";
5
+ export { CodexEventNormalizer } from "./providers/codex-normalizer.js";
6
+ export { codexCapabilities, createCodexAdapter, createCodexPort, createCodexThreadParams } from "./providers/codex.js";
7
+ export { claudeCapabilities, createClaudeAdapter, createClaudePort } from "./providers/claude.js";
8
+ export { geminiCapabilities, createGeminiAdapter, createGeminiPort } from "./providers/gemini.js";
9
+ export { JsonRpcLineConnection } from "./json-rpc.js";
10
+ export { StructuredTurnError } from "./structured.js";
11
+ export async function createPort(input) {
12
+ switch (input.provider) {
13
+ case "codex":
14
+ return createCodexPort({ ...input, provider: "codex" });
15
+ case "claude":
16
+ return createClaudePort({ ...input, provider: "claude" });
17
+ case "gemini":
18
+ return createGeminiPort({ ...input, provider: "gemini" });
19
+ default:
20
+ throw new Error(`Unsupported provider: ${input.provider}`);
21
+ }
22
+ }
@@ -0,0 +1,31 @@
1
+ import type { Readable, Writable } from "node:stream";
2
+ import type { JsonValue } from "./types.ts";
3
+ export type JsonRpcId = string | number | null;
4
+ export type JsonRpcMessage = {
5
+ jsonrpc?: "2.0";
6
+ id?: JsonRpcId;
7
+ method?: string;
8
+ params?: JsonValue;
9
+ result?: JsonValue;
10
+ error?: {
11
+ code?: number;
12
+ message?: string;
13
+ data?: JsonValue;
14
+ };
15
+ };
16
+ export type JsonRpcNotification = {
17
+ method: string;
18
+ params?: JsonValue;
19
+ };
20
+ type RequestListener = (request: JsonRpcNotification) => JsonValue | undefined | Promise<JsonValue | undefined>;
21
+ export declare class JsonRpcLineConnection {
22
+ #private;
23
+ constructor(input: Readable, output: Writable);
24
+ onNotification(listener: (notification: JsonRpcNotification) => void): () => void;
25
+ onRequest(listener: RequestListener): () => void;
26
+ request(method: string, params?: JsonValue): Promise<JsonValue | undefined>;
27
+ notify(method: string, params?: JsonValue): void;
28
+ close(): void;
29
+ }
30
+ export {};
31
+ //# sourceMappingURL=json-rpc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-rpc.d.ts","sourceRoot":"","sources":["../src/json-rpc.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;AAE/C,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,CAAC,EAAE,KAAK,CAAC;IAChB,EAAE,CAAC,EAAE,SAAS,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,KAAK,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,SAAS,CAAA;KAAE,CAAC;CAC/D,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB,CAAC;AAOF,KAAK,eAAe,GAAG,CAAC,OAAO,EAAE,mBAAmB,KAAK,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;AAEhH,qBAAa,qBAAqB;;gBAQpB,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ;IAS7C,cAAc,CAAC,QAAQ,EAAE,CAAC,YAAY,EAAE,mBAAmB,KAAK,IAAI,GAAG,MAAM,IAAI;IAKjF,SAAS,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,IAAI;IAKhD,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAS3E,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI;IAIhD,KAAK,IAAI,IAAI;CA0Fd"}
@@ -0,0 +1,122 @@
1
+ import { EventEmitter } from "node:events";
2
+ export class JsonRpcLineConnection {
3
+ #input;
4
+ #output;
5
+ #nextId = 0;
6
+ #buffer = "";
7
+ #pending = new Map();
8
+ #events = new EventEmitter();
9
+ constructor(input, output) {
10
+ this.#input = input;
11
+ this.#output = output;
12
+ this.#input.setEncoding("utf8");
13
+ this.#input.on("data", (chunk) => this.#acceptChunk(chunk));
14
+ this.#input.on("error", (error) => this.#rejectAll(error));
15
+ this.#input.on("close", () => this.#rejectAll(new Error("JSON-RPC input closed")));
16
+ }
17
+ onNotification(listener) {
18
+ this.#events.on("notification", listener);
19
+ return () => this.#events.off("notification", listener);
20
+ }
21
+ onRequest(listener) {
22
+ this.#events.on("request", listener);
23
+ return () => this.#events.off("request", listener);
24
+ }
25
+ request(method, params) {
26
+ const id = ++this.#nextId;
27
+ const message = { jsonrpc: "2.0", id, method, params };
28
+ this.#write(message);
29
+ return new Promise((resolve, reject) => {
30
+ this.#pending.set(id, { resolve, reject });
31
+ });
32
+ }
33
+ notify(method, params) {
34
+ this.#write({ jsonrpc: "2.0", method, params });
35
+ }
36
+ close() {
37
+ this.#rejectAll(new Error("JSON-RPC connection closed"));
38
+ this.#input.destroy();
39
+ this.#output.end();
40
+ }
41
+ #write(message) {
42
+ this.#output.write(`${JSON.stringify(message)}\n`);
43
+ }
44
+ #acceptChunk(chunk) {
45
+ this.#buffer += chunk;
46
+ while (true) {
47
+ const newlineIndex = this.#buffer.indexOf("\n");
48
+ if (newlineIndex === -1)
49
+ break;
50
+ const line = this.#buffer.slice(0, newlineIndex).trim();
51
+ this.#buffer = this.#buffer.slice(newlineIndex + 1);
52
+ if (line)
53
+ this.#acceptLine(line);
54
+ }
55
+ }
56
+ #acceptLine(line) {
57
+ let message;
58
+ try {
59
+ message = JSON.parse(line);
60
+ }
61
+ catch {
62
+ this.#events.emit("notification", {
63
+ method: "port/invalid-json",
64
+ params: { line },
65
+ });
66
+ return;
67
+ }
68
+ if (message.id !== undefined && (message.result !== undefined || message.error !== undefined)) {
69
+ const pending = this.#pending.get(message.id);
70
+ if (!pending)
71
+ return;
72
+ this.#pending.delete(message.id);
73
+ if (message.error) {
74
+ pending.reject(new Error(message.error.message ?? "JSON-RPC request failed"));
75
+ return;
76
+ }
77
+ pending.resolve(message.result);
78
+ return;
79
+ }
80
+ if (message.id !== undefined && typeof message.method === "string") {
81
+ void this.#acceptRequest(message.id, message.method, message.params);
82
+ return;
83
+ }
84
+ if (typeof message.method === "string") {
85
+ this.#events.emit("notification", {
86
+ method: message.method,
87
+ params: message.params,
88
+ });
89
+ }
90
+ }
91
+ async #acceptRequest(id, method, params) {
92
+ const listeners = this.#events.listeners("request");
93
+ if (listeners.length === 0) {
94
+ this.#write({
95
+ jsonrpc: "2.0",
96
+ id,
97
+ error: { code: -32601, message: `Method not found: ${method}` },
98
+ });
99
+ return;
100
+ }
101
+ try {
102
+ const result = await listeners[0]({ method, params });
103
+ this.#write({ jsonrpc: "2.0", id, result });
104
+ }
105
+ catch (error) {
106
+ this.#write({
107
+ jsonrpc: "2.0",
108
+ id,
109
+ error: {
110
+ code: -32000,
111
+ message: error instanceof Error ? error.message : String(error),
112
+ },
113
+ });
114
+ }
115
+ }
116
+ #rejectAll(error) {
117
+ for (const pending of this.#pending.values()) {
118
+ pending.reject(error);
119
+ }
120
+ this.#pending.clear();
121
+ }
122
+ }
@@ -0,0 +1,4 @@
1
+ import type { ProviderAdapter } from "./providers/adapter.ts";
2
+ import type { Port, ProviderId } from "./types.ts";
3
+ export declare function createLifecyclePort<P extends ProviderId>(adapter: ProviderAdapter<P>): Port<P>;
4
+ //# sourceMappingURL=lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,KAAK,EAMV,IAAI,EACJ,UAAU,EAkBX,MAAM,YAAY,CAAC;AAiCpB,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,UAAU,EAAE,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAE9F"}