@labelbox/rl-sdk 0.0.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/README.md +90 -0
- package/dist/client-options.d.ts +39 -0
- package/dist/client-options.js +29 -0
- package/dist/generated/client/client.gen.d.ts +2 -0
- package/dist/generated/client/client.gen.js +216 -0
- package/dist/generated/client/index.d.ts +8 -0
- package/dist/generated/client/index.js +6 -0
- package/dist/generated/client/types.gen.d.ts +120 -0
- package/dist/generated/client/types.gen.js +2 -0
- package/dist/generated/client/utils.gen.d.ts +37 -0
- package/dist/generated/client/utils.gen.js +228 -0
- package/dist/generated/client.gen.d.ts +12 -0
- package/dist/generated/client.gen.js +3 -0
- package/dist/generated/core/auth.gen.d.ts +18 -0
- package/dist/generated/core/auth.gen.js +14 -0
- package/dist/generated/core/bodySerializer.gen.d.ts +25 -0
- package/dist/generated/core/bodySerializer.gen.js +57 -0
- package/dist/generated/core/params.gen.d.ts +43 -0
- package/dist/generated/core/params.gen.js +100 -0
- package/dist/generated/core/pathSerializer.gen.d.ts +33 -0
- package/dist/generated/core/pathSerializer.gen.js +106 -0
- package/dist/generated/core/queryKeySerializer.gen.d.ts +18 -0
- package/dist/generated/core/queryKeySerializer.gen.js +92 -0
- package/dist/generated/core/serverSentEvents.gen.d.ts +71 -0
- package/dist/generated/core/serverSentEvents.gen.js +132 -0
- package/dist/generated/core/types.gen.d.ts +78 -0
- package/dist/generated/core/types.gen.js +2 -0
- package/dist/generated/core/utils.gen.d.ts +19 -0
- package/dist/generated/core/utils.gen.js +87 -0
- package/dist/generated/index.d.ts +2 -0
- package/dist/generated/index.js +2 -0
- package/dist/generated/sdk.gen.d.ts +3054 -0
- package/dist/generated/sdk.gen.js +5962 -0
- package/dist/generated/types.gen.d.ts +39711 -0
- package/dist/generated/types.gen.js +2 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +39 -0
- package/dist/nesting.d.ts +18 -0
- package/dist/nesting.js +75 -0
- package/dist/reference/concepts-reference.generated.d.ts +9 -0
- package/dist/reference/concepts-reference.generated.js +356 -0
- package/dist/reference/concepts-schema.d.ts +16 -0
- package/dist/reference/concepts-schema.js +39 -0
- package/dist/reference/concepts.d.ts +15 -0
- package/dist/reference/concepts.js +354 -0
- package/dist/reference/domains.d.ts +65 -0
- package/dist/reference/domains.js +32 -0
- package/dist/reference/recipes-reference.generated.d.ts +11 -0
- package/dist/reference/recipes-reference.generated.js +11415 -0
- package/dist/reference/recipes-schema.d.ts +676 -0
- package/dist/reference/recipes-schema.js +406 -0
- package/dist/reference/resources-reference.generated.d.ts +11 -0
- package/dist/reference/resources-reference.generated.js +7174 -0
- package/dist/reference/resources-schema.d.ts +78 -0
- package/dist/reference/resources-schema.js +71 -0
- package/dist/reference/schema.d.ts +153 -0
- package/dist/reference/schema.js +151 -0
- package/dist/reference/sdk-reference.generated.d.ts +10 -0
- package/dist/reference/sdk-reference.generated.js +65703 -0
- package/dist/reference/tutorials.d.ts +76 -0
- package/dist/reference/tutorials.js +65 -0
- package/dist/session-events.d.ts +92 -0
- package/dist/session-events.js +213 -0
- package/package.json +126 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export type TutorialReferenceEntry = {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
file: string;
|
|
5
|
+
format?: 'md' | 'ipynb';
|
|
6
|
+
};
|
|
7
|
+
export declare const TUTORIAL_REFERENCE: readonly [{
|
|
8
|
+
readonly id: "getting-started";
|
|
9
|
+
readonly title: "Getting started";
|
|
10
|
+
readonly file: "getting-started.md";
|
|
11
|
+
}, {
|
|
12
|
+
readonly id: "set-up-an-environment";
|
|
13
|
+
readonly title: "Set up an environment";
|
|
14
|
+
readonly file: "set-up-an-environment.md";
|
|
15
|
+
}, {
|
|
16
|
+
readonly id: "first-problem";
|
|
17
|
+
readonly title: "Your first problem";
|
|
18
|
+
readonly file: "first-problem.md";
|
|
19
|
+
}, {
|
|
20
|
+
readonly id: "first-evaluation";
|
|
21
|
+
readonly title: "Your first evaluation";
|
|
22
|
+
readonly file: "first-evaluation.md";
|
|
23
|
+
}, {
|
|
24
|
+
readonly id: "export-your-results";
|
|
25
|
+
readonly title: "Export your results";
|
|
26
|
+
readonly file: "export-your-results.md";
|
|
27
|
+
}, {
|
|
28
|
+
readonly id: "author-a-problem-end-to-end";
|
|
29
|
+
readonly title: "Author a problem end to end";
|
|
30
|
+
readonly file: "author-a-problem-end-to-end.md";
|
|
31
|
+
}, {
|
|
32
|
+
readonly id: "import-a-problem-set";
|
|
33
|
+
readonly title: "Import a problem set and evaluate it";
|
|
34
|
+
readonly file: "import-a-problem-set.md";
|
|
35
|
+
}, {
|
|
36
|
+
readonly id: "run-a-problem-with-your-own-run-config";
|
|
37
|
+
readonly title: "Run a problem with your own run config";
|
|
38
|
+
readonly file: "run-a-problem-with-your-own-run-config.md";
|
|
39
|
+
}, {
|
|
40
|
+
readonly id: "bring-a-real-agent";
|
|
41
|
+
readonly title: "Bring a real agent with Claude Code";
|
|
42
|
+
readonly file: "bring-a-real-agent.md";
|
|
43
|
+
}, {
|
|
44
|
+
readonly id: "run-and-export-an-evaluation";
|
|
45
|
+
readonly title: "Run and export an evaluation";
|
|
46
|
+
readonly file: "run-and-export-an-evaluation.md";
|
|
47
|
+
}, {
|
|
48
|
+
readonly id: "add-a-quality-gate";
|
|
49
|
+
readonly title: "Add a quality gate";
|
|
50
|
+
readonly file: "add-a-quality-gate.md";
|
|
51
|
+
}, {
|
|
52
|
+
readonly id: "improve-problems-with-the-synthesizer";
|
|
53
|
+
readonly title: "Improve problems with the synthesizer";
|
|
54
|
+
readonly file: "improve-problems-with-the-synthesizer.md";
|
|
55
|
+
}, {
|
|
56
|
+
readonly id: "control-your-costs";
|
|
57
|
+
readonly title: "Control your costs";
|
|
58
|
+
readonly file: "control-your-costs.md";
|
|
59
|
+
}, {
|
|
60
|
+
readonly id: "ts-sdk-getting-started";
|
|
61
|
+
readonly title: "TypeScript SDK";
|
|
62
|
+
readonly file: "ts-sdk-getting-started.md";
|
|
63
|
+
}, {
|
|
64
|
+
readonly id: "cli-getting-started";
|
|
65
|
+
readonly title: "CLI";
|
|
66
|
+
readonly file: "cli-getting-started.md";
|
|
67
|
+
}, {
|
|
68
|
+
readonly id: "headless-recursion-runbook";
|
|
69
|
+
readonly title: "Headless runbook";
|
|
70
|
+
readonly file: "headless-recursion-runbook.ipynb";
|
|
71
|
+
readonly format: "ipynb";
|
|
72
|
+
}, {
|
|
73
|
+
readonly id: "claude-code";
|
|
74
|
+
readonly title: "Claude Code";
|
|
75
|
+
readonly file: "claude-code.md";
|
|
76
|
+
}];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Canonical **tutorials registry** — the single source of truth for the docs
|
|
2
|
+
// Tutorials quadrant (the getting-started learning arc + the SDK/CLI on-ramps).
|
|
3
|
+
//
|
|
4
|
+
// Consumed by both the frontend docs nav (`apps/recursion/web/src/features/docs/constants.ts`)
|
|
5
|
+
// and the dx CLI-manifest generator (`tools/dx/src/commands/manifest.ts`), so
|
|
6
|
+
// the two surfaces can never drift — a parity test pins the nav to this list.
|
|
7
|
+
//
|
|
8
|
+
// `file` is resolved relative to `apps/recursion/web/public/docs/`. Order is the docs nav
|
|
9
|
+
// order (top → bottom). An `ipynb` entry is a notebook: it renders as an
|
|
10
|
+
// "open in the app" link and its body is never dumped into the manifest JSON.
|
|
11
|
+
export const TUTORIAL_REFERENCE = [
|
|
12
|
+
{ id: 'getting-started', title: 'Getting started', file: 'getting-started.md' },
|
|
13
|
+
{
|
|
14
|
+
id: 'set-up-an-environment',
|
|
15
|
+
title: 'Set up an environment',
|
|
16
|
+
file: 'set-up-an-environment.md',
|
|
17
|
+
},
|
|
18
|
+
{ id: 'first-problem', title: 'Your first problem', file: 'first-problem.md' },
|
|
19
|
+
{ id: 'first-evaluation', title: 'Your first evaluation', file: 'first-evaluation.md' },
|
|
20
|
+
{ id: 'export-your-results', title: 'Export your results', file: 'export-your-results.md' },
|
|
21
|
+
// Multi-recipe workflow tutorials — guided journeys that chain several recipes
|
|
22
|
+
// (the getting-started arc above covers each task once; these go deeper into a
|
|
23
|
+
// workflow). A tutorial can embed many recipes via the ```recipe fence.
|
|
24
|
+
{
|
|
25
|
+
id: 'author-a-problem-end-to-end',
|
|
26
|
+
title: 'Author a problem end to end',
|
|
27
|
+
file: 'author-a-problem-end-to-end.md',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'import-a-problem-set',
|
|
31
|
+
title: 'Import a problem set and evaluate it',
|
|
32
|
+
file: 'import-a-problem-set.md',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: 'run-a-problem-with-your-own-run-config',
|
|
36
|
+
title: 'Run a problem with your own run config',
|
|
37
|
+
file: 'run-a-problem-with-your-own-run-config.md',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'bring-a-real-agent',
|
|
41
|
+
title: 'Bring a real agent with Claude Code',
|
|
42
|
+
file: 'bring-a-real-agent.md',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'run-and-export-an-evaluation',
|
|
46
|
+
title: 'Run and export an evaluation',
|
|
47
|
+
file: 'run-and-export-an-evaluation.md',
|
|
48
|
+
},
|
|
49
|
+
{ id: 'add-a-quality-gate', title: 'Add a quality gate', file: 'add-a-quality-gate.md' },
|
|
50
|
+
{
|
|
51
|
+
id: 'improve-problems-with-the-synthesizer',
|
|
52
|
+
title: 'Improve problems with the synthesizer',
|
|
53
|
+
file: 'improve-problems-with-the-synthesizer.md',
|
|
54
|
+
},
|
|
55
|
+
{ id: 'control-your-costs', title: 'Control your costs', file: 'control-your-costs.md' },
|
|
56
|
+
{ id: 'ts-sdk-getting-started', title: 'TypeScript SDK', file: 'ts-sdk-getting-started.md' },
|
|
57
|
+
{ id: 'cli-getting-started', title: 'CLI', file: 'cli-getting-started.md' },
|
|
58
|
+
{
|
|
59
|
+
id: 'headless-recursion-runbook',
|
|
60
|
+
title: 'Headless runbook',
|
|
61
|
+
file: 'headless-recursion-runbook.ipynb',
|
|
62
|
+
format: 'ipynb',
|
|
63
|
+
},
|
|
64
|
+
{ id: 'claude-code', title: 'Claude Code', file: 'claude-code.md' },
|
|
65
|
+
];
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type RecursionClientOptions } from './client-options.js';
|
|
2
|
+
import type { ManagedAgentsContentBlock, ManagedAgentsEvent, ManagedAgentsUsage } from './generated/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* One decoded frame from a session event stream.
|
|
5
|
+
*
|
|
6
|
+
* Field names are the wire's own, matching every other managed-agents type in
|
|
7
|
+
* this package: those are imported verbatim from the Go service's spec rather
|
|
8
|
+
* than derived from a camelCase DTO, and a stream frame that disagreed with
|
|
9
|
+
* `ManagedAgentsEvent` would be the odd one out.
|
|
10
|
+
*/
|
|
11
|
+
export interface ManagedAgentSessionStreamEvent {
|
|
12
|
+
/**
|
|
13
|
+
* Fields the service has added since this decoder was written.
|
|
14
|
+
*
|
|
15
|
+
* This type is hand-maintained rather than generated, so an index signature
|
|
16
|
+
* is what keeps the decoder a passthrough: a new field on the Go
|
|
17
|
+
* `managedStreamEvent` struct reaches the caller as `unknown` instead of
|
|
18
|
+
* being silently dropped while someone notices. The declared fields below
|
|
19
|
+
* stay precisely typed.
|
|
20
|
+
*/
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
/** Frame type, e.g. `session.status_running`, `message`, `session.error`. */
|
|
23
|
+
type: string;
|
|
24
|
+
/** Event id. Pass the last one seen as `afterEventId` to resume without gaps. */
|
|
25
|
+
id?: string;
|
|
26
|
+
session_id?: string;
|
|
27
|
+
session_thread_id?: string;
|
|
28
|
+
processed_at?: string;
|
|
29
|
+
content?: ManagedAgentsContentBlock[];
|
|
30
|
+
/** Provider-specific; shape varies by model and is deliberately not narrowed. */
|
|
31
|
+
stop_reason?: unknown;
|
|
32
|
+
usage?: ManagedAgentsUsage;
|
|
33
|
+
error?: Record<string, unknown>;
|
|
34
|
+
anchor_event_id?: string;
|
|
35
|
+
raw_event?: ManagedAgentsEvent;
|
|
36
|
+
}
|
|
37
|
+
export interface ManagedAgentSessionStreamParams {
|
|
38
|
+
/** Session id (UUID). Any id in the tree resolves to its root session. */
|
|
39
|
+
sessionId: string;
|
|
40
|
+
/**
|
|
41
|
+
* Resume cursor: only events after this id are delivered.
|
|
42
|
+
*
|
|
43
|
+
* Sent as the `after_event_id` query parameter rather than the `Last-Event-ID`
|
|
44
|
+
* header. The public gateway's cross-origin allowlist admits the query but not
|
|
45
|
+
* that non-safelisted header, and using one cursor everywhere keeps a browser
|
|
46
|
+
* caller and a server caller on the same code path.
|
|
47
|
+
*/
|
|
48
|
+
afterEventId?: string;
|
|
49
|
+
/** Restrict delivery to these frame types. Omit for everything. */
|
|
50
|
+
types?: readonly string[];
|
|
51
|
+
/** Abort the request and end the iteration. */
|
|
52
|
+
signal?: AbortSignal;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Stream a managed agent session's events as they are produced.
|
|
56
|
+
*
|
|
57
|
+
* Iteration ends when the server closes the stream, which it does once the
|
|
58
|
+
* session reaches a terminal status. Aborting `signal` ends it early.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* for await (const event of streamManagedAgentSessionEvents(
|
|
62
|
+
* { apiKey: process.env.LABELBOX_API_KEY! },
|
|
63
|
+
* { sessionId },
|
|
64
|
+
* )) {
|
|
65
|
+
* if (event.type === 'session.error') break;
|
|
66
|
+
* console.log(event.type, event.content);
|
|
67
|
+
* }
|
|
68
|
+
*/
|
|
69
|
+
export declare function streamManagedAgentSessionEvents(options: RecursionClientOptions, params: ManagedAgentSessionStreamParams): AsyncGenerator<ManagedAgentSessionStreamEvent, void, undefined>;
|
|
70
|
+
/** A stream that failed before any frame was delivered. */
|
|
71
|
+
export declare class ManagedAgentSessionStreamError extends Error {
|
|
72
|
+
readonly status: number;
|
|
73
|
+
/** Stable machine-readable code from the service, when the body carried one. */
|
|
74
|
+
readonly code: string | undefined;
|
|
75
|
+
constructor(status: number, body: {
|
|
76
|
+
code?: string;
|
|
77
|
+
message?: string;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Decode an SSE byte stream into frames.
|
|
82
|
+
*
|
|
83
|
+
* Exported for the tests in this package only, and deliberately not re-exported
|
|
84
|
+
* from `index.ts`: it takes a raw `ReadableStream<Uint8Array>`, which is a
|
|
85
|
+
* transport detail rather than something a consumer of this SDK should have to
|
|
86
|
+
* hold.
|
|
87
|
+
*
|
|
88
|
+
* Buffering is required because a chunk boundary can fall anywhere, including
|
|
89
|
+
* mid-frame and mid-UTF-8-sequence, so decoding is incremental and frames are
|
|
90
|
+
* only emitted on a complete blank-line terminator.
|
|
91
|
+
*/
|
|
92
|
+
export declare function decodeEventStream(body: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<ManagedAgentSessionStreamEvent, void, undefined>;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// Hand-written transport for the one managed-agents operation the generated
|
|
2
|
+
// client cannot express.
|
|
3
|
+
//
|
|
4
|
+
// `GET /managed-agents/v1/sessions/{session_id}/events/stream` returns
|
|
5
|
+
// `text/event-stream`. The generated SDK is configured with
|
|
6
|
+
// `responseStyle: 'data'`, so every generated method parses a JSON body and
|
|
7
|
+
// resolves once — which is the opposite of what a long-lived stream needs.
|
|
8
|
+
// Rather than leave the console holding a capability no API key could
|
|
9
|
+
// reproduce, the operation is marked `sdkExcluded` in the backend proxy
|
|
10
|
+
// contract and reached through this module instead.
|
|
11
|
+
//
|
|
12
|
+
// The frame format is produced by `writeSSEEvent` in
|
|
13
|
+
// `apps/recursion/control-plane/internal/httpapi/streaming.go`: an optional
|
|
14
|
+
// `event:` line carrying the type, an optional `id:` line, then a single
|
|
15
|
+
// `data:` line holding the whole JSON payload. Comment frames (`: connected`,
|
|
16
|
+
// heartbeats) carry no data and are skipped.
|
|
17
|
+
import { DEFAULT_BASE_URL } from './client-options.js';
|
|
18
|
+
/**
|
|
19
|
+
* Stream a managed agent session's events as they are produced.
|
|
20
|
+
*
|
|
21
|
+
* Iteration ends when the server closes the stream, which it does once the
|
|
22
|
+
* session reaches a terminal status. Aborting `signal` ends it early.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* for await (const event of streamManagedAgentSessionEvents(
|
|
26
|
+
* { apiKey: process.env.LABELBOX_API_KEY! },
|
|
27
|
+
* { sessionId },
|
|
28
|
+
* )) {
|
|
29
|
+
* if (event.type === 'session.error') break;
|
|
30
|
+
* console.log(event.type, event.content);
|
|
31
|
+
* }
|
|
32
|
+
*/
|
|
33
|
+
export async function* streamManagedAgentSessionEvents(options, params) {
|
|
34
|
+
const base = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
|
|
35
|
+
const url = new URL(`${base}/managed-agents/v1/sessions/${encodeURIComponent(params.sessionId)}/events/stream`);
|
|
36
|
+
if (params.afterEventId !== undefined) {
|
|
37
|
+
url.searchParams.set('after_event_id', params.afterEventId);
|
|
38
|
+
}
|
|
39
|
+
for (const type of params.types ?? []) {
|
|
40
|
+
url.searchParams.append('types', type);
|
|
41
|
+
}
|
|
42
|
+
const response = await fetch(url, {
|
|
43
|
+
method: 'GET',
|
|
44
|
+
headers: {
|
|
45
|
+
...options.headers,
|
|
46
|
+
Authorization: `Bearer ${options.apiKey}`,
|
|
47
|
+
Accept: 'text/event-stream',
|
|
48
|
+
},
|
|
49
|
+
...(params.signal ? { signal: params.signal } : {}),
|
|
50
|
+
});
|
|
51
|
+
if (!response.ok || !response.body) {
|
|
52
|
+
// The error body is the Go envelope, `{ error: { code, message, ... } }`.
|
|
53
|
+
// `code` is the stable contract, so it is surfaced rather than the status.
|
|
54
|
+
throw new ManagedAgentSessionStreamError(response.status, await readErrorBody(response));
|
|
55
|
+
}
|
|
56
|
+
yield* decodeEventStream(response.body, params.signal);
|
|
57
|
+
}
|
|
58
|
+
/** A stream that failed before any frame was delivered. */
|
|
59
|
+
export class ManagedAgentSessionStreamError extends Error {
|
|
60
|
+
status;
|
|
61
|
+
/** Stable machine-readable code from the service, when the body carried one. */
|
|
62
|
+
code;
|
|
63
|
+
constructor(status, body) {
|
|
64
|
+
super(body.message ?? `session event stream failed with status ${status}`);
|
|
65
|
+
this.name = 'ManagedAgentSessionStreamError';
|
|
66
|
+
this.status = status;
|
|
67
|
+
this.code = body.code;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function isRecord(value) {
|
|
71
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Reads whichever error envelope came back.
|
|
75
|
+
*
|
|
76
|
+
* A stream request can fail at either hop and the two answer differently. The
|
|
77
|
+
* managed-agents service returns `{error: {code, message}}`, forwarded byte for
|
|
78
|
+
* byte. This API's own guards -- the ones a caller hits first, on a missing
|
|
79
|
+
* permission or an exhausted rate limit -- reject with the platform's
|
|
80
|
+
* `{statusCode, message}` before the request is forwarded at all. Understanding
|
|
81
|
+
* only the first shape meant the errors a caller is most likely to see arrived
|
|
82
|
+
* with no message.
|
|
83
|
+
*/
|
|
84
|
+
async function readErrorBody(response) {
|
|
85
|
+
let parsed;
|
|
86
|
+
try {
|
|
87
|
+
parsed = await response.json();
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
if (!isRecord(parsed))
|
|
93
|
+
return {};
|
|
94
|
+
const nested = parsed['error'];
|
|
95
|
+
if (isRecord(nested))
|
|
96
|
+
return pickCodeAndMessage(nested);
|
|
97
|
+
// Platform envelope. `error` is a short status name ("Forbidden") rather than
|
|
98
|
+
// a stable code, so it is not surfaced as one; `message` is the useful part,
|
|
99
|
+
// and can be a Zod field-error object on a validation failure.
|
|
100
|
+
return pickCodeAndMessage({ message: parsed['message'] });
|
|
101
|
+
}
|
|
102
|
+
function pickCodeAndMessage(source) {
|
|
103
|
+
const code = source['code'];
|
|
104
|
+
const message = source['message'];
|
|
105
|
+
return {
|
|
106
|
+
...(typeof code === 'string' ? { code } : {}),
|
|
107
|
+
...(typeof message === 'string' ? { message } : {}),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Decode an SSE byte stream into frames.
|
|
112
|
+
*
|
|
113
|
+
* Exported for the tests in this package only, and deliberately not re-exported
|
|
114
|
+
* from `index.ts`: it takes a raw `ReadableStream<Uint8Array>`, which is a
|
|
115
|
+
* transport detail rather than something a consumer of this SDK should have to
|
|
116
|
+
* hold.
|
|
117
|
+
*
|
|
118
|
+
* Buffering is required because a chunk boundary can fall anywhere, including
|
|
119
|
+
* mid-frame and mid-UTF-8-sequence, so decoding is incremental and frames are
|
|
120
|
+
* only emitted on a complete blank-line terminator.
|
|
121
|
+
*/
|
|
122
|
+
export async function* decodeEventStream(body, signal) {
|
|
123
|
+
const reader = body.getReader();
|
|
124
|
+
const decoder = new TextDecoder();
|
|
125
|
+
let buffer = '';
|
|
126
|
+
try {
|
|
127
|
+
while (true) {
|
|
128
|
+
if (signal?.aborted)
|
|
129
|
+
return;
|
|
130
|
+
const { done, value } = await reader.read();
|
|
131
|
+
if (done)
|
|
132
|
+
break;
|
|
133
|
+
buffer += decoder.decode(value, { stream: true });
|
|
134
|
+
// `\r\n\r\n` is admitted alongside `\n\n` because the spec permits CRLF
|
|
135
|
+
// and an intermediary may rewrite line endings.
|
|
136
|
+
let boundary = findFrameBoundary(buffer);
|
|
137
|
+
while (boundary !== undefined) {
|
|
138
|
+
const frame = buffer.slice(0, boundary.index);
|
|
139
|
+
buffer = buffer.slice(boundary.index + boundary.length);
|
|
140
|
+
const event = parseFrame(frame);
|
|
141
|
+
if (event)
|
|
142
|
+
yield event;
|
|
143
|
+
boundary = findFrameBoundary(buffer);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
reader.releaseLock();
|
|
149
|
+
// A consumer that breaks out of the loop leaves the socket open otherwise.
|
|
150
|
+
await body.cancel().catch(() => undefined);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function findFrameBoundary(buffer) {
|
|
154
|
+
const lf = buffer.indexOf('\n\n');
|
|
155
|
+
const crlf = buffer.indexOf('\r\n\r\n');
|
|
156
|
+
if (crlf !== -1 && (lf === -1 || crlf < lf))
|
|
157
|
+
return { index: crlf, length: 4 };
|
|
158
|
+
if (lf !== -1)
|
|
159
|
+
return { index: lf, length: 2 };
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
function parseFrame(frame) {
|
|
163
|
+
const dataLines = [];
|
|
164
|
+
let type;
|
|
165
|
+
let id;
|
|
166
|
+
for (const rawLine of frame.split('\n')) {
|
|
167
|
+
const line = rawLine.endsWith('\r') ? rawLine.slice(0, -1) : rawLine;
|
|
168
|
+
// A comment frame — the `: connected` preamble and the periodic heartbeat.
|
|
169
|
+
if (line.startsWith(':') || line === '')
|
|
170
|
+
continue;
|
|
171
|
+
const separator = line.indexOf(':');
|
|
172
|
+
const field = separator === -1 ? line : line.slice(0, separator);
|
|
173
|
+
const rawValue = separator === -1 ? '' : line.slice(separator + 1);
|
|
174
|
+
const value = rawValue.startsWith(' ') ? rawValue.slice(1) : rawValue;
|
|
175
|
+
if (field === 'data')
|
|
176
|
+
dataLines.push(value);
|
|
177
|
+
else if (field === 'event')
|
|
178
|
+
type = value;
|
|
179
|
+
else if (field === 'id')
|
|
180
|
+
id = value;
|
|
181
|
+
}
|
|
182
|
+
if (dataLines.length === 0)
|
|
183
|
+
return undefined;
|
|
184
|
+
let payload;
|
|
185
|
+
try {
|
|
186
|
+
payload = JSON.parse(dataLines.join('\n'));
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
// A frame the service did not produce, or a truncated body. Skipping keeps
|
|
190
|
+
// one bad frame from ending an otherwise healthy stream.
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
if (!isRecord(payload))
|
|
194
|
+
return undefined;
|
|
195
|
+
// The `event:`/`id:` lines and the JSON body carry the same values, but the
|
|
196
|
+
// body is authoritative: the lines are omitted when the field is empty.
|
|
197
|
+
const bodyType = payload['type'];
|
|
198
|
+
const resolvedType = typeof bodyType === 'string' ? bodyType : type;
|
|
199
|
+
if (resolvedType === undefined) {
|
|
200
|
+
// Not a frame this service produces: the Go writer marshals `type` without
|
|
201
|
+
// `omitempty`, so the body always carries it. Dropping the frame is what
|
|
202
|
+
// keeps `ManagedAgentSessionStreamEvent.type` a `string` the caller can
|
|
203
|
+
// switch on rather than one more field they have to null-check.
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
const bodyID = payload['id'];
|
|
207
|
+
const resolvedID = typeof bodyID === 'string' ? bodyID : id;
|
|
208
|
+
return {
|
|
209
|
+
...payload,
|
|
210
|
+
type: resolvedType,
|
|
211
|
+
...(resolvedID === undefined ? {} : { id: resolvedID }),
|
|
212
|
+
};
|
|
213
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@labelbox/rl-sdk",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public",
|
|
12
|
+
"registry": "https://registry.npmjs.org",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./nesting": {
|
|
20
|
+
"types": "./dist/nesting.d.ts",
|
|
21
|
+
"import": "./dist/nesting.js"
|
|
22
|
+
},
|
|
23
|
+
"./domains": {
|
|
24
|
+
"types": "./dist/reference/domains.d.ts",
|
|
25
|
+
"import": "./dist/reference/domains.js"
|
|
26
|
+
},
|
|
27
|
+
"./schema": {
|
|
28
|
+
"types": "./dist/reference/schema.d.ts",
|
|
29
|
+
"import": "./dist/reference/schema.js"
|
|
30
|
+
},
|
|
31
|
+
"./resources-schema": {
|
|
32
|
+
"types": "./dist/reference/resources-schema.d.ts",
|
|
33
|
+
"import": "./dist/reference/resources-schema.js"
|
|
34
|
+
},
|
|
35
|
+
"./reference": {
|
|
36
|
+
"types": "./dist/reference/sdk-reference.generated.d.ts",
|
|
37
|
+
"import": "./dist/reference/sdk-reference.generated.js"
|
|
38
|
+
},
|
|
39
|
+
"./resources-reference": {
|
|
40
|
+
"types": "./dist/reference/resources-reference.generated.d.ts",
|
|
41
|
+
"import": "./dist/reference/resources-reference.generated.js"
|
|
42
|
+
},
|
|
43
|
+
"./recipes-schema": {
|
|
44
|
+
"types": "./dist/reference/recipes-schema.d.ts",
|
|
45
|
+
"import": "./dist/reference/recipes-schema.js"
|
|
46
|
+
},
|
|
47
|
+
"./recipes-reference": {
|
|
48
|
+
"types": "./dist/reference/recipes-reference.generated.d.ts",
|
|
49
|
+
"import": "./dist/reference/recipes-reference.generated.js"
|
|
50
|
+
},
|
|
51
|
+
"./concepts": {
|
|
52
|
+
"types": "./dist/reference/concepts.d.ts",
|
|
53
|
+
"import": "./dist/reference/concepts.js"
|
|
54
|
+
},
|
|
55
|
+
"./tutorials": {
|
|
56
|
+
"types": "./dist/reference/tutorials.d.ts",
|
|
57
|
+
"import": "./dist/reference/tutorials.js"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"exports": {
|
|
62
|
+
".": {
|
|
63
|
+
"types": "./src/index.ts",
|
|
64
|
+
"import": "./dist/index.js"
|
|
65
|
+
},
|
|
66
|
+
"./nesting": {
|
|
67
|
+
"types": "./src/nesting.ts",
|
|
68
|
+
"import": "./dist/nesting.js"
|
|
69
|
+
},
|
|
70
|
+
"./domains": {
|
|
71
|
+
"types": "./src/reference/domains.ts",
|
|
72
|
+
"import": "./dist/reference/domains.js"
|
|
73
|
+
},
|
|
74
|
+
"./schema": {
|
|
75
|
+
"types": "./src/reference/schema.ts",
|
|
76
|
+
"import": "./dist/reference/schema.js"
|
|
77
|
+
},
|
|
78
|
+
"./resources-schema": {
|
|
79
|
+
"types": "./src/reference/resources-schema.ts",
|
|
80
|
+
"import": "./dist/reference/resources-schema.js"
|
|
81
|
+
},
|
|
82
|
+
"./reference": {
|
|
83
|
+
"types": "./src/reference/sdk-reference.generated.ts",
|
|
84
|
+
"import": "./dist/reference/sdk-reference.generated.js"
|
|
85
|
+
},
|
|
86
|
+
"./resources-reference": {
|
|
87
|
+
"types": "./src/reference/resources-reference.generated.ts",
|
|
88
|
+
"import": "./dist/reference/resources-reference.generated.js"
|
|
89
|
+
},
|
|
90
|
+
"./recipes-schema": {
|
|
91
|
+
"types": "./src/reference/recipes-schema.ts",
|
|
92
|
+
"import": "./dist/reference/recipes-schema.js"
|
|
93
|
+
},
|
|
94
|
+
"./recipes-reference": {
|
|
95
|
+
"types": "./src/reference/recipes-reference.generated.ts",
|
|
96
|
+
"import": "./dist/reference/recipes-reference.generated.js"
|
|
97
|
+
},
|
|
98
|
+
"./concepts": {
|
|
99
|
+
"types": "./src/reference/concepts.ts",
|
|
100
|
+
"import": "./dist/reference/concepts.js"
|
|
101
|
+
},
|
|
102
|
+
"./tutorials": {
|
|
103
|
+
"types": "./src/reference/tutorials.ts",
|
|
104
|
+
"import": "./dist/reference/tutorials.js"
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"scripts": {
|
|
108
|
+
"build": "tsc -p tsconfig.build.json",
|
|
109
|
+
"check": "yarn lint && tsc --noEmit",
|
|
110
|
+
"lint": "biome ci --error-on-warnings --config-path ./biome.json .",
|
|
111
|
+
"lint:fix": "biome check --config-path ./biome.json --write .",
|
|
112
|
+
"test": "vitest run",
|
|
113
|
+
"prepare": "yarn build",
|
|
114
|
+
"type-check": "tsc --noEmit"
|
|
115
|
+
},
|
|
116
|
+
"dependencies": {
|
|
117
|
+
"zod": "^4.3.6"
|
|
118
|
+
},
|
|
119
|
+
"devDependencies": {
|
|
120
|
+
"@biomejs/biome": "2.4.6",
|
|
121
|
+
"@hey-api/client-fetch": "^0.13.1",
|
|
122
|
+
"@hey-api/openapi-ts": "^0.97.3",
|
|
123
|
+
"typescript": "^5.9.3",
|
|
124
|
+
"vitest": "^4.0.18"
|
|
125
|
+
}
|
|
126
|
+
}
|