@hlix/sdk 0.2.0 → 0.3.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/api-client/src/client.d.ts +43 -0
- package/dist/api-client/src/generated/schema.d.ts +26163 -0
- package/dist/api-client/src/index.d.ts +13 -0
- package/dist/auth.d.ts +41 -0
- package/dist/errors.d.ts +78 -0
- package/dist/http.d.ts +45 -0
- package/dist/index.d.ts +268 -9395
- package/dist/index.js +449 -277
- package/dist/observe.d.ts +58 -0
- package/dist/retry.d.ts +47 -0
- package/dist/revision-watch.d.ts +29 -0
- package/dist/streaming.d.ts +33 -0
- package/dist/test-schema-guard.d.ts +27 -0
- package/package.json +6 -6
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A worker's live coder output, as frames.
|
|
3
|
+
*
|
|
4
|
+
* `GET /v1/api/projects/:projectId/agents/:agentId/observe` is NOT in
|
|
5
|
+
* `openapi.json`, so this type is written by hand rather than generated. That
|
|
6
|
+
* is a deliberate, narrow exception and it is the reason `parseObserveFrame`
|
|
7
|
+
* exists: a hand-written type is a claim about somebody else's route, so the
|
|
8
|
+
* parser treats the wire as untrusted and returns `null` for anything it does
|
|
9
|
+
* not recognise instead of casting. When the route is added to the contract,
|
|
10
|
+
* delete this file and take the generated type.
|
|
11
|
+
*
|
|
12
|
+
* The vocabulary is the backend's `toWorkerObserveFrame`
|
|
13
|
+
* (`apps/backend/src/routes/agent-turn-observe.ts`) plus `coderFrameToSse`
|
|
14
|
+
* (`engine/sandbox/coder-stream.ts`). Both project onto the SAME shape on
|
|
15
|
+
* purpose — the native `hlix` coder streams as a Mastra durable run and an
|
|
16
|
+
* external ACP coder streams as driver frames, and a watcher should not have
|
|
17
|
+
* to know which one it got.
|
|
18
|
+
*/
|
|
19
|
+
export type ObserveFrame =
|
|
20
|
+
/** No task yet, or its latest task is not being coded. The stream then ends. */
|
|
21
|
+
{
|
|
22
|
+
type: "idle";
|
|
23
|
+
}
|
|
24
|
+
/** A live turn was found. `runId` is the durable run, or the task's own id. */
|
|
25
|
+
| {
|
|
26
|
+
type: "active";
|
|
27
|
+
runId?: string;
|
|
28
|
+
} | {
|
|
29
|
+
type: "on_chat_model_stream";
|
|
30
|
+
delta: string;
|
|
31
|
+
} | {
|
|
32
|
+
type: "reasoning";
|
|
33
|
+
delta: string;
|
|
34
|
+
} | {
|
|
35
|
+
type: "on_tool_start";
|
|
36
|
+
name: string;
|
|
37
|
+
input?: unknown;
|
|
38
|
+
} | {
|
|
39
|
+
type: "on_tool_end";
|
|
40
|
+
name: string;
|
|
41
|
+
output?: unknown;
|
|
42
|
+
} | {
|
|
43
|
+
type: "done";
|
|
44
|
+
} | {
|
|
45
|
+
type: "error";
|
|
46
|
+
message: string;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* One SSE `data:` payload → a frame, or `null` when it is not one.
|
|
50
|
+
*
|
|
51
|
+
* Unparseable JSON, a missing `type`, and a `type` this client has no branch
|
|
52
|
+
* for all return `null`. A caller iterating frames therefore never sees a
|
|
53
|
+
* half-built object, and a frame added to the route later is ignored rather
|
|
54
|
+
* than rendered as `undefined`.
|
|
55
|
+
*/
|
|
56
|
+
export declare function parseObserveFrame(data: string): ObserveFrame | null;
|
|
57
|
+
/** Frames after which no more will arrive, so a reader can stop rather than wait. */
|
|
58
|
+
export declare const isTerminalObserveFrame: (frame: ObserveFrame) => boolean;
|
package/dist/retry.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded retry — and, far more importantly, the refusal to retry.
|
|
3
|
+
*
|
|
4
|
+
* THE RULE THAT MUST NOT BEND: a non-idempotent request is never retried, ever,
|
|
5
|
+
* under any status, including a network failure where the SDK cannot know
|
|
6
|
+
* whether the server acted. `POST /v1/api/cycles/:id/execute` dispatches
|
|
7
|
+
* paid coder runs; `POST /v1/api/tasks` creates work. A retry that "helpfully"
|
|
8
|
+
* recovers a dropped response duplicates that work, which is precisely the
|
|
9
|
+
* hazard durable attempt identity and the reconciler exist to eliminate. An SDK
|
|
10
|
+
* that reintroduces it at the client layer defeats both.
|
|
11
|
+
*
|
|
12
|
+
* So the set below is the RFC 9110 idempotent set, and it is an allowlist, not
|
|
13
|
+
* a denylist: a verb nobody thought about is not retried.
|
|
14
|
+
*
|
|
15
|
+
* A note on DELETE, which is idempotent by the RFC and still has a sharp edge:
|
|
16
|
+
* retrying one whose response was lost can answer 404 the second time, so a
|
|
17
|
+
* caller may see "not found" for a delete that in fact succeeded. That is a
|
|
18
|
+
* reporting artefact rather than duplicated work — the server state is what the
|
|
19
|
+
* caller asked for either way — which is why it stays in the set. It is called
|
|
20
|
+
* out because a reader deserves to know it was considered rather than missed.
|
|
21
|
+
*/
|
|
22
|
+
/** RFC 9110 §9.2.2. POST and PATCH are absent on purpose. */
|
|
23
|
+
export declare const IDEMPOTENT_METHODS: ReadonlySet<string>;
|
|
24
|
+
export declare function isIdempotent(method: string): boolean;
|
|
25
|
+
export declare function isRetryableStatus(status: number): boolean;
|
|
26
|
+
export interface RetryPolicy {
|
|
27
|
+
/** Total attempts including the first. 1 disables retry entirely. */
|
|
28
|
+
attempts: number;
|
|
29
|
+
/** First backoff step in ms; doubles per attempt. */
|
|
30
|
+
baseDelayMs: number;
|
|
31
|
+
/** Ceiling for a single wait, before jitter. */
|
|
32
|
+
maxDelayMs: number;
|
|
33
|
+
}
|
|
34
|
+
export declare const DEFAULT_RETRY: RetryPolicy;
|
|
35
|
+
/**
|
|
36
|
+
* Exponential backoff with full jitter, honouring `Retry-After` when the
|
|
37
|
+
* server stated one. Jitter matters under load: without it, every client that
|
|
38
|
+
* saw the same 503 returns in the same millisecond.
|
|
39
|
+
*/
|
|
40
|
+
export declare function backoffDelayMs(attempt: number, policy: RetryPolicy, retryAfterSeconds: number | null, random?: () => number): number;
|
|
41
|
+
/** Whether another attempt is permitted. The verb check comes first. */
|
|
42
|
+
export declare function shouldRetry(input: {
|
|
43
|
+
method: string;
|
|
44
|
+
attempt: number;
|
|
45
|
+
policy: RetryPolicy;
|
|
46
|
+
status: number | null;
|
|
47
|
+
}): boolean;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type RevisionWatchFrame =
|
|
2
|
+
/** Listening. Read the head now: nothing that moved before this is replayed. */
|
|
3
|
+
{
|
|
4
|
+
kind: "ready";
|
|
5
|
+
}
|
|
6
|
+
/** The revision head advanced to this generation. */
|
|
7
|
+
| {
|
|
8
|
+
kind: "head";
|
|
9
|
+
revisionId: string;
|
|
10
|
+
generation: number;
|
|
11
|
+
}
|
|
12
|
+
/** The Coding Workspace's files changed; a head read turns that into a revision. */
|
|
13
|
+
| {
|
|
14
|
+
kind: "workspace";
|
|
15
|
+
};
|
|
16
|
+
export declare function parseRevisionWatchFrame(data: string): RevisionWatchFrame | null;
|
|
17
|
+
/**
|
|
18
|
+
* A refusal the server will repeat however often it is asked: a bad credential,
|
|
19
|
+
* a project out of reach, a repo-backed project this sync does not serve. Only
|
|
20
|
+
* those end the watch — a dropped connection, a restart or a 5xx reconnects,
|
|
21
|
+
* because a mirror left deaf by a deploy is the failure this exists to prevent.
|
|
22
|
+
*/
|
|
23
|
+
export declare function isPermanentWatchFailure(error: unknown): boolean;
|
|
24
|
+
export declare const WATCH_RECONNECT: {
|
|
25
|
+
firstDelayMs: number;
|
|
26
|
+
maxDelayMs: number;
|
|
27
|
+
};
|
|
28
|
+
/** Resolves after `ms`, or at once when `signal` aborts. */
|
|
29
|
+
export declare function pause(ms: number, signal?: AbortSignal): Promise<void>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-Sent Events, parsed to the WHATWG spec.
|
|
3
|
+
*
|
|
4
|
+
* Streaming is this product's defining behaviour — task status, cycle
|
|
5
|
+
* lifecycle, live agent output — so the SDK consumes it directly rather than
|
|
6
|
+
* handing back a raw body and wishing the caller luck. `EventSource` is not an
|
|
7
|
+
* option: it cannot send an `x-api-key` header or a cross-origin cookie, and it
|
|
8
|
+
* only speaks GET.
|
|
9
|
+
*
|
|
10
|
+
* Spec details that are easy to get wrong and are handled here:
|
|
11
|
+
* - a field line is `name: value`, and exactly ONE leading space of the value
|
|
12
|
+
* is stripped (`data: x` is ` x`);
|
|
13
|
+
* - multiple `data:` lines join with "\n";
|
|
14
|
+
* - a line starting with `:` is a comment — servers use it as a heartbeat;
|
|
15
|
+
* - CRLF, CR and LF are all line terminators;
|
|
16
|
+
* - an event with an EMPTY data buffer is not dispatched. That is the spec,
|
|
17
|
+
* and it means the backend's `event: keepalive` frames (which carry no
|
|
18
|
+
* data) never surface as events. They still do their job — they keep the
|
|
19
|
+
* connection warm — but a caller that wants a liveness signal must use a
|
|
20
|
+
* timeout, not wait for a keepalive it will never see.
|
|
21
|
+
*/
|
|
22
|
+
export interface SseEvent {
|
|
23
|
+
/** The `event:` field, or null when the server sent none. */
|
|
24
|
+
event: string | null;
|
|
25
|
+
/** Joined `data:` lines, newline-separated. Never empty when dispatched. */
|
|
26
|
+
data: string;
|
|
27
|
+
/** The `id:` field, or null. */
|
|
28
|
+
id: string | null;
|
|
29
|
+
/** The `retry:` field in ms, or null. */
|
|
30
|
+
retry: number | null;
|
|
31
|
+
}
|
|
32
|
+
/** Parse a byte stream of `text/event-stream` into events. */
|
|
33
|
+
export declare function parseSseStream(body: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<SseEvent>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns "the database is not migrated" into an instruction instead of a
|
|
3
|
+
* confusing red.
|
|
4
|
+
*
|
|
5
|
+
* TEST-ONLY. It is not exported from `index.ts` and is not a tsup entry, so it
|
|
6
|
+
* never reaches the published bundle.
|
|
7
|
+
*
|
|
8
|
+
* Why it exists: these tests preload the backend's `pool-lifecycle.ts` for its
|
|
9
|
+
* pool discipline, and that preload also carries a default — when
|
|
10
|
+
* `DATABASE_URL` is unset it redirects to the `hlix` database. That default is
|
|
11
|
+
* correct for `apps/backend`, where `hlix` IS the dev database, and wrong here.
|
|
12
|
+
* A developer running `bun run --filter '@hlix/sdk' test` with no env set then
|
|
13
|
+
* silently drives a database that may be several migrations behind, and the
|
|
14
|
+
* failures read as SDK bugs rather than as a stale schema. That is how this was
|
|
15
|
+
* first hit: two failures on a merged tree that were nothing to do with the
|
|
16
|
+
* change under review.
|
|
17
|
+
*
|
|
18
|
+
* A README line would not have prevented it. This does, because it runs.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Run test setup, and if it fails because the schema is behind, rethrow with
|
|
22
|
+
* the command that fixes it and the URL it must be run against.
|
|
23
|
+
*
|
|
24
|
+
* Any other failure propagates untouched — a guard that swallowed real errors
|
|
25
|
+
* would be worse than the confusion it removes.
|
|
26
|
+
*/
|
|
27
|
+
export declare function withSchemaGuard<T>(setup: () => Promise<T>): Promise<T>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hlix/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Typed client for the hlix API — auth, organization scoping, typed errors, SSE streaming, and bounded retry on idempotent verbs only.",
|
|
6
6
|
"keywords": [
|
|
@@ -38,17 +38,17 @@
|
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
|
-
"build": "
|
|
42
|
-
"test": "bun test --isolate",
|
|
41
|
+
"build": "rm -rf dist && bun build src/index.ts --outdir dist --target node --format esm --external openapi-fetch && bunx tsc -p tsconfig.json --emitDeclarationOnly --outDir dist && bun run scripts/fix-dts.ts",
|
|
42
|
+
"test": "bun test --isolate src --path-ignore-patterns \"**/*.integration.test.ts\" --timeout 20000",
|
|
43
43
|
"typecheck": "tsc --noEmit",
|
|
44
|
-
"prepublishOnly": "bun run typecheck && bun run build && bun test
|
|
44
|
+
"prepublishOnly": "bun run typecheck && bun run build && bun run test",
|
|
45
|
+
"test:integration": "NODE_ENV=test bun run ../../apps/backend/scripts/with-aimock.ts -- bun run ../../apps/backend/scripts/with-test-db.ts -- bun test --isolate --preload ../../apps/backend/test/helpers/pool-lifecycle.ts --timeout 60000 .integration.test.ts"
|
|
45
46
|
},
|
|
46
47
|
"dependencies": {
|
|
47
48
|
"openapi-fetch": "^0.17.0"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@hlix/api-client": "workspace:*",
|
|
51
|
-
"
|
|
52
|
-
"typescript": "^5.7.2"
|
|
52
|
+
"typescript": "^7.0.2"
|
|
53
53
|
}
|
|
54
54
|
}
|