@huaweidevtools/aishell-dsh-credentials-store 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.
Files changed (75) hide show
  1. package/lib/accounts.d.ts +31 -20
  2. package/lib/accounts.d.ts.map +1 -1
  3. package/lib/accounts.js +57 -24
  4. package/lib/accounts.js.map +1 -1
  5. package/lib/{index-JlyJE1E8.d.ts → index-Cdsu1kDC.d.ts} +87 -12
  6. package/lib/index-Cdsu1kDC.d.ts.map +1 -0
  7. package/lib/index.d.ts +3 -3
  8. package/lib/index.js +165 -29
  9. package/lib/index.js.map +1 -1
  10. package/lib/{paths-DtFRzop6.js → paths-C0wxA5LO.js} +3 -3
  11. package/lib/paths-C0wxA5LO.js.map +1 -0
  12. package/lib/paths.d.ts +10 -9
  13. package/lib/paths.d.ts.map +1 -1
  14. package/lib/paths.js +1 -1
  15. package/lib/{scope-CFetnbG3.d.ts → scope-C7w2eTvE.d.ts} +12 -12
  16. package/lib/{scope-CFetnbG3.d.ts.map → scope-C7w2eTvE.d.ts.map} +1 -1
  17. package/lib/{scope-QmrHZaZt.js → scope-DJcK27Uc.js} +12 -12
  18. package/lib/scope-DJcK27Uc.js.map +1 -0
  19. package/lib/scope.d.ts +1 -1
  20. package/lib/scope.js +1 -1
  21. package/lib/signer-Bx88x7dI.d.ts +72 -0
  22. package/lib/signer-Bx88x7dI.d.ts.map +1 -0
  23. package/lib/{signer-BPR0UAF0.js → signer-CVOjpi6y.js} +39 -3
  24. package/lib/signer-CVOjpi6y.js.map +1 -0
  25. package/lib/signer.d.ts +2 -2
  26. package/lib/signer.js +2 -2
  27. package/lib/types/accounts.d.ts +61 -39
  28. package/lib/types/accounts.d.ts.map +1 -1
  29. package/lib/types/accounts.js +108 -52
  30. package/lib/types/accounts.js.map +1 -1
  31. package/lib/types/backends/fingerprint.d.ts +78 -0
  32. package/lib/types/backends/fingerprint.d.ts.map +1 -1
  33. package/lib/types/backends/fingerprint.js +155 -8
  34. package/lib/types/backends/fingerprint.js.map +1 -1
  35. package/lib/types/backends/keyring.d.ts +7 -7
  36. package/lib/types/backends/keyring.d.ts.map +1 -1
  37. package/lib/types/backends/keyring.js +15 -9
  38. package/lib/types/backends/keyring.js.map +1 -1
  39. package/lib/types/backends/memory.d.ts +4 -17
  40. package/lib/types/backends/memory.d.ts.map +1 -1
  41. package/lib/types/backends/memory.js +21 -32
  42. package/lib/types/backends/memory.js.map +1 -1
  43. package/lib/types/failover.d.ts +16 -18
  44. package/lib/types/failover.d.ts.map +1 -1
  45. package/lib/types/failover.js +19 -21
  46. package/lib/types/failover.js.map +1 -1
  47. package/lib/types/index.d.ts +4 -4
  48. package/lib/types/index.d.ts.map +1 -1
  49. package/lib/types/index.js +4 -4
  50. package/lib/types/index.js.map +1 -1
  51. package/lib/types/paths.d.ts +10 -9
  52. package/lib/types/paths.d.ts.map +1 -1
  53. package/lib/types/paths.js +10 -9
  54. package/lib/types/paths.js.map +1 -1
  55. package/lib/types/scope.d.ts +11 -11
  56. package/lib/types/scope.js +11 -11
  57. package/lib/types/signer.d.ts +32 -0
  58. package/lib/types/signer.d.ts.map +1 -1
  59. package/lib/types/signer.js +40 -5
  60. package/lib/types/signer.js.map +1 -1
  61. package/lib/types/verify.d.ts +7 -11
  62. package/lib/types/verify.d.ts.map +1 -1
  63. package/lib/types/verify.js +27 -11
  64. package/lib/types/verify.js.map +1 -1
  65. package/lib/verify.d.ts +1 -1
  66. package/lib/verify.d.ts.map +1 -1
  67. package/lib/verify.js +11 -1
  68. package/lib/verify.js.map +1 -1
  69. package/package.json +1 -1
  70. package/lib/index-JlyJE1E8.d.ts.map +0 -1
  71. package/lib/paths-DtFRzop6.js.map +0 -1
  72. package/lib/scope-QmrHZaZt.js.map +0 -1
  73. package/lib/signer-BPR0UAF0.js.map +0 -1
  74. package/lib/signer-Et0DIH6o.d.ts +0 -40
  75. package/lib/signer-Et0DIH6o.d.ts.map +0 -1
@@ -18,25 +18,25 @@
18
18
  * | keyring service name | `aishell-dsh-plugins` |
19
19
  * | encrypted file name | `plugins.credentials.enc` |
20
20
  *
21
- * ## Why it is not a plugin config option
21
+ * ## Why it is a build-time constant
22
22
  *
23
- * It was one, briefly: `keyringService` sat on four plugins' `Config`. Only one
24
- * of the four writes and all four read, so a deployment that set it on a reader
25
- * but not the writer stored credentials in one namespace and looked for them in
26
- * another — the form said "saved" while every tool said "not authenticated".
27
- * A knob whose only observable effect is that failure is not worth having.
23
+ * The value has to be the same on both sides of a credential: the writer that
24
+ * stores it and the reader that looks it up. A runtime setting can be applied
25
+ * to one plugin and not another, and the result is a form that says "saved"
26
+ * beside tools that say "not authenticated" — a failure whose only symptom is
27
+ * the one thing the setting exists to control.
28
28
  *
29
29
  * A constant cannot be set on one side only. Two deployments that must not see
30
30
  * each other's credentials (a staging profile beside a production one) differ
31
31
  * by *building* differently, which is the honest place for a persistence
32
32
  * boundary.
33
33
  *
34
- * ## Why there is no migration
34
+ * ## Why the default namespace carries no migration path
35
35
  *
36
- * The default changed from `aishell-dsh` to `aishell-dsh-plugins` before the
37
- * first release, so no install ever wrote the old namespace. Nothing reads it,
38
- * and adding a migration path would mean keeping the old name alive forever for
39
- * a state that never existed.
36
+ * `plugins` is the first and only scope, so a build that sets nothing lands on
37
+ * it and a build that sets something has deliberately moved to a namespace of
38
+ * its own. There are no two names to reconcile, so there is no migration path
39
+ * to write — and none is needed until a release renames this default.
40
40
  *
41
41
  * @module @huaweidevtools/aishell-dsh-credentials-store/scope
42
42
  */
@@ -93,4 +93,4 @@ const CREDENTIALS_FILE_NAME = `${CREDENTIAL_SCOPE}.credentials.enc`;
93
93
 
94
94
  //#endregion
95
95
  export { CREDENTIALS_FILE_NAME, CREDENTIAL_SCOPE, DEFAULT_SCOPE, KEYRING_SERVICE };
96
- //# sourceMappingURL=scope-QmrHZaZt.js.map
96
+ //# sourceMappingURL=scope-DJcK27Uc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope-DJcK27Uc.js","names":[],"sources":["../src/scope.ts"],"sourcesContent":["/**\n * Which credential namespace this build uses.\n *\n * ## The knob\n *\n * A build-time constant, not a runtime setting:\n *\n * ```sh\n * AISHELL_CREDENTIAL_SCOPE=staging pnpm run build\n * ```\n *\n * Default `plugins`. It decides two things, and it decides both at once —\n * which is the point:\n *\n * | Used as | Default |\n * |---|---|\n * | keyring service name | `aishell-dsh-plugins` |\n * | encrypted file name | `plugins.credentials.enc` |\n *\n * ## Why it is a build-time constant\n *\n * The value has to be the same on both sides of a credential: the writer that\n * stores it and the reader that looks it up. A runtime setting can be applied\n * to one plugin and not another, and the result is a form that says \"saved\"\n * beside tools that say \"not authenticated\" — a failure whose only symptom is\n * the one thing the setting exists to control.\n *\n * A constant cannot be set on one side only. Two deployments that must not see\n * each other's credentials (a staging profile beside a production one) differ\n * by *building* differently, which is the honest place for a persistence\n * boundary.\n *\n * ## Why the default namespace carries no migration path\n *\n * `plugins` is the first and only scope, so a build that sets nothing lands on\n * it and a build that sets something has deliberately moved to a namespace of\n * its own. There are no two names to reconcile, so there is no migration path\n * to write — and none is needed until a release renames this default.\n *\n * @module @huaweidevtools/aishell-dsh-credentials-store/scope\n */\n\n/**\n * The default scope.\n *\n * Named for the repository, not for a feature: the credential store is shared\n * by every plugin in this set, so it is not `llm`'s or `e2b`'s.\n */\nexport const DEFAULT_SCOPE = 'plugins'\n\n/**\n * Injected at build time; see `tsdown.config.ts`.\n *\n * Declared as possibly-absent because a source run (the test suite, which\n * resolves `src/` directly) never passes through the bundler. The `typeof`\n * guard below handles that without a `ReferenceError`.\n */\ndeclare const __AISHELL_CREDENTIAL_SCOPE__: string | undefined\n\n/**\n * Accepted scope shape: a lowercase identifier.\n *\n * The value reaches a keyring service name and a file name, so it is validated\n * as a strict identifier rather than merely \"not obviously a path\": no\n * separators, no dots, no leading dash. Assuming a value is benign because the\n * caller is trusted is how path traversal gets in.\n */\nconst SCOPE_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/\n\n/**\n * Resolve the scope for this build.\n *\n * Fails loudly on a malformed value rather than falling back to the default:\n * a typo would otherwise silently share the namespace with every unset build,\n * which is exactly the confusion scoping exists to avoid.\n *\n * @returns the validated scope.\n * @throws when the injected value is not a valid scope.\n */\nfunction resolveScope(): string {\n // `typeof` rather than a direct read: an undeclared identifier throws, and a\n // source run legitimately has no injected value.\n const injected = typeof __AISHELL_CREDENTIAL_SCOPE__ === 'string'\n ? __AISHELL_CREDENTIAL_SCOPE__\n : undefined\n if (injected === undefined || injected === '') return DEFAULT_SCOPE\n const normalized = injected.trim().toLowerCase()\n if (normalized.length === 0) return DEFAULT_SCOPE\n if (!SCOPE_PATTERN.test(normalized)) {\n throw new Error(\n `AISHELL_CREDENTIAL_SCOPE=${JSON.stringify(injected)} is not a valid credential scope: `\n + `expected 1-64 characters of [a-z0-9_-], starting with a letter or digit `\n + '(for example \"plugins\" or \"staging\").',\n )\n }\n return normalized\n}\n\n/** The scope this build uses. */\nexport const CREDENTIAL_SCOPE = resolveScope()\n\n/**\n * The keyring service every entry of this build lives under.\n *\n * Part of the persistence contract: renaming it orphans every stored entry\n * while looking like a harmless constant change.\n */\nexport const KEYRING_SERVICE = `aishell-dsh-${CREDENTIAL_SCOPE}`\n\n/**\n * The encrypted file name for this build.\n *\n * Scoped the same way as the keyring service, so two builds on one machine\n * cannot collide in either backend.\n */\nexport const CREDENTIALS_FILE_NAME = `${CREDENTIAL_SCOPE}.credentials.enc`"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,MAAa,gBAAgB;;;;;;;;;AAmB7B,MAAM,gBAAgB;;;;;;;;;;;AAYtB,SAAS,eAAuB;CAG9B,MAAM;AAGN,KAAI,uBAA0B,aAAa,GAAI,QAAO;CACtD,MAAM,aAAa,SAAS,MAAM,CAAC,aAAa;AAChD,KAAI,WAAW,WAAW,EAAG,QAAO;AACpC,MAAK,cAAc,KAAK,WAAW,CACjC,OAAM,IAAI,OACP,2BAA2B,KAAK,UAAU,SAAS,CAAC;AAKzD,QAAO;AACR;;AAGD,MAAa,mBAAmB,cAAc;;;;;;;AAQ9C,MAAa,mBAAmB,cAAc,iBAAiB;;;;;;;AAQ/D,MAAa,yBAAyB,EAAE,iBAAiB"}
package/lib/scope.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { CREDENTIALS_FILE_NAME$1 as CREDENTIALS_FILE_NAME, CREDENTIAL_SCOPE$1 as CREDENTIAL_SCOPE, DEFAULT_SCOPE$1 as DEFAULT_SCOPE, KEYRING_SERVICE$1 as KEYRING_SERVICE } from "./scope-CFetnbG3.js";
1
+ import { CREDENTIALS_FILE_NAME$1 as CREDENTIALS_FILE_NAME, CREDENTIAL_SCOPE$1 as CREDENTIAL_SCOPE, DEFAULT_SCOPE$1 as DEFAULT_SCOPE, KEYRING_SERVICE$1 as KEYRING_SERVICE } from "./scope-C7w2eTvE.js";
2
2
  export { CREDENTIALS_FILE_NAME, CREDENTIAL_SCOPE, DEFAULT_SCOPE, KEYRING_SERVICE };
package/lib/scope.js CHANGED
@@ -1,3 +1,3 @@
1
- import { CREDENTIALS_FILE_NAME, CREDENTIAL_SCOPE, DEFAULT_SCOPE, KEYRING_SERVICE } from "./scope-QmrHZaZt.js";
1
+ import { CREDENTIALS_FILE_NAME, CREDENTIAL_SCOPE, DEFAULT_SCOPE, KEYRING_SERVICE } from "./scope-DJcK27Uc.js";
2
2
 
3
3
  export { CREDENTIALS_FILE_NAME, CREDENTIAL_SCOPE, DEFAULT_SCOPE, KEYRING_SERVICE };
@@ -0,0 +1,72 @@
1
+ //#region src/signer.d.ts
2
+ /**
3
+ * Huawei Cloud SDK-HMAC-SHA256 request signing.
4
+ *
5
+ * Signs an HTTP request with AK/SK (and an optional temporary SecurityToken)
6
+ * using the same algorithm as the official Huawei Cloud SDK. Produces the
7
+ * `Authorization`, `x-sdk-date`, and `x-security-token` headers to attach
8
+ * to a `fetch` call.
9
+ *
10
+ * @module @huaweidevtools/aishell-dsh-credentials-store/signer
11
+ */
12
+ /** Credentials for signing. */
13
+ interface SignCredentials {
14
+ readonly ak: string;
15
+ readonly sk: string;
16
+ readonly securityToken?: string;
17
+ }
18
+ /** The headers produced by {@link sign}. */
19
+ interface SignedHeaders {
20
+ readonly host: string;
21
+ readonly 'x-sdk-date': string;
22
+ readonly Authorization: string;
23
+ readonly 'x-security-token'?: string;
24
+ }
25
+ /**
26
+ * Code-unit comparison — the order the signing specification requires.
27
+ *
28
+ * Exported because it is the part of signing that is checkable without a
29
+ * signature: everything downstream of it is a hash, so a test that compares two
30
+ * signatures cannot tell a correct ordering from a consistently wrong one. See
31
+ * `tests/format.spec.ts`.
32
+ *
33
+ * @param a - one string.
34
+ * @param b - the other.
35
+ * @returns negative, zero or positive in code-unit order.
36
+ */
37
+ declare function compareCodeUnits(a: string, b: string): number;
38
+ /**
39
+ * Build the canonical query string, sorted by parameter name.
40
+ *
41
+ * ## Why byte order and not `localeCompare`
42
+ *
43
+ * The specification sorts by code point. Locale-aware collation disagrees with
44
+ * it on mixed case: `Zebra` sorts *before* `apple` by code unit and *after* it
45
+ * under the default locale. The signed string would then differ from the one
46
+ * the gateway rebuilds from the same URL, and the request fails to authenticate
47
+ * with nothing in the error naming the sort.
48
+ *
49
+ * Every caller in this repo passes lowercase ASCII parameter names today, which
50
+ * is why this survived; `openapi_request` takes a URL from the model, so mixed
51
+ * case reaches this path from outside.
52
+ *
53
+ * @param query - the query parameters.
54
+ * @returns the canonical string, `k=v&k=v`, with both sides URL-encoded.
55
+ */
56
+ declare function canonicalQuery(query: Record<string, string>): string;
57
+ /**
58
+ * Sign an HTTP request with SDK-HMAC-SHA256.
59
+ *
60
+ * @param method - HTTP method (GET, POST, ...).
61
+ * @param host - Host component of the request URL (e.g. `openai.aishell.openbuildlab.com`).
62
+ * @param path - Path component of the request URL (e.g. `/chat/completions`).
63
+ * @param query - Query parameters as a key-value map.
64
+ * @param body - Request body (stringified for hashing; empty string for GET).
65
+ * @param credentials - AK/SK and optional SecurityToken.
66
+ * @returns The headers to attach to the fetch call.
67
+ */
68
+ declare function sign(method: string, host: string, path: string, query: Record<string, string>, body: string, credentials: SignCredentials): SignedHeaders; //#endregion
69
+
70
+ //# sourceMappingURL=signer.d.ts.map
71
+ export { SignCredentials, SignedHeaders, canonicalQuery as canonicalQuery$1, compareCodeUnits as compareCodeUnits$1, sign as sign$1 };
72
+ //# sourceMappingURL=signer-Bx88x7dI.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signer-Bx88x7dI.d.ts","names":[],"sources":["../../src/signer.ts"],"sourcesContent":null,"mappings":";;;;;;;;;;;;UAciB,eAAA;EAAA,SAAA,EAAA,EAAA,MAAe;;EAOf,SAAA,aAAa,CAAA,EAAA,MAAA;;;UAAb,aAAA;;;;;;;;;;AAkDjB;;;;;;;;iBAAgB,gBAAA;;;;;;;;;;;AAsBhB;;;;;;;;iBAAgB,cAAA,QAAsB;;;;AAkBtC;;;;;AAOgB;;;iBAPA,IAAA,oDAIP,mDAEM,kBACZ"}
@@ -24,6 +24,42 @@ function sdkTimestamp() {
24
24
  return new Date().toISOString().replace(/[-:]/g, "").replace(/\.\d+/, "");
25
25
  }
26
26
  /**
27
+ * Code-unit comparison — the order the signing specification requires.
28
+ *
29
+ * Exported because it is the part of signing that is checkable without a
30
+ * signature: everything downstream of it is a hash, so a test that compares two
31
+ * signatures cannot tell a correct ordering from a consistently wrong one. See
32
+ * `tests/format.spec.ts`.
33
+ *
34
+ * @param a - one string.
35
+ * @param b - the other.
36
+ * @returns negative, zero or positive in code-unit order.
37
+ */
38
+ function compareCodeUnits(a, b) {
39
+ return a < b ? -1 : a > b ? 1 : 0;
40
+ }
41
+ /**
42
+ * Build the canonical query string, sorted by parameter name.
43
+ *
44
+ * ## Why byte order and not `localeCompare`
45
+ *
46
+ * The specification sorts by code point. Locale-aware collation disagrees with
47
+ * it on mixed case: `Zebra` sorts *before* `apple` by code unit and *after* it
48
+ * under the default locale. The signed string would then differ from the one
49
+ * the gateway rebuilds from the same URL, and the request fails to authenticate
50
+ * with nothing in the error naming the sort.
51
+ *
52
+ * Every caller in this repo passes lowercase ASCII parameter names today, which
53
+ * is why this survived; `openapi_request` takes a URL from the model, so mixed
54
+ * case reaches this path from outside.
55
+ *
56
+ * @param query - the query parameters.
57
+ * @returns the canonical string, `k=v&k=v`, with both sides URL-encoded.
58
+ */
59
+ function canonicalQuery(query) {
60
+ return Object.entries(query).sort(([a], [b]) => compareCodeUnits(a, b)).map(([k, v]) => `${urlEncode(k)}=${urlEncode(v)}`).join("&");
61
+ }
62
+ /**
27
63
  * Sign an HTTP request with SDK-HMAC-SHA256.
28
64
  *
29
65
  * @param method - HTTP method (GET, POST, ...).
@@ -36,7 +72,7 @@ function sdkTimestamp() {
36
72
  */
37
73
  function sign(method, host, path, query, body, credentials) {
38
74
  const ts = sdkTimestamp();
39
- const cqs = Object.entries(query).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${urlEncode(k)}=${urlEncode(v)}`).join("&");
75
+ const cqs = canonicalQuery(query);
40
76
  const curi = "/" + path.split("/").filter(Boolean).map(urlEncode).join("/") + "/";
41
77
  const hasToken = credentials.securityToken !== void 0;
42
78
  const signedHeaders = hasToken ? "host;x-sdk-date;x-security-token" : "host;x-sdk-date";
@@ -62,5 +98,5 @@ function sign(method, host, path, query, body, credentials) {
62
98
  }
63
99
 
64
100
  //#endregion
65
- export { sign };
66
- //# sourceMappingURL=signer-BPR0UAF0.js.map
101
+ export { canonicalQuery, compareCodeUnits, sign };
102
+ //# sourceMappingURL=signer-CVOjpi6y.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signer-CVOjpi6y.js","names":["data: string","key: string","str: string","c: number","a: string","b: string","query: Record<string, string>","method: string","host: string","path: string","body: string","credentials: SignCredentials","headers: SignedHeaders"],"sources":["../src/signer.ts"],"sourcesContent":["/**\n * Huawei Cloud SDK-HMAC-SHA256 request signing.\n *\n * Signs an HTTP request with AK/SK (and an optional temporary SecurityToken)\n * using the same algorithm as the official Huawei Cloud SDK. Produces the\n * `Authorization`, `x-sdk-date`, and `x-security-token` headers to attach\n * to a `fetch` call.\n *\n * @module @huaweidevtools/aishell-dsh-credentials-store/signer\n */\n\nimport crypto from 'node:crypto'\n\n/** Credentials for signing. */\nexport interface SignCredentials {\n readonly ak: string\n readonly sk: string\n readonly securityToken?: string\n}\n\n/** The headers produced by {@link sign}. */\nexport interface SignedHeaders {\n readonly host: string\n readonly 'x-sdk-date': string\n readonly Authorization: string\n readonly 'x-security-token'?: string\n}\n\nfunction sha256Hex(data: string): string {\n return crypto.createHash('sha256').update(data).digest('hex')\n}\n\nfunction hmacSha256(key: string, data: string): string {\n return crypto.createHmac('sha256', key).update(data).digest('hex')\n}\n\n/** RFC 3986 URL-encode a string (unreserved chars stay literal). */\nfunction urlEncode(str: string): string {\n const hex = (c: number): string => '%' + (c < 16 ? '0' : '') + c.toString(16).toUpperCase()\n const noEscape = new Set('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~'.split(''))\n let out = ''\n for (const ch of str) {\n const c = ch.codePointAt(0)!\n if (c < 0x80) {\n out += noEscape.has(ch) ? ch : hex(c)\n } else {\n out += encodeURIComponent(ch)\n }\n }\n return out\n}\n\n/** ISO 8601 timestamp in Huawei Cloud format: `20260912T064552Z`. */\nfunction sdkTimestamp(): string {\n // toISOString() returns \"2026-09-12T06:45:52.123Z\"; strip separators and\n // milliseconds but keep the trailing Z — do NOT append another one.\n return new Date().toISOString().replace(/[-:]/g, '').replace(/\\.\\d+/, '')\n}\n\n/**\n * Code-unit comparison — the order the signing specification requires.\n *\n * Exported because it is the part of signing that is checkable without a\n * signature: everything downstream of it is a hash, so a test that compares two\n * signatures cannot tell a correct ordering from a consistently wrong one. See\n * `tests/format.spec.ts`.\n *\n * @param a - one string.\n * @param b - the other.\n * @returns negative, zero or positive in code-unit order.\n */\nexport function compareCodeUnits(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0\n}\n\n/**\n * Build the canonical query string, sorted by parameter name.\n *\n * ## Why byte order and not `localeCompare`\n *\n * The specification sorts by code point. Locale-aware collation disagrees with\n * it on mixed case: `Zebra` sorts *before* `apple` by code unit and *after* it\n * under the default locale. The signed string would then differ from the one\n * the gateway rebuilds from the same URL, and the request fails to authenticate\n * with nothing in the error naming the sort.\n *\n * Every caller in this repo passes lowercase ASCII parameter names today, which\n * is why this survived; `openapi_request` takes a URL from the model, so mixed\n * case reaches this path from outside.\n *\n * @param query - the query parameters.\n * @returns the canonical string, `k=v&k=v`, with both sides URL-encoded.\n */\nexport function canonicalQuery(query: Record<string, string>): string {\n return Object.entries(query)\n .sort(([a], [b]) => compareCodeUnits(a, b))\n .map(([k, v]) => `${urlEncode(k)}=${urlEncode(v)}`)\n .join('&')\n}\n\n/**\n * Sign an HTTP request with SDK-HMAC-SHA256.\n *\n * @param method - HTTP method (GET, POST, ...).\n * @param host - Host component of the request URL (e.g. `openai.aishell.openbuildlab.com`).\n * @param path - Path component of the request URL (e.g. `/chat/completions`).\n * @param query - Query parameters as a key-value map.\n * @param body - Request body (stringified for hashing; empty string for GET).\n * @param credentials - AK/SK and optional SecurityToken.\n * @returns The headers to attach to the fetch call.\n */\nexport function sign(\n method: string,\n host: string,\n path: string,\n query: Record<string, string>,\n body: string,\n credentials: SignCredentials,\n): SignedHeaders {\n const ts = sdkTimestamp()\n const cqs = canonicalQuery(query)\n\n // Canonical URI: path segments URL-encoded, joined with /, trailing /\n const curi = '/' + path.split('/').filter(Boolean).map(urlEncode).join('/') + '/'\n\n // Signed headers\n const hasToken = credentials.securityToken !== undefined\n const signedHeaders = hasToken ? 'host;x-sdk-date;x-security-token' : 'host;x-sdk-date'\n const canonicalHeaders = hasToken\n ? `host:${host}\\nx-sdk-date:${ts}\\nx-security-token:${credentials.securityToken}\\n`\n : `host:${host}\\nx-sdk-date:${ts}\\n`\n\n // Payload hash\n const payloadHash = sha256Hex(body)\n\n // Canonical request\n const canonicalRequest = [method, curi, cqs, canonicalHeaders, signedHeaders, payloadHash].join('\\n')\n\n // String to sign\n const stringToSign = `SDK-HMAC-SHA256\\n${ts}\\n${sha256Hex(canonicalRequest)}`\n const signature = hmacSha256(credentials.sk, stringToSign)\n\n const headers: SignedHeaders = {\n host,\n 'x-sdk-date': ts,\n Authorization: `SDK-HMAC-SHA256 Access=${credentials.ak}, SignedHeaders=${signedHeaders}, Signature=${signature}`,\n ...(hasToken ? { 'x-security-token': credentials.securityToken } : {}),\n }\n return headers\n}\n"],"mappings":";;;AA4BA,SAAS,UAAUA,MAAsB;AACvC,QAAO,OAAO,WAAW,SAAS,CAAC,OAAO,KAAK,CAAC,OAAO,MAAM;AAC9D;AAED,SAAS,WAAWC,KAAaD,MAAsB;AACrD,QAAO,OAAO,WAAW,UAAU,IAAI,CAAC,OAAO,KAAK,CAAC,OAAO,MAAM;AACnE;;AAGD,SAAS,UAAUE,KAAqB;CACtC,MAAM,MAAM,CAACC,MAAsB,OAAO,IAAI,KAAK,MAAM,MAAM,EAAE,SAAS,GAAG,CAAC,aAAa;CAC3F,MAAM,WAAW,IAAI,IAAI,qEAAqE,MAAM,GAAG;CACvG,IAAI,MAAM;AACV,MAAK,MAAM,MAAM,KAAK;EACpB,MAAM,IAAI,GAAG,YAAY,EAAE;AAC3B,MAAI,IAAI,IACN,QAAO,SAAS,IAAI,GAAG,GAAG,KAAK,IAAI,EAAE;MAErC,QAAO,mBAAmB,GAAG;CAEhC;AACD,QAAO;AACR;;AAGD,SAAS,eAAuB;AAG9B,QAAO,IAAI,OAAO,aAAa,CAAC,QAAQ,SAAS,GAAG,CAAC,QAAQ,SAAS,GAAG;AAC1E;;;;;;;;;;;;;AAcD,SAAgB,iBAAiBC,GAAWC,GAAmB;AAC7D,QAAO,IAAI,IAAA,KAAS,IAAI,IAAI,IAAI;AACjC;;;;;;;;;;;;;;;;;;;AAoBD,SAAgB,eAAeC,OAAuC;AACpE,QAAO,OAAO,QAAQ,MAAM,CACzB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,KAAK,iBAAiB,GAAG,EAAE,CAAC,CAC1C,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,CAClD,KAAK,IAAI;AACb;;;;;;;;;;;;AAaD,SAAgB,KACdC,QACAC,MACAC,MACAH,OACAI,MACAC,aACe;CACf,MAAM,KAAK,cAAc;CACzB,MAAM,MAAM,eAAe,MAAM;CAGjC,MAAM,OAAO,MAAM,KAAK,MAAM,IAAI,CAAC,OAAO,QAAQ,CAAC,IAAI,UAAU,CAAC,KAAK,IAAI,GAAG;CAG9E,MAAM,WAAW,YAAY;CAC7B,MAAM,gBAAgB,WAAW,qCAAqC;CACtE,MAAM,mBAAmB,YACpB,OAAO,KAAK,eAAe,GAAG,qBAAqB,YAAY,cAAc,OAC7E,OAAO,KAAK,eAAe,GAAG;CAGnC,MAAM,cAAc,UAAU,KAAK;CAGnC,MAAM,mBAAmB;EAAC;EAAQ;EAAM;EAAK;EAAkB;EAAe;CAAY,EAAC,KAAK,KAAK;CAGrG,MAAM,gBAAgB,mBAAmB,GAAG,IAAI,UAAU,iBAAiB,CAAC;CAC5E,MAAM,YAAY,WAAW,YAAY,IAAI,aAAa;CAE1D,MAAMC,UAAyB;EAC7B;EACA,cAAc;EACd,gBAAgB,yBAAyB,YAAY,GAAG,kBAAkB,cAAc,cAAc,UAAU;EAChH,GAAI,WAAW,EAAE,oBAAoB,YAAY,cAAe,IAAG,CAAE;CACtE;AACD,QAAO;AACR"}
package/lib/signer.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { SignCredentials, SignedHeaders, sign$1 as sign } from "./signer-Et0DIH6o.js";
2
- export { SignCredentials, SignedHeaders, sign };
1
+ import { SignCredentials, SignedHeaders, canonicalQuery$1 as canonicalQuery, compareCodeUnits$1 as compareCodeUnits, sign$1 as sign } from "./signer-Bx88x7dI.js";
2
+ export { SignCredentials, SignedHeaders, canonicalQuery, compareCodeUnits, sign };
package/lib/signer.js CHANGED
@@ -1,3 +1,3 @@
1
- import { sign } from "./signer-BPR0UAF0.js";
1
+ import { canonicalQuery, compareCodeUnits, sign } from "./signer-CVOjpi6y.js";
2
2
 
3
- export { sign };
3
+ export { canonicalQuery, compareCodeUnits, sign };
@@ -1,18 +1,15 @@
1
1
  /**
2
2
  * The Huawei Cloud credential vocabulary every plugin in this set shares.
3
3
  *
4
- * ## Why this module exists
4
+ * ## Why one module owns the account names
5
5
  *
6
- * This repo once read the same keyring entries from four places, each declaring
7
- * the account names itself and re-implementing "read AK and SK as a pair". The
8
- * account names are a *persistence* contract, so a divergence would not fail a
9
- * build — it would surface as one plugin working and another reporting "not
10
- * authenticated" on a machine where the person had stored exactly one pair, and
11
- * the cause would be invisible in the diff that introduced it.
6
+ * The account names and the "AK and SK are one unit" rule are a *persistence*
7
+ * contract. A second declaration of either does not fail a build: it surfaces
8
+ * as one plugin working and another reporting "not authenticated" on a machine
9
+ * where exactly one pair is stored, and the diff that introduced it looks
10
+ * harmless. Keeping them in one file makes the contract have one owner.
12
11
  *
13
- * Everything now goes through this file.
14
- *
15
- * ## One entry, not three
12
+ * ## One record, holding the whole bundle
16
13
  *
17
14
  * The store holds **one** record, `aishell-credential`, whose value is a single
18
15
  * JSON bundle encoded as base64:
@@ -24,18 +21,16 @@
24
21
  * }
25
22
  * ```
26
23
  *
27
- * The earlier layout was three separate entries (`HW_ACCESS_KEY`,
28
- * `HW_SECRET_KEY`, `HW_SECURITY_TOKEN`). That had two problems this fixes:
29
- *
30
- * - **A save was four operations.** Clear, then write AK, then SK, then
31
- * token. Every backend is per-entry, so a failure between them left a
32
- * half-set behind, and nothing rolled it back.
33
- * - **A read was three reads.** A concurrent write landing between them could
34
- * hand back a new AK with an old SK — a pair that passes "both present" and
35
- * then fails at the gateway with 401, pointing nowhere near the cause.
24
+ * One record is what makes a save one operation and a read one consistent
25
+ * snapshot:
36
26
  *
37
- * One entry makes a write one atomic operation and a read one consistent
38
- * snapshot.
27
+ * - **A save is one write.** Splitting the pair across entries makes every
28
+ * save a sequence, and every backend is per-entry, so a failure between two
29
+ * of them leaves a half-set behind that nothing rolls back.
30
+ * - **A read is one read.** Reading the AK and the SK separately lets a
31
+ * concurrent write land between them: the caller gets a new AK with an old
32
+ * SK, which passes "both present" and then fails at the gateway with 401,
33
+ * pointing nowhere near the cause.
39
34
  *
40
35
  * ## base64 is an encoding, not encryption
41
36
  *
@@ -50,9 +45,25 @@
50
45
  * back. Two processes doing that concurrently can lose one of the changes. That
51
46
  * is accepted rather than solved: the only writer is a person via the
52
47
  * credential form, and both the e2b key and the AK/SK are refreshed by that
53
- * same person. A lost update would be corrected by the next save. Solving it
48
+ * same person, so a lost update is corrected by the next save. Solving it
54
49
  * properly needs compare-and-swap, which the keyring API does not offer.
55
50
  *
51
+ * ## One accepted consequence: a partial bundle can overwrite an unreadable one
52
+ *
53
+ * A read that cannot decrypt the file reports "nothing stored", so an
54
+ * {@link updateCredentials} built on it computes its next bundle from an empty
55
+ * one and writes that. For `clearE2BKey` that means the written record carries
56
+ * only whatever the updater added, and the section it could not read is no
57
+ * longer in the live file.
58
+ *
59
+ * That is not repaired here, because there is nothing to repair it *from* — the
60
+ * bytes are unreadable. What limits the damage is the file backend's write path:
61
+ * an unreadable file is moved to `<name>.unreadable-<timestamp>` rather than
62
+ * overwritten, so the old record survives on disk and the next successful save
63
+ * starts from an empty map. A reader who cannot decrypt the file has already
64
+ * lost those credentials; the guarantee is only that the file they were in is
65
+ * not destroyed.
66
+ *
56
67
  * @module @huaweidevtools/aishell-dsh-credentials-store/accounts
57
68
  */
58
69
  import type { CredentialsStore } from './index.ts';
@@ -65,18 +76,22 @@ import type { SignCredentials } from './signer.ts';
65
76
  */
66
77
  export declare const CREDENTIAL_ACCOUNT = "aishell-credential";
67
78
  /**
68
- * Entries written by the earlier three-key layout, in the order they are read
69
- * during migration.
79
+ * The per-field account names a store may still hold beside the bundle.
80
+ *
81
+ * These are the read-side names of a store written field-by-field instead of
82
+ * as one record. {@link readCredentials} folds them into
83
+ * {@link CREDENTIAL_ACCOUNT} on first read, so a machine that holds them keeps
84
+ * working; nothing writes them.
70
85
  *
71
- * Kept only so an existing install keeps working: see {@link readCredentials}.
72
- * New writes never touch these.
86
+ * The order is the order the migration reads them: access key, secret key,
87
+ * token.
73
88
  */
74
89
  export declare const LEGACY_ACCOUNTS: readonly ["HW_ACCESS_KEY", "HW_SECRET_KEY", "HW_SECURITY_TOKEN"];
75
90
  /**
76
- * The legacy entry holding an e2b API key on its own.
91
+ * The standalone e2b key entry, beside the per-field names above.
77
92
  *
78
- * Folded into the bundle by {@link readCredentials}. Kept as a constant because
79
- * the migration has to name it.
93
+ * Folded into the bundle by {@link readCredentials}, for the same reason: a
94
+ * store written before the bundle existed keeps working. Nothing writes it.
80
95
  */
81
96
  export declare const LEGACY_E2B_ACCOUNT = "AISHELL_E2B_API_KEY";
82
97
  /** The Huawei Cloud pair, as the bundle stores it. Snake-case on disk. */
@@ -128,12 +143,12 @@ export declare function encodeBundle(bundle: CredentialBundle): string;
128
143
  */
129
144
  export declare function decodeBundle(raw: string | undefined): CredentialBundle;
130
145
  /**
131
- * Read the whole bundle, migrating a legacy layout on the way.
146
+ * Read the whole bundle, folding a per-field store into it on the way.
132
147
  *
133
- * Migration is lazy and idempotent: the first read that finds no bundle but
134
- * finds legacy entries merges them into one record, writes it, and removes the
135
- * old entries. A failed write leaves the legacy entries alone, so the next read
136
- * tries again — the person is never left with neither layout.
148
+ * The fold is lazy and idempotent: a read that finds no bundle but finds the
149
+ * per-field names merges them into one record, writes it, and removes the old
150
+ * entries. A failed write leaves them in place so the next read tries again —
151
+ * a store is never left with neither layout.
137
152
  *
138
153
  * @param store - the caller's credential store.
139
154
  * @returns the bundle; empty when nothing is stored.
@@ -182,11 +197,11 @@ export declare function readE2BKey(store: CredentialsStore): E2BSection | undefi
182
197
  */
183
198
  export declare function updateCredentials(store: CredentialsStore, update: (current: CredentialBundle) => CredentialBundle | undefined): CredentialBundle;
184
199
  /**
185
- * Store the Huawei Cloud pair, replacing any previous one.
200
+ * Store the Huawei Cloud pair, replacing whatever is there.
186
201
  *
187
202
  * One write, so the pair can never be left half-replaced. A save without a
188
- * token removes a previously stored one: a token belongs to the credential set
189
- * that carried it, and inheriting a stale one would sign the next request with
203
+ * token removes a stored one: a token belongs to the credential set that
204
+ * carried it, and inheriting a stale one would sign the next request with
190
205
  * someone else's session.
191
206
  *
192
207
  * @param store - the caller's credential store.
@@ -211,10 +226,17 @@ export declare function clearE2BKey(store: CredentialsStore): void;
211
226
  *
212
227
  * The whole record goes, so a clear cannot leave a token or a key behind for
213
228
  * the next pair to inherit. Deleting an absent entry is a no-op in every
214
- * backend, and the legacy entries are removed too so a half-migrated store does
215
- * not come back.
229
+ * backend.
230
+ *
231
+ * The per-field names are removed as well, so a store the fold never reached
232
+ * does not keep answering after a clear. Those deletes are best-effort and the
233
+ * main record's is not: a refusal on `CREDENTIAL_ACCOUNT` means the credentials
234
+ * are still there and the person has to be told, while the per-field names are
235
+ * leftovers whose removal nothing depends on. Swallowing the first would report
236
+ * a clear that did not happen.
216
237
  *
217
238
  * @param store - the caller's credential store.
239
+ * @throws when the backend refused to remove the main record.
218
240
  */
219
241
  export declare function clearCredentials(store: CredentialsStore): void;
220
242
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"accounts.d.ts","sourceRoot":"","sources":["../../src/accounts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAElD;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,uBAAuB,CAAA;AAEtD;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,kEAAmE,CAAA;AAE/F;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,wBAAwB,CAAA;AAEvD,0EAA0E;AAC1E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CACjC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CACjC;AAED,sDAAsD;AACtD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,WAAW,CAAC,EAAE,kBAAkB,CAAA;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,UAAU,CAAA;CAClC;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAE7D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,gBAAgB,CAoBtE;AAoCD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,CAOzE;AAsED,0DAA0D;AAC1D,eAAO,MAAM,cAAc,6BAA6B,CAAA;AAExD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,gBAAgB,GAAG,eAAe,GAAG,SAAS,CAQnF;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAE9D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,UAAU,GAAG,SAAS,CAE1E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,gBAAgB,GAAG,SAAS,GAClE,gBAAgB,CAMlB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,eAAe,GAAG,IAAI,CAS5F;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI,CAE1E;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAMzD;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAI9D;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE9C"}
1
+ {"version":3,"file":"accounts.d.ts","sourceRoot":"","sources":["../../src/accounts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAElD;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,uBAAuB,CAAA;AAEtD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,kEAAmE,CAAA;AAE/F;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,wBAAwB,CAAA;AAEvD,0EAA0E;AAC1E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CACjC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CACjC;AAED,sDAAsD;AACtD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,WAAW,CAAC,EAAE,kBAAkB,CAAA;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,UAAU,CAAA;CAClC;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAE7D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,gBAAgB,CAoBtE;AAoCD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,CAOzE;AA8ED,0DAA0D;AAC1D,eAAO,MAAM,cAAc,6BAA6B,CAAA;AA2BxD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,gBAAgB,GAAG,eAAe,GAAG,SAAS,CAQnF;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAE9D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,UAAU,GAAG,SAAS,CAE1E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,gBAAgB,GAAG,SAAS,GAClE,gBAAgB,CAMlB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,eAAe,GAAG,IAAI,CAS5F;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI,CAE1E;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAMzD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAI9D;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE9C"}