@cortexkit/subc-client 0.5.0 → 0.8.0
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/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +17 -5
- package/dist/auth.js.map +1 -1
- package/dist/client.d.ts +57 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +166 -15
- package/dist/client.js.map +1 -1
- package/dist/connection-file.d.ts.map +1 -1
- package/dist/connection-file.js +6 -1
- package/dist/connection-file.js.map +1 -1
- package/dist/envelope.d.ts.map +1 -1
- package/dist/envelope.js +36 -1
- package/dist/envelope.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/provider.d.ts +5 -25
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +27 -30
- package/dist/provider.js.map +1 -1
- package/dist/socket.d.ts +12 -0
- package/dist/socket.d.ts.map +1 -1
- package/dist/socket.js +33 -3
- package/dist/socket.js.map +1 -1
- package/package.json +1 -1
- package/src/auth.ts +21 -9
- package/src/client.ts +208 -21
- package/src/connection-file.ts +6 -1
- package/src/envelope.ts +54 -1
- package/src/index.ts +1 -5
- package/src/provider.ts +34 -57
- package/src/socket.ts +41 -3
package/src/provider.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type ReconnectBackoff,
|
|
8
8
|
type RequestOptions,
|
|
9
9
|
type RouteTarget,
|
|
10
|
+
type SubcCallErrorKind,
|
|
10
11
|
} from "./client.js";
|
|
11
12
|
import { ConnectionFileError, readConnectionFile, type ConnectionInfo } from "./connection-file.js";
|
|
12
13
|
import {
|
|
@@ -33,6 +34,7 @@ import {
|
|
|
33
34
|
SocketWriteNotQueuedError,
|
|
34
35
|
SocketWriteQueuedError,
|
|
35
36
|
SubcSocket,
|
|
37
|
+
writeBorrowed,
|
|
36
38
|
} from "./socket.js";
|
|
37
39
|
|
|
38
40
|
const DEFAULT_HANDSHAKE_TIMEOUT_MS = 10_000;
|
|
@@ -53,7 +55,6 @@ export type ObservabilityKind = "snapshot" | "stream";
|
|
|
53
55
|
export type InternalTransport = "bulk";
|
|
54
56
|
export type StorageKind = "sqlite";
|
|
55
57
|
export type StorageScope = "project";
|
|
56
|
-
export type LeaseScope = "project";
|
|
57
58
|
export type HealthStatus = "ok" | "degraded" | "failing";
|
|
58
59
|
|
|
59
60
|
export interface HealthReport {
|
|
@@ -69,7 +70,6 @@ export interface ManifestInput {
|
|
|
69
70
|
trust_tier: TrustTier;
|
|
70
71
|
provides: ProviderRoleInput[];
|
|
71
72
|
consumes: ConsumerRoleInput[];
|
|
72
|
-
scheduled_tasks: ScheduledTaskInput[];
|
|
73
73
|
bindings: BindingsInput;
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -97,6 +97,7 @@ export type ProviderRoleInput =
|
|
|
97
97
|
config_schema: unknown;
|
|
98
98
|
observability: ObservabilitySurfaceInput[];
|
|
99
99
|
identity_scope: IdentityScope[];
|
|
100
|
+
concurrency: Concurrency;
|
|
100
101
|
}
|
|
101
102
|
| {
|
|
102
103
|
role: "internal_service";
|
|
@@ -133,31 +134,6 @@ export type ConsumerRoleInput =
|
|
|
133
134
|
| { role: "llm_client"; via: string; auth: string }
|
|
134
135
|
| { role: "service_client"; of: string[] };
|
|
135
136
|
|
|
136
|
-
export interface ScheduledTaskInput {
|
|
137
|
-
task_id: string;
|
|
138
|
-
eligibility: TaskEligibilityInput;
|
|
139
|
-
lease_scope: LeaseScope;
|
|
140
|
-
renews_during_calls: boolean;
|
|
141
|
-
toolset: string[];
|
|
142
|
-
model_policy: ModelPolicyInput;
|
|
143
|
-
step_cap: number;
|
|
144
|
-
circuit_breaker: CircuitBreakerInput;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export interface TaskEligibilityInput {
|
|
148
|
-
cooldown: string;
|
|
149
|
-
window: string;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export interface ModelPolicyInput {
|
|
153
|
-
tier: string;
|
|
154
|
-
fallback_chain: string[];
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export interface CircuitBreakerInput {
|
|
158
|
-
identical_failures: number;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
137
|
export interface BindingsInput {
|
|
162
138
|
storage: StorageBindingInput;
|
|
163
139
|
vault_grants: VaultGrantInput[];
|
|
@@ -185,6 +161,7 @@ export interface ManagementSurfaceManifestOptions {
|
|
|
185
161
|
moduleId: string;
|
|
186
162
|
operations: Array<string | ManagementOperationInput>;
|
|
187
163
|
moduleVersion?: string;
|
|
164
|
+
concurrency?: Concurrency;
|
|
188
165
|
}
|
|
189
166
|
|
|
190
167
|
/**
|
|
@@ -374,6 +351,7 @@ export class SubcProviderError extends Error {
|
|
|
374
351
|
constructor(
|
|
375
352
|
message: string,
|
|
376
353
|
readonly code?: string,
|
|
354
|
+
readonly kind: SubcCallErrorKind = "terminal",
|
|
377
355
|
) {
|
|
378
356
|
super(message);
|
|
379
357
|
}
|
|
@@ -398,10 +376,10 @@ export function managementSurfaceManifest(opts: ManagementSurfaceManifestOptions
|
|
|
398
376
|
config_schema: { type: "object" },
|
|
399
377
|
observability: [],
|
|
400
378
|
identity_scope: [],
|
|
379
|
+
concurrency: opts.concurrency ?? "module_managed",
|
|
401
380
|
},
|
|
402
381
|
],
|
|
403
382
|
consumes: [],
|
|
404
|
-
scheduled_tasks: [],
|
|
405
383
|
bindings: {
|
|
406
384
|
storage: {
|
|
407
385
|
kind: "sqlite",
|
|
@@ -545,22 +523,22 @@ export class SubcProvider {
|
|
|
545
523
|
|
|
546
524
|
cancel(handle: RouteHandle, corr: bigint): void {
|
|
547
525
|
this.assertLiveHandle(handle);
|
|
548
|
-
|
|
526
|
+
this.sendOn(
|
|
549
527
|
this.sock,
|
|
550
528
|
this.generation,
|
|
551
529
|
buildFrame(FrameType.Cancel, controlFlags(), handle.channel, handle.epoch, corr, new Uint8Array(0)),
|
|
552
|
-
);
|
|
530
|
+
).catch(() => undefined);
|
|
553
531
|
}
|
|
554
532
|
|
|
555
533
|
closeRoute(handle: RouteHandle): void {
|
|
556
534
|
this.assertLiveHandle(handle);
|
|
557
535
|
this.liveRoutes.delete(handle.channel);
|
|
558
536
|
this.abortHandle(handle);
|
|
559
|
-
|
|
537
|
+
this.sendOn(
|
|
560
538
|
this.sock,
|
|
561
539
|
this.generation,
|
|
562
540
|
buildFrame(FrameType.Goodbye, controlFlags(), handle.channel, handle.epoch, 0n, new Uint8Array(0)),
|
|
563
|
-
);
|
|
541
|
+
).catch(() => undefined);
|
|
564
542
|
}
|
|
565
543
|
|
|
566
544
|
/** Read the connection file, authenticate as a client, register the manifest with HELLO, and serve frames. */
|
|
@@ -888,8 +866,15 @@ export class SubcProvider {
|
|
|
888
866
|
const releasePermit = await (this.requestGate ?? new AsyncPermitPool(DEFAULT_PROVIDER_HANDLER_CAPACITY)).acquire();
|
|
889
867
|
const dataFlags = buildFlags(false, Priority.Interactive, false);
|
|
890
868
|
try {
|
|
869
|
+
if (controller.signal.aborted) {
|
|
870
|
+
await this.sendError(frame, "cancelled", "request cancelled", dataFlags, sock, generation);
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
891
873
|
const body = await this.opts.handler(handle, frame.body, context);
|
|
892
|
-
if (controller.signal.aborted)
|
|
874
|
+
if (controller.signal.aborted) {
|
|
875
|
+
await this.sendError(frame, "cancelled", "request cancelled", dataFlags, sock, generation);
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
893
878
|
this.assertLiveHandle(handle);
|
|
894
879
|
if (body === undefined) {
|
|
895
880
|
await this.sendOn(
|
|
@@ -923,7 +908,11 @@ export class SubcProvider {
|
|
|
923
908
|
throw new SubcProviderError("provider handler must return a Uint8Array or void", "invalid_handler_response");
|
|
924
909
|
}
|
|
925
910
|
} catch (error) {
|
|
926
|
-
if (error instanceof StaleRouteHandleError
|
|
911
|
+
if (error instanceof StaleRouteHandleError) return;
|
|
912
|
+
if (controller.signal.aborted) {
|
|
913
|
+
await this.sendError(frame, "cancelled", "request cancelled", dataFlags, sock, generation);
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
927
916
|
await this.sendError(
|
|
928
917
|
frame,
|
|
929
918
|
error instanceof SubcProviderError && error.code ? error.code : "handler_error",
|
|
@@ -988,6 +977,10 @@ export class SubcProvider {
|
|
|
988
977
|
sock: SubcSocket,
|
|
989
978
|
generation: number,
|
|
990
979
|
): Promise<void> {
|
|
980
|
+
if (frame.header.channel !== 0) {
|
|
981
|
+
const handle = this.liveRoutes.get(frame.header.channel);
|
|
982
|
+
if (!handle || handle.epoch !== frame.header.epoch || !this.isLiveHandle(handle)) return;
|
|
983
|
+
}
|
|
991
984
|
await this.sendOn(
|
|
992
985
|
sock,
|
|
993
986
|
generation,
|
|
@@ -1230,7 +1223,11 @@ function routeKey(handle: RouteHandle, corr: bigint): string {
|
|
|
1230
1223
|
function providerErrorFromFrame(frame: Frame): SubcProviderError {
|
|
1231
1224
|
try {
|
|
1232
1225
|
const body = parseJson(frame.body) as { code?: string; message?: string };
|
|
1233
|
-
return new SubcProviderError(
|
|
1226
|
+
return new SubcProviderError(
|
|
1227
|
+
body.message ?? "subc error",
|
|
1228
|
+
body.code,
|
|
1229
|
+
body.code === "stale_route_epoch" || body.code === "unknown_channel" ? "not_sent" : "terminal",
|
|
1230
|
+
);
|
|
1234
1231
|
} catch {
|
|
1235
1232
|
return new SubcProviderError(Buffer.from(frame.body).toString("utf8") || "subc error");
|
|
1236
1233
|
}
|
|
@@ -1324,7 +1321,7 @@ function controlFlags(): number {
|
|
|
1324
1321
|
}
|
|
1325
1322
|
|
|
1326
1323
|
async function sendFrame(sock: SubcSocket, frame: Frame): Promise<void> {
|
|
1327
|
-
await sock
|
|
1324
|
+
await writeBorrowed(sock, encodeFrame(frame), Date.now() + WRITE_TIMEOUT_MS);
|
|
1328
1325
|
}
|
|
1329
1326
|
|
|
1330
1327
|
async function expectHelloAck(sock: SubcSocket, deadline: number): Promise<ModuleHelloAckBody> {
|
|
@@ -1387,7 +1384,6 @@ function normalizeManifest(manifest: ManifestInput): ManifestInput {
|
|
|
1387
1384
|
trust_tier: manifest.trust_tier,
|
|
1388
1385
|
provides: manifest.provides.map(normalizeProviderRole),
|
|
1389
1386
|
consumes: manifest.consumes.map(normalizeConsumerRole),
|
|
1390
|
-
scheduled_tasks: manifest.scheduled_tasks.map(normalizeScheduledTask),
|
|
1391
1387
|
bindings: {
|
|
1392
1388
|
storage: {
|
|
1393
1389
|
kind: manifest.bindings.storage.kind,
|
|
@@ -1448,6 +1444,7 @@ function normalizeProviderRole(role: ProviderRoleInput): ProviderRoleInput {
|
|
|
1448
1444
|
kind: surface.kind,
|
|
1449
1445
|
})),
|
|
1450
1446
|
identity_scope: [...role.identity_scope],
|
|
1447
|
+
concurrency: role.concurrency,
|
|
1451
1448
|
};
|
|
1452
1449
|
case "internal_service":
|
|
1453
1450
|
return {
|
|
@@ -1471,23 +1468,3 @@ function normalizeConsumerRole(role: ConsumerRoleInput): ConsumerRoleInput {
|
|
|
1471
1468
|
}
|
|
1472
1469
|
}
|
|
1473
1470
|
|
|
1474
|
-
function normalizeScheduledTask(task: ScheduledTaskInput): ScheduledTaskInput {
|
|
1475
|
-
return {
|
|
1476
|
-
task_id: task.task_id,
|
|
1477
|
-
eligibility: {
|
|
1478
|
-
cooldown: task.eligibility.cooldown,
|
|
1479
|
-
window: task.eligibility.window,
|
|
1480
|
-
},
|
|
1481
|
-
lease_scope: task.lease_scope,
|
|
1482
|
-
renews_during_calls: task.renews_during_calls,
|
|
1483
|
-
toolset: [...task.toolset],
|
|
1484
|
-
model_policy: {
|
|
1485
|
-
tier: task.model_policy.tier,
|
|
1486
|
-
fallback_chain: [...task.model_policy.fallback_chain],
|
|
1487
|
-
},
|
|
1488
|
-
step_cap: task.step_cap,
|
|
1489
|
-
circuit_breaker: {
|
|
1490
|
-
identical_failures: task.circuit_breaker.identical_failures,
|
|
1491
|
-
},
|
|
1492
|
-
};
|
|
1493
|
-
}
|
package/src/socket.ts
CHANGED
|
@@ -44,6 +44,28 @@ export interface SocketWriteResult {
|
|
|
44
44
|
completed: Promise<void>;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
export function toWriteBuffer(bytes: Uint8Array): Buffer {
|
|
48
|
+
// Internal frame writes pass encodeFrame's fresh, single-use Uint8Array.
|
|
49
|
+
// Authentication writes likewise use fresh prefix/JSON storage and await each
|
|
50
|
+
// write without mutating it, so this view remains stable while Node drains it.
|
|
51
|
+
return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const WRITE_BORROWED = Symbol("subc.socket.writeBorrowed");
|
|
55
|
+
const WRITE_TRACKED_BORROWED = Symbol("subc.socket.writeTrackedBorrowed");
|
|
56
|
+
|
|
57
|
+
/** @internal Write fresh, single-use internal storage without copying it. */
|
|
58
|
+
export function writeBorrowed(socket: SubcSocket, bytes: Uint8Array, deadlineMs: number): Promise<void> {
|
|
59
|
+
const borrowed = socket[WRITE_BORROWED];
|
|
60
|
+
return borrowed ? borrowed.call(socket, bytes, deadlineMs) : socket.write(bytes, deadlineMs);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** @internal Track a write of fresh, single-use internal storage without copying it. */
|
|
64
|
+
export function writeTrackedBorrowed(socket: SubcSocket, bytes: Uint8Array, deadlineMs: number): SocketWriteResult {
|
|
65
|
+
const borrowed = socket[WRITE_TRACKED_BORROWED];
|
|
66
|
+
return borrowed ? borrowed.call(socket, bytes, deadlineMs) : socket.writeTracked(bytes, deadlineMs);
|
|
67
|
+
}
|
|
68
|
+
|
|
47
69
|
interface Waiter {
|
|
48
70
|
need: number;
|
|
49
71
|
resolve: (bytes: Uint8Array) => void;
|
|
@@ -174,8 +196,24 @@ export class SubcSocket {
|
|
|
174
196
|
}
|
|
175
197
|
|
|
176
198
|
async write(bytes: Uint8Array, deadlineMs: number): Promise<void> {
|
|
199
|
+
await this.writeBuffer(Buffer.from(bytes), deadlineMs);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
writeTracked(bytes: Uint8Array, deadlineMs: number): SocketWriteResult {
|
|
203
|
+
return this.writeTrackedBuffer(Buffer.from(bytes), deadlineMs);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async [WRITE_BORROWED](bytes: Uint8Array, deadlineMs: number): Promise<void> {
|
|
207
|
+
await this.writeBuffer(toWriteBuffer(bytes), deadlineMs);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
[WRITE_TRACKED_BORROWED](bytes: Uint8Array, deadlineMs: number): SocketWriteResult {
|
|
211
|
+
return this.writeTrackedBuffer(toWriteBuffer(bytes), deadlineMs);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private async writeBuffer(buffer: Buffer, deadlineMs: number): Promise<void> {
|
|
177
215
|
try {
|
|
178
|
-
await this.
|
|
216
|
+
await this.writeTrackedBuffer(buffer, deadlineMs).completed;
|
|
179
217
|
} catch (err) {
|
|
180
218
|
if (err instanceof SocketWriteNotQueuedError || err instanceof SocketWriteQueuedError) {
|
|
181
219
|
throw err.cause ?? err;
|
|
@@ -184,7 +222,7 @@ export class SubcSocket {
|
|
|
184
222
|
}
|
|
185
223
|
}
|
|
186
224
|
|
|
187
|
-
|
|
225
|
+
private writeTrackedBuffer(buffer: Buffer, deadlineMs: number): SocketWriteResult {
|
|
188
226
|
if (this.closedErr) {
|
|
189
227
|
return {
|
|
190
228
|
queued: false,
|
|
@@ -230,7 +268,7 @@ export class SubcSocket {
|
|
|
230
268
|
}, remaining);
|
|
231
269
|
|
|
232
270
|
try {
|
|
233
|
-
this.sock.write(
|
|
271
|
+
this.sock.write(buffer, (err) => {
|
|
234
272
|
settle(() => {
|
|
235
273
|
if (err) {
|
|
236
274
|
reject(
|