@huaweidevtools/aishell-dsh-e2b 0.0.15 → 0.0.17
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/lib/index.js +94 -37
- package/lib/types/client.d.ts +25 -0
- package/lib/types/credentials.d.ts +10 -12
- package/lib/types/prompt.d.ts +6 -7
- package/lib/types/sandbox-key.d.ts +38 -9
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -901,10 +901,9 @@ defineMethod("transform", [
|
|
|
901
901
|
/**
|
|
902
902
|
* How many pages of a list response to follow before giving up.
|
|
903
903
|
*
|
|
904
|
-
*
|
|
905
|
-
*
|
|
906
|
-
*
|
|
907
|
-
* used against should reach it.
|
|
904
|
+
* Bounds one listing at 1000 sandboxes. A filter that matches nothing would
|
|
905
|
+
* otherwise walk an entire account one page at a time; no deployment this
|
|
906
|
+
* plugin is used against should reach the cap.
|
|
908
907
|
*/
|
|
909
908
|
const MAX_LIST_PAGES = 10;
|
|
910
909
|
/**
|
|
@@ -964,6 +963,41 @@ var SandboxClient = class {
|
|
|
964
963
|
return sandbox;
|
|
965
964
|
}
|
|
966
965
|
/**
|
|
966
|
+
* Walk the sandbox listing, following the pagination cursor.
|
|
967
|
+
*
|
|
968
|
+
* ## Why it follows the cursor
|
|
969
|
+
*
|
|
970
|
+
* The gateway pages at 100 and reports more with a cursor. A single page is
|
|
971
|
+
* enough for the common case and wrong for a busy account — and wrong in the
|
|
972
|
+
* direction that matters: the caller is told "these are all of them" when
|
|
973
|
+
* they are the first hundred, and a metadata search for one specific sandbox
|
|
974
|
+
* reports "none" because that sandbox happens to be on a later page.
|
|
975
|
+
*
|
|
976
|
+
* ## Why the bound and the sequential awaits
|
|
977
|
+
*
|
|
978
|
+
* `MAX_LIST_PAGES` caps a filter that matches nothing, which would otherwise
|
|
979
|
+
* walk the whole account one page at a time. The awaits are sequential by
|
|
980
|
+
* necessity: each page's cursor comes from the previous response, so the
|
|
981
|
+
* pages cannot be requested together.
|
|
982
|
+
*
|
|
983
|
+
* Shared by both listing calls so the two cannot drift in either respect.
|
|
984
|
+
*
|
|
985
|
+
* @param apiKey - the key to present.
|
|
986
|
+
* @param query - the SDK query, when one narrows the listing.
|
|
987
|
+
* @returns every row the walk collected.
|
|
988
|
+
*/
|
|
989
|
+
async collectPages(apiKey, query) {
|
|
990
|
+
const paginator = Sandbox.list({
|
|
991
|
+
apiKey,
|
|
992
|
+
domain: this.options.resolveDomain(),
|
|
993
|
+
requestTimeoutMs: this.options.resolveRequestTimeoutMs(),
|
|
994
|
+
...query
|
|
995
|
+
});
|
|
996
|
+
const collected = [];
|
|
997
|
+
for (let page = 0; page < MAX_LIST_PAGES && paginator.hasNext; page += 1) collected.push(...await paginator.nextItems());
|
|
998
|
+
return collected;
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
967
1001
|
* Find the sandboxes carrying one metadata pair.
|
|
968
1002
|
*
|
|
969
1003
|
* The SDK encodes the pair into the `metadata` query parameter and the
|
|
@@ -983,19 +1017,7 @@ var SandboxClient = class {
|
|
|
983
1017
|
* @returns one row per matching sandbox.
|
|
984
1018
|
*/
|
|
985
1019
|
async findByMetadata(metadata, signal) {
|
|
986
|
-
const
|
|
987
|
-
const requestTimeoutMs = this.options.resolveRequestTimeoutMs();
|
|
988
|
-
const items = await this.runWithKeyRefresh(signal, async (apiKey) => {
|
|
989
|
-
const paginator = Sandbox.list({
|
|
990
|
-
apiKey,
|
|
991
|
-
domain,
|
|
992
|
-
requestTimeoutMs,
|
|
993
|
-
query: { metadata }
|
|
994
|
-
});
|
|
995
|
-
const found = [];
|
|
996
|
-
for (let page = 0; page < MAX_LIST_PAGES && paginator.hasNext; page += 1) found.push(...await paginator.nextItems());
|
|
997
|
-
return found;
|
|
998
|
-
});
|
|
1020
|
+
const items = await this.runWithKeyRefresh(signal, (apiKey) => this.collectPages(apiKey, { query: { metadata } }));
|
|
999
1021
|
return items.map((item) => ({
|
|
1000
1022
|
sandboxId: item.sandboxId,
|
|
1001
1023
|
state: item.state
|
|
@@ -1012,18 +1034,7 @@ var SandboxClient = class {
|
|
|
1012
1034
|
* @returns one row per sandbox, in the shape the tool renders.
|
|
1013
1035
|
*/
|
|
1014
1036
|
async list(signal) {
|
|
1015
|
-
const
|
|
1016
|
-
const requestTimeoutMs = this.options.resolveRequestTimeoutMs();
|
|
1017
|
-
const items = await this.runWithKeyRefresh(signal, async (apiKey) => {
|
|
1018
|
-
const paginator = Sandbox.list({
|
|
1019
|
-
apiKey,
|
|
1020
|
-
domain,
|
|
1021
|
-
requestTimeoutMs
|
|
1022
|
-
});
|
|
1023
|
-
const all = [];
|
|
1024
|
-
for (let page = 0; page < MAX_LIST_PAGES && paginator.hasNext; page += 1) all.push(...await paginator.nextItems());
|
|
1025
|
-
return all;
|
|
1026
|
-
});
|
|
1037
|
+
const items = await this.runWithKeyRefresh(signal, (apiKey) => this.collectPages(apiKey, {}));
|
|
1027
1038
|
return items.map((item) => ({
|
|
1028
1039
|
sandboxId: item.sandboxId,
|
|
1029
1040
|
templateId: item.templateId,
|
|
@@ -1196,6 +1207,7 @@ function toStoredSection(key) {
|
|
|
1196
1207
|
/** One key, read from the store and replaced when it expires or is refused. */
|
|
1197
1208
|
var SandboxKeySource = class {
|
|
1198
1209
|
constructor(options) {
|
|
1210
|
+
this.generation = 0;
|
|
1199
1211
|
this.options = options;
|
|
1200
1212
|
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
1201
1213
|
}
|
|
@@ -1233,13 +1245,33 @@ var SandboxKeySource = class {
|
|
|
1233
1245
|
* Forget the key, so the next {@link get} creates a new one.
|
|
1234
1246
|
*
|
|
1235
1247
|
* Called when the gateway refuses the key as unauthenticated — the one
|
|
1236
|
-
* signal that it has stopped working earlier than its expiry said
|
|
1237
|
-
*
|
|
1238
|
-
*
|
|
1248
|
+
* signal that it has stopped working earlier than its expiry said — and when
|
|
1249
|
+
* the stored credentials change.
|
|
1250
|
+
*
|
|
1251
|
+
* The replacement is a different key, and therefore a different tenant:
|
|
1252
|
+
* sandboxes created under the old one stop being reachable through this
|
|
1253
|
+
* plugin.
|
|
1254
|
+
*
|
|
1255
|
+
* ## Why the generation is bumped and `pending` is not cleared
|
|
1256
|
+
*
|
|
1257
|
+
* Clearing `pending` would let a concurrent caller start a second create
|
|
1258
|
+
* immediately, which is two keys and two tenants — the thing the shared
|
|
1259
|
+
* promise exists to prevent. Bumping the generation instead leaves the
|
|
1260
|
+
* in-flight create shared, and makes it fail when it lands: its key was
|
|
1261
|
+
* minted under credentials that no longer exist, so adopting it would put a
|
|
1262
|
+
* credential granted under a cleared account back into the store.
|
|
1263
|
+
*
|
|
1264
|
+
* The clear is best-effort. A store that refuses the delete still has the key
|
|
1265
|
+
* dropped from memory, and the fingerprint check rejects it on the next read
|
|
1266
|
+
* anyway — throwing here would turn a refresh into a failure the caller
|
|
1267
|
+
* cannot act on.
|
|
1239
1268
|
*/
|
|
1240
1269
|
invalidate() {
|
|
1241
1270
|
this.current = void 0;
|
|
1242
|
-
|
|
1271
|
+
this.generation += 1;
|
|
1272
|
+
try {
|
|
1273
|
+
clearE2BKey(this.options.store);
|
|
1274
|
+
} catch {}
|
|
1243
1275
|
}
|
|
1244
1276
|
/**
|
|
1245
1277
|
* Create a key, store it, and return it.
|
|
@@ -1247,9 +1279,12 @@ var SandboxKeySource = class {
|
|
|
1247
1279
|
* @param credentials - the pair to sign with; already read by {@link get},
|
|
1248
1280
|
* so the fingerprint recorded on the key is the one it was minted under.
|
|
1249
1281
|
* @param signal - caller cancellation, forwarded to the fetch.
|
|
1282
|
+
* @throws {SandboxKeyError} when the credentials changed while the request
|
|
1283
|
+
* was in flight, so the key that came back belongs to none of them.
|
|
1250
1284
|
*/
|
|
1251
1285
|
async createKey(credentials, signal) {
|
|
1252
1286
|
if (credentials === void 0) throw new SandboxKeyError("未进行华为云 IAM 认证,请认证后重试。 / Not authenticated with Huawei Cloud IAM. Please authenticate, then retry.");
|
|
1287
|
+
const generation = this.generation;
|
|
1253
1288
|
const url = new URL(`${this.options.baseUrl}${KEYS_PATH}`);
|
|
1254
1289
|
const headers = sign("POST", url.host, url.pathname, {}, "", credentials);
|
|
1255
1290
|
const timeout = AbortSignal.timeout(this.options.timeoutMs);
|
|
@@ -1274,9 +1309,10 @@ var SandboxKeySource = class {
|
|
|
1274
1309
|
...await readCreatedKey(response),
|
|
1275
1310
|
akFingerprint: fingerprint(credentials.ak)
|
|
1276
1311
|
};
|
|
1312
|
+
if (generation !== this.generation) throw new SandboxKeyError("aishell-e2b: the Huawei Cloud credentials changed while a sandbox key was being created, so that key belongs to the previous account and was discarded. Retry with the current credentials.");
|
|
1277
1313
|
try {
|
|
1278
1314
|
writeE2BKey(this.options.store, toStoredSection(created));
|
|
1279
|
-
} catch
|
|
1315
|
+
} catch {}
|
|
1280
1316
|
return created;
|
|
1281
1317
|
}
|
|
1282
1318
|
};
|
|
@@ -1579,16 +1615,37 @@ function describeInfo(info) {
|
|
|
1579
1615
|
...info.metadata === void 0 ? {} : { metadata: info.metadata }
|
|
1580
1616
|
};
|
|
1581
1617
|
}
|
|
1582
|
-
/**
|
|
1618
|
+
/**
|
|
1619
|
+
* How much of a command's output a result carries before it is elided.
|
|
1620
|
+
*
|
|
1621
|
+
* Counted in code points, which is what a reader means by "characters" and what
|
|
1622
|
+
* the context budget is actually spent in. A UTF-16 code unit is a third of the
|
|
1623
|
+
* cost for a three-byte character, so measuring in units would let 30 000 CJK
|
|
1624
|
+
* characters through as 90 KB of text.
|
|
1625
|
+
*/
|
|
1583
1626
|
const OUTPUT_LIMIT = 3e4;
|
|
1584
|
-
/**
|
|
1627
|
+
/**
|
|
1628
|
+
* Keep a stream useful without letting one command flood the context.
|
|
1629
|
+
*
|
|
1630
|
+
* Truncation walks code points rather than slicing at a unit index: a slice at
|
|
1631
|
+
* an odd offset inside a surrogate pair leaves a lone surrogate, which is not
|
|
1632
|
+
* valid text and renders as a replacement character at the cut.
|
|
1633
|
+
*
|
|
1634
|
+
* @param text - the stream as received.
|
|
1635
|
+
* @returns the text, and whether any of it was dropped.
|
|
1636
|
+
*/
|
|
1585
1637
|
function clip(text) {
|
|
1586
1638
|
if (text.length <= OUTPUT_LIMIT) return {
|
|
1587
1639
|
text,
|
|
1588
1640
|
truncated: false
|
|
1589
1641
|
};
|
|
1642
|
+
const points = [...text];
|
|
1643
|
+
if (points.length <= OUTPUT_LIMIT) return {
|
|
1644
|
+
text,
|
|
1645
|
+
truncated: false
|
|
1646
|
+
};
|
|
1590
1647
|
return {
|
|
1591
|
-
text:
|
|
1648
|
+
text: points.slice(0, OUTPUT_LIMIT).join(""),
|
|
1592
1649
|
truncated: true
|
|
1593
1650
|
};
|
|
1594
1651
|
}
|
package/lib/types/client.d.ts
CHANGED
|
@@ -68,6 +68,31 @@ export declare class SandboxClient {
|
|
|
68
68
|
readonly metadata?: Record<string, string>;
|
|
69
69
|
readonly signal?: AbortSignal;
|
|
70
70
|
}): Promise<Sandbox>;
|
|
71
|
+
/**
|
|
72
|
+
* Walk the sandbox listing, following the pagination cursor.
|
|
73
|
+
*
|
|
74
|
+
* ## Why it follows the cursor
|
|
75
|
+
*
|
|
76
|
+
* The gateway pages at 100 and reports more with a cursor. A single page is
|
|
77
|
+
* enough for the common case and wrong for a busy account — and wrong in the
|
|
78
|
+
* direction that matters: the caller is told "these are all of them" when
|
|
79
|
+
* they are the first hundred, and a metadata search for one specific sandbox
|
|
80
|
+
* reports "none" because that sandbox happens to be on a later page.
|
|
81
|
+
*
|
|
82
|
+
* ## Why the bound and the sequential awaits
|
|
83
|
+
*
|
|
84
|
+
* `MAX_LIST_PAGES` caps a filter that matches nothing, which would otherwise
|
|
85
|
+
* walk the whole account one page at a time. The awaits are sequential by
|
|
86
|
+
* necessity: each page's cursor comes from the previous response, so the
|
|
87
|
+
* pages cannot be requested together.
|
|
88
|
+
*
|
|
89
|
+
* Shared by both listing calls so the two cannot drift in either respect.
|
|
90
|
+
*
|
|
91
|
+
* @param apiKey - the key to present.
|
|
92
|
+
* @param query - the SDK query, when one narrows the listing.
|
|
93
|
+
* @returns every row the walk collected.
|
|
94
|
+
*/
|
|
95
|
+
private collectPages;
|
|
71
96
|
/**
|
|
72
97
|
* Find the sandboxes carrying one metadata pair.
|
|
73
98
|
*
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Reading the operator's Huawei Cloud credentials for control-plane requests.
|
|
3
3
|
*
|
|
4
|
-
* The read is delegated to `credentials-store/accounts`,
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* The read is delegated to `credentials-store/accounts`, the one place the
|
|
5
|
+
* account names and the pairing rule live. Every plugin in this set that signs
|
|
6
|
+
* with the same key pair goes through it — this module, the LLM adapter, the
|
|
7
|
+
* credential form's Host route and the ops tools — so they cannot disagree
|
|
8
|
+
* about which keyring entries are the credentials, or about what "half a pair"
|
|
9
|
+
* means.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* would surface as one plugin working and another reporting "not
|
|
16
|
-
* authenticated" on a machine where the operator had stored exactly one pair.
|
|
11
|
+
* Those names are a persistence contract: a second copy of them does not fail a
|
|
12
|
+
* build, it surfaces as one plugin working while another reports "not
|
|
13
|
+
* authenticated" on a machine where the operator has stored exactly one pair.
|
|
14
|
+
* One shared definition is what rules that out.
|
|
17
15
|
*
|
|
18
16
|
* @module @huaweidevtools/aishell-dsh-e2b/credentials
|
|
19
17
|
*/
|
package/lib/types/prompt.d.ts
CHANGED
|
@@ -10,15 +10,14 @@
|
|
|
10
10
|
*
|
|
11
11
|
* ## Why the split is stated three ways
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* Two things make it answerable instead:
|
|
13
|
+
* The deciding property is lifecycle — does the result need to outlive the
|
|
14
|
+
* task? — but that is one the model has to *infer* about work nobody has
|
|
15
|
+
* described in those terms yet. Two more signals make the same question
|
|
16
|
+
* answerable directly:
|
|
19
17
|
*
|
|
20
18
|
* - **What the user asked for.** "A web page or a demo" versus "a production
|
|
21
|
-
* system" is a distinction a person can answer
|
|
19
|
+
* system" is a distinction a person can answer without knowing what a
|
|
20
|
+
* sandbox is.
|
|
22
21
|
* - **What the work needs.** The one capability a sandbox has that this
|
|
23
22
|
* machine does not is a public address. A request to deploy something and
|
|
24
23
|
* get a URL back is the strongest signal there is, and it is stated first
|
|
@@ -33,13 +33,12 @@
|
|
|
33
33
|
*
|
|
34
34
|
* ## Why the key is bound to the AK that minted it
|
|
35
35
|
*
|
|
36
|
-
* A stored key outlives the credentials that created it
|
|
37
|
-
* the AK/SK in the credential form
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* permanent rather than temporary.
|
|
36
|
+
* A stored key outlives the credentials that created it, so clearing or
|
|
37
|
+
* replacing the AK/SK in the credential form has to take the key with it — a
|
|
38
|
+
* key with no expiry otherwise survives the credential for good, and the old
|
|
39
|
+
* tenant and all its sandboxes stay reachable.
|
|
41
40
|
*
|
|
42
|
-
*
|
|
41
|
+
* The key records a fingerprint of the AK that minted it, and {@link get}
|
|
43
42
|
* discards a stored key whose fingerprint no longer matches the stored
|
|
44
43
|
* credentials. Three consequences are deliberate:
|
|
45
44
|
*
|
|
@@ -143,6 +142,17 @@ export declare class SandboxKeySource {
|
|
|
143
142
|
private current;
|
|
144
143
|
/** The in-flight create, so concurrent callers share one request. */
|
|
145
144
|
private pending;
|
|
145
|
+
/**
|
|
146
|
+
* Bumped by {@link invalidate}.
|
|
147
|
+
*
|
|
148
|
+
* A create signs with the credentials it was handed, and the network round
|
|
149
|
+
* trip is long enough to outlast them: a person can clear or replace their
|
|
150
|
+
* AK/SK while a key is being minted. The generation is how the finished
|
|
151
|
+
* create notices — its value is the one captured before the request, so a
|
|
152
|
+
* mismatch means the credentials it signed under are gone and the key it is
|
|
153
|
+
* holding must not be adopted or stored.
|
|
154
|
+
*/
|
|
155
|
+
private generation;
|
|
146
156
|
constructor(options: SandboxKeyOptions);
|
|
147
157
|
/**
|
|
148
158
|
* Return a usable API key.
|
|
@@ -161,9 +171,26 @@ export declare class SandboxKeySource {
|
|
|
161
171
|
* Forget the key, so the next {@link get} creates a new one.
|
|
162
172
|
*
|
|
163
173
|
* Called when the gateway refuses the key as unauthenticated — the one
|
|
164
|
-
* signal that it has stopped working earlier than its expiry said
|
|
165
|
-
*
|
|
166
|
-
*
|
|
174
|
+
* signal that it has stopped working earlier than its expiry said — and when
|
|
175
|
+
* the stored credentials change.
|
|
176
|
+
*
|
|
177
|
+
* The replacement is a different key, and therefore a different tenant:
|
|
178
|
+
* sandboxes created under the old one stop being reachable through this
|
|
179
|
+
* plugin.
|
|
180
|
+
*
|
|
181
|
+
* ## Why the generation is bumped and `pending` is not cleared
|
|
182
|
+
*
|
|
183
|
+
* Clearing `pending` would let a concurrent caller start a second create
|
|
184
|
+
* immediately, which is two keys and two tenants — the thing the shared
|
|
185
|
+
* promise exists to prevent. Bumping the generation instead leaves the
|
|
186
|
+
* in-flight create shared, and makes it fail when it lands: its key was
|
|
187
|
+
* minted under credentials that no longer exist, so adopting it would put a
|
|
188
|
+
* credential granted under a cleared account back into the store.
|
|
189
|
+
*
|
|
190
|
+
* The clear is best-effort. A store that refuses the delete still has the key
|
|
191
|
+
* dropped from memory, and the fingerprint check rejects it on the next read
|
|
192
|
+
* anyway — throwing here would turn a refresh into a failure the caller
|
|
193
|
+
* cannot act on.
|
|
167
194
|
*/
|
|
168
195
|
invalidate(): void;
|
|
169
196
|
/**
|
|
@@ -172,6 +199,8 @@ export declare class SandboxKeySource {
|
|
|
172
199
|
* @param credentials - the pair to sign with; already read by {@link get},
|
|
173
200
|
* so the fingerprint recorded on the key is the one it was minted under.
|
|
174
201
|
* @param signal - caller cancellation, forwarded to the fetch.
|
|
202
|
+
* @throws {SandboxKeyError} when the credentials changed while the request
|
|
203
|
+
* was in flight, so the key that came back belongs to none of them.
|
|
175
204
|
*/
|
|
176
205
|
private createKey;
|
|
177
206
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huaweidevtools/aishell-dsh-e2b",
|
|
3
3
|
"description": "Runs throwaway and preview deployments in E2B sandboxes on DSH — sandbox lifecycle, commands, and files as tools",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.17",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"license": "Apache-2.0",
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"e2b": "2.59.0",
|
|
30
|
-
"@huaweidevtools/aishell-dsh-credentials-store": "0.0.
|
|
30
|
+
"@huaweidevtools/aishell-dsh-credentials-store": "0.0.17"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@deepseek-ai/cordis": "^4.0.4",
|