@evilstar9527/tool-bridge 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.
- package/README.md +288 -0
- package/dist/sdk/admin.js +157 -0
- package/dist/sdk/chunks/client-BWz3o-l-.js +36 -0
- package/dist/sdk/chunks/errors-DJj3RaDk.js +54 -0
- package/dist/sdk/host.js +98 -0
- package/dist/sdk/index.js +6 -0
- package/dist/sdk/tb.js +0 -0
- package/dist/sdk/transport.js +14 -0
- package/dist/sdk/tunnel-agent.js +43 -0
- package/dist/sdk/worker.js +2940 -0
- package/dist/types/sdk/admin/index.d.ts +237 -0
- package/dist/types/sdk/client.d.ts +17 -0
- package/dist/types/sdk/host/index.d.ts +65 -0
- package/dist/types/sdk/index.d.ts +5 -0
- package/dist/types/sdk/transport.d.ts +8 -0
- package/dist/types/sdk/tunnel-agent/index.d.ts +29 -0
- package/dist/types/worker/index.d.ts +19 -0
- package/dist/types/worker/tb/adapters/builtin.d.ts +4 -0
- package/dist/types/worker/tb/adapters/directory.d.ts +6 -0
- package/dist/types/worker/tb/adapters/http.d.ts +2 -0
- package/dist/types/worker/tb/adapters/index.d.ts +2 -0
- package/dist/types/worker/tb/adapters/mcp.d.ts +2 -0
- package/dist/types/worker/tb/adapters/mount.d.ts +2 -0
- package/dist/types/worker/tb/adapters/remote.d.ts +2 -0
- package/dist/types/worker/tb/audit.d.ts +56 -0
- package/dist/types/worker/tb/crawl.d.ts +11 -0
- package/dist/types/worker/tb/device.d.ts +80 -0
- package/dist/types/worker/tb/dynamic-servers.d.ts +12 -0
- package/dist/types/worker/tb/entities.d.ts +74 -0
- package/dist/types/worker/tb/errors.d.ts +34 -0
- package/dist/types/worker/tb/help.d.ts +2 -0
- package/dist/types/worker/tb/host-api.d.ts +12 -0
- package/dist/types/worker/tb/materialize.d.ts +4 -0
- package/dist/types/worker/tb/mcp-client.d.ts +17 -0
- package/dist/types/worker/tb/provider-api.d.ts +11 -0
- package/dist/types/worker/tb/registry.d.ts +11 -0
- package/dist/types/worker/tb/remote-client.d.ts +3 -0
- package/dist/types/worker/tb/resolve.d.ts +8 -0
- package/dist/types/worker/tb/storage-r2.d.ts +2 -0
- package/dist/types/worker/tb/tenant.d.ts +28 -0
- package/dist/types/worker/tb/testing/fake-kv.d.ts +20 -0
- package/dist/types/worker/tb/types.d.ts +155 -0
- package/dist/types/worker/tb/util.d.ts +18 -0
- package/dist/types/worker/tb/virtualize.d.ts +7 -0
- package/docs/sdk.md +149 -0
- package/package.json +79 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface ErrorEnvelope {
|
|
2
|
+
error: {
|
|
3
|
+
code: string;
|
|
4
|
+
message: string;
|
|
5
|
+
details?: unknown;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export declare const RETRYABLE_CODES: ReadonlySet<string>;
|
|
9
|
+
export declare function isRetryable(code: string): boolean;
|
|
10
|
+
export declare class TBError extends Error {
|
|
11
|
+
readonly code: string;
|
|
12
|
+
readonly status: number;
|
|
13
|
+
readonly details?: unknown;
|
|
14
|
+
constructor(code: string, status: number, message: string, details?: unknown);
|
|
15
|
+
get retryable(): boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare class NotFoundError extends TBError {
|
|
18
|
+
constructor(message: string, details?: unknown);
|
|
19
|
+
}
|
|
20
|
+
export declare class UpstreamError extends TBError {
|
|
21
|
+
constructor(message: string, details?: unknown);
|
|
22
|
+
}
|
|
23
|
+
export declare class EndpointUnavailableError extends TBError {
|
|
24
|
+
constructor(message: string, details?: unknown);
|
|
25
|
+
}
|
|
26
|
+
export declare class ForbiddenError extends TBError {
|
|
27
|
+
constructor(message: string, details?: unknown);
|
|
28
|
+
}
|
|
29
|
+
export declare class BadRequestError extends TBError {
|
|
30
|
+
constructor(message: string, details?: unknown);
|
|
31
|
+
}
|
|
32
|
+
export declare function errorEnvelope(code: string, message: string, details?: unknown): ErrorEnvelope;
|
|
33
|
+
export declare function errorResponse(status: number, code: string, message: string, details?: unknown): Response;
|
|
34
|
+
export declare function errorResponseOf(error: unknown): Response;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ApiPrincipal } from './provider-api';
|
|
2
|
+
import { AppEnv } from './types';
|
|
3
|
+
export interface HostRecord {
|
|
4
|
+
id: string;
|
|
5
|
+
tenantId: string;
|
|
6
|
+
providerId: string;
|
|
7
|
+
confirmDelegated?: boolean;
|
|
8
|
+
createdAt: string;
|
|
9
|
+
updatedAt: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function getHost(env: AppEnv, id: string): Promise<HostRecord | null>;
|
|
12
|
+
export declare function routeHostApi(request: Request, env: AppEnv, principal: ApiPrincipal): Promise<Response | undefined>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { AppEnv } from './types';
|
|
2
|
+
export declare function materializeHeaders(env: AppEnv, headers: Record<string, string> | undefined): Record<string, string>;
|
|
3
|
+
export declare function requireSecureUrl(env: AppEnv, rawUrl: string, label: string): string;
|
|
4
|
+
export declare function assertRemoteHostAllowed(env: AppEnv, rawUrl: string): void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AppEnv, McpNode } from './types';
|
|
2
|
+
export interface McpTool {
|
|
3
|
+
name: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
inputSchema?: unknown;
|
|
6
|
+
outputSchema?: unknown;
|
|
7
|
+
annotations?: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface ResolvedMcpServer {
|
|
10
|
+
id: string;
|
|
11
|
+
endpoint: string;
|
|
12
|
+
allowedTools?: string[];
|
|
13
|
+
resolvedHeaders: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
export declare function resolveMcpServer(env: AppEnv, node: McpNode): ResolvedMcpServer;
|
|
16
|
+
export declare function listMcpTools(server: ResolvedMcpServer): Promise<McpTool[]>;
|
|
17
|
+
export declare function callMcpTool(server: ResolvedMcpServer, toolName: string, args: unknown): Promise<unknown>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PrincipalKind } from './tenant';
|
|
2
|
+
import { AppEnv } from './types';
|
|
3
|
+
export interface ApiPrincipal {
|
|
4
|
+
isAdmin: boolean;
|
|
5
|
+
principal?: PrincipalKind;
|
|
6
|
+
providerId?: string;
|
|
7
|
+
hostId?: string;
|
|
8
|
+
tenantId?: string;
|
|
9
|
+
subject?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function routeProviderApi(request: Request, env: AppEnv, principal: ApiPrincipal): Promise<Response | undefined>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AppEnv, DirectoryNode, TreeNode } from './types';
|
|
2
|
+
export declare const TREE_PREFIX = "/htbp";
|
|
3
|
+
export declare function parseTree(env: AppEnv): DirectoryNode;
|
|
4
|
+
export declare function parseTreeFromJson(raw: string): DirectoryNode;
|
|
5
|
+
export declare function normalizeNodeConfig(value: unknown, label: string): TreeNode;
|
|
6
|
+
export declare function linkParents(node: TreeNode): void;
|
|
7
|
+
export declare function nodePath(node: TreeNode): string;
|
|
8
|
+
export declare function findNode(root: DirectoryNode, segments: string[]): {
|
|
9
|
+
node: TreeNode;
|
|
10
|
+
sub: string[];
|
|
11
|
+
} | undefined;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AppEnv, AuthMode, BuiltinHandlerRegistry, DirectoryNode, HelpPayload } from './types';
|
|
2
|
+
export interface ResolvedHelp {
|
|
3
|
+
payload: HelpPayload;
|
|
4
|
+
resourcePath: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function resolveHelp(env: AppEnv, root: DirectoryNode, segments: string[], authMode: AuthMode, accept: string, builtinHandlers?: BuiltinHandlerRegistry): Promise<Response>;
|
|
7
|
+
export declare function resolveCall(env: AppEnv, root: DirectoryNode, segments: string[], authMode: AuthMode, input: unknown, builtinHandlers?: BuiltinHandlerRegistry): Promise<Response>;
|
|
8
|
+
export { NotFoundError } from './util';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AppEnv, DirectoryNode } from './types';
|
|
2
|
+
export type PrincipalKind = 'agent' | 'provider' | 'host' | 'admin' | 'service';
|
|
3
|
+
export interface PrincipalRecord {
|
|
4
|
+
principal: PrincipalKind;
|
|
5
|
+
tenantId?: string;
|
|
6
|
+
providerId?: string;
|
|
7
|
+
hostId?: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
createdAt?: string;
|
|
10
|
+
expiresAt?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface PrincipalContext extends PrincipalRecord {
|
|
13
|
+
root?: DirectoryNode;
|
|
14
|
+
}
|
|
15
|
+
export interface TenantContext {
|
|
16
|
+
tenantId: string;
|
|
17
|
+
root: DirectoryNode;
|
|
18
|
+
}
|
|
19
|
+
export declare function tenantModeEnabled(env: AppEnv): boolean;
|
|
20
|
+
export declare function sha256Hex(value: string): Promise<string>;
|
|
21
|
+
export declare function resolvePrincipal(env: AppEnv, token: string): Promise<PrincipalContext | null>;
|
|
22
|
+
export declare function resolveTenant(env: AppEnv, token: string): Promise<TenantContext | null>;
|
|
23
|
+
export declare function generateKey(prefix: 'tbk' | 'tbp' | 'tbs'): string;
|
|
24
|
+
export declare function storeApiKey(env: AppEnv, rawKey: string, record: PrincipalRecord): Promise<void>;
|
|
25
|
+
export declare function listApiKeyRecords(env: AppEnv): Promise<Array<{
|
|
26
|
+
hash: string;
|
|
27
|
+
record: PrincipalRecord;
|
|
28
|
+
}>>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface FakeKV {
|
|
2
|
+
get(key: string, type?: 'json' | 'text'): Promise<unknown>;
|
|
3
|
+
put(key: string, value: string, opts?: {
|
|
4
|
+
expirationTtl?: number;
|
|
5
|
+
}): Promise<void>;
|
|
6
|
+
delete(key: string): Promise<void>;
|
|
7
|
+
list(opts?: {
|
|
8
|
+
prefix?: string;
|
|
9
|
+
cursor?: string;
|
|
10
|
+
limit?: number;
|
|
11
|
+
}): Promise<{
|
|
12
|
+
keys: Array<{
|
|
13
|
+
name: string;
|
|
14
|
+
}>;
|
|
15
|
+
list_complete: boolean;
|
|
16
|
+
cursor?: string;
|
|
17
|
+
}>;
|
|
18
|
+
dump(): Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
export declare function fakeKV(seed?: Record<string, string>): FakeKV;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
export type NodeKind = 'directory' | 'mcp' | 'http' | 'remote' | 'mount' | 'builtin';
|
|
2
|
+
export type ToolEffect = 'read' | 'write' | 'destructive' | 'external';
|
|
3
|
+
export type AuthMode = 'none' | 'bearer' | 'oauth';
|
|
4
|
+
export type AppEnv = Env & {
|
|
5
|
+
AUTH_BEARER_TOKEN?: string;
|
|
6
|
+
OAUTH_ISSUER?: string;
|
|
7
|
+
OAUTH_JWKS_URI?: string;
|
|
8
|
+
ALLOW_INSECURE_MCP_HTTP?: string;
|
|
9
|
+
HTBP_REMOTE_ALLOWLIST?: string;
|
|
10
|
+
TENANT_MODE?: string;
|
|
11
|
+
};
|
|
12
|
+
export interface ResourceRef {
|
|
13
|
+
name: string;
|
|
14
|
+
path: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ToolSpec {
|
|
18
|
+
name: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
inputSchema?: unknown;
|
|
21
|
+
outputSchema?: unknown;
|
|
22
|
+
effect?: ToolEffect;
|
|
23
|
+
scope?: string;
|
|
24
|
+
confirm?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface EndpointSpec {
|
|
27
|
+
method: string;
|
|
28
|
+
inputSchema?: unknown;
|
|
29
|
+
outputSchema?: unknown;
|
|
30
|
+
example?: unknown;
|
|
31
|
+
tools?: ToolSpec[];
|
|
32
|
+
effect?: ToolEffect;
|
|
33
|
+
scope?: string;
|
|
34
|
+
confirm?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface HelpPayload {
|
|
37
|
+
htbp: 'draft';
|
|
38
|
+
kind: NodeKind;
|
|
39
|
+
title: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
cachable?: boolean;
|
|
42
|
+
resources?: ResourceRef[];
|
|
43
|
+
endpoint?: EndpointSpec;
|
|
44
|
+
}
|
|
45
|
+
export interface BaseNode {
|
|
46
|
+
kind: NodeKind;
|
|
47
|
+
id: string;
|
|
48
|
+
title: string;
|
|
49
|
+
summary?: string;
|
|
50
|
+
parent?: TreeNode;
|
|
51
|
+
}
|
|
52
|
+
export interface DirectoryNode extends BaseNode {
|
|
53
|
+
kind: 'directory';
|
|
54
|
+
children: TreeNode[];
|
|
55
|
+
}
|
|
56
|
+
export interface ToolOverride {
|
|
57
|
+
hide?: boolean;
|
|
58
|
+
rename?: string;
|
|
59
|
+
description?: string;
|
|
60
|
+
effect?: ToolEffect;
|
|
61
|
+
scope?: string;
|
|
62
|
+
confirm?: boolean;
|
|
63
|
+
}
|
|
64
|
+
export interface McpNode extends BaseNode {
|
|
65
|
+
kind: 'mcp';
|
|
66
|
+
endpoint: string;
|
|
67
|
+
description?: string;
|
|
68
|
+
headers?: Record<string, string>;
|
|
69
|
+
allowedTools?: string[];
|
|
70
|
+
namespace?: string;
|
|
71
|
+
toolOverrides?: Record<string, ToolOverride>;
|
|
72
|
+
}
|
|
73
|
+
export interface HttpEndpointConfig {
|
|
74
|
+
name: string;
|
|
75
|
+
method: string;
|
|
76
|
+
url: string;
|
|
77
|
+
description?: string;
|
|
78
|
+
inputSchema?: unknown;
|
|
79
|
+
outputSchema?: unknown;
|
|
80
|
+
example?: unknown;
|
|
81
|
+
headers?: Record<string, string>;
|
|
82
|
+
effect?: ToolEffect;
|
|
83
|
+
scope?: string;
|
|
84
|
+
confirm?: boolean;
|
|
85
|
+
}
|
|
86
|
+
export interface HttpNode extends BaseNode {
|
|
87
|
+
kind: 'http';
|
|
88
|
+
endpoints: HttpEndpointConfig[];
|
|
89
|
+
}
|
|
90
|
+
export interface RemoteNode extends BaseNode {
|
|
91
|
+
kind: 'remote';
|
|
92
|
+
helpUrl: string;
|
|
93
|
+
headers?: Record<string, string>;
|
|
94
|
+
}
|
|
95
|
+
export interface MountNode extends BaseNode {
|
|
96
|
+
kind: 'mount';
|
|
97
|
+
bucket: string;
|
|
98
|
+
prefix?: string;
|
|
99
|
+
description?: string;
|
|
100
|
+
}
|
|
101
|
+
export type TreeNode = DirectoryNode | McpNode | HttpNode | RemoteNode | MountNode | BuiltinNode;
|
|
102
|
+
export interface BuiltinToolConfig {
|
|
103
|
+
name: string;
|
|
104
|
+
description?: string;
|
|
105
|
+
handler: string;
|
|
106
|
+
inputSchema?: unknown;
|
|
107
|
+
outputSchema?: unknown;
|
|
108
|
+
effect?: ToolEffect;
|
|
109
|
+
scope?: string;
|
|
110
|
+
confirm?: boolean;
|
|
111
|
+
}
|
|
112
|
+
export interface BuiltinNode extends BaseNode {
|
|
113
|
+
kind: 'builtin';
|
|
114
|
+
description?: string;
|
|
115
|
+
tools: BuiltinToolConfig[];
|
|
116
|
+
}
|
|
117
|
+
export type BuiltinHandler = (input: unknown, ctx: AdapterContext) => Promise<unknown> | unknown;
|
|
118
|
+
export type BuiltinHandlerRegistry = Record<string, BuiltinHandler>;
|
|
119
|
+
export interface StorageEntry {
|
|
120
|
+
name: string;
|
|
121
|
+
key: string;
|
|
122
|
+
isDir: boolean;
|
|
123
|
+
}
|
|
124
|
+
export interface StorageObject {
|
|
125
|
+
key: string;
|
|
126
|
+
body: string;
|
|
127
|
+
contentType?: string;
|
|
128
|
+
size?: number;
|
|
129
|
+
}
|
|
130
|
+
export interface StorageProvider {
|
|
131
|
+
list(prefix: string): Promise<StorageEntry[]>;
|
|
132
|
+
get(key: string): Promise<StorageObject | null>;
|
|
133
|
+
}
|
|
134
|
+
export interface AdapterContext {
|
|
135
|
+
env: AppEnv;
|
|
136
|
+
authMode: AuthMode;
|
|
137
|
+
basePath: string;
|
|
138
|
+
builtinHandlers?: BuiltinHandlerRegistry;
|
|
139
|
+
}
|
|
140
|
+
export interface TBAdapter<TNode extends TreeNode = TreeNode> {
|
|
141
|
+
kind: NodeKind;
|
|
142
|
+
describe(node: TNode, ctx: AdapterContext, sub: string[]): Promise<HelpPayload>;
|
|
143
|
+
call(node: TNode, ctx: AdapterContext, sub: string[], input: unknown): Promise<unknown>;
|
|
144
|
+
}
|
|
145
|
+
export interface CrawlNode {
|
|
146
|
+
kind: NodeKind;
|
|
147
|
+
path: string;
|
|
148
|
+
title?: string;
|
|
149
|
+
description?: string;
|
|
150
|
+
helpUrl: string;
|
|
151
|
+
children: CrawlNode[];
|
|
152
|
+
endpoint?: EndpointSpec;
|
|
153
|
+
error?: string;
|
|
154
|
+
truncated?: boolean;
|
|
155
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const MCP_PROTOCOL_VERSION = "2025-11-25";
|
|
2
|
+
export declare const CLIENT_NAME = "tool-bridge";
|
|
3
|
+
export declare const CLIENT_VERSION = "draft";
|
|
4
|
+
export declare const MAX_JSON_BYTES = 1000000;
|
|
5
|
+
export declare const MAX_SSE_BYTES = 4000000;
|
|
6
|
+
export declare const REMOTE_FETCH_TIMEOUT_MS = 5000;
|
|
7
|
+
export type JsonObject = Record<string, unknown>;
|
|
8
|
+
export declare function json(data: unknown, init?: ResponseInit): Response;
|
|
9
|
+
export declare function text(data: string, init?: ResponseInit): Response;
|
|
10
|
+
export { errorResponse, NotFoundError } from './errors';
|
|
11
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
12
|
+
export declare function stringField(value: Record<string, unknown>, key: string): string | undefined;
|
|
13
|
+
export declare function recordOfStrings(value: unknown): Record<string, string> | undefined;
|
|
14
|
+
export declare function arrayOfStrings(value: unknown): string[] | undefined;
|
|
15
|
+
export declare function oneLine(value: string): string;
|
|
16
|
+
export declare function messageOf(error: unknown): string;
|
|
17
|
+
export declare function readBoundedText(response: Response, maxBytes: number): Promise<string>;
|
|
18
|
+
export declare function safeErrorText(response: Response): Promise<string>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { McpNode, ToolSpec } from './types';
|
|
2
|
+
export interface Virtualized {
|
|
3
|
+
exposed: ToolSpec[];
|
|
4
|
+
reverse: Map<string, string>;
|
|
5
|
+
}
|
|
6
|
+
export declare function virtualizeTools(node: McpNode, upstream: ToolSpec[]): Virtualized;
|
|
7
|
+
export declare function resolveUpstreamTool(node: McpNode, upstream: ToolSpec[], virtualName: string): string;
|
package/docs/sdk.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Tool Bridge SDK(单包多入口)— API 与 curl 等价表
|
|
2
|
+
|
|
3
|
+
SPEC-001 §8.1 不变量:wire contract 是产品本体,SDK 只是便利层——**不存在只有 SDK
|
|
4
|
+
能做的事**。下表给出每个 SDK 方法对应的公开 API 与 curl 复现方式。conformance
|
|
5
|
+
测试(`src/sdk/host/host.test.ts`、`src/worker/tb/provider-api.test.ts`)对 SDK 与
|
|
6
|
+
原始请求跑同一断言集。
|
|
7
|
+
|
|
8
|
+
约定:`$TB` = bridge base URL;`$ADMIN` = admin key;`$HOST` = 宿主 S2S key
|
|
9
|
+
(`tbk_`);`$TBP` = provider key(`tbp_`)。所有 token 一律走 `Authorization:
|
|
10
|
+
Bearer`,绝不进 query string(§8.2)。
|
|
11
|
+
|
|
12
|
+
## 错误契约(M0)
|
|
13
|
+
|
|
14
|
+
所有错误统一信封:`{"error":{"code":"...","message":"...","details":...}}`。
|
|
15
|
+
|
|
16
|
+
| code | HTTP | retryable |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| `UpstreamError`(上游传输/供给方失败) | 502 | ✅ |
|
|
19
|
+
| `EndpointUnavailable`(保留给 Tunnel/Device) | 503 | ✅ |
|
|
20
|
+
| `Forbidden`(已认证但无权限) | 403 | ❌ |
|
|
21
|
+
| `not_found` / `bad_request` / `unauthorized` / `method_not_allowed` / `internal_error` | 404/400/401/405/500 | ❌ |
|
|
22
|
+
|
|
23
|
+
retryable 语义按 code 判定(`RETRYABLE_CODES`),wire 信封不变;SDK 侧暴露为
|
|
24
|
+
`TBApiError.retryable`。
|
|
25
|
+
|
|
26
|
+
## 发布形态:一个包,多个入口
|
|
27
|
+
|
|
28
|
+
当前 SDK 作为同一个 npm 包发布/接入,不拆成 `tb-host`、`tb-admin`、
|
|
29
|
+
`tb-tunnel-agent` 三个包。npm 包名为 `@evilstar9527/tool-bridge`,不同角色通过 subpath exports 选择入口:
|
|
30
|
+
|
|
31
|
+
| 入口 | 用途 |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| `@evilstar9527/tool-bridge/host` | Host SDK:宿主嵌入、builtin 注入、mounts.sync、树调用 |
|
|
34
|
+
| `@evilstar9527/tool-bridge/admin` | Admin SDK:provider、host、endpoint、audit 等管理面 |
|
|
35
|
+
| `@evilstar9527/tool-bridge/tunnel-agent` | Tunnel Agent Kit skeleton |
|
|
36
|
+
| `@evilstar9527/tool-bridge/transport` | `https` / `serviceBinding` transport |
|
|
37
|
+
| `@evilstar9527/tool-bridge/worker` | Worker bridge embedding surface |
|
|
38
|
+
|
|
39
|
+
这样保持一个版本号和一个安装包,同时避免 API 混在同一个顶层 namespace 里。
|
|
40
|
+
|
|
41
|
+
## Host SDK(M1)
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { createToolBridgeHost, https, serviceBinding } from '@evilstar9527/tool-bridge/host';
|
|
45
|
+
|
|
46
|
+
const tb = createToolBridgeHost({
|
|
47
|
+
transport: serviceBinding(env.TOOLBRIDGE), // 或 https('https://bridge.example.com')
|
|
48
|
+
credential: env.TB_HOST_KEY, // 浅档 S2S key(principal=host)
|
|
49
|
+
hostId: 'watt',
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
| SDK 调用 | 等价请求 |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| `tb.tree.help('watt/websearch')` | `curl -H "Authorization: Bearer $HOST" -H "Accept: application/json" $TB/htbp/watt/websearch/~help` |
|
|
56
|
+
| `tb.tree.help(path, {accept:'text'})` | 同上,`Accept: text/plain` |
|
|
57
|
+
| `tb.tree.call('watt/websearch/search', {arguments:{q:'x'}}, {as:'user-42'})` | `curl -X POST -H "Authorization: Bearer $HOST" -H "X-TB-On-Behalf-Of: user-42" -H "Content-Type: application/json" -d '{"arguments":{"q":"x"}}' $TB/htbp/watt/websearch/search` |
|
|
58
|
+
| `tb.mounts.sync(mounts)` | `curl -X POST -H "Authorization: Bearer $HOST" -H "Content-Type: application/json" -d '{"mounts":[{"path":"watt/websearch","binding":{"type":"builtin","tools":[{"name":"search","handler":"websearch"}]}}]}' "$TB/api/hosts/watt/mounts:sync"` |
|
|
59
|
+
| `tb.builtins.register(name, fn)` | 无运行时 API——builtin 注入是**部署期代码行为**:`export default createBridge({ builtinHandlers: tb.builtins.registry() })` 随宿主 Worker 部署 |
|
|
60
|
+
| `tb.adapters.wattError()` | 纯本地映射:401→`unauthenticated`、403→`permission_denied`、404→`not_found`、409→`confirmation_required`、502/503→`unavailable(retryable)` |
|
|
61
|
+
| `tb.adapters.effectMap({external:'destructive'})` | 纯本地映射:平台 `effect` 四值 → 宿主方言(未申报默认 `external`,保守映射) |
|
|
62
|
+
|
|
63
|
+
`as` / `traceId` / `reason` 分别对应 `X-TB-On-Behalf-Of` / `X-TB-Trace-Id` /
|
|
64
|
+
`X-TB-Reason` 头:**审计标注,不是凭据**,不参与授权。
|
|
65
|
+
|
|
66
|
+
### 宿主注册(admin)
|
|
67
|
+
|
|
68
|
+
| 操作 | 请求 |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| 注册宿主 | `curl -X POST -H "Authorization: Bearer $ADMIN" -d '{"id":"watt","confirmDelegated":true}' $TB/api/hosts` (同时创建 first-party provider `watt` 与空租户树 `tenant:watt`) |
|
|
71
|
+
| 签发 S2S key | `curl -X POST -H "Authorization: Bearer $ADMIN" -d '{"label":"watt-gateway"}' $TB/api/hosts/watt/keys` (raw key 只返回一次) |
|
|
72
|
+
| 查看宿主 | `curl -H "Authorization: Bearer $ADMIN" $TB/api/hosts/watt` |
|
|
73
|
+
|
|
74
|
+
## Admin SDK(M1/M2/M3/M4 管理面)
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { createToolBridgeAdmin } from '@evilstar9527/tool-bridge/admin';
|
|
78
|
+
import { https } from '@evilstar9527/tool-bridge/transport';
|
|
79
|
+
|
|
80
|
+
const admin = createToolBridgeAdmin({ transport: https(baseUrl), credential: adminKey });
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
| SDK 调用 | 等价请求 |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| `admin.providers.list()` | `curl -H "Authorization: Bearer $ADMIN" $TB/api/providers` |
|
|
86
|
+
| `admin.providers.create({id:'acme'})` | `curl -X POST -H "Authorization: Bearer $ADMIN" -d '{"id":"acme"}' $TB/api/providers` |
|
|
87
|
+
| `admin.providers.get/update/delete('acme')` | `GET/PUT/DELETE $TB/api/providers/acme` |
|
|
88
|
+
| `admin.providers.createKey('acme')` | `curl -X POST -H "Authorization: Bearer $ADMIN" -d '{}' $TB/api/providers/acme/keys` → `tbp_` key,只返回一次 |
|
|
89
|
+
| `admin.publications.create('acme', pub)` | `curl -X POST -H "Authorization: Bearer $TBP" -d '{"pubId":"search","binding":{...}}' $TB/api/providers/acme/pubs` (tbp_ key 只能写自己名下) |
|
|
90
|
+
| `admin.publications.publish('acme','search')` | `curl -X POST -d '{}' $TB/api/providers/acme/pubs/search/publish` |
|
|
91
|
+
| `admin.placements.list('a')` | `curl -H "Authorization: Bearer $ADMIN" "$TB/api/placements?tenant=a"` |
|
|
92
|
+
| `admin.placements.dryRun(p)` | `curl -X POST -d '{...,"dryRun":true}' $TB/api/placements` → 受影响 grant/path 预检报告,不落库 |
|
|
93
|
+
| `admin.placements.put(p)` | `curl -X POST -d '{"tenantId":"a","path":"tools/search","pubRef":{"providerId":"acme","pubId":"search"}}' $TB/api/placements` |
|
|
94
|
+
| `admin.placements.delete(id,'a',{dryRun})` | `curl -X DELETE "$TB/api/placements/{id}?tenant=a[&dryRun=true]"` |
|
|
95
|
+
| `admin.hosts.create({id:'watt'})` | `curl -X POST -H "Authorization: Bearer $ADMIN" -d '{"id":"watt"}' $TB/api/hosts` |
|
|
96
|
+
| `admin.hosts.get/createKey('watt')` | `GET /api/hosts/watt` / `POST /api/hosts/watt/keys` |
|
|
97
|
+
| `admin.endpoints.list/create/get/update/revoke()` | `GET/POST /api/endpoints`、`GET/PUT/DELETE /api/endpoints/{id}` |
|
|
98
|
+
| `admin.commandPolicies.list/create/get/update/delete()` | `GET/POST /api/command-policies`、`GET/PUT/DELETE /api/command-policies/{id}` |
|
|
99
|
+
| `admin.audit.events({tenant:'a',limit:50})` | `curl -H "Authorization: Bearer $ADMIN" "$TB/api/audit/events?tenant=a&limit=50"` |
|
|
100
|
+
| `admin.servers.list/create/delete()` | `GET/POST /api/servers`、`DELETE /api/servers/{id}`(legacy MCP server 兼容层,写入 provider/publication/placement) |
|
|
101
|
+
| `admin.servers.tools/help/skill/call(id, tool)` | `GET /api/servers/{id}/tools`、`GET /api/servers/{id}/~help`、`GET /api/servers/{id}/~skill`、`POST /api/servers/{id}/tools/{tool}` |
|
|
102
|
+
| `admin.bridge.tools/call({endpoint}, tool)` | `POST /api/bridge/tools`、`POST /api/bridge/call`(ad-hoc MCP server) |
|
|
103
|
+
| `admin.tree.get/crawl/help/call()` | `GET /api/tree`、`POST /api/crawl`、`GET /htbp/<path>/~help`、`POST /htbp/<path>` |
|
|
104
|
+
|
|
105
|
+
## Tunnel / Device(M2)
|
|
106
|
+
|
|
107
|
+
M2 使用占位接口边界:`/tunnel/*` 只处理已注册 endpoint 的最小 session 形状;
|
|
108
|
+
不实现凭据加密、token 轮换、deep verifier、OAuth 或 S2S key 生命周期。
|
|
109
|
+
|
|
110
|
+
| 操作 | 请求 |
|
|
111
|
+
| --- | --- |
|
|
112
|
+
| 注册 endpoint | `curl -X POST -H "Authorization: Bearer $ADMIN" -d '{"id":"sbx_1","tenantId":"a","kind":"sandbox","capabilities":["exec.run","fs.read","logs.tail"]}' $TB/api/endpoints` |
|
|
113
|
+
| 注册命令策略 | `curl -X POST -H "Authorization: Bearer $ADMIN" -d '{"id":"safe","defaultMode":"deny","allowCommands":["npm","pnpm"],"maxTimeoutMs":30000}' $TB/api/command-policies` |
|
|
114
|
+
| endpoint 建连 | `curl -X POST -d '{"endpointId":"sbx_1"}' $TB/tunnel/connect` → `{sessionId}` |
|
|
115
|
+
| 能力上报 | `curl -X POST -d '{"endpointId":"sbx_1","sessionId":"...","capabilities":["exec.run"]}' $TB/tunnel/capabilities`(只能收窄预注册能力) |
|
|
116
|
+
| device help | `curl -H "Authorization: Bearer $TBK" $TB/htbp/~device/sbx_1/~help` |
|
|
117
|
+
| argv 执行 | `curl -X POST -H "Authorization: Bearer $TBK" -d '{"argv":["npm","test"],"timeoutMs":30000}' $TB/htbp/~device/sbx_1/exec.run` |
|
|
118
|
+
| 文件读取 | `curl -X POST -H "Authorization: Bearer $TBK" -d '{"path":"/workspace/README.md"}' $TB/htbp/~device/sbx_1/fs.read` |
|
|
119
|
+
|
|
120
|
+
`exec.run` 只接受结构化 `argv`,`shell.run` 默认不暴露;全局策略会在 broker
|
|
121
|
+
dispatch 前拒绝危险命令模式。离线 endpoint 返回 `EndpointUnavailable → 503`。
|
|
122
|
+
|
|
123
|
+
Tunnel Agent Kit skeleton:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { createTunnelAgent } from '@evilstar9527/tool-bridge/tunnel-agent';
|
|
127
|
+
|
|
128
|
+
const agent = createTunnelAgent({
|
|
129
|
+
transport,
|
|
130
|
+
endpointId: 'sbx_1',
|
|
131
|
+
dispatch: async (request) => runLocalCapability(request),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const { sessionId } = await agent.connect();
|
|
135
|
+
await agent.heartbeat();
|
|
136
|
+
await agent.reportCapabilities(['exec.run']);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## 审计(M4)
|
|
140
|
+
|
|
141
|
+
| 操作 | 请求 |
|
|
142
|
+
| --- | --- |
|
|
143
|
+
| 最近事件 | `curl -H "Authorization: Bearer $ADMIN" "$TB/api/audit/events?limit=50"`(租户 key 只能看本租户事件) |
|
|
144
|
+
|
|
145
|
+
每次 `describe`/`call`(含 401/403/404 拒绝路径)产生一条结构化事件:
|
|
146
|
+
`actor / tenant / path / tool / provider / effect / scope / decision / result /
|
|
147
|
+
status / errorCode / traceId / latencyMs`,并预留 SPEC-005 计量字段(`usage`)。
|
|
148
|
+
脱敏红线:绝不落 key、凭据、token、原始入参/出参(只记录字节数与顶层键名)。
|
|
149
|
+
响应携带 `X-TB-Trace-Id`(可用同名请求头传入既有 traceId)。
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@evilstar9527/tool-bridge",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Tool Bridge SDK and Cloudflare Worker bridge for HTBP/MCP tool routing.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/sdk/index.js",
|
|
8
|
+
"types": "./dist/types/sdk/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/sdk",
|
|
11
|
+
"dist/types",
|
|
12
|
+
"README.md",
|
|
13
|
+
"docs/sdk.md"
|
|
14
|
+
],
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/types/sdk/index.d.ts",
|
|
18
|
+
"import": "./dist/sdk/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./admin": {
|
|
21
|
+
"types": "./dist/types/sdk/admin/index.d.ts",
|
|
22
|
+
"import": "./dist/sdk/admin.js"
|
|
23
|
+
},
|
|
24
|
+
"./host": {
|
|
25
|
+
"types": "./dist/types/sdk/host/index.d.ts",
|
|
26
|
+
"import": "./dist/sdk/host.js"
|
|
27
|
+
},
|
|
28
|
+
"./tunnel-agent": {
|
|
29
|
+
"types": "./dist/types/sdk/tunnel-agent/index.d.ts",
|
|
30
|
+
"import": "./dist/sdk/tunnel-agent.js"
|
|
31
|
+
},
|
|
32
|
+
"./transport": {
|
|
33
|
+
"types": "./dist/types/sdk/transport.d.ts",
|
|
34
|
+
"import": "./dist/sdk/transport.js"
|
|
35
|
+
},
|
|
36
|
+
"./worker": {
|
|
37
|
+
"types": "./dist/types/worker/index.d.ts",
|
|
38
|
+
"import": "./dist/sdk/worker.js"
|
|
39
|
+
},
|
|
40
|
+
"./tb": {
|
|
41
|
+
"types": "./dist/types/worker/tb/types.d.ts",
|
|
42
|
+
"import": "./dist/sdk/tb.js"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"dev": "npm run build && wrangler dev --ip 127.0.0.1 --port 8787",
|
|
50
|
+
"dev:ui": "vite --host 127.0.0.1",
|
|
51
|
+
"build": "vite build",
|
|
52
|
+
"build:sdk": "rm -rf dist/sdk dist/types && vite build --config vite.sdk.config.ts && tsc -p tsconfig.sdk.json",
|
|
53
|
+
"typecheck": "tsc --noEmit",
|
|
54
|
+
"worker:dev": "wrangler dev --ip 127.0.0.1 --port 8787",
|
|
55
|
+
"deploy": "npm run build && wrangler deploy",
|
|
56
|
+
"check": "npm run typecheck && npm run build && npm run build:sdk",
|
|
57
|
+
"prepack": "npm run build:sdk",
|
|
58
|
+
"test": "vitest run"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@tanstack/react-query": "^5.90.12",
|
|
62
|
+
"@tanstack/react-router": "^1.140.0",
|
|
63
|
+
"jose": "^6.2.0",
|
|
64
|
+
"lucide-react": "^0.562.0",
|
|
65
|
+
"react": "^19.2.3",
|
|
66
|
+
"react-dom": "^19.2.3"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@cloudflare/workers-types": "^4.20260625.1",
|
|
70
|
+
"@types/node": "^25.0.2",
|
|
71
|
+
"@types/react": "^19.2.7",
|
|
72
|
+
"@types/react-dom": "^19.2.3",
|
|
73
|
+
"@vitejs/plugin-react": "^5.1.2",
|
|
74
|
+
"typescript": "^6.0.3",
|
|
75
|
+
"vite": "^8.0.16",
|
|
76
|
+
"vitest": "^3.2.6",
|
|
77
|
+
"wrangler": "^4.56.0"
|
|
78
|
+
}
|
|
79
|
+
}
|