@botiverse/raft-sdk 0.1.0 → 0.1.1
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 +59 -1
- package/dist/cjs/index.cjs +234 -5
- package/dist/esm/index.js +221 -4
- package/dist/index.d.ts +71 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,7 +38,49 @@ const { createRaftClient } = require("@botiverse/raft-sdk");
|
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
The credential must belong to the external agent sending the message and must
|
|
41
|
-
|
|
41
|
+
be a long-lived `sk_agent_*` credential with the `send` capability. Other
|
|
42
|
+
credential families fail before transport.
|
|
43
|
+
|
|
44
|
+
### Persist a credential without the Raft CLI
|
|
45
|
+
|
|
46
|
+
For a long-running Node.js bot, bootstrap an already-created `sk_agent_*`
|
|
47
|
+
credential once, then build clients from an explicit file store:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import {
|
|
51
|
+
bootstrapRaftCredential,
|
|
52
|
+
createFileCredentialStore,
|
|
53
|
+
createRaftClientFromStore,
|
|
54
|
+
} from "@botiverse/raft-sdk";
|
|
55
|
+
|
|
56
|
+
const store = createFileCredentialStore(
|
|
57
|
+
"/var/lib/raft-bot-rss-notifier/raft-credential.json",
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// First run only. The returned identity never includes the credential bytes.
|
|
61
|
+
await bootstrapRaftCredential({
|
|
62
|
+
serverUrl: "https://api.raft.build",
|
|
63
|
+
credential: process.env.RAFT_AGENT_CREDENTIAL!,
|
|
64
|
+
store,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Later runs need only the caller-selected store.
|
|
68
|
+
const raft = await createRaftClientFromStore({ store });
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The SDK validates the credential through the credential-authenticated Agent API
|
|
72
|
+
and requires its `send` capability before saving it. The file store requires an
|
|
73
|
+
absolute caller-selected path,
|
|
74
|
+
atomically replaces the file, writes mode `0600`, rejects broader permissions
|
|
75
|
+
on POSIX, and never searches CLI profiles, environment-specific Raft homes, or
|
|
76
|
+
the host user's home directory. Use a custom `RaftCredentialStore` when the
|
|
77
|
+
deployment already has a managed secret backend.
|
|
78
|
+
|
|
79
|
+
This bootstrap accepts an existing long-lived External Agent credential. It
|
|
80
|
+
does not run browser device-code login, mint a new credential, rotate one, or
|
|
81
|
+
revoke one. Once a store contains a credential, only the identical credential
|
|
82
|
+
may be bootstrapped again as an idempotent check. A different credential never
|
|
83
|
+
overwrites the store, even when it resolves to the same Server and Agent.
|
|
42
84
|
|
|
43
85
|
## API
|
|
44
86
|
|
|
@@ -58,6 +100,22 @@ Creates a client with these options:
|
|
|
58
100
|
Invalid client configuration throws `RaftSdkConfigurationError` before a
|
|
59
101
|
request is sent.
|
|
60
102
|
|
|
103
|
+
### `bootstrapRaftCredential(options)`
|
|
104
|
+
|
|
105
|
+
Validates an existing External Agent credential, derives its Agent, Server,
|
|
106
|
+
credential, and scope metadata from the Agent API, and saves the complete
|
|
107
|
+
record through `options.store`. It returns only non-secret identity metadata.
|
|
108
|
+
|
|
109
|
+
### `createFileCredentialStore(path)`
|
|
110
|
+
|
|
111
|
+
Creates the explicit Node.js file store described above. Relative paths and
|
|
112
|
+
unsafe stored-file permissions fail closed.
|
|
113
|
+
|
|
114
|
+
### `createRaftClientFromStore(options)`
|
|
115
|
+
|
|
116
|
+
Loads and validates one `RaftCredentialStore` record, then creates the same
|
|
117
|
+
typed client returned by `createRaftClient`.
|
|
118
|
+
|
|
61
119
|
### `client.messages.send(request)`
|
|
62
120
|
|
|
63
121
|
Sends a message. The request accepts a Raft target, message content, optional
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -28,8 +28,12 @@ var __webpack_require__ = {};
|
|
|
28
28
|
var __webpack_exports__ = {};
|
|
29
29
|
__webpack_require__.r(__webpack_exports__);
|
|
30
30
|
__webpack_require__.d(__webpack_exports__, {
|
|
31
|
+
RaftCredentialError: ()=>RaftCredentialError,
|
|
31
32
|
RaftSdkConfigurationError: ()=>RaftSdkConfigurationError,
|
|
32
|
-
|
|
33
|
+
bootstrapRaftCredential: ()=>bootstrapRaftCredential,
|
|
34
|
+
createFileCredentialStore: ()=>createFileCredentialStore,
|
|
35
|
+
createRaftClient: ()=>createRaftClient,
|
|
36
|
+
createRaftClientFromStore: ()=>createRaftClientFromStore
|
|
33
37
|
});
|
|
34
38
|
var regexes_namespaceObject = {};
|
|
35
39
|
__webpack_require__.r(regexes_namespaceObject);
|
|
@@ -278,7 +282,7 @@ __webpack_require__.d(classic_schemas_namespaceObject, {
|
|
|
278
282
|
preprocess: ()=>preprocess,
|
|
279
283
|
promise: ()=>promise,
|
|
280
284
|
readonly: ()=>readonly,
|
|
281
|
-
record: ()=>
|
|
285
|
+
record: ()=>schemas_record,
|
|
282
286
|
refine: ()=>refine,
|
|
283
287
|
set: ()=>schemas_set,
|
|
284
288
|
strictObject: ()=>strictObject,
|
|
@@ -5741,7 +5745,7 @@ const ZodRecord = /*@__PURE__*/ $constructor("ZodRecord", (inst, def)=>{
|
|
|
5741
5745
|
inst.keyType = def.keyType;
|
|
5742
5746
|
inst.valueType = def.valueType;
|
|
5743
5747
|
});
|
|
5744
|
-
function
|
|
5748
|
+
function schemas_record(keyType, valueType, params) {
|
|
5745
5749
|
return new ZodRecord({
|
|
5746
5750
|
type: "record",
|
|
5747
5751
|
keyType,
|
|
@@ -6180,7 +6184,7 @@ function schemas_json(params) {
|
|
|
6180
6184
|
schemas_boolean(),
|
|
6181
6185
|
schemas_null(),
|
|
6182
6186
|
schemas_array(jsonSchema),
|
|
6183
|
-
|
|
6187
|
+
schemas_record(schemas_string(), jsonSchema)
|
|
6184
6188
|
]));
|
|
6185
6189
|
return jsonSchema;
|
|
6186
6190
|
}
|
|
@@ -6563,6 +6567,7 @@ function requireServerUrl(value) {
|
|
|
6563
6567
|
function requireAgentCredential(value) {
|
|
6564
6568
|
const credential = value.trim();
|
|
6565
6569
|
if (!credential) throw new RaftSdkConfigurationError("MISSING_AGENT_CREDENTIAL", "Raft Agent credential is required");
|
|
6570
|
+
if (!credential.startsWith("sk_agent_") || 9 === credential.length) throw new RaftSdkConfigurationError("INVALID_AGENT_CREDENTIAL", "Raft Agent credential must be a long-lived External Agent credential");
|
|
6566
6571
|
return credential;
|
|
6567
6572
|
}
|
|
6568
6573
|
function createRaftClient(options) {
|
|
@@ -6579,11 +6584,235 @@ function createRaftClient(options) {
|
|
|
6579
6584
|
}
|
|
6580
6585
|
});
|
|
6581
6586
|
}
|
|
6587
|
+
function currentTimeMs() {
|
|
6588
|
+
return Date.now();
|
|
6589
|
+
}
|
|
6590
|
+
function currentDate() {
|
|
6591
|
+
return new Date(currentTimeMs());
|
|
6592
|
+
}
|
|
6593
|
+
class RaftCredentialError extends Error {
|
|
6594
|
+
code;
|
|
6595
|
+
name = "RaftCredentialError";
|
|
6596
|
+
constructor(code, message){
|
|
6597
|
+
super(message), this.code = code;
|
|
6598
|
+
}
|
|
6599
|
+
}
|
|
6600
|
+
function isNonEmptyString(value) {
|
|
6601
|
+
return "string" == typeof value && value.trim().length > 0;
|
|
6602
|
+
}
|
|
6603
|
+
function parseIdentityResponse(value) {
|
|
6604
|
+
if (!value || "object" != typeof value || Array.isArray(value)) return null;
|
|
6605
|
+
const input = value;
|
|
6606
|
+
if (!isNonEmptyString(input.agentId) || !isNonEmptyString(input.agentName) || null !== input.agentDisplayName && "string" != typeof input.agentDisplayName || !isNonEmptyString(input.serverId) || !isNonEmptyString(input.credentialId) || !Array.isArray(input.scopes) || !input.scopes.every(isNonEmptyString)) return null;
|
|
6607
|
+
return {
|
|
6608
|
+
agentId: input.agentId,
|
|
6609
|
+
agentName: input.agentName,
|
|
6610
|
+
agentDisplayName: input.agentDisplayName,
|
|
6611
|
+
serverId: input.serverId,
|
|
6612
|
+
credentialId: input.credentialId,
|
|
6613
|
+
scopes: [
|
|
6614
|
+
...input.scopes
|
|
6615
|
+
]
|
|
6616
|
+
};
|
|
6617
|
+
}
|
|
6618
|
+
function parseStoredRaftCredential(value) {
|
|
6619
|
+
if (!value || "object" != typeof value || Array.isArray(value)) return null;
|
|
6620
|
+
const input = value;
|
|
6621
|
+
if (1 !== input.schemaVersion || !isNonEmptyString(input.serverUrl) || !isNonEmptyString(input.credential) || !isNonEmptyString(input.storedAt)) return null;
|
|
6622
|
+
const identity = parseIdentityResponse(input);
|
|
6623
|
+
if (!identity || Number.isNaN(Date.parse(input.storedAt))) return null;
|
|
6624
|
+
let serverUrl;
|
|
6625
|
+
let credential;
|
|
6626
|
+
try {
|
|
6627
|
+
serverUrl = requireServerUrl(input.serverUrl);
|
|
6628
|
+
credential = requireAgentCredential(input.credential);
|
|
6629
|
+
} catch {
|
|
6630
|
+
return null;
|
|
6631
|
+
}
|
|
6632
|
+
return {
|
|
6633
|
+
schemaVersion: 1,
|
|
6634
|
+
serverUrl,
|
|
6635
|
+
credential,
|
|
6636
|
+
storedAt: input.storedAt,
|
|
6637
|
+
...identity
|
|
6638
|
+
};
|
|
6639
|
+
}
|
|
6640
|
+
async function bootstrapRaftCredential(options) {
|
|
6641
|
+
const serverUrl = requireServerUrl(options.serverUrl);
|
|
6642
|
+
const credential = requireAgentCredential(options.credential);
|
|
6643
|
+
const httpFetch = options.fetch ?? globalThis.fetch;
|
|
6644
|
+
let existing;
|
|
6645
|
+
try {
|
|
6646
|
+
existing = await options.store.load();
|
|
6647
|
+
} catch {
|
|
6648
|
+
throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The existing Agent credential could not be checked before saving");
|
|
6649
|
+
}
|
|
6650
|
+
if (existing) {
|
|
6651
|
+
const validatedExisting = parseStoredRaftCredential(existing);
|
|
6652
|
+
if (!validatedExisting) throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The existing Agent credential record is invalid");
|
|
6653
|
+
existing = validatedExisting;
|
|
6654
|
+
if (existing.serverUrl !== serverUrl || existing.credential !== credential) throw new RaftCredentialError("CREDENTIAL_STORE_CONFLICT", "The credential store already contains a different Raft Agent credential");
|
|
6655
|
+
}
|
|
6656
|
+
let response;
|
|
6657
|
+
try {
|
|
6658
|
+
response = await httpFetch(`${serverUrl}/internal/agent-api/`, {
|
|
6659
|
+
method: "GET",
|
|
6660
|
+
headers: {
|
|
6661
|
+
authorization: `Bearer ${credential}`
|
|
6662
|
+
}
|
|
6663
|
+
});
|
|
6664
|
+
} catch {
|
|
6665
|
+
throw new RaftCredentialError("CREDENTIAL_VERIFY_FAILED", "Could not reach the Raft Server to verify the Agent credential");
|
|
6666
|
+
}
|
|
6667
|
+
if (!response.ok) {
|
|
6668
|
+
if (401 === response.status || 403 === response.status) throw new RaftCredentialError("CREDENTIAL_REJECTED", "The Raft Server rejected the Agent credential");
|
|
6669
|
+
throw new RaftCredentialError("CREDENTIAL_VERIFY_FAILED", `The Raft Server could not verify the Agent credential (status ${response.status})`);
|
|
6670
|
+
}
|
|
6671
|
+
let payload;
|
|
6672
|
+
try {
|
|
6673
|
+
payload = await response.json();
|
|
6674
|
+
} catch {
|
|
6675
|
+
throw new RaftCredentialError("CREDENTIAL_RESPONSE_INVALID", "The Raft Server returned an invalid Agent identity response");
|
|
6676
|
+
}
|
|
6677
|
+
const identity = parseIdentityResponse(payload);
|
|
6678
|
+
if (!identity) throw new RaftCredentialError("CREDENTIAL_RESPONSE_INVALID", "The Raft Server returned an invalid Agent identity response");
|
|
6679
|
+
if (!identity.scopes.includes("send")) throw new RaftCredentialError("CREDENTIAL_SCOPE_MISSING", "The Raft Agent credential does not include the send capability required by this SDK");
|
|
6680
|
+
if (existing && (existing.serverId !== identity.serverId || existing.agentId !== identity.agentId)) throw new RaftCredentialError("CREDENTIAL_STORE_CONFLICT", "The credential store is already bound to a different Raft Agent identity");
|
|
6681
|
+
if (existing) return {
|
|
6682
|
+
serverUrl,
|
|
6683
|
+
...identity
|
|
6684
|
+
};
|
|
6685
|
+
const record = {
|
|
6686
|
+
schemaVersion: 1,
|
|
6687
|
+
serverUrl,
|
|
6688
|
+
credential,
|
|
6689
|
+
storedAt: currentDate().toISOString(),
|
|
6690
|
+
...identity
|
|
6691
|
+
};
|
|
6692
|
+
try {
|
|
6693
|
+
await options.store.save(record);
|
|
6694
|
+
} catch (error) {
|
|
6695
|
+
if (error instanceof RaftCredentialError && "CREDENTIAL_STORE_CONFLICT" === error.code) throw error;
|
|
6696
|
+
throw new RaftCredentialError("CREDENTIAL_STORE_WRITE_FAILED", "The verified Agent credential could not be saved");
|
|
6697
|
+
}
|
|
6698
|
+
return {
|
|
6699
|
+
serverUrl,
|
|
6700
|
+
...identity
|
|
6701
|
+
};
|
|
6702
|
+
}
|
|
6703
|
+
async function createRaftClientFromStore(options) {
|
|
6704
|
+
let record;
|
|
6705
|
+
try {
|
|
6706
|
+
record = await options.store.load();
|
|
6707
|
+
} catch {
|
|
6708
|
+
throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The stored Agent credential could not be loaded");
|
|
6709
|
+
}
|
|
6710
|
+
if (!record) throw new RaftCredentialError("CREDENTIAL_NOT_FOUND", "No stored Raft Agent credential was found");
|
|
6711
|
+
const validated = parseStoredRaftCredential(record);
|
|
6712
|
+
if (!validated) throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The stored Agent credential is invalid");
|
|
6713
|
+
if (!validated.scopes.includes("send")) throw new RaftCredentialError("CREDENTIAL_SCOPE_MISSING", "The stored Raft Agent credential does not include the send capability required by this SDK");
|
|
6714
|
+
return createRaftClient({
|
|
6715
|
+
serverUrl: validated.serverUrl,
|
|
6716
|
+
credential: validated.credential,
|
|
6717
|
+
fetch: options.fetch,
|
|
6718
|
+
headers: options.headers,
|
|
6719
|
+
retry: options.retry,
|
|
6720
|
+
throttle: options.throttle
|
|
6721
|
+
});
|
|
6722
|
+
}
|
|
6723
|
+
const external_node_crypto_namespaceObject = require("node:crypto");
|
|
6724
|
+
const external_node_fs_namespaceObject = require("node:fs");
|
|
6725
|
+
const promises_namespaceObject = require("node:fs/promises");
|
|
6726
|
+
const external_node_path_namespaceObject = require("node:path");
|
|
6727
|
+
const MAX_CREDENTIAL_FILE_BYTES = 65536;
|
|
6728
|
+
function createFileCredentialStore(filePath) {
|
|
6729
|
+
if (!filePath.trim() || !(0, external_node_path_namespaceObject.isAbsolute)(filePath)) throw new RaftSdkConfigurationError("INVALID_CREDENTIAL_PATH", "Raft credential file path must be an explicit absolute path");
|
|
6730
|
+
return {
|
|
6731
|
+
load: ()=>loadFileCredential(filePath),
|
|
6732
|
+
async save (record) {
|
|
6733
|
+
const validated = parseStoredRaftCredential(record);
|
|
6734
|
+
if (!validated) throw new RaftCredentialError("CREDENTIAL_STORE_WRITE_FAILED", "Refusing to write an invalid Raft Agent credential record");
|
|
6735
|
+
const directory = (0, external_node_path_namespaceObject.dirname)(filePath);
|
|
6736
|
+
const temporaryPath = `${filePath}.${process.pid}.${(0, external_node_crypto_namespaceObject.randomUUID)()}.tmp`;
|
|
6737
|
+
let handle;
|
|
6738
|
+
try {
|
|
6739
|
+
await (0, promises_namespaceObject.mkdir)(directory, {
|
|
6740
|
+
recursive: true,
|
|
6741
|
+
mode: 448
|
|
6742
|
+
});
|
|
6743
|
+
const existing = await loadFileCredential(filePath);
|
|
6744
|
+
if (existing) return void assertSameCredential(existing, validated);
|
|
6745
|
+
handle = await (0, promises_namespaceObject.open)(temporaryPath, "wx", 384);
|
|
6746
|
+
await handle.writeFile(`${JSON.stringify(validated, null, 2)}\n`, "utf8");
|
|
6747
|
+
await handle.sync();
|
|
6748
|
+
await (0, promises_namespaceObject.chmod)(temporaryPath, 384);
|
|
6749
|
+
await handle.close();
|
|
6750
|
+
handle = void 0;
|
|
6751
|
+
try {
|
|
6752
|
+
await (0, promises_namespaceObject.link)(temporaryPath, filePath);
|
|
6753
|
+
} catch (error) {
|
|
6754
|
+
if ("EEXIST" !== error.code) throw error;
|
|
6755
|
+
const winner = await loadFileCredential(filePath);
|
|
6756
|
+
if (!winner) throw error;
|
|
6757
|
+
assertSameCredential(winner, validated);
|
|
6758
|
+
}
|
|
6759
|
+
await (0, promises_namespaceObject.unlink)(temporaryPath);
|
|
6760
|
+
} catch (error) {
|
|
6761
|
+
if (handle) await handle.close().catch(()=>void 0);
|
|
6762
|
+
await (0, promises_namespaceObject.unlink)(temporaryPath).catch(()=>void 0);
|
|
6763
|
+
if (error instanceof RaftCredentialError) throw error;
|
|
6764
|
+
throw new RaftCredentialError("CREDENTIAL_STORE_WRITE_FAILED", "The Raft Agent credential could not be written atomically");
|
|
6765
|
+
}
|
|
6766
|
+
}
|
|
6767
|
+
};
|
|
6768
|
+
}
|
|
6769
|
+
async function loadFileCredential(filePath) {
|
|
6770
|
+
let initialMetadata;
|
|
6771
|
+
try {
|
|
6772
|
+
initialMetadata = await (0, promises_namespaceObject.lstat)(filePath);
|
|
6773
|
+
} catch (error) {
|
|
6774
|
+
if ("ENOENT" === error.code) return null;
|
|
6775
|
+
throw fileReadError();
|
|
6776
|
+
}
|
|
6777
|
+
if (!initialMetadata.isFile() || initialMetadata.isSymbolicLink() || initialMetadata.size > MAX_CREDENTIAL_FILE_BYTES) throw fileReadError();
|
|
6778
|
+
let parsed;
|
|
6779
|
+
let handle;
|
|
6780
|
+
try {
|
|
6781
|
+
const noFollow = "win32" === process.platform ? 0 : external_node_fs_namespaceObject.constants.O_NOFOLLOW;
|
|
6782
|
+
handle = await (0, promises_namespaceObject.open)(filePath, external_node_fs_namespaceObject.constants.O_RDONLY | noFollow);
|
|
6783
|
+
const openedMetadata = await handle.stat();
|
|
6784
|
+
if (!openedMetadata.isFile() || openedMetadata.size > MAX_CREDENTIAL_FILE_BYTES || initialMetadata.dev !== openedMetadata.dev || initialMetadata.ino !== openedMetadata.ino) throw fileReadError();
|
|
6785
|
+
if ("win32" !== process.platform && (63 & openedMetadata.mode) !== 0) throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The Raft credential file permissions are too broad; expected mode 0600");
|
|
6786
|
+
parsed = JSON.parse(await handle.readFile("utf8"));
|
|
6787
|
+
} catch (error) {
|
|
6788
|
+
if (error instanceof RaftCredentialError) throw error;
|
|
6789
|
+
throw fileReadError();
|
|
6790
|
+
} finally{
|
|
6791
|
+
await handle?.close().catch(()=>void 0);
|
|
6792
|
+
}
|
|
6793
|
+
const record = parseStoredRaftCredential(parsed);
|
|
6794
|
+
if (!record) throw fileReadError();
|
|
6795
|
+
return record;
|
|
6796
|
+
}
|
|
6797
|
+
function assertSameCredential(existing, candidate) {
|
|
6798
|
+
if (existing.credential !== candidate.credential) throw new RaftCredentialError("CREDENTIAL_STORE_CONFLICT", "The credential store already contains a different Raft Agent credential");
|
|
6799
|
+
}
|
|
6800
|
+
function fileReadError() {
|
|
6801
|
+
return new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The Raft credential file is invalid or unreadable");
|
|
6802
|
+
}
|
|
6803
|
+
exports.RaftCredentialError = __webpack_exports__.RaftCredentialError;
|
|
6582
6804
|
exports.RaftSdkConfigurationError = __webpack_exports__.RaftSdkConfigurationError;
|
|
6805
|
+
exports.bootstrapRaftCredential = __webpack_exports__.bootstrapRaftCredential;
|
|
6806
|
+
exports.createFileCredentialStore = __webpack_exports__.createFileCredentialStore;
|
|
6583
6807
|
exports.createRaftClient = __webpack_exports__.createRaftClient;
|
|
6808
|
+
exports.createRaftClientFromStore = __webpack_exports__.createRaftClientFromStore;
|
|
6584
6809
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
6810
|
+
"RaftCredentialError",
|
|
6585
6811
|
"RaftSdkConfigurationError",
|
|
6586
|
-
"
|
|
6812
|
+
"bootstrapRaftCredential",
|
|
6813
|
+
"createFileCredentialStore",
|
|
6814
|
+
"createRaftClient",
|
|
6815
|
+
"createRaftClientFromStore"
|
|
6587
6816
|
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
6588
6817
|
Object.defineProperty(exports, '__esModule', {
|
|
6589
6818
|
value: true
|
package/dist/esm/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { chmod, link as promises_link, lstat, mkdir, open as promises_open, unlink } from "node:fs/promises";
|
|
4
|
+
import { dirname, isAbsolute } from "node:path";
|
|
1
5
|
var __webpack_require__ = {};
|
|
2
6
|
(()=>{
|
|
3
7
|
__webpack_require__.d = (exports, getters, values)=>{
|
|
@@ -209,7 +213,7 @@ __webpack_require__.d(classic_schemas_namespaceObject, {
|
|
|
209
213
|
preprocess: ()=>preprocess,
|
|
210
214
|
promise: ()=>promise,
|
|
211
215
|
readonly: ()=>readonly,
|
|
212
|
-
record: ()=>
|
|
216
|
+
record: ()=>schemas_record,
|
|
213
217
|
refine: ()=>refine,
|
|
214
218
|
set: ()=>schemas_set,
|
|
215
219
|
strictObject: ()=>strictObject,
|
|
@@ -5734,7 +5738,7 @@ const ZodRecord = /*@__PURE__*/ $constructor("ZodRecord", (inst, def)=>{
|
|
|
5734
5738
|
inst.keyType = def.keyType;
|
|
5735
5739
|
inst.valueType = def.valueType;
|
|
5736
5740
|
});
|
|
5737
|
-
function
|
|
5741
|
+
function schemas_record(keyType, valueType, params) {
|
|
5738
5742
|
return new ZodRecord({
|
|
5739
5743
|
type: "record",
|
|
5740
5744
|
keyType,
|
|
@@ -6173,7 +6177,7 @@ function schemas_json(params) {
|
|
|
6173
6177
|
schemas_boolean(),
|
|
6174
6178
|
schemas_null(),
|
|
6175
6179
|
schemas_array(jsonSchema),
|
|
6176
|
-
|
|
6180
|
+
schemas_record(schemas_string(), jsonSchema)
|
|
6177
6181
|
]));
|
|
6178
6182
|
return jsonSchema;
|
|
6179
6183
|
}
|
|
@@ -6556,6 +6560,7 @@ function requireServerUrl(value) {
|
|
|
6556
6560
|
function requireAgentCredential(value) {
|
|
6557
6561
|
const credential = value.trim();
|
|
6558
6562
|
if (!credential) throw new RaftSdkConfigurationError("MISSING_AGENT_CREDENTIAL", "Raft Agent credential is required");
|
|
6563
|
+
if (!credential.startsWith("sk_agent_") || 9 === credential.length) throw new RaftSdkConfigurationError("INVALID_AGENT_CREDENTIAL", "Raft Agent credential must be a long-lived External Agent credential");
|
|
6559
6564
|
return credential;
|
|
6560
6565
|
}
|
|
6561
6566
|
function createRaftClient(options) {
|
|
@@ -6572,4 +6577,216 @@ function createRaftClient(options) {
|
|
|
6572
6577
|
}
|
|
6573
6578
|
});
|
|
6574
6579
|
}
|
|
6575
|
-
|
|
6580
|
+
function currentTimeMs() {
|
|
6581
|
+
return Date.now();
|
|
6582
|
+
}
|
|
6583
|
+
function currentDate() {
|
|
6584
|
+
return new Date(currentTimeMs());
|
|
6585
|
+
}
|
|
6586
|
+
class RaftCredentialError extends Error {
|
|
6587
|
+
code;
|
|
6588
|
+
name = "RaftCredentialError";
|
|
6589
|
+
constructor(code, message){
|
|
6590
|
+
super(message), this.code = code;
|
|
6591
|
+
}
|
|
6592
|
+
}
|
|
6593
|
+
function isNonEmptyString(value) {
|
|
6594
|
+
return "string" == typeof value && value.trim().length > 0;
|
|
6595
|
+
}
|
|
6596
|
+
function parseIdentityResponse(value) {
|
|
6597
|
+
if (!value || "object" != typeof value || Array.isArray(value)) return null;
|
|
6598
|
+
const input = value;
|
|
6599
|
+
if (!isNonEmptyString(input.agentId) || !isNonEmptyString(input.agentName) || null !== input.agentDisplayName && "string" != typeof input.agentDisplayName || !isNonEmptyString(input.serverId) || !isNonEmptyString(input.credentialId) || !Array.isArray(input.scopes) || !input.scopes.every(isNonEmptyString)) return null;
|
|
6600
|
+
return {
|
|
6601
|
+
agentId: input.agentId,
|
|
6602
|
+
agentName: input.agentName,
|
|
6603
|
+
agentDisplayName: input.agentDisplayName,
|
|
6604
|
+
serverId: input.serverId,
|
|
6605
|
+
credentialId: input.credentialId,
|
|
6606
|
+
scopes: [
|
|
6607
|
+
...input.scopes
|
|
6608
|
+
]
|
|
6609
|
+
};
|
|
6610
|
+
}
|
|
6611
|
+
function parseStoredRaftCredential(value) {
|
|
6612
|
+
if (!value || "object" != typeof value || Array.isArray(value)) return null;
|
|
6613
|
+
const input = value;
|
|
6614
|
+
if (1 !== input.schemaVersion || !isNonEmptyString(input.serverUrl) || !isNonEmptyString(input.credential) || !isNonEmptyString(input.storedAt)) return null;
|
|
6615
|
+
const identity = parseIdentityResponse(input);
|
|
6616
|
+
if (!identity || Number.isNaN(Date.parse(input.storedAt))) return null;
|
|
6617
|
+
let serverUrl;
|
|
6618
|
+
let credential;
|
|
6619
|
+
try {
|
|
6620
|
+
serverUrl = requireServerUrl(input.serverUrl);
|
|
6621
|
+
credential = requireAgentCredential(input.credential);
|
|
6622
|
+
} catch {
|
|
6623
|
+
return null;
|
|
6624
|
+
}
|
|
6625
|
+
return {
|
|
6626
|
+
schemaVersion: 1,
|
|
6627
|
+
serverUrl,
|
|
6628
|
+
credential,
|
|
6629
|
+
storedAt: input.storedAt,
|
|
6630
|
+
...identity
|
|
6631
|
+
};
|
|
6632
|
+
}
|
|
6633
|
+
async function bootstrapRaftCredential(options) {
|
|
6634
|
+
const serverUrl = requireServerUrl(options.serverUrl);
|
|
6635
|
+
const credential = requireAgentCredential(options.credential);
|
|
6636
|
+
const httpFetch = options.fetch ?? globalThis.fetch;
|
|
6637
|
+
let existing;
|
|
6638
|
+
try {
|
|
6639
|
+
existing = await options.store.load();
|
|
6640
|
+
} catch {
|
|
6641
|
+
throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The existing Agent credential could not be checked before saving");
|
|
6642
|
+
}
|
|
6643
|
+
if (existing) {
|
|
6644
|
+
const validatedExisting = parseStoredRaftCredential(existing);
|
|
6645
|
+
if (!validatedExisting) throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The existing Agent credential record is invalid");
|
|
6646
|
+
existing = validatedExisting;
|
|
6647
|
+
if (existing.serverUrl !== serverUrl || existing.credential !== credential) throw new RaftCredentialError("CREDENTIAL_STORE_CONFLICT", "The credential store already contains a different Raft Agent credential");
|
|
6648
|
+
}
|
|
6649
|
+
let response;
|
|
6650
|
+
try {
|
|
6651
|
+
response = await httpFetch(`${serverUrl}/internal/agent-api/`, {
|
|
6652
|
+
method: "GET",
|
|
6653
|
+
headers: {
|
|
6654
|
+
authorization: `Bearer ${credential}`
|
|
6655
|
+
}
|
|
6656
|
+
});
|
|
6657
|
+
} catch {
|
|
6658
|
+
throw new RaftCredentialError("CREDENTIAL_VERIFY_FAILED", "Could not reach the Raft Server to verify the Agent credential");
|
|
6659
|
+
}
|
|
6660
|
+
if (!response.ok) {
|
|
6661
|
+
if (401 === response.status || 403 === response.status) throw new RaftCredentialError("CREDENTIAL_REJECTED", "The Raft Server rejected the Agent credential");
|
|
6662
|
+
throw new RaftCredentialError("CREDENTIAL_VERIFY_FAILED", `The Raft Server could not verify the Agent credential (status ${response.status})`);
|
|
6663
|
+
}
|
|
6664
|
+
let payload;
|
|
6665
|
+
try {
|
|
6666
|
+
payload = await response.json();
|
|
6667
|
+
} catch {
|
|
6668
|
+
throw new RaftCredentialError("CREDENTIAL_RESPONSE_INVALID", "The Raft Server returned an invalid Agent identity response");
|
|
6669
|
+
}
|
|
6670
|
+
const identity = parseIdentityResponse(payload);
|
|
6671
|
+
if (!identity) throw new RaftCredentialError("CREDENTIAL_RESPONSE_INVALID", "The Raft Server returned an invalid Agent identity response");
|
|
6672
|
+
if (!identity.scopes.includes("send")) throw new RaftCredentialError("CREDENTIAL_SCOPE_MISSING", "The Raft Agent credential does not include the send capability required by this SDK");
|
|
6673
|
+
if (existing && (existing.serverId !== identity.serverId || existing.agentId !== identity.agentId)) throw new RaftCredentialError("CREDENTIAL_STORE_CONFLICT", "The credential store is already bound to a different Raft Agent identity");
|
|
6674
|
+
if (existing) return {
|
|
6675
|
+
serverUrl,
|
|
6676
|
+
...identity
|
|
6677
|
+
};
|
|
6678
|
+
const record = {
|
|
6679
|
+
schemaVersion: 1,
|
|
6680
|
+
serverUrl,
|
|
6681
|
+
credential,
|
|
6682
|
+
storedAt: currentDate().toISOString(),
|
|
6683
|
+
...identity
|
|
6684
|
+
};
|
|
6685
|
+
try {
|
|
6686
|
+
await options.store.save(record);
|
|
6687
|
+
} catch (error) {
|
|
6688
|
+
if (error instanceof RaftCredentialError && "CREDENTIAL_STORE_CONFLICT" === error.code) throw error;
|
|
6689
|
+
throw new RaftCredentialError("CREDENTIAL_STORE_WRITE_FAILED", "The verified Agent credential could not be saved");
|
|
6690
|
+
}
|
|
6691
|
+
return {
|
|
6692
|
+
serverUrl,
|
|
6693
|
+
...identity
|
|
6694
|
+
};
|
|
6695
|
+
}
|
|
6696
|
+
async function createRaftClientFromStore(options) {
|
|
6697
|
+
let record;
|
|
6698
|
+
try {
|
|
6699
|
+
record = await options.store.load();
|
|
6700
|
+
} catch {
|
|
6701
|
+
throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The stored Agent credential could not be loaded");
|
|
6702
|
+
}
|
|
6703
|
+
if (!record) throw new RaftCredentialError("CREDENTIAL_NOT_FOUND", "No stored Raft Agent credential was found");
|
|
6704
|
+
const validated = parseStoredRaftCredential(record);
|
|
6705
|
+
if (!validated) throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The stored Agent credential is invalid");
|
|
6706
|
+
if (!validated.scopes.includes("send")) throw new RaftCredentialError("CREDENTIAL_SCOPE_MISSING", "The stored Raft Agent credential does not include the send capability required by this SDK");
|
|
6707
|
+
return createRaftClient({
|
|
6708
|
+
serverUrl: validated.serverUrl,
|
|
6709
|
+
credential: validated.credential,
|
|
6710
|
+
fetch: options.fetch,
|
|
6711
|
+
headers: options.headers,
|
|
6712
|
+
retry: options.retry,
|
|
6713
|
+
throttle: options.throttle
|
|
6714
|
+
});
|
|
6715
|
+
}
|
|
6716
|
+
const MAX_CREDENTIAL_FILE_BYTES = 65536;
|
|
6717
|
+
function createFileCredentialStore(filePath) {
|
|
6718
|
+
if (!filePath.trim() || !isAbsolute(filePath)) throw new RaftSdkConfigurationError("INVALID_CREDENTIAL_PATH", "Raft credential file path must be an explicit absolute path");
|
|
6719
|
+
return {
|
|
6720
|
+
load: ()=>loadFileCredential(filePath),
|
|
6721
|
+
async save (record) {
|
|
6722
|
+
const validated = parseStoredRaftCredential(record);
|
|
6723
|
+
if (!validated) throw new RaftCredentialError("CREDENTIAL_STORE_WRITE_FAILED", "Refusing to write an invalid Raft Agent credential record");
|
|
6724
|
+
const directory = dirname(filePath);
|
|
6725
|
+
const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
6726
|
+
let handle;
|
|
6727
|
+
try {
|
|
6728
|
+
await mkdir(directory, {
|
|
6729
|
+
recursive: true,
|
|
6730
|
+
mode: 448
|
|
6731
|
+
});
|
|
6732
|
+
const existing = await loadFileCredential(filePath);
|
|
6733
|
+
if (existing) return void assertSameCredential(existing, validated);
|
|
6734
|
+
handle = await promises_open(temporaryPath, "wx", 384);
|
|
6735
|
+
await handle.writeFile(`${JSON.stringify(validated, null, 2)}\n`, "utf8");
|
|
6736
|
+
await handle.sync();
|
|
6737
|
+
await chmod(temporaryPath, 384);
|
|
6738
|
+
await handle.close();
|
|
6739
|
+
handle = void 0;
|
|
6740
|
+
try {
|
|
6741
|
+
await promises_link(temporaryPath, filePath);
|
|
6742
|
+
} catch (error) {
|
|
6743
|
+
if ("EEXIST" !== error.code) throw error;
|
|
6744
|
+
const winner = await loadFileCredential(filePath);
|
|
6745
|
+
if (!winner) throw error;
|
|
6746
|
+
assertSameCredential(winner, validated);
|
|
6747
|
+
}
|
|
6748
|
+
await unlink(temporaryPath);
|
|
6749
|
+
} catch (error) {
|
|
6750
|
+
if (handle) await handle.close().catch(()=>void 0);
|
|
6751
|
+
await unlink(temporaryPath).catch(()=>void 0);
|
|
6752
|
+
if (error instanceof RaftCredentialError) throw error;
|
|
6753
|
+
throw new RaftCredentialError("CREDENTIAL_STORE_WRITE_FAILED", "The Raft Agent credential could not be written atomically");
|
|
6754
|
+
}
|
|
6755
|
+
}
|
|
6756
|
+
};
|
|
6757
|
+
}
|
|
6758
|
+
async function loadFileCredential(filePath) {
|
|
6759
|
+
let initialMetadata;
|
|
6760
|
+
try {
|
|
6761
|
+
initialMetadata = await lstat(filePath);
|
|
6762
|
+
} catch (error) {
|
|
6763
|
+
if ("ENOENT" === error.code) return null;
|
|
6764
|
+
throw fileReadError();
|
|
6765
|
+
}
|
|
6766
|
+
if (!initialMetadata.isFile() || initialMetadata.isSymbolicLink() || initialMetadata.size > MAX_CREDENTIAL_FILE_BYTES) throw fileReadError();
|
|
6767
|
+
let parsed;
|
|
6768
|
+
let handle;
|
|
6769
|
+
try {
|
|
6770
|
+
const noFollow = "win32" === process.platform ? 0 : constants.O_NOFOLLOW;
|
|
6771
|
+
handle = await promises_open(filePath, constants.O_RDONLY | noFollow);
|
|
6772
|
+
const openedMetadata = await handle.stat();
|
|
6773
|
+
if (!openedMetadata.isFile() || openedMetadata.size > MAX_CREDENTIAL_FILE_BYTES || initialMetadata.dev !== openedMetadata.dev || initialMetadata.ino !== openedMetadata.ino) throw fileReadError();
|
|
6774
|
+
if ("win32" !== process.platform && (63 & openedMetadata.mode) !== 0) throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The Raft credential file permissions are too broad; expected mode 0600");
|
|
6775
|
+
parsed = JSON.parse(await handle.readFile("utf8"));
|
|
6776
|
+
} catch (error) {
|
|
6777
|
+
if (error instanceof RaftCredentialError) throw error;
|
|
6778
|
+
throw fileReadError();
|
|
6779
|
+
} finally{
|
|
6780
|
+
await handle?.close().catch(()=>void 0);
|
|
6781
|
+
}
|
|
6782
|
+
const record = parseStoredRaftCredential(parsed);
|
|
6783
|
+
if (!record) throw fileReadError();
|
|
6784
|
+
return record;
|
|
6785
|
+
}
|
|
6786
|
+
function assertSameCredential(existing, candidate) {
|
|
6787
|
+
if (existing.credential !== candidate.credential) throw new RaftCredentialError("CREDENTIAL_STORE_CONFLICT", "The credential store already contains a different Raft Agent credential");
|
|
6788
|
+
}
|
|
6789
|
+
function fileReadError() {
|
|
6790
|
+
return new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The Raft credential file is invalid or unreadable");
|
|
6791
|
+
}
|
|
6792
|
+
export { RaftCredentialError, RaftSdkConfigurationError, bootstrapRaftCredential, createFileCredentialStore, createRaftClient, createRaftClientFromStore };
|
package/dist/index.d.ts
CHANGED
|
@@ -178,6 +178,32 @@ declare interface AgentApiSendSentResponse {
|
|
|
178
178
|
[key: string]: unknown;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Validate an existing External Agent credential against Raft, derive its
|
|
183
|
+
* bound identity from the credential-authenticated `whoami` surface, and save
|
|
184
|
+
* it through the caller's explicit store. This does not mint or rotate a
|
|
185
|
+
* credential and never returns the credential bytes.
|
|
186
|
+
*/
|
|
187
|
+
export declare function bootstrapRaftCredential(options: BootstrapRaftCredentialOptions): Promise<RaftCredentialIdentity>;
|
|
188
|
+
|
|
189
|
+
export declare interface BootstrapRaftCredentialOptions {
|
|
190
|
+
/** Raft Server origin whose Agent API will validate the credential. */
|
|
191
|
+
serverUrl: string;
|
|
192
|
+
/** Existing long-lived External Agent credential (`sk_agent_*`). */
|
|
193
|
+
credential: string;
|
|
194
|
+
/** Caller-selected durable store. */
|
|
195
|
+
store: RaftCredentialStore;
|
|
196
|
+
/** Optional fetch implementation for runtimes, tests, or network policy wrappers. */
|
|
197
|
+
fetch?: typeof fetch;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Create a Node.js credential store at one explicit absolute path. The SDK
|
|
202
|
+
* never discovers CLI profiles or a home directory. Writes use a same-directory
|
|
203
|
+
* temporary file followed by atomic rename, and the final file is mode 0600.
|
|
204
|
+
*/
|
|
205
|
+
export declare function createFileCredentialStore(filePath: string): RaftCredentialStore;
|
|
206
|
+
|
|
181
207
|
/**
|
|
182
208
|
* Create a typed client for the credential-derived `/internal/agent-api/*`
|
|
183
209
|
* surface. The SDK does not read CLI profiles, environment variables, or the
|
|
@@ -185,6 +211,17 @@ declare interface AgentApiSendSentResponse {
|
|
|
185
211
|
*/
|
|
186
212
|
export declare function createRaftClient(options: CreateRaftClientOptions): RaftClient;
|
|
187
213
|
|
|
214
|
+
/** Load the caller-selected credential store and create a normal Raft client. */
|
|
215
|
+
export declare function createRaftClientFromStore(options: CreateRaftClientFromStoreOptions): Promise<RaftClient>;
|
|
216
|
+
|
|
217
|
+
export declare interface CreateRaftClientFromStoreOptions {
|
|
218
|
+
store: RaftCredentialStore;
|
|
219
|
+
fetch?: CreateRaftClientOptions["fetch"];
|
|
220
|
+
headers?: CreateRaftClientOptions["headers"];
|
|
221
|
+
retry?: CreateRaftClientOptions["retry"];
|
|
222
|
+
throttle?: CreateRaftClientOptions["throttle"];
|
|
223
|
+
}
|
|
224
|
+
|
|
188
225
|
export declare interface CreateRaftClientOptions {
|
|
189
226
|
/** Raft Server origin, for example `https://api.raft.build`. */
|
|
190
227
|
serverUrl: string;
|
|
@@ -210,12 +247,45 @@ export declare type RaftClientResult = AgentApiMessageClientResult;
|
|
|
210
247
|
|
|
211
248
|
export declare type RaftClientSuccess = AgentApiMessageClientSuccess;
|
|
212
249
|
|
|
250
|
+
export declare class RaftCredentialError extends Error {
|
|
251
|
+
readonly code: RaftCredentialErrorCode;
|
|
252
|
+
readonly name = "RaftCredentialError";
|
|
253
|
+
constructor(code: RaftCredentialErrorCode, message: string);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export declare type RaftCredentialErrorCode = "CREDENTIAL_REJECTED" | "CREDENTIAL_VERIFY_FAILED" | "CREDENTIAL_RESPONSE_INVALID" | "CREDENTIAL_SCOPE_MISSING" | "CREDENTIAL_STORE_READ_FAILED" | "CREDENTIAL_STORE_WRITE_FAILED" | "CREDENTIAL_STORE_CONFLICT" | "CREDENTIAL_NOT_FOUND";
|
|
257
|
+
|
|
258
|
+
export declare interface RaftCredentialIdentity {
|
|
259
|
+
serverUrl: string;
|
|
260
|
+
agentId: string;
|
|
261
|
+
agentName: string;
|
|
262
|
+
agentDisplayName: string | null;
|
|
263
|
+
serverId: string;
|
|
264
|
+
credentialId: string;
|
|
265
|
+
scopes: string[];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export declare interface RaftCredentialStore {
|
|
269
|
+
load(): Promise<StoredRaftCredential | null>;
|
|
270
|
+
/**
|
|
271
|
+
* Persist the first credential. Implementations must reject replacement by
|
|
272
|
+
* a different credential; saving the identical credential may be a no-op.
|
|
273
|
+
*/
|
|
274
|
+
save(record: StoredRaftCredential): Promise<void>;
|
|
275
|
+
}
|
|
276
|
+
|
|
213
277
|
export declare class RaftSdkConfigurationError extends Error {
|
|
214
278
|
readonly code: RaftSdkConfigurationErrorCode;
|
|
215
279
|
readonly name = "RaftSdkConfigurationError";
|
|
216
280
|
constructor(code: RaftSdkConfigurationErrorCode, message: string);
|
|
217
281
|
}
|
|
218
282
|
|
|
219
|
-
export declare type RaftSdkConfigurationErrorCode = "MISSING_SERVER_URL" | "INVALID_SERVER_URL" | "MISSING_AGENT_CREDENTIAL";
|
|
283
|
+
export declare type RaftSdkConfigurationErrorCode = "MISSING_SERVER_URL" | "INVALID_SERVER_URL" | "MISSING_AGENT_CREDENTIAL" | "INVALID_AGENT_CREDENTIAL" | "INVALID_CREDENTIAL_PATH";
|
|
284
|
+
|
|
285
|
+
export declare interface StoredRaftCredential extends RaftCredentialIdentity {
|
|
286
|
+
schemaVersion: 1;
|
|
287
|
+
credential: string;
|
|
288
|
+
storedAt: string;
|
|
289
|
+
}
|
|
220
290
|
|
|
221
291
|
export { }
|