aweshare 0.2.0 → 0.2.2
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 +3 -3
- package/apps/agent/dist/config.js +1 -1
- package/apps/agent/dist/connection.js +1 -1
- package/apps/hub/dist/admin.js +1 -1
- package/apps/hub/dist/consumer.js +1 -1
- package/apps/hub/dist/tokens.js +1 -1
- package/apps/hub/dist/tunnel.js +1 -1
- package/package.json +6 -6
- package/packages/protocol/dist/alias.d.ts +6 -0
- package/packages/protocol/dist/alias.js +23 -0
- package/packages/protocol/dist/alias.js.map +1 -0
- package/packages/protocol/dist/errors.d.ts +23 -0
- package/packages/protocol/dist/errors.js +23 -0
- package/packages/protocol/dist/errors.js.map +1 -0
- package/packages/protocol/dist/frames.d.ts +20 -0
- package/packages/protocol/dist/frames.js +54 -0
- package/packages/protocol/dist/frames.js.map +1 -0
- package/packages/protocol/dist/index.d.ts +5 -0
- package/packages/protocol/dist/index.js +6 -0
- package/packages/protocol/dist/index.js.map +1 -0
- package/packages/protocol/dist/messages.d.ts +86 -0
- package/packages/protocol/dist/messages.js +33 -0
- package/packages/protocol/dist/messages.js.map +1 -0
- package/packages/protocol/dist/tokens.d.ts +10 -0
- package/packages/protocol/dist/tokens.js +16 -0
- package/packages/protocol/dist/tokens.js.map +1 -0
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ Consumer (standard SDK, zero changes) Producer side
|
|
|
46
46
|
|
|
47
47
|
## Quickstart
|
|
48
48
|
|
|
49
|
-
Published as [`aweshare`](https://www.npmjs.com/package/aweshare) on npm (requires Node ≥ 22) and as a Docker image (`ghcr.io/
|
|
49
|
+
Published as [`aweshare`](https://www.npmjs.com/package/aweshare) on npm (requires Node ≥ 22) and as a Docker image (`ghcr.io/wehuman01/aweshare`). No clone needed.
|
|
50
50
|
|
|
51
51
|
### 1. Start the hub (operator, one VPS)
|
|
52
52
|
|
|
@@ -62,7 +62,7 @@ aweshare hub serve # listens on :8787 (put Caddy/nginx TLS in front)
|
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
64
|
docker run -d --name aweshare-hub --restart unless-stopped \
|
|
65
|
-
-p 127.0.0.1:8787:8787 -v "$PWD/data:/data" ghcr.io/
|
|
65
|
+
-p 127.0.0.1:8787:8787 -v "$PWD/data:/data" ghcr.io/wehuman01/aweshare:latest
|
|
66
66
|
docker exec aweshare-hub node apps/hub/dist/cli.js init # first run: prints the admin token, save it
|
|
67
67
|
```
|
|
68
68
|
|
|
@@ -232,7 +232,7 @@ aweshare hub serve
|
|
|
232
232
|
aweshare agent doctor
|
|
233
233
|
```
|
|
234
234
|
|
|
235
|
-
Releasing: push a `v*` tag with a matching `## [x.y.z]` section in `docs/CHANGELOG.md`; CI publishes `aweshare
|
|
235
|
+
Releasing: push a `v*` tag with a matching `## [x.y.z]` section in `docs/CHANGELOG.md`; CI publishes the `aweshare` package to npm via Trusted Publishing (OIDC, no token secret) and the Docker image to `ghcr.io/wehuman01/aweshare`.
|
|
236
236
|
|
|
237
237
|
Layout: `packages/protocol` (shared wire protocol) · `apps/hub` (HTTP+WS+SQLite+CLI) · `apps/agent` (CLI). Design docs live in `docs/specs/`; the changelog in `docs/CHANGELOG.md`; contribution scope in [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
238
238
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
|
-
import { isValidAlias, PROTOCOLS } from '
|
|
4
|
+
import { isValidAlias, PROTOCOLS } from '../../../packages/protocol/dist/index.js';
|
|
5
5
|
import { parse as parseToml } from 'smol-toml';
|
|
6
6
|
export function agentDir(env = process.env) {
|
|
7
7
|
return env.AWESHARE_AGENT_DIR ?? join(homedir(), '.aweshare');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BACKPRESSURE_LIMIT_BYTES, chunkBody, decodeChunkFrame, parseControlMessage, WIRE_VERSION, } from '
|
|
1
|
+
import { BACKPRESSURE_LIMIT_BYTES, chunkBody, decodeChunkFrame, parseControlMessage, WIRE_VERSION, } from '../../../packages/protocol/dist/index.js';
|
|
2
2
|
import pino, {} from 'pino';
|
|
3
3
|
import { WebSocket } from 'ws';
|
|
4
4
|
import { buildUpstreamRequest } from './adapters.js';
|
package/apps/hub/dist/admin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseAlias, roleForToken } from '
|
|
1
|
+
import { parseAlias, roleForToken } from '../../../packages/protocol/dist/index.js';
|
|
2
2
|
import { requireAdmin, requireConsumer, requireProducer } from './auth.js';
|
|
3
3
|
import { addGrant, getConsumerByName, getProducerByName, insertConsumer, insertProducer, listGrants, listTokens, queryUsage, removeGrant, revokeToken, setConsumerConfig, } from './db.js';
|
|
4
4
|
import { HttpError, readJson, sendJson } from './http.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseAlias } from '
|
|
1
|
+
import { parseAlias } from '../../../packages/protocol/dist/index.js';
|
|
2
2
|
import { requireConsumer } from './auth.js';
|
|
3
3
|
import { getGrant, getOffering, insertUsageEvent, listOfferingsForConsumer, sumTokensUsed, } from './db.js';
|
|
4
4
|
import { HttpError, readBody, sendError, sendJson } from './http.js';
|
package/apps/hub/dist/tokens.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { TOKEN_PREFIXES } from '
|
|
3
|
+
import { TOKEN_PREFIXES } from '../../../packages/protocol/dist/index.js';
|
|
4
4
|
/** 32 random bytes -> 43 base64url chars; matches TOKEN_PATTERN. */
|
|
5
5
|
export function generateToken(role) {
|
|
6
6
|
return TOKEN_PREFIXES[role] + randomBytes(32).toString('base64url');
|
package/apps/hub/dist/tunnel.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { EventEmitter } from 'node:events';
|
|
3
|
-
import { BACKPRESSURE_LIMIT_BYTES, chunkBody, decodeChunkFrame, isValidAlias, PROTOCOLS, parseControlMessage, WIRE_VERSION, } from '
|
|
3
|
+
import { BACKPRESSURE_LIMIT_BYTES, chunkBody, decodeChunkFrame, isValidAlias, PROTOCOLS, parseControlMessage, WIRE_VERSION, } from '../../../packages/protocol/dist/index.js';
|
|
4
4
|
import { WebSocket, WebSocketServer } from 'ws';
|
|
5
5
|
import { findProducerByHash, upsertOfferings } from './db.js';
|
|
6
6
|
import { hashToken } from './tokens.js';
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aweshare",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Open-source, local-first AI capability relay",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/
|
|
9
|
+
"url": "git+https://github.com/wehuman01/aweshare.git"
|
|
10
10
|
},
|
|
11
11
|
"bin": {
|
|
12
12
|
"aweshare": "./bin/aweshare.mjs"
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"apps/hub/dist",
|
|
20
20
|
"apps/hub/package.json",
|
|
21
21
|
"apps/agent/dist",
|
|
22
|
-
"apps/agent/package.json"
|
|
22
|
+
"apps/agent/package.json",
|
|
23
|
+
"packages/protocol/dist"
|
|
23
24
|
],
|
|
24
25
|
"publishConfig": {
|
|
25
26
|
"access": "public"
|
|
@@ -28,8 +29,7 @@
|
|
|
28
29
|
"better-sqlite3": "^12.2.0",
|
|
29
30
|
"pino": "^9.7.0",
|
|
30
31
|
"smol-toml": "^1.4.2",
|
|
31
|
-
"ws": "^8.18.3"
|
|
32
|
-
"aweshare-protocol": "0.2.0"
|
|
32
|
+
"ws": "^8.18.3"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@anthropic-ai/sdk": "^0.117.1",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"vitest": "^3.2.4"
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
|
-
"build": "tsc -b",
|
|
43
|
+
"build": "tsc -b && node scripts/inline-protocol.mjs",
|
|
44
44
|
"test": "vitest run",
|
|
45
45
|
"check": "biome check ."
|
|
46
46
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model alias: `namespace/name`, e.g. `peng/gpt-4o`.
|
|
3
|
+
* namespace == producer name; name is the public label, mapped to a real
|
|
4
|
+
* upstream model id by the producer's offering.
|
|
5
|
+
*/
|
|
6
|
+
const NAMESPACE = /^[a-z0-9][a-z0-9-]{1,31}$/;
|
|
7
|
+
const NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
8
|
+
export function parseAlias(alias) {
|
|
9
|
+
if (typeof alias !== 'string')
|
|
10
|
+
return null;
|
|
11
|
+
const slash = alias.indexOf('/');
|
|
12
|
+
if (slash <= 0 || slash !== alias.lastIndexOf('/'))
|
|
13
|
+
return null;
|
|
14
|
+
const namespace = alias.slice(0, slash);
|
|
15
|
+
const name = alias.slice(slash + 1);
|
|
16
|
+
if (!NAMESPACE.test(namespace) || !NAME.test(name))
|
|
17
|
+
return null;
|
|
18
|
+
return { namespace, name };
|
|
19
|
+
}
|
|
20
|
+
export function isValidAlias(alias) {
|
|
21
|
+
return parseAlias(alias) !== null;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=alias.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alias.js","sourceRoot":"","sources":["../src/alias.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,SAAS,GAAG,2BAA2B,CAAA;AAC7C,MAAM,IAAI,GAAG,6BAA6B,CAAA;AAO1C,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAChC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,KAAK,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IAC/D,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;IACvC,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAA;IACnC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAC/D,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAA;AAC5B,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,UAAU,CAAC,KAAK,CAAC,KAAK,IAAI,CAAA;AACnC,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Stable error codes shared by hub, agent and tunnel wire messages. */
|
|
2
|
+
export declare const ERROR_CODES: {
|
|
3
|
+
readonly PROTOCOL_MISMATCH: "PROTOCOL_MISMATCH";
|
|
4
|
+
readonly INVALID_KEY: "INVALID_KEY";
|
|
5
|
+
readonly NOT_GRANTED: "NOT_GRANTED";
|
|
6
|
+
readonly ALIAS_NOT_FOUND: "ALIAS_NOT_FOUND";
|
|
7
|
+
readonly RATE_LIMITED: "RATE_LIMITED";
|
|
8
|
+
readonly PRODUCER_MAX_CONCURRENCY: "PRODUCER_MAX_CONCURRENCY";
|
|
9
|
+
readonly GRANT_EXPIRED: "GRANT_EXPIRED";
|
|
10
|
+
readonly QUOTA_EXCEEDED: "QUOTA_EXCEEDED";
|
|
11
|
+
readonly UPSTREAM_ERROR: "UPSTREAM_ERROR";
|
|
12
|
+
readonly BACKEND_UNREACHABLE: "BACKEND_UNREACHABLE";
|
|
13
|
+
readonly BACKEND_TIMEOUT: "BACKEND_TIMEOUT";
|
|
14
|
+
readonly PRODUCER_OFFLINE: "PRODUCER_OFFLINE";
|
|
15
|
+
readonly BACKEND_DEGRADED: "BACKEND_DEGRADED";
|
|
16
|
+
readonly STREAM_IDLE_TIMEOUT: "STREAM_IDLE_TIMEOUT";
|
|
17
|
+
readonly TICKET_EXPIRED: "TICKET_EXPIRED";
|
|
18
|
+
readonly TUNNEL_CLOSED: "TUNNEL_CLOSED";
|
|
19
|
+
readonly BACKEND_NOT_FOUND: "BACKEND_NOT_FOUND";
|
|
20
|
+
readonly REQUEST_CANCELED: "REQUEST_CANCELED";
|
|
21
|
+
readonly WIRE_VERSION_MISMATCH: "WIRE_VERSION_MISMATCH";
|
|
22
|
+
};
|
|
23
|
+
export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Stable error codes shared by hub, agent and tunnel wire messages. */
|
|
2
|
+
export const ERROR_CODES = {
|
|
3
|
+
PROTOCOL_MISMATCH: 'PROTOCOL_MISMATCH',
|
|
4
|
+
INVALID_KEY: 'INVALID_KEY',
|
|
5
|
+
NOT_GRANTED: 'NOT_GRANTED',
|
|
6
|
+
ALIAS_NOT_FOUND: 'ALIAS_NOT_FOUND',
|
|
7
|
+
RATE_LIMITED: 'RATE_LIMITED',
|
|
8
|
+
PRODUCER_MAX_CONCURRENCY: 'PRODUCER_MAX_CONCURRENCY',
|
|
9
|
+
GRANT_EXPIRED: 'GRANT_EXPIRED',
|
|
10
|
+
QUOTA_EXCEEDED: 'QUOTA_EXCEEDED',
|
|
11
|
+
UPSTREAM_ERROR: 'UPSTREAM_ERROR',
|
|
12
|
+
BACKEND_UNREACHABLE: 'BACKEND_UNREACHABLE',
|
|
13
|
+
BACKEND_TIMEOUT: 'BACKEND_TIMEOUT',
|
|
14
|
+
PRODUCER_OFFLINE: 'PRODUCER_OFFLINE',
|
|
15
|
+
BACKEND_DEGRADED: 'BACKEND_DEGRADED',
|
|
16
|
+
STREAM_IDLE_TIMEOUT: 'STREAM_IDLE_TIMEOUT',
|
|
17
|
+
TICKET_EXPIRED: 'TICKET_EXPIRED',
|
|
18
|
+
TUNNEL_CLOSED: 'TUNNEL_CLOSED',
|
|
19
|
+
BACKEND_NOT_FOUND: 'BACKEND_NOT_FOUND',
|
|
20
|
+
REQUEST_CANCELED: 'REQUEST_CANCELED',
|
|
21
|
+
WIRE_VERSION_MISMATCH: 'WIRE_VERSION_MISMATCH',
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,iBAAiB,EAAE,mBAAmB;IACtC,WAAW,EAAE,aAAa;IAC1B,WAAW,EAAE,aAAa;IAC1B,eAAe,EAAE,iBAAiB;IAClC,YAAY,EAAE,cAAc;IAC5B,wBAAwB,EAAE,0BAA0B;IACpD,aAAa,EAAE,eAAe;IAC9B,cAAc,EAAE,gBAAgB;IAChC,cAAc,EAAE,gBAAgB;IAChC,mBAAmB,EAAE,qBAAqB;IAC1C,eAAe,EAAE,iBAAiB;IAClC,gBAAgB,EAAE,kBAAkB;IACpC,gBAAgB,EAAE,kBAAkB;IACpC,mBAAmB,EAAE,qBAAqB;IAC1C,cAAc,EAAE,gBAAgB;IAChC,aAAa,EAAE,eAAe;IAC9B,iBAAiB,EAAE,mBAAmB;IACtC,gBAAgB,EAAE,kBAAkB;IACpC,qBAAqB,EAAE,uBAAuB;CACtC,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binary data frame: `[16-byte requestId][payload]`, payload <= MAX_CHUNK_BYTES.
|
|
3
|
+
*
|
|
4
|
+
* Direction gives the frame its meaning — there is no type byte:
|
|
5
|
+
* - hub -> agent: request body chunk
|
|
6
|
+
* - agent -> hub: response body chunk
|
|
7
|
+
* This holds because a connection only ever carries requests in one direction.
|
|
8
|
+
*/
|
|
9
|
+
export declare const REQUEST_ID_BYTES = 16;
|
|
10
|
+
export declare const MAX_CHUNK_BYTES: number;
|
|
11
|
+
/** Sender pauses feeding its source stream above this pending buffer. */
|
|
12
|
+
export declare const BACKPRESSURE_LIMIT_BYTES: number;
|
|
13
|
+
export interface ChunkFrame {
|
|
14
|
+
requestId: string;
|
|
15
|
+
payload: Uint8Array;
|
|
16
|
+
}
|
|
17
|
+
export declare function encodeChunkFrame(requestId: string, payload: Uint8Array): Uint8Array;
|
|
18
|
+
export declare function decodeChunkFrame(data: ArrayBufferView | ArrayBuffer): ChunkFrame;
|
|
19
|
+
/** Split a body into chunk frames; an empty body yields zero frames. */
|
|
20
|
+
export declare function chunkBody(requestId: string, body: Uint8Array): Uint8Array[];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binary data frame: `[16-byte requestId][payload]`, payload <= MAX_CHUNK_BYTES.
|
|
3
|
+
*
|
|
4
|
+
* Direction gives the frame its meaning — there is no type byte:
|
|
5
|
+
* - hub -> agent: request body chunk
|
|
6
|
+
* - agent -> hub: response body chunk
|
|
7
|
+
* This holds because a connection only ever carries requests in one direction.
|
|
8
|
+
*/
|
|
9
|
+
export const REQUEST_ID_BYTES = 16;
|
|
10
|
+
export const MAX_CHUNK_BYTES = 64 * 1024;
|
|
11
|
+
/** Sender pauses feeding its source stream above this pending buffer. */
|
|
12
|
+
export const BACKPRESSURE_LIMIT_BYTES = 1024 * 1024;
|
|
13
|
+
const HEX = /^[0-9a-f]{32}$/;
|
|
14
|
+
export function encodeChunkFrame(requestId, payload) {
|
|
15
|
+
if (!HEX.test(requestId)) {
|
|
16
|
+
throw new Error(`invalid requestId (want 32 lowercase hex chars): ${requestId}`);
|
|
17
|
+
}
|
|
18
|
+
if (payload.byteLength > MAX_CHUNK_BYTES) {
|
|
19
|
+
throw new Error(`chunk too large: ${payload.byteLength} > ${MAX_CHUNK_BYTES}`);
|
|
20
|
+
}
|
|
21
|
+
const frame = new Uint8Array(REQUEST_ID_BYTES + payload.byteLength);
|
|
22
|
+
frame.set(hexToBytes(requestId), 0);
|
|
23
|
+
frame.set(payload, REQUEST_ID_BYTES);
|
|
24
|
+
return frame;
|
|
25
|
+
}
|
|
26
|
+
export function decodeChunkFrame(data) {
|
|
27
|
+
const bytes = data instanceof ArrayBuffer
|
|
28
|
+
? new Uint8Array(data)
|
|
29
|
+
: new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
30
|
+
if (bytes.byteLength < REQUEST_ID_BYTES) {
|
|
31
|
+
throw new Error(`binary frame too short: ${bytes.byteLength}`);
|
|
32
|
+
}
|
|
33
|
+
const idBytes = bytes.subarray(0, REQUEST_ID_BYTES);
|
|
34
|
+
let requestId = '';
|
|
35
|
+
for (const b of idBytes)
|
|
36
|
+
requestId += b.toString(16).padStart(2, '0');
|
|
37
|
+
return { requestId, payload: bytes.subarray(REQUEST_ID_BYTES) };
|
|
38
|
+
}
|
|
39
|
+
/** Split a body into chunk frames; an empty body yields zero frames. */
|
|
40
|
+
export function chunkBody(requestId, body) {
|
|
41
|
+
const frames = [];
|
|
42
|
+
for (let off = 0; off < body.byteLength; off += MAX_CHUNK_BYTES) {
|
|
43
|
+
frames.push(encodeChunkFrame(requestId, body.subarray(off, off + MAX_CHUNK_BYTES)));
|
|
44
|
+
}
|
|
45
|
+
return frames;
|
|
46
|
+
}
|
|
47
|
+
function hexToBytes(hex) {
|
|
48
|
+
const out = new Uint8Array(hex.length / 2);
|
|
49
|
+
for (let i = 0; i < out.length; i++) {
|
|
50
|
+
out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=frames.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frames.js","sourceRoot":"","sources":["../src/frames.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ErrorCode } from './errors.js';
|
|
2
|
+
export declare const WIRE_VERSION = 1;
|
|
3
|
+
export type Protocol = 'openai' | 'anthropic' | 'responses';
|
|
4
|
+
export declare const PROTOCOLS: readonly Protocol[];
|
|
5
|
+
/** Registered by the agent; alias must be `<producerName>/<name>`. */
|
|
6
|
+
export interface Offering {
|
|
7
|
+
alias: string;
|
|
8
|
+
protocol: Protocol;
|
|
9
|
+
/** Real upstream model id (e.g. the full ollama tag), may differ from the alias. */
|
|
10
|
+
upstreamModel: string;
|
|
11
|
+
backendId: string;
|
|
12
|
+
/** Per-offering concurrent request cap the hub enforces; default 1. */
|
|
13
|
+
maxConcurrency: number;
|
|
14
|
+
}
|
|
15
|
+
export type BackendState = 'ok' | 'degraded';
|
|
16
|
+
export interface BackendStatus {
|
|
17
|
+
state: BackendState;
|
|
18
|
+
/** Failure class when degraded: AUTH | QUOTA | HTTP | NETWORK | CANCELLED */
|
|
19
|
+
reason?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface RegisterMessage {
|
|
22
|
+
type: 'register';
|
|
23
|
+
offerings: Offering[];
|
|
24
|
+
}
|
|
25
|
+
export interface HeartbeatMessage {
|
|
26
|
+
type: 'heartbeat';
|
|
27
|
+
backendStatus: Record<string, BackendStatus>;
|
|
28
|
+
}
|
|
29
|
+
export interface ResponseHeadMessage {
|
|
30
|
+
type: 'response.head';
|
|
31
|
+
requestId: string;
|
|
32
|
+
status: number;
|
|
33
|
+
headers: Record<string, string>;
|
|
34
|
+
}
|
|
35
|
+
export interface ResponseEndMessage {
|
|
36
|
+
type: 'response.end';
|
|
37
|
+
requestId: string;
|
|
38
|
+
}
|
|
39
|
+
export interface ResponseErrorMessage {
|
|
40
|
+
type: 'response.error';
|
|
41
|
+
requestId: string;
|
|
42
|
+
code: ErrorCode | string;
|
|
43
|
+
message: string;
|
|
44
|
+
}
|
|
45
|
+
export interface HelloMessage {
|
|
46
|
+
type: 'hello';
|
|
47
|
+
wireVersion: number;
|
|
48
|
+
}
|
|
49
|
+
export interface RegisteredMessage {
|
|
50
|
+
type: 'registered';
|
|
51
|
+
accepted: string[];
|
|
52
|
+
rejected: {
|
|
53
|
+
alias: string;
|
|
54
|
+
reason: string;
|
|
55
|
+
}[];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Tunnel path is version-free: `/chat/completions`, `/messages` or `/responses`.
|
|
59
|
+
* The agent's protocol adapter rebuilds the upstream URL from backend rules
|
|
60
|
+
* (openai-style baseUrl includes /v1, anthropic baseUrl does not).
|
|
61
|
+
*/
|
|
62
|
+
export interface RequestStartMessage {
|
|
63
|
+
type: 'request.start';
|
|
64
|
+
requestId: string;
|
|
65
|
+
protocol: Protocol;
|
|
66
|
+
/** Backend the hub routed to (from the offering); the agent maps it to its config. */
|
|
67
|
+
backendId: string;
|
|
68
|
+
method: string;
|
|
69
|
+
path: string;
|
|
70
|
+
query: Record<string, string> | null;
|
|
71
|
+
/** Sanitized: consumer auth and hop-by-hop headers already removed. */
|
|
72
|
+
headers: Record<string, string>;
|
|
73
|
+
}
|
|
74
|
+
export interface RequestEndMessage {
|
|
75
|
+
type: 'request.end';
|
|
76
|
+
requestId: string;
|
|
77
|
+
}
|
|
78
|
+
export interface RequestCancelMessage {
|
|
79
|
+
type: 'request.cancel';
|
|
80
|
+
requestId: string;
|
|
81
|
+
}
|
|
82
|
+
export type AgentToHubMessage = RegisterMessage | HeartbeatMessage | ResponseHeadMessage | ResponseEndMessage | ResponseErrorMessage;
|
|
83
|
+
export type HubToAgentMessage = HelloMessage | RegisteredMessage | RequestStartMessage | RequestEndMessage | RequestCancelMessage;
|
|
84
|
+
export type ControlMessage = AgentToHubMessage | HubToAgentMessage;
|
|
85
|
+
/** Parse a JSON text frame into a control message; throws on garbage. */
|
|
86
|
+
export declare function parseControlMessage(raw: string): ControlMessage;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const WIRE_VERSION = 1;
|
|
2
|
+
export const PROTOCOLS = ['openai', 'anthropic', 'responses'];
|
|
3
|
+
const KNOWN_TYPES = new Set([
|
|
4
|
+
'register',
|
|
5
|
+
'heartbeat',
|
|
6
|
+
'response.head',
|
|
7
|
+
'response.end',
|
|
8
|
+
'response.error',
|
|
9
|
+
'hello',
|
|
10
|
+
'registered',
|
|
11
|
+
'request.start',
|
|
12
|
+
'request.end',
|
|
13
|
+
'request.cancel',
|
|
14
|
+
]);
|
|
15
|
+
/** Parse a JSON text frame into a control message; throws on garbage. */
|
|
16
|
+
export function parseControlMessage(raw) {
|
|
17
|
+
let parsed;
|
|
18
|
+
try {
|
|
19
|
+
parsed = JSON.parse(raw);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
throw new Error('control frame is not valid JSON');
|
|
23
|
+
}
|
|
24
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
25
|
+
throw new Error('control frame is not an object');
|
|
26
|
+
}
|
|
27
|
+
const type = parsed.type;
|
|
28
|
+
if (typeof type !== 'string' || !KNOWN_TYPES.has(type)) {
|
|
29
|
+
throw new Error(`unknown control message type: ${String(type)}`);
|
|
30
|
+
}
|
|
31
|
+
return parsed;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAA;AAI7B,MAAM,CAAC,MAAM,SAAS,GAAwB,CAAC,QAAQ,EAAE,WAAW,EAAE,WAAW,CAAC,CAAA;AA6GlF,MAAM,WAAW,GAAG,IAAI,GAAG,CAAS;IAClC,UAAU;IACV,WAAW;IACX,eAAe;IACf,cAAc;IACd,gBAAgB;IAChB,OAAO;IACP,YAAY;IACZ,eAAe;IACf,aAAa;IACb,gBAAgB;CACjB,CAAC,CAAA;AAEF,yEAAyE;AACzE,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC7C,IAAI,MAAe,CAAA;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAA;IACpD,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAA;IACnD,CAAC;IACD,MAAM,IAAI,GAAI,MAA6B,CAAC,IAAI,CAAA;IAChD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CAAC,iCAAiC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAClE,CAAC;IACD,OAAO,MAAwB,CAAA;AACjC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Token prefixes identify a token's role without a database lookup. */
|
|
2
|
+
export declare const TOKEN_PREFIXES: {
|
|
3
|
+
readonly producer: "asp_";
|
|
4
|
+
readonly consumer: "asc_";
|
|
5
|
+
readonly admin: "asa_";
|
|
6
|
+
};
|
|
7
|
+
export type TokenRole = keyof typeof TOKEN_PREFIXES;
|
|
8
|
+
/** Tokens are `prefix + base64url(32 random bytes)`; base64url alphabet is [-_A-Za-z0-9]. */
|
|
9
|
+
export declare const TOKEN_PATTERN: RegExp;
|
|
10
|
+
export declare function roleForToken(token: string): TokenRole | null;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Token prefixes identify a token's role without a database lookup. */
|
|
2
|
+
export const TOKEN_PREFIXES = {
|
|
3
|
+
producer: 'asp_',
|
|
4
|
+
consumer: 'asc_',
|
|
5
|
+
admin: 'asa_',
|
|
6
|
+
};
|
|
7
|
+
/** Tokens are `prefix + base64url(32 random bytes)`; base64url alphabet is [-_A-Za-z0-9]. */
|
|
8
|
+
export const TOKEN_PATTERN = /^as[pca]_[A-Za-z0-9_-]{43}$/;
|
|
9
|
+
export function roleForToken(token) {
|
|
10
|
+
for (const [role, prefix] of Object.entries(TOKEN_PREFIXES)) {
|
|
11
|
+
if (token.startsWith(prefix))
|
|
12
|
+
return role;
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,QAAQ,EAAE,MAAM;IAChB,QAAQ,EAAE,MAAM;IAChB,KAAK,EAAE,MAAM;CACL,CAAA;AAIV,6FAA6F;AAC7F,MAAM,CAAC,MAAM,aAAa,GAAG,6BAA6B,CAAA;AAE1D,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,cAAc,CAA0B,EAAE,CAAC;QACrF,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAA;IAC3C,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
|