@huaweidevtools/aishell-dsh-e2b 0.0.0-stage → 0.0.1-beta.4
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/LICENSE +201 -0
- package/README.md +200 -2
- package/cordis.patch.yml +19 -0
- package/lib/index.js +2545 -0
- package/lib/types/client.d.ts +137 -0
- package/lib/types/credentials.d.ts +47 -0
- package/lib/types/errors.d.ts +47 -0
- package/lib/types/index.d.ts +91 -0
- package/lib/types/prompt.d.ts +62 -0
- package/lib/types/sandbox-key.d.ts +178 -0
- package/lib/types/sandbox-registry.d.ts +114 -0
- package/lib/types/tools.d.ts +46 -0
- package/package.json +53 -3
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sandbox a conversation works in.
|
|
3
|
+
*
|
|
4
|
+
* One conversation, one sandbox. That is the whole model: there is no
|
|
5
|
+
* "which sandbox did you mean" question for the tools to ask, because there is
|
|
6
|
+
* only ever one answer, and no `sandboxId` parameter for the model to get
|
|
7
|
+
* wrong. The tools act on *the* sandbox, and creating one is the explicit act
|
|
8
|
+
* that binds it to the conversation.
|
|
9
|
+
*
|
|
10
|
+
* ## Why the binding is created explicitly, never lazily
|
|
11
|
+
*
|
|
12
|
+
* A tool that quietly created a sandbox on first use would have to choose the
|
|
13
|
+
* template and the lifetime itself, and those are exactly the two facts the
|
|
14
|
+
* operator should be deciding — a preview page wants a different lifetime from
|
|
15
|
+
* a debugging session, and that choice is the reason the create tool takes
|
|
16
|
+
* parameters at all. A lazy create would move that decision into the plugin
|
|
17
|
+
* and out of the conversation, where nobody can see it or correct it.
|
|
18
|
+
*
|
|
19
|
+
* So a tool with no sandbox bound fails, and names `aishell__sandbox_create` as the way
|
|
20
|
+
* forward. The failure is cheap and legible; a silently invented sandbox is
|
|
21
|
+
* neither.
|
|
22
|
+
*
|
|
23
|
+
* ## Why the binding survives a restart
|
|
24
|
+
*
|
|
25
|
+
* The mapping cannot live in memory alone. A plugin restart — a reload, a
|
|
26
|
+
* crash, a profile edit — would otherwise orphan a running sandbox: the model
|
|
27
|
+
* would find no binding, create a second sandbox, and the first would keep
|
|
28
|
+
* billing until its lifetime expired, with nothing in the conversation
|
|
29
|
+
* accounting for it.
|
|
30
|
+
*
|
|
31
|
+
* So the sandbox carries its conversation in its own `metadata` at creation,
|
|
32
|
+
* and the registry rebuilds the binding by querying for it. The gateway
|
|
33
|
+
* forwards `metadata` filters to the upstream unchanged, and
|
|
34
|
+
* `Sandbox.list({ query: { metadata } })` is ordinary SDK surface, so the
|
|
35
|
+
* recovery needs nothing from either service.
|
|
36
|
+
*
|
|
37
|
+
* A recovered binding is refused if the sandbox is not running. A paused
|
|
38
|
+
* sandbox is not adopted: connecting to it would resume it and silently
|
|
39
|
+
* restart its clock, which is a billing decision nobody asked for.
|
|
40
|
+
*
|
|
41
|
+
* @module @huaweidevtools/aishell-dsh-e2b/sandbox-registry
|
|
42
|
+
*/
|
|
43
|
+
/** The metadata key a sandbox carries its owning conversation under. */
|
|
44
|
+
export declare const SESSION_METADATA_KEY = "aishell-session";
|
|
45
|
+
/** One sandbox the registry knows about. */
|
|
46
|
+
export interface BoundSandbox {
|
|
47
|
+
/** The sandbox id. */
|
|
48
|
+
readonly sandboxId: string;
|
|
49
|
+
/** Whether the gateway reports it as running. */
|
|
50
|
+
readonly state: string;
|
|
51
|
+
}
|
|
52
|
+
/** Lists the sandboxes carrying one metadata pair. */
|
|
53
|
+
export type SandboxFinder = (metadata: Record<string, string>, signal: AbortSignal | undefined) => Promise<ReadonlyArray<BoundSandbox>>;
|
|
54
|
+
/** In-memory bindings plus the lookup used to rebuild them. */
|
|
55
|
+
export declare class SandboxRegistry {
|
|
56
|
+
/** Session id → the sandbox bound to it. */
|
|
57
|
+
private readonly bySession;
|
|
58
|
+
/** The metadata lookup, injected so tests need no gateway. */
|
|
59
|
+
private readonly find;
|
|
60
|
+
constructor(find: SandboxFinder);
|
|
61
|
+
/**
|
|
62
|
+
* Bind a sandbox to its conversation.
|
|
63
|
+
*
|
|
64
|
+
* @param sessionId - the owning conversation, or `undefined` for a call that
|
|
65
|
+
* ran outside one. Such a call is recorded nowhere: without a conversation
|
|
66
|
+
* to key on, there would be nothing to bind later calls to.
|
|
67
|
+
* @param sandboxId - the sandbox to bind.
|
|
68
|
+
*/
|
|
69
|
+
bind(sessionId: string | undefined, sandboxId: string): void;
|
|
70
|
+
/**
|
|
71
|
+
* The sandbox this conversation is bound to, recovering the binding if this
|
|
72
|
+
* process has not seen one.
|
|
73
|
+
*
|
|
74
|
+
* @param sessionId - the calling conversation.
|
|
75
|
+
* @param signal - caller cancellation, forwarded to the lookup.
|
|
76
|
+
* @returns the sandbox id, or `undefined` when the conversation has none.
|
|
77
|
+
*/
|
|
78
|
+
resolve(sessionId: string | undefined, signal?: AbortSignal): Promise<string | undefined>;
|
|
79
|
+
/**
|
|
80
|
+
* The sandbox bound right now, without a lookup.
|
|
81
|
+
*
|
|
82
|
+
* Used where a call has already resolved the binding and needs it again
|
|
83
|
+
* without a second round trip.
|
|
84
|
+
*
|
|
85
|
+
* @param sessionId - the calling conversation.
|
|
86
|
+
* @returns the bound sandbox id, or `undefined`.
|
|
87
|
+
*/
|
|
88
|
+
bound(sessionId: string | undefined): string | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* Drop a binding, because the sandbox it names no longer exists.
|
|
91
|
+
*
|
|
92
|
+
* @param sandboxId - the sandbox that is gone.
|
|
93
|
+
*/
|
|
94
|
+
forget(sandboxId: string): void;
|
|
95
|
+
}
|
|
96
|
+
/** The metadata a new sandbox carries so its conversation can be recovered. */
|
|
97
|
+
export declare function sessionMetadata(sessionId: string | undefined, extra: Record<string, string> | undefined): Record<string, string> | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* The message a tool gets when the conversation has no sandbox.
|
|
100
|
+
*
|
|
101
|
+
* Names the remedy, and deliberately does not offer to create one: the model
|
|
102
|
+
* chooses the template and the lifetime, and it cannot do that from inside a
|
|
103
|
+
* tool that was asked to write a file.
|
|
104
|
+
*/
|
|
105
|
+
export declare const NO_SANDBOX_MESSAGE: string;
|
|
106
|
+
/**
|
|
107
|
+
* The message a tool gets when a sandbox existed but is now gone.
|
|
108
|
+
*
|
|
109
|
+
* Distinct from {@link NO_SANDBOX_MESSAGE} because the remedy differs: the
|
|
110
|
+
* binding is stale rather than absent, and the model should know the previous
|
|
111
|
+
* sandbox's contents are not recoverable — creating a new one starts empty.
|
|
112
|
+
*/
|
|
113
|
+
export declare const SANDBOX_GONE_MESSAGE: string;
|
|
114
|
+
//# sourceMappingURL=sandbox-registry.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model-facing tools.
|
|
3
|
+
*
|
|
4
|
+
* Each one is a thin projection of an E2B SDK call: the tool's parameters are
|
|
5
|
+
* the SDK's own options, and its result is the SDK's own return value in a
|
|
6
|
+
* shape that survives JSON. That correspondence is the point — an operator who
|
|
7
|
+
* reads the E2B documentation should find nothing surprising here, and the
|
|
8
|
+
* next plugin in this family can keep the same contract.
|
|
9
|
+
*
|
|
10
|
+
* Three conventions are load-bearing and are repeated in the descriptions the
|
|
11
|
+
* model reads:
|
|
12
|
+
*
|
|
13
|
+
* - **One conversation, one sandbox.** No tool but `aishell__sandbox_create` takes a
|
|
14
|
+
* `sandboxId`. There is nothing to choose between, so there is no parameter
|
|
15
|
+
* to get wrong, and no way for a call to land in a different sandbox than
|
|
16
|
+
* the model meant.
|
|
17
|
+
* - **`timeoutMs` is milliseconds.** The TypeScript SDK counts milliseconds
|
|
18
|
+
* where the Python one counts seconds; a model that learned the Python
|
|
19
|
+
* spelling would ask for a five-second sandbox and get five thousand.
|
|
20
|
+
* - **A non-zero exit is a result, not a failure.** `aishell__sandbox_commands_run`
|
|
21
|
+
* reports the code and both streams, so a failing command reads as an
|
|
22
|
+
* outcome to reason about rather than a broken tool.
|
|
23
|
+
*
|
|
24
|
+
* @module @huaweidevtools/aishell-dsh-e2b/tools
|
|
25
|
+
*/
|
|
26
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
27
|
+
import type { SandboxClient } from './client.ts';
|
|
28
|
+
import { type SandboxRegistry } from './sandbox-registry.ts';
|
|
29
|
+
/** What every tool needs to act. */
|
|
30
|
+
export interface ToolDeps {
|
|
31
|
+
/** The sandbox gateway connection. */
|
|
32
|
+
readonly client: SandboxClient;
|
|
33
|
+
/** The sandbox this conversation is bound to. */
|
|
34
|
+
readonly registry: SandboxRegistry;
|
|
35
|
+
/** The filesystem the local workspace is reached through, when one is mounted. */
|
|
36
|
+
readonly resolveFs: () => Context['fs'] | undefined;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Register every sandbox tool.
|
|
40
|
+
*
|
|
41
|
+
* @param ctx - the plugin context, providing the tool registry.
|
|
42
|
+
* @param deps - the shared dependencies.
|
|
43
|
+
* @returns disposers for the registrations.
|
|
44
|
+
*/
|
|
45
|
+
export declare function registerSandboxTools(ctx: Context, deps: ToolDeps): Array<() => void>;
|
|
46
|
+
//# sourceMappingURL=tools.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,56 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huaweidevtools/aishell-dsh-e2b",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"description": "Runs throwaway and preview deployments in E2B sandboxes on DSH — sandbox lifecycle, commands, and files as tools",
|
|
4
|
+
"version": "0.0.1-beta.4",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/yusheng-g/aishell-dsh-plugins.git",
|
|
11
|
+
"directory": "packages/aishell-e2b"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"lib/index.js",
|
|
24
|
+
"lib/types/**/*.d.ts",
|
|
25
|
+
"cordis.patch.yml"
|
|
26
|
+
],
|
|
27
|
+
"license": "Apache-2.0",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"e2b": "2.59.0",
|
|
30
|
+
"@huaweidevtools/aishell-dsh-credentials-store": "0.0.1-beta.4"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
34
|
+
"@deepseek-ai/dsh-fs": "0.2.0-rc.1 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1",
|
|
35
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.0-rc.1 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1",
|
|
36
|
+
"@deepseek-ai/dsh-tools": "0.2.0-rc.1 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
40
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.5",
|
|
41
|
+
"@deepseek-ai/dsh-fs": "0.2.0-rc.1 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1",
|
|
42
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.0-rc.1 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1",
|
|
43
|
+
"@deepseek-ai/dsh-tools": "0.2.0-rc.1 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1",
|
|
44
|
+
"@deepseek-ai/schemastery": "3.18.4",
|
|
45
|
+
"tsdown": "^0.10.0",
|
|
46
|
+
"typescript": "^5.7.0"
|
|
47
|
+
},
|
|
48
|
+
"dsh": {
|
|
49
|
+
"bundle": {
|
|
50
|
+
"patch": "./cordis.patch.yml"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build:bundle": "tsdown"
|
|
55
|
+
}
|
|
6
56
|
}
|