@econ-v1/app-sdk 5.24.0 → 5.26.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.
@@ -0,0 +1,57 @@
1
+ /**
2
+ * TypeScript consumer SDK client for the public api-store LLM endpoint
3
+ * (feature 472).
4
+ *
5
+ * Wraps the host's `/api/v2/public/api_store/llm/{call,stream}` endpoints
6
+ * with a typed surface that threads the `route` field (LlmRoute) through
7
+ * every call method and surfaces the structured 400 body as typed errors.
8
+ *
9
+ * This client is dependency-injection-friendly: the constructor takes a
10
+ * `fetcher` callback so applications can plug in their existing `apiClient`
11
+ * singleton (which already handles JWT auth, token refresh, and L402
12
+ * payment cycles) without the SDK reaching into application HTTP plumbing
13
+ * directly.
14
+ *
15
+ * **Honest framing reminder** (S5/U4): `route: "private"` isolates the
16
+ * upstream provider's API credentials and routes egress through a separate
17
+ * device — it does NOT mask prompt contents from the upstream provider or
18
+ * anonymize the user.
19
+ */
20
+ import { PrivateUnavailableError, InvalidRouteModeCombinationError } from "./errors";
21
+ import type { ExecuteBestModelRequest, ExecuteChatRequest, ExecuteChatResponse, ExecuteRequest, LlmRoute, StreamChatChunk, StreamChatRequest } from "./types";
22
+ /**
23
+ * Minimal fetcher interface — narrow enough that the project's existing
24
+ * `apiClient` satisfies it directly. The fetcher MUST throw for non-2xx
25
+ * responses with the raw body attached as `error.body` so the SDK can
26
+ * decode the structured 400 shapes.
27
+ */
28
+ export interface ApiStoreFetcher {
29
+ post<TIn, TOut>(path: string, body: TIn): Promise<TOut>;
30
+ /**
31
+ * Streaming POST — returns an async iterable of parsed chunks. The
32
+ * fetcher owns the SSE parser; the SDK only orchestrates request shape
33
+ * + error mapping.
34
+ */
35
+ postStream<TIn>(path: string, body: TIn): AsyncIterable<StreamChatChunk>;
36
+ }
37
+ /** Consumer client for the public api-store LLM endpoint. */
38
+ export declare class ApiStoreClient {
39
+ private readonly fetcher;
40
+ constructor(fetcher: ApiStoreFetcher);
41
+ /** Unary execute call (single-message). */
42
+ execute(request: ExecuteRequest): Promise<ExecuteChatResponse>;
43
+ /** Unary execute_chat call. */
44
+ executeChat(request: ExecuteChatRequest): Promise<ExecuteChatResponse>;
45
+ /** Unary execute_best_model call. */
46
+ executeBestModel(request: ExecuteBestModelRequest): Promise<ExecuteChatResponse>;
47
+ /**
48
+ * Streaming chat call. Yields chunks in order; the final chunk is always
49
+ * either an `end` event (carrying execution_route + token usage + cost) or
50
+ * an `error` event (FR-016 / S3 invariant — no silent fallback to direct
51
+ * provider). Consumers MUST handle the `error` variant explicitly.
52
+ */
53
+ streamChat(request: StreamChatRequest): AsyncGenerator<StreamChatChunk, void, void>;
54
+ private callWithRoute;
55
+ }
56
+ export { PrivateUnavailableError, InvalidRouteModeCombinationError };
57
+ export type { ExecuteBestModelRequest, ExecuteChatRequest, ExecuteChatResponse, ExecuteRequest, LlmRoute, StreamChatChunk, StreamChatRequest, };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Typed errors for the public api-store LLM endpoint (feature 472).
3
+ *
4
+ * Resolves UI finding U1: applications need a typed, pattern-matchable error
5
+ * for the structured 400 body produced when `route: "private"` cannot be
6
+ * honoured on the selected executor.
7
+ *
8
+ * The host emits the canonical body shape (see OpenAPI contract):
9
+ * {
10
+ * "code": "api_store.private_unavailable",
11
+ * "requested_route": "private",
12
+ * "reason": "no_esp32_endpoint" | "firmware_upgrade_required" | …,
13
+ * "fallback_available": true | false
14
+ * }
15
+ *
16
+ * `apiClient` errors with `code === "api_store.private_unavailable"` are
17
+ * mapped to a [[PrivateUnavailableError]] instance via [[mapApiErrorBody]].
18
+ */
19
+ import type { LlmRoute, PrivateUnavailableReason } from "./types";
20
+ /** Wire shape of the structured 400 body. */
21
+ export interface PrivateUnavailableBody {
22
+ code: "api_store.private_unavailable";
23
+ requested_route: "private";
24
+ reason: PrivateUnavailableReason;
25
+ fallback_available: boolean;
26
+ }
27
+ /** Wire shape of the route_mode + execution_preference rejection body. */
28
+ export interface InvalidRouteModeCombinationBody {
29
+ code: "api_store.invalid_route_mode_combination";
30
+ route_mode: LlmRoute;
31
+ execution_preference?: string;
32
+ hint: string;
33
+ }
34
+ /**
35
+ * Thrown by the SDK when the host rejects a `route: "private"` request
36
+ * because the selected executor has no custodial-hardware path (or its
37
+ * firmware is too old, the peer is offline, or the platform is not enabled
38
+ * by the operator).
39
+ *
40
+ * Catch this specifically to either:
41
+ * - retry with `route: "auto"` (the `fallback_available` field is true when
42
+ * the host believes a public-path fallback would succeed), or
43
+ * - surface a precise message to the user explaining which condition
44
+ * blocked the private path.
45
+ *
46
+ * **Honest framing reminder**: Even when the call succeeds with
47
+ * `route: "private"`, the prompt contents are not masked from the upstream
48
+ * provider — privacy is about credential isolation and egress device, not
49
+ * payload secrecy.
50
+ */
51
+ export declare class PrivateUnavailableError extends Error {
52
+ readonly code: "api_store.private_unavailable";
53
+ readonly reason: PrivateUnavailableReason;
54
+ readonly fallback_available: boolean;
55
+ constructor(body: PrivateUnavailableBody);
56
+ }
57
+ /**
58
+ * Thrown by the SDK when `route` + `execution_preference` form a rejected
59
+ * combination per the Routing Truth Table in `data-model.md §4`.
60
+ */
61
+ export declare class InvalidRouteModeCombinationError extends Error {
62
+ readonly code: "api_store.invalid_route_mode_combination";
63
+ readonly route_mode: LlmRoute;
64
+ readonly execution_preference?: string;
65
+ readonly hint: string;
66
+ constructor(body: InvalidRouteModeCombinationBody);
67
+ }
68
+ /**
69
+ * Map a 400 response body (already JSON-parsed) into the typed SDK error
70
+ * variant, or return `null` to let the caller fall through to the generic
71
+ * `ApiError` path.
72
+ *
73
+ * Used by [[ApiStoreClient]] inside its 400 branch so applications can
74
+ * `catch (e) { if (e instanceof PrivateUnavailableError) … }` without
75
+ * having to introspect the wire body themselves.
76
+ */
77
+ export declare function mapApiErrorBody(body: unknown): Error | null;
@@ -0,0 +1,158 @@
1
+ /**
2
+ * TypeScript consumer SDK types for the public api-store LLM endpoint
3
+ * (feature 472 — public/private LLM proxy routing).
4
+ *
5
+ * These types intentionally cover ONLY the surface introduced by feature 472:
6
+ * the `LlmRoute` enum, the `route` / `executionRoute` / `viaNodeId` fields, and
7
+ * the corresponding additions to the four LLM request/response shapes. They
8
+ * are exported so applications consuming the api-store endpoint can stay
9
+ * type-safe end-to-end.
10
+ *
11
+ * The wire form follows the OpenAPI contract at
12
+ * `specs/472-public-private-llm-proxy/contracts/public_api_store_llm.yaml`:
13
+ *
14
+ * request: { …, "route_mode": "auto" | "public" | "private" }
15
+ * response: { …, "execution_route": "public" | "private", "via_node_id": "…" }
16
+ *
17
+ * Existing snake_case fields are preserved; in TypeScript-land we map them to
18
+ * the snake_case names used over the wire (no auto-camelCasing in the SDK).
19
+ *
20
+ * **Honest framing** (security finding S5 / U4, surfaced via JSDoc on
21
+ * `LlmRoute`): Private mode isolates the upstream provider's API credentials
22
+ * on the peer's host and routes egress through a separate device; it does NOT
23
+ * mask prompt contents from the upstream provider or anonymize the user.
24
+ */
25
+ /**
26
+ * Consumer-expressed routing intent for an LLM call.
27
+ *
28
+ * - `"public"` — force direct provider execution; skip the custodial-hardware
29
+ * hop even on peers that have it configured.
30
+ * - `"private"` — force routing through the custodial-hardware path. Returns
31
+ * a typed `PrivateUnavailableError` when no hardware path is available on
32
+ * the selected executor.
33
+ * - `"auto"` — default; pick the more-isolated path when available, never
34
+ * hard-fail because of routing.
35
+ *
36
+ * **Honest framing**: Private mode isolates the upstream provider's API
37
+ * credentials on the peer's host and routes egress through a separate device;
38
+ * it does NOT mask prompt contents from the upstream provider or anonymize
39
+ * the user.
40
+ */
41
+ export type LlmRoute = "auto" | "public" | "private";
42
+ /**
43
+ * Truthful echo of what actually executed the call. Never `"auto"` — the
44
+ * service resolves auto-intent at the endpoint layer and reports the
45
+ * concrete path used in the response.
46
+ */
47
+ export type ExecutionRoute = "public" | "private";
48
+ /**
49
+ * Reason the peer rejected a `route_mode=private` request. Surfaced via
50
+ * `PrivateUnavailableError.reason` in `./errors.ts`.
51
+ */
52
+ export type PrivateUnavailableReason = "no_esp32_endpoint" | "firmware_upgrade_required" | "peer_offline" | "platform_not_enabled";
53
+ /**
54
+ * Local-vs-network preference. Composes with `route` per the Routing Truth
55
+ * Table in `data-model.md §4`.
56
+ */
57
+ export type ExecutionPreference = "prefer_local" | "local_only" | "network_only";
58
+ /**
59
+ * Shared shape carried by every public api-store LLM call/stream request.
60
+ * Applications wrap their own input shape around this and submit it via
61
+ * `ApiStoreClient` — see `./ApiStoreClient.ts`.
62
+ *
63
+ * **JSDoc honest-framing reminder**: `route: "private"` does NOT mask prompt
64
+ * contents from the upstream provider — it only changes which device's
65
+ * credentials issue the egress call. Document this in your application UI.
66
+ */
67
+ export interface ExecuteChatRequest {
68
+ /** The prompt or chat-style messages. Application-specific shape. */
69
+ messages: Array<{
70
+ role: string;
71
+ content: string;
72
+ }>;
73
+ /** Platform name, e.g. "OpenAI", "Anthropic". */
74
+ platform?: string;
75
+ /** Model name, e.g. "gpt-4o", "claude-3-5-sonnet-20240620". */
76
+ model?: string;
77
+ /**
78
+ * Consumer-expressed routing intent. Omit (or set to `"auto"`) to keep the
79
+ * default behaviour — the peer picks the more-isolated path when available
80
+ * and falls back to public when not.
81
+ *
82
+ * Setting `"private"` MAY return a `PrivateUnavailableError` when the
83
+ * selected executor has no custodial-hardware path; check
84
+ * `.fallback_available` on the error and decide whether to retry with
85
+ * `route: "auto"`.
86
+ *
87
+ * **Honest framing**: Private mode isolates the upstream provider's API
88
+ * credentials on the peer's host and routes egress through a separate
89
+ * device; it does NOT mask prompt contents from the upstream provider or
90
+ * anonymize the user.
91
+ */
92
+ route?: LlmRoute;
93
+ /**
94
+ * Local-vs-network executor preference. Composes with `route` per the
95
+ * Routing Truth Table.
96
+ */
97
+ execution_preference?: ExecutionPreference;
98
+ /** Correlation id for logs / audit (allowlisted log field per S4). */
99
+ correlation_id?: string;
100
+ /** Optional cap on output tokens. */
101
+ max_output_tokens?: number;
102
+ /** Optional tool specs (OpenAI-style). Opaque to the SDK. */
103
+ tools?: unknown;
104
+ /** Optional tool_choice directive. Opaque to the SDK. */
105
+ tool_choice?: unknown;
106
+ /** Sampling temperature. */
107
+ temperature?: number;
108
+ /** Optional response_format (e.g., `json_schema`). Opaque to the SDK. */
109
+ response_format?: unknown;
110
+ }
111
+ /** Streaming variant of [[ExecuteChatRequest]] — structurally identical. */
112
+ export type StreamChatRequest = ExecuteChatRequest;
113
+ /** "best-model" variant — model resolution happens server-side. */
114
+ export interface ExecuteBestModelRequest extends Omit<ExecuteChatRequest, "model"> {
115
+ }
116
+ /** Single-message execute variant. */
117
+ export interface ExecuteRequest extends ExecuteChatRequest {
118
+ }
119
+ /**
120
+ * Common echo fields appended to every successful public api-store LLM
121
+ * response. Carries the truthful execution path so the consumer can log it,
122
+ * retry under different intent, or shape downstream behaviour.
123
+ */
124
+ export interface RouteExecutionEcho {
125
+ /**
126
+ * Truthful echo of which path actually executed the call. Always
127
+ * `"public"` or `"private"`, never `"auto"`.
128
+ */
129
+ execution_route: ExecutionRoute;
130
+ /** Identifier of the node that executed the call (self or peer). */
131
+ via_node_id: string;
132
+ }
133
+ /** Unary call response. */
134
+ export interface ExecuteChatResponse extends RouteExecutionEcho {
135
+ response_text: string;
136
+ platform: string;
137
+ model: string;
138
+ prompt_tokens: number;
139
+ completion_tokens: number;
140
+ cost_sats: number;
141
+ correlation_id?: string;
142
+ }
143
+ /** Streaming chunk. The final chunk MUST carry `RouteExecutionEcho`. */
144
+ export type StreamChatChunk = {
145
+ type: "delta";
146
+ text: string;
147
+ } | ({
148
+ type: "end";
149
+ } & RouteExecutionEcho & {
150
+ prompt_tokens: number;
151
+ completion_tokens: number;
152
+ cost_sats: number;
153
+ correlation_id?: string;
154
+ }) | {
155
+ type: "error";
156
+ code: string;
157
+ message: string;
158
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@econ-v1/app-sdk",
3
- "version": "5.24.0",
3
+ "version": "5.26.0",
4
4
  "description": "TypeScript SDK for building Node mini-app plugins (Bun runtime) on the Lightning-powered marketplace daemon",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "author": "Node contributors",