@loomcycle/client 0.10.4 → 0.11.1

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 CHANGED
@@ -6,18 +6,23 @@ TypeScript client for the [loomcycle](https://github.com/denn-gubsky/loomcycle)
6
6
 
7
7
  ## Status
8
8
 
9
- **v0.11.0** — 31 methods covering run streaming, agent metadata, transcript, pause/resume/state, snapshot lifecycle, memory admin, interruption resolve, hook registration, **v0.8.22 substrate admin (agentDef + skillDef)**, **v0.9.x n8n Phase 0 (listChannels + streamUserRunStates)**, **v0.9.x content_sha256** (the bundle-vs-deployed comparison workflow for Docker-bundled operators), and health.
9
+ **v0.11.1** — 41 methods covering run streaming, agent metadata, transcript, pause/resume/state, snapshot lifecycle, memory admin, interruption resolve, hook registration, **v0.8.22 substrate admin (agentDef + skillDef)**, **v0.9.x n8n Phase 0 (listChannels + streamUserRunStates)**, **v0.9.x content_sha256**, **v0.9.x dynamic MCP server registration (mcpServerDef)**, **v0.10.3 Library v2 enumeration (listLibraryAgents/Skills/McpServers)**, **v0.11.0 LLM Gateway (llmChat + llmStream)**, and health.
10
10
 
11
11
  > Migrating from raw `fetch` against `/v1/*`? See **[docs/MIGRATING-FROM-HTTP.md](./docs/MIGRATING-FROM-HTTP.md)** for a side-by-side walkthrough.
12
12
 
13
13
  ### What's new since v0.8.18
14
14
 
15
- - **`agentDef` / `skillDef`** (v0.8.22) — runtime fork / promote / retire / get / list / `verify` on the substrate. Lets a containerised app push agent + skill definitions to a remote loomcycle at startup without restarting it.
16
- - **`listChannels`** (v0.9.x) — list operator-declared channels with aggregate stats (message_count, oldest/newest visible_at). The substrate companion to the existing Channel tool; useful for credential pickers + dashboards.
17
- - **`streamUserRunStates`** (v0.9.x) — SSE stream of run state transitions scoped to one `user_id`. Yields `{ kind: "open" | "event", payload }` items until the connection closes (30-min server cap). The primary substrate hook for orchestration UIs that need to react when an agent run completes / fails / cancels.
18
- - **Content signatures** (v0.9.x) — every `agent_defs` / `skill_defs` row now carries a deterministic `content_sha256`. Combined with the `verify` op and the `loomcycle hash agent|skill` CLI subcommand, this gives Docker-bundled operators a one-call answer to *"is what I have in my image identical to what's deployed?"* — see [Content signatures](#content-signatures-v09x) below for the end-to-end workflow.
19
- - **Transcript first-cycle types** (v0.9.1) — `UserInputPayload` + `SystemPromptPayload` typed interfaces for the two new transcript events that surface "what the agent actually received" (the resolved system prompt + the caller's segments) as the first frames of every run.
20
- - **n8n polish — `debug` toggle + `parentAgentId` filter** (v0.13.0) — opt-in synthetic `stream_open` / `stream_close` frames on `runStreaming` / `continueSession` / `streamUserRunStates` plus a client-side `parentAgentId` filter on `listUserAgents` + `streamUserRunStates`. Default behaviour is unchanged for existing callers; both knobs are off until set. See [Patterns](#patterns) for when to reach for them.
15
+ - **`llmChat` / `llmStream`** (v0.11.0) — direct LLM call surface that bypasses the agent loop. Provider routing + auth + retry without the ~50-200 ms per-turn overhead of a full `runStreaming` spawn. Drives n8n's `LoomCycleChatModel` AI Agent sub-node + any LangChain `BaseChatModel` consumer.
16
+ - **`listLibraryAgents` / `listLibrarySkills` / `listLibraryMcpServers`** (v0.10.3) — typed wrappers around the v0.9.3 Library v2 endpoints. Each returns a `LibraryListResponse<T>` with source-tagged entries (`"static-only"` / `"dynamic-only"` / `"both"`) merging yaml + substrate views.
17
+ - **`mcpServerDef`** (v0.9.x) — runtime registration of HTTP / Streamable-HTTP MCP servers without yaml edits. Same op grammar (create / fork / promote / retire / rediscover) as `agentDef` / `skillDef`.
18
+ - **`agentDef` / `skillDef`** (v0.8.22) — runtime fork / promote / retire / get / list / `verify` on the substrate.
19
+ - **`listChannels`** (v0.9.x) — list operator-declared channels with aggregate stats (message_count, oldest/newest visible_at).
20
+ - **`streamUserRunStates`** (v0.9.x) — SSE stream of run state transitions scoped to one `user_id`. Yields `{ kind: "open" | "event", payload }` items until the connection closes (30-min server cap).
21
+ - **Channel CRUD** (v0.9.x) — `publishChannel` / `subscribeChannel` / `peekChannel` / `ackChannel` with both admin scope (`scope: "global"`) and per-user scope (`scope: "user"` + `userId`).
22
+ - **Content signatures** (v0.9.x) — every `agent_defs` / `skill_defs` row carries a deterministic `content_sha256`. Combined with the `verify` op gives operators a one-call answer to *"is what I have identical to what's deployed?"*.
23
+ - **Transcript first-cycle types** (v0.9.1) — `UserInputPayload` + `SystemPromptPayload` typed interfaces for the two transcript events that surface "what the agent actually received" as the first frames of every run.
24
+ - **Dual ESM + CJS distribution** (v0.10.1) — n8n's community-node loader (CommonJS) now works alongside ESM consumers.
25
+ - **First-run UX on the binary** (v0.11.1) — paired CLI commands `loomcycle init` (bootstrap config) + `loomcycle doctor` (health check) + auto-discovery of `~/.config/loomcycle/loomcycle.yaml`. No adapter changes; lockstep version bump only.
21
26
 
22
27
  ## Install
23
28
 
@@ -463,6 +463,64 @@ class LoomcycleClient {
463
463
  async listLibraryMcpServers(opts) {
464
464
  return (0, fetch_helpers_js_1.jsonFetch)(this.ctx, "/v1/_library/mcp-servers", opts);
465
465
  }
466
+ // ---- v0.11.0 LLM Gateway ----
467
+ /** Non-streaming LLM chat completion via the gateway endpoint.
468
+ * Wraps `POST /v1/_llm/chat` with `stream: false`. The gateway
469
+ * resolves a provider per the routing precedence
470
+ * (explicit-pin > explicit-provider > explicit-model > resolver
471
+ * default), invokes it directly (no agent loop), and returns the
472
+ * aggregated response with usage counters + the chosen
473
+ * provider/model echoed back.
474
+ *
475
+ * Use this when you want loomcycle's routing benefits (one
476
+ * credential, one quota, one observability surface across
477
+ * providers) without paying for the agent runtime overhead.
478
+ * Tool-calling works: pass `tools[]` and read `tool_use` content
479
+ * blocks back; the per-provider schema translation is handled by
480
+ * the substrate's existing driver layer.
481
+ *
482
+ * Raises {@link AuthError} on 401; {@link UnavailableError} on 503
483
+ * (resolver not configured, store unwired);
484
+ * {@link InvalidArgumentError} on 400 (bad request shape). */
485
+ async llmChat(opts) {
486
+ const body = serializeLLMOptions(opts, false);
487
+ return (0, fetch_helpers_js_1.postJSON)(this.ctx, "/v1/_llm/chat", body, {
488
+ signal: opts.signal,
489
+ });
490
+ }
491
+ /** Streaming LLM chat completion. Wraps `POST /v1/_llm/chat` with
492
+ * `stream: true`. Yields one {@link LLMChatStreamItem} per SSE
493
+ * frame in Anthropic-style: provider_chosen first, then
494
+ * content_block_start / content_block_delta / content_block_stop
495
+ * pairs, then message_delta + done.
496
+ *
497
+ * Iteration terminates when the gateway closes the stream. On a
498
+ * terminal error the gateway emits an `error` frame; the iterator
499
+ * yields it and the caller decides whether to throw.
500
+ *
501
+ * Use this for live token streaming into LangChain BaseChatModel
502
+ * `_stream` callbacks. */
503
+ async *llmStream(opts) {
504
+ const body = serializeLLMOptions(opts, true);
505
+ const resp = await this.ctx.fetchImpl(this.ctx.baseUrl + "/v1/_llm/chat", {
506
+ method: "POST",
507
+ headers: {
508
+ ...(0, fetch_helpers_js_1.authHeaders)(this.ctx),
509
+ "Content-Type": "application/json",
510
+ Accept: "text/event-stream",
511
+ },
512
+ body: JSON.stringify(body),
513
+ signal: opts.signal,
514
+ });
515
+ if (!resp.ok) {
516
+ await (0, fetch_helpers_js_1.raiseFromResponse)(resp);
517
+ }
518
+ if (!resp.body) {
519
+ throw new Error("llmStream: response has no body");
520
+ }
521
+ const reader = resp.body.getReader();
522
+ yield* parseLLMStreamFrames(reader);
523
+ }
466
524
  // ---- Internal helpers ----
467
525
  /** Shared SSE POST → stream-of-AgentEvent path. Used by
468
526
  * runStreaming + continueSession.
@@ -773,3 +831,72 @@ function channelOpPath(channel, scope, userId, op) {
773
831
  }
774
832
  return `/v1/_channels/${enc}/${op}`;
775
833
  }
834
+ // ---- v0.11.0 LLM Gateway helpers ----
835
+ /** serializeLLMOptions strips the AbortSignal (transport concern) and
836
+ * forces the stream flag to match the call mode. */
837
+ function serializeLLMOptions(opts, stream) {
838
+ const { signal: _signal, ...rest } = opts;
839
+ return { ...rest, stream };
840
+ }
841
+ /** parseLLMStreamFrames drains an SSE stream from /v1/_llm/chat and
842
+ * yields one LLMChatStreamItem per frame. Unlike parseSSE in
843
+ * stream.ts, the gateway's frames discriminate purely by the SSE
844
+ * event name — there's no `type` field on the data payload. */
845
+ async function* parseLLMStreamFrames(reader) {
846
+ const decoder = new TextDecoder("utf-8");
847
+ let buf = "";
848
+ let event = "";
849
+ let data = "";
850
+ const flush = () => {
851
+ if (!event || !data) {
852
+ event = "";
853
+ data = "";
854
+ return null;
855
+ }
856
+ try {
857
+ const payload = JSON.parse(data);
858
+ const item = { kind: event, payload };
859
+ event = "";
860
+ data = "";
861
+ return item;
862
+ }
863
+ catch {
864
+ event = "";
865
+ data = "";
866
+ return null;
867
+ }
868
+ };
869
+ while (true) {
870
+ const { value, done } = await reader.read();
871
+ if (done)
872
+ break;
873
+ buf += decoder.decode(value, { stream: true });
874
+ let idx;
875
+ while ((idx = buf.indexOf("\n")) !== -1) {
876
+ const line = buf.slice(0, idx).replace(/\r$/, "");
877
+ buf = buf.slice(idx + 1);
878
+ if (line === "") {
879
+ const item = flush();
880
+ if (item)
881
+ yield item;
882
+ continue;
883
+ }
884
+ if (line.startsWith("event:"))
885
+ event = line.slice("event:".length).trim();
886
+ else if (line.startsWith("data:"))
887
+ data = line.slice("data:".length).trim();
888
+ // Keepalive comment lines (`:keepalive`) are silently dropped.
889
+ }
890
+ }
891
+ // Drain a final un-newlined frame on connection close.
892
+ if (buf.length > 0) {
893
+ const line = buf.replace(/\r$/, "");
894
+ if (line.startsWith("event:"))
895
+ event = line.slice("event:".length).trim();
896
+ else if (line.startsWith("data:"))
897
+ data = line.slice("data:".length).trim();
898
+ }
899
+ const item = flush();
900
+ if (item)
901
+ yield item;
902
+ }
package/dist/cjs/index.js CHANGED
@@ -52,6 +52,10 @@
52
52
  * listLibrarySkills(): Promise<LibraryListResponse<LibrarySkillDefinition>>
53
53
  * listLibraryMcpServers(): Promise<LibraryListResponse<LibraryMcpServerDefinition>>
54
54
  *
55
+ * // LLM Gateway (v0.11.0 — direct provider routing, no agent loop)
56
+ * llmChat(opts: LLMChatOptions): Promise<LLMChatResponse>
57
+ * llmStream(opts: LLMChatOptions): AsyncIterable<LLMChatStreamItem>
58
+ *
55
59
  * Errors (typed subclasses of LoomcycleError; see README for the
56
60
  * full HTTP-status → typed-error mapping table):
57
61
  * LoomcycleError, AgentNotFoundError, SessionNotFoundError,
package/dist/client.d.ts CHANGED
@@ -23,7 +23,7 @@
23
23
  * via fetch-helpers.ts:raiseFromResponse — see README.md for the
24
24
  * full mapping table.
25
25
  */
26
- import type { Agent, AgentEvent, AgentStatus, CancelAgentResult, ClientOptions, ContinueOptions, CreateSnapshotOptions, HealthResponse, Hook, InterruptListResponse, InterruptStatus, AckChannelOptions, ChannelAckResult, ChannelPeekResult, ChannelPublishResult, ChannelSubscribeResult, ListChannelsResponse, PeekChannelOptions, PublishChannelOptions, SubscribeChannelOptions, LibraryAgentDefinition, LibraryListResponse, LibraryMcpServerDefinition, LibrarySkillDefinition, ListUsersResponse, MemoryEntriesResponse, MemoryEntryResponse, MemoryScopeIDsResponse, MemoryScopesResponse, PauseResult, RegisterHookOptions, RegisterHookResponse, ResolveInterruptOptions, ResumeResult, RunOptions, RunStateStreamItem, RuntimeStateResponse, SnapshotCreateResponse, SnapshotDescriptor, SnapshotEnvelope, SnapshotRestoreResponse, StreamUserRunStatesOptions, SubstrateToolInput, SubstrateToolResponse, TranscriptResponse } from "./types.js";
26
+ import type { Agent, AgentEvent, AgentStatus, CancelAgentResult, ClientOptions, ContinueOptions, CreateSnapshotOptions, HealthResponse, Hook, InterruptListResponse, InterruptStatus, AckChannelOptions, ChannelAckResult, ChannelPeekResult, ChannelPublishResult, ChannelSubscribeResult, ListChannelsResponse, PeekChannelOptions, PublishChannelOptions, SubscribeChannelOptions, LibraryAgentDefinition, LibraryListResponse, LibraryMcpServerDefinition, LibrarySkillDefinition, ListUsersResponse, LLMChatOptions, LLMChatResponse, LLMChatStreamItem, MemoryEntriesResponse, MemoryEntryResponse, MemoryScopeIDsResponse, MemoryScopesResponse, PauseResult, RegisterHookOptions, RegisterHookResponse, ResolveInterruptOptions, ResumeResult, RunOptions, RunStateStreamItem, RuntimeStateResponse, SnapshotCreateResponse, SnapshotDescriptor, SnapshotEnvelope, SnapshotRestoreResponse, StreamUserRunStatesOptions, SubstrateToolInput, SubstrateToolResponse, TranscriptResponse } from "./types.js";
27
27
  export declare class LoomcycleClient {
28
28
  private ctx;
29
29
  constructor(opts?: ClientOptions);
@@ -329,6 +329,38 @@ export declare class LoomcycleClient {
329
329
  listLibraryMcpServers(opts?: {
330
330
  signal?: AbortSignal;
331
331
  }): Promise<LibraryListResponse<LibraryMcpServerDefinition>>;
332
+ /** Non-streaming LLM chat completion via the gateway endpoint.
333
+ * Wraps `POST /v1/_llm/chat` with `stream: false`. The gateway
334
+ * resolves a provider per the routing precedence
335
+ * (explicit-pin > explicit-provider > explicit-model > resolver
336
+ * default), invokes it directly (no agent loop), and returns the
337
+ * aggregated response with usage counters + the chosen
338
+ * provider/model echoed back.
339
+ *
340
+ * Use this when you want loomcycle's routing benefits (one
341
+ * credential, one quota, one observability surface across
342
+ * providers) without paying for the agent runtime overhead.
343
+ * Tool-calling works: pass `tools[]` and read `tool_use` content
344
+ * blocks back; the per-provider schema translation is handled by
345
+ * the substrate's existing driver layer.
346
+ *
347
+ * Raises {@link AuthError} on 401; {@link UnavailableError} on 503
348
+ * (resolver not configured, store unwired);
349
+ * {@link InvalidArgumentError} on 400 (bad request shape). */
350
+ llmChat(opts: LLMChatOptions): Promise<LLMChatResponse>;
351
+ /** Streaming LLM chat completion. Wraps `POST /v1/_llm/chat` with
352
+ * `stream: true`. Yields one {@link LLMChatStreamItem} per SSE
353
+ * frame in Anthropic-style: provider_chosen first, then
354
+ * content_block_start / content_block_delta / content_block_stop
355
+ * pairs, then message_delta + done.
356
+ *
357
+ * Iteration terminates when the gateway closes the stream. On a
358
+ * terminal error the gateway emits an `error` frame; the iterator
359
+ * yields it and the caller decides whether to throw.
360
+ *
361
+ * Use this for live token streaming into LangChain BaseChatModel
362
+ * `_stream` callbacks. */
363
+ llmStream(opts: LLMChatOptions): AsyncIterable<LLMChatStreamItem>;
332
364
  /** Shared SSE POST → stream-of-AgentEvent path. Used by
333
365
  * runStreaming + continueSession.
334
366
  *
package/dist/client.js CHANGED
@@ -23,7 +23,7 @@
23
23
  * via fetch-helpers.ts:raiseFromResponse — see README.md for the
24
24
  * full mapping table.
25
25
  */
26
- import { deleteRequest, jsonFetch, postJSON, raiseFromResponse, } from "./fetch-helpers.js";
26
+ import { authHeaders, deleteRequest, jsonFetch, postJSON, raiseFromResponse, } from "./fetch-helpers.js";
27
27
  import { parseSSE } from "./stream.js";
28
28
  export class LoomcycleClient {
29
29
  ctx;
@@ -460,6 +460,64 @@ export class LoomcycleClient {
460
460
  async listLibraryMcpServers(opts) {
461
461
  return jsonFetch(this.ctx, "/v1/_library/mcp-servers", opts);
462
462
  }
463
+ // ---- v0.11.0 LLM Gateway ----
464
+ /** Non-streaming LLM chat completion via the gateway endpoint.
465
+ * Wraps `POST /v1/_llm/chat` with `stream: false`. The gateway
466
+ * resolves a provider per the routing precedence
467
+ * (explicit-pin > explicit-provider > explicit-model > resolver
468
+ * default), invokes it directly (no agent loop), and returns the
469
+ * aggregated response with usage counters + the chosen
470
+ * provider/model echoed back.
471
+ *
472
+ * Use this when you want loomcycle's routing benefits (one
473
+ * credential, one quota, one observability surface across
474
+ * providers) without paying for the agent runtime overhead.
475
+ * Tool-calling works: pass `tools[]` and read `tool_use` content
476
+ * blocks back; the per-provider schema translation is handled by
477
+ * the substrate's existing driver layer.
478
+ *
479
+ * Raises {@link AuthError} on 401; {@link UnavailableError} on 503
480
+ * (resolver not configured, store unwired);
481
+ * {@link InvalidArgumentError} on 400 (bad request shape). */
482
+ async llmChat(opts) {
483
+ const body = serializeLLMOptions(opts, false);
484
+ return postJSON(this.ctx, "/v1/_llm/chat", body, {
485
+ signal: opts.signal,
486
+ });
487
+ }
488
+ /** Streaming LLM chat completion. Wraps `POST /v1/_llm/chat` with
489
+ * `stream: true`. Yields one {@link LLMChatStreamItem} per SSE
490
+ * frame in Anthropic-style: provider_chosen first, then
491
+ * content_block_start / content_block_delta / content_block_stop
492
+ * pairs, then message_delta + done.
493
+ *
494
+ * Iteration terminates when the gateway closes the stream. On a
495
+ * terminal error the gateway emits an `error` frame; the iterator
496
+ * yields it and the caller decides whether to throw.
497
+ *
498
+ * Use this for live token streaming into LangChain BaseChatModel
499
+ * `_stream` callbacks. */
500
+ async *llmStream(opts) {
501
+ const body = serializeLLMOptions(opts, true);
502
+ const resp = await this.ctx.fetchImpl(this.ctx.baseUrl + "/v1/_llm/chat", {
503
+ method: "POST",
504
+ headers: {
505
+ ...authHeaders(this.ctx),
506
+ "Content-Type": "application/json",
507
+ Accept: "text/event-stream",
508
+ },
509
+ body: JSON.stringify(body),
510
+ signal: opts.signal,
511
+ });
512
+ if (!resp.ok) {
513
+ await raiseFromResponse(resp);
514
+ }
515
+ if (!resp.body) {
516
+ throw new Error("llmStream: response has no body");
517
+ }
518
+ const reader = resp.body.getReader();
519
+ yield* parseLLMStreamFrames(reader);
520
+ }
463
521
  // ---- Internal helpers ----
464
522
  /** Shared SSE POST → stream-of-AgentEvent path. Used by
465
523
  * runStreaming + continueSession.
@@ -769,3 +827,72 @@ function channelOpPath(channel, scope, userId, op) {
769
827
  }
770
828
  return `/v1/_channels/${enc}/${op}`;
771
829
  }
830
+ // ---- v0.11.0 LLM Gateway helpers ----
831
+ /** serializeLLMOptions strips the AbortSignal (transport concern) and
832
+ * forces the stream flag to match the call mode. */
833
+ function serializeLLMOptions(opts, stream) {
834
+ const { signal: _signal, ...rest } = opts;
835
+ return { ...rest, stream };
836
+ }
837
+ /** parseLLMStreamFrames drains an SSE stream from /v1/_llm/chat and
838
+ * yields one LLMChatStreamItem per frame. Unlike parseSSE in
839
+ * stream.ts, the gateway's frames discriminate purely by the SSE
840
+ * event name — there's no `type` field on the data payload. */
841
+ async function* parseLLMStreamFrames(reader) {
842
+ const decoder = new TextDecoder("utf-8");
843
+ let buf = "";
844
+ let event = "";
845
+ let data = "";
846
+ const flush = () => {
847
+ if (!event || !data) {
848
+ event = "";
849
+ data = "";
850
+ return null;
851
+ }
852
+ try {
853
+ const payload = JSON.parse(data);
854
+ const item = { kind: event, payload };
855
+ event = "";
856
+ data = "";
857
+ return item;
858
+ }
859
+ catch {
860
+ event = "";
861
+ data = "";
862
+ return null;
863
+ }
864
+ };
865
+ while (true) {
866
+ const { value, done } = await reader.read();
867
+ if (done)
868
+ break;
869
+ buf += decoder.decode(value, { stream: true });
870
+ let idx;
871
+ while ((idx = buf.indexOf("\n")) !== -1) {
872
+ const line = buf.slice(0, idx).replace(/\r$/, "");
873
+ buf = buf.slice(idx + 1);
874
+ if (line === "") {
875
+ const item = flush();
876
+ if (item)
877
+ yield item;
878
+ continue;
879
+ }
880
+ if (line.startsWith("event:"))
881
+ event = line.slice("event:".length).trim();
882
+ else if (line.startsWith("data:"))
883
+ data = line.slice("data:".length).trim();
884
+ // Keepalive comment lines (`:keepalive`) are silently dropped.
885
+ }
886
+ }
887
+ // Drain a final un-newlined frame on connection close.
888
+ if (buf.length > 0) {
889
+ const line = buf.replace(/\r$/, "");
890
+ if (line.startsWith("event:"))
891
+ event = line.slice("event:".length).trim();
892
+ else if (line.startsWith("data:"))
893
+ data = line.slice("data:".length).trim();
894
+ }
895
+ const item = flush();
896
+ if (item)
897
+ yield item;
898
+ }
package/dist/index.d.ts CHANGED
@@ -51,6 +51,10 @@
51
51
  * listLibrarySkills(): Promise<LibraryListResponse<LibrarySkillDefinition>>
52
52
  * listLibraryMcpServers(): Promise<LibraryListResponse<LibraryMcpServerDefinition>>
53
53
  *
54
+ * // LLM Gateway (v0.11.0 — direct provider routing, no agent loop)
55
+ * llmChat(opts: LLMChatOptions): Promise<LLMChatResponse>
56
+ * llmStream(opts: LLMChatOptions): AsyncIterable<LLMChatStreamItem>
57
+ *
54
58
  * Errors (typed subclasses of LoomcycleError; see README for the
55
59
  * full HTTP-status → typed-error mapping table):
56
60
  * LoomcycleError, AgentNotFoundError, SessionNotFoundError,
@@ -69,5 +73,5 @@
69
73
  * See `adapters/ts/README.md` for usage examples.
70
74
  */
71
75
  export { LoomcycleClient } from "./client.js";
72
- export type { AgentEvent, ClientOptions, ContinueOptions, EventType, HostWidening, PromptContent, PromptSegment, RetryInfo, RunOptions, ToolUse, Usage, Agent, AgentStatus, AgentUsage, CancelAgentResult, ListAgentsResponse, TranscriptEvent, TranscriptResponse, HealthResponse, ListUsersResponse, UserSummary, PauseResult, ResumeResult, RuntimeStateResponse, RuntimeStateStatus, CreateSnapshotOptions, SnapshotCreateResponse, SnapshotDescriptor, SnapshotEnvelope, SnapshotListResponse, SnapshotRestoreResponse, MemoryEntriesResponse, MemoryEntry, MemoryEntryResponse, MemoryScopeIDsResponse, MemoryScopeIDSummary, MemoryScopeKind, MemoryScopesResponse, InterruptListResponse, InterruptRow, InterruptStatus, ResolveInterruptOptions, Hook, HookFailMode, HookPhase, HookToolCall, HookToolResult, ListHooksResponse, PostHookCall, PostHookResult, PreHookCall, PreHookResult, RegisterHookOptions, RegisterHookResponse, SubstrateToolInput, SubstrateToolResponse, SystemPromptPayload, UserInputPayload, ChannelDescriptor, ListChannelsResponse, RunStateEvent, RunStateStreamClose, RunStateStreamItem, RunStateStreamOpen, StreamUserRunStatesOptions, AckChannelOptions, ChannelAckResult, ChannelMessageItem, ChannelPeekResult, ChannelPublishResult, ChannelScope, ChannelSubscribeResult, PeekChannelOptions, PublishChannelOptions, SubscribeChannelOptions, AgentDefRowResponse, AgentDefVerifyResult, SkillDefVerifyResult, MCPServerDefRowResponse, MCPServerDefVerifyResult, LibraryAgentDefinition, LibraryEntry, LibraryListResponse, LibraryMcpServerDefinition, LibrarySkillDefinition, } from "./types.js";
76
+ export type { AgentEvent, ClientOptions, ContinueOptions, EventType, HostWidening, PromptContent, PromptSegment, RetryInfo, RunOptions, ToolUse, Usage, Agent, AgentStatus, AgentUsage, CancelAgentResult, ListAgentsResponse, TranscriptEvent, TranscriptResponse, HealthResponse, ListUsersResponse, UserSummary, PauseResult, ResumeResult, RuntimeStateResponse, RuntimeStateStatus, CreateSnapshotOptions, SnapshotCreateResponse, SnapshotDescriptor, SnapshotEnvelope, SnapshotListResponse, SnapshotRestoreResponse, MemoryEntriesResponse, MemoryEntry, MemoryEntryResponse, MemoryScopeIDsResponse, MemoryScopeIDSummary, MemoryScopeKind, MemoryScopesResponse, InterruptListResponse, InterruptRow, InterruptStatus, ResolveInterruptOptions, Hook, HookFailMode, HookPhase, HookToolCall, HookToolResult, ListHooksResponse, PostHookCall, PostHookResult, PreHookCall, PreHookResult, RegisterHookOptions, RegisterHookResponse, SubstrateToolInput, SubstrateToolResponse, SystemPromptPayload, UserInputPayload, ChannelDescriptor, ListChannelsResponse, RunStateEvent, RunStateStreamClose, RunStateStreamItem, RunStateStreamOpen, StreamUserRunStatesOptions, AckChannelOptions, ChannelAckResult, ChannelMessageItem, ChannelPeekResult, ChannelPublishResult, ChannelScope, ChannelSubscribeResult, PeekChannelOptions, PublishChannelOptions, SubscribeChannelOptions, AgentDefRowResponse, AgentDefVerifyResult, SkillDefVerifyResult, MCPServerDefRowResponse, MCPServerDefVerifyResult, LibraryAgentDefinition, LibraryEntry, LibraryListResponse, LibraryMcpServerDefinition, LibrarySkillDefinition, LLMChatContent, LLMChatMessage, LLMChatOptions, LLMChatResponse, LLMChatStreamDelta, LLMChatStreamItem, LLMChatToolCall, LLMChatUsage, LLMTool, } from "./types.js";
73
77
  export { AgentIDInUseError, AgentNotFoundError, AlreadyPausingError, AuthError, BackpressureError, HookNotFoundError, NotFoundError, InvalidArgumentError, ChannelCursorRegressionError, LoomcycleError, NotPausedError, PauseNotConfiguredError, PerUserQuotaExhaustedError, SessionBusyError, SessionNotFoundError, SnapshotNotFoundError, SnapshotTooLargeError, SnapshotVersionError, SubstrateToolRefusedError, UnavailableError, } from "./errors.js";
package/dist/index.js CHANGED
@@ -51,6 +51,10 @@
51
51
  * listLibrarySkills(): Promise<LibraryListResponse<LibrarySkillDefinition>>
52
52
  * listLibraryMcpServers(): Promise<LibraryListResponse<LibraryMcpServerDefinition>>
53
53
  *
54
+ * // LLM Gateway (v0.11.0 — direct provider routing, no agent loop)
55
+ * llmChat(opts: LLMChatOptions): Promise<LLMChatResponse>
56
+ * llmStream(opts: LLMChatOptions): AsyncIterable<LLMChatStreamItem>
57
+ *
54
58
  * Errors (typed subclasses of LoomcycleError; see README for the
55
59
  * full HTTP-status → typed-error mapping table):
56
60
  * LoomcycleError, AgentNotFoundError, SessionNotFoundError,
package/dist/types.d.ts CHANGED
@@ -879,3 +879,151 @@ export interface LibraryEntry<T = unknown> {
879
879
  export interface LibraryListResponse<T = unknown> {
880
880
  entries: LibraryEntry<T>[];
881
881
  }
882
+ /** One message in the gateway conversation. Mirrors LangChain's
883
+ * BaseMessage shape so consumers map without re-shaping. */
884
+ export interface LLMChatMessage {
885
+ role: "system" | "user" | "assistant" | "tool";
886
+ /** Flat string content. For "assistant" turns with tool_calls,
887
+ * the content may be empty. For "tool" turns, this is the tool
888
+ * result text. */
889
+ content?: string;
890
+ /** Set on "assistant" turns that requested tool invocations. */
891
+ tool_calls?: LLMChatToolCall[];
892
+ /** Set on "tool" turns; correlates back to the assistant's
893
+ * tool_calls[].id. */
894
+ tool_call_id?: string;
895
+ }
896
+ /** One assistant-requested tool invocation. */
897
+ export interface LLMChatToolCall {
898
+ id: string;
899
+ name: string;
900
+ input: Record<string, unknown>;
901
+ }
902
+ /** Tool the model may call. The substrate translates this into the
903
+ * driver-native shape (Anthropic input_schema vs OpenAI function.
904
+ * parameters vs Gemini function_declarations) — caller passes the
905
+ * flat JSON schema and trusts the gateway's per-driver translation. */
906
+ export interface LLMTool {
907
+ name: string;
908
+ description?: string;
909
+ input_schema: Record<string, unknown>;
910
+ }
911
+ /** Request body for llmChat / llmStream.
912
+ *
913
+ * Two RFC-mentioned fields are deliberately absent in v1:
914
+ * - `stop_sequences`: providers.Request has no matching field today;
915
+ * accepting it would silently drop it. Lands when the providers
916
+ * package surface grows the equivalent.
917
+ * - `user_bearer`: the gateway calls provider.Call() directly with
918
+ * no MCP transport, so `${run.user_bearer}` substitution has
919
+ * nowhere to apply. Lands when the gateway grows an MCP path. */
920
+ export interface LLMChatOptions {
921
+ messages: LLMChatMessage[];
922
+ tools?: LLMTool[];
923
+ max_tokens?: number;
924
+ temperature?: number | null;
925
+ /** Routing hint. When set with `model`, the resolver short-circuits
926
+ * to that explicit pin. When set alone, the resolver picks the
927
+ * best model in that provider given tier/user_tier. */
928
+ provider?: string;
929
+ /** Routing hint. When set with `provider`, explicit pin. When set
930
+ * alone, the resolver picks the provider hosting that model. */
931
+ model?: string;
932
+ /** Tier for resolver dispatch. Defaults to "default" when neither
933
+ * pin nor tier supplied. */
934
+ tier?: string;
935
+ /** Per-user quota tracking. Empty bypasses the per-user cap. */
936
+ user_id?: string;
937
+ /** Per-user tier overlay; takes precedence over `tier` when set. */
938
+ user_tier?: string;
939
+ /** Optional AbortSignal for caller-driven cancellation. */
940
+ signal?: AbortSignal;
941
+ }
942
+ /** Non-streaming response shape. */
943
+ export interface LLMChatResponse {
944
+ /** Per-response id (llm_<hex>); useful in audit logs. */
945
+ id: string;
946
+ /** Per-request id (req_<hex>); cross-references the audit log. */
947
+ request_id: string;
948
+ /** Which provider the resolver picked. */
949
+ provider: string;
950
+ /** Specific model id picked. */
951
+ model: string;
952
+ /** Content blocks; one per text or tool_use output. */
953
+ content: LLMChatContent[];
954
+ stop_reason: "end_turn" | "max_tokens" | "tool_use" | "stop_sequence";
955
+ usage: LLMChatUsage;
956
+ }
957
+ /** One output content block. */
958
+ export type LLMChatContent = {
959
+ type: "text";
960
+ text: string;
961
+ } | {
962
+ type: "tool_use";
963
+ id: string;
964
+ name: string;
965
+ input: Record<string, unknown>;
966
+ };
967
+ /** Token-accounting payload. Cache fields are populated only on
968
+ * providers that surface them (Anthropic today). */
969
+ export interface LLMChatUsage {
970
+ input_tokens: number;
971
+ output_tokens: number;
972
+ cache_creation_input_tokens?: number;
973
+ cache_read_input_tokens?: number;
974
+ }
975
+ /** One streaming-mode SSE frame. The `kind` field is the SSE event
976
+ * name; the `payload` carries the per-frame shape. v1 mirrors
977
+ * Anthropic's streaming event names. */
978
+ export type LLMChatStreamItem = {
979
+ kind: "provider_chosen";
980
+ payload: {
981
+ provider: string;
982
+ model: string;
983
+ request_id: string;
984
+ };
985
+ } | {
986
+ kind: "content_block_start";
987
+ payload: {
988
+ index: number;
989
+ block: LLMChatContent;
990
+ };
991
+ } | {
992
+ kind: "content_block_delta";
993
+ payload: {
994
+ index: number;
995
+ delta: LLMChatStreamDelta;
996
+ };
997
+ } | {
998
+ kind: "content_block_stop";
999
+ payload: {
1000
+ index: number;
1001
+ };
1002
+ } | {
1003
+ kind: "message_delta";
1004
+ payload: {
1005
+ delta: {
1006
+ stop_reason?: string;
1007
+ };
1008
+ usage: LLMChatUsage;
1009
+ };
1010
+ } | {
1011
+ kind: "done";
1012
+ payload: {
1013
+ id: string;
1014
+ stop_reason: string;
1015
+ usage: LLMChatUsage;
1016
+ };
1017
+ } | {
1018
+ kind: "error";
1019
+ payload: {
1020
+ type: string;
1021
+ code: string;
1022
+ message: string;
1023
+ };
1024
+ };
1025
+ export interface LLMChatStreamDelta {
1026
+ type: "text_delta" | "input_json_delta";
1027
+ text?: string;
1028
+ partial_json?: string;
1029
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@loomcycle/client",
3
- "version": "0.10.4",
4
- "description": "TypeScript client for the loomcycle sidecar (HTTP+SSE). 39 methods covering run streaming, agent metadata, pause/resume/state, snapshot lifecycle, memory admin (incl. v0.9.0 Vector Memory embed_stats + reembed), interruption resolve, hook management, v0.8.22 substrate admin (agentDef + skillDef), v0.9.x n8n Phase 0 (listChannels + streamUserRunStates — with debug-mode synthetic open/close meta-frames + client-side parentAgentId filter), v0.9.x Channel CRUD (publishChannel + subscribeChannel + peekChannel + ackChannel — admin scope=global + per-user scope=user surfaces), v0.9.x content_sha256 (AgentDefVerifyResult + SkillDefVerifyResult types for the bundle-vs-deployed comparison workflow), v0.9.1 transcript first-cycle (SystemPromptPayload + UserInputPayload), v0.9.x dynamic MCP server registration (mcpServerDef + MCPServerDefVerifyResult — register HTTP/Streamable-HTTP MCP servers at runtime without yaml edits), and v0.10.3 Library v2 enumeration (listLibraryAgents + listLibrarySkills + listLibraryMcpServers — typed wrappers around the v0.9.3 yaml+substrate merged endpoints for n8n + workflow-editor integrations). v0.10.1 — dual ESM + CommonJS distribution (additive — ESM consumers unchanged; CJS consumers like n8n's community-node loader now work).",
3
+ "version": "0.11.1",
4
+ "description": "TypeScript client for the loomcycle sidecar (HTTP+SSE). 41 methods covering run streaming, agent metadata, pause/resume/state, snapshot lifecycle, memory admin (incl. v0.9.0 Vector Memory embed_stats + reembed), interruption resolve, hook management, v0.8.22 substrate admin (agentDef + skillDef), v0.9.x n8n Phase 0 (listChannels + streamUserRunStates), v0.9.x Channel CRUD (publishChannel + subscribeChannel + peekChannel + ackChannel), v0.9.x content_sha256 verify, v0.9.1 transcript first-cycle, v0.9.x dynamic MCP server registration (mcpServerDef + MCPServerDefVerifyResult), v0.10.3 Library v2 enumeration (listLibraryAgents + listLibrarySkills + listLibraryMcpServers), and v0.11.0 LLM Gateway (llmChat + llmStream — direct provider routing without agent overhead; primary target is n8n's LoomCycleChatModel AI Agent sub-node and any LangChain-compatible consumer). v0.10.1 — dual ESM + CommonJS distribution (additive — ESM consumers unchanged; CJS consumers like n8n's community-node loader now work).",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "repository": {