@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.
- package/dist/llm/ApiStoreClient.d.ts +57 -0
- package/dist/llm/errors.d.ts +77 -0
- package/dist/llm/types.d.ts +158 -0
- package/package.json +1 -1
|
@@ -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.
|
|
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",
|