aura-llm 0.1.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/CHANGELOG.md +25 -0
- package/README.md +200 -0
- package/dist/index.cjs +413 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +371 -0
- package/dist/index.d.ts +371 -0
- package/dist/index.js +387 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the `aura-llm` TypeScript SDK are documented here.
|
|
4
|
+
This SDK is versioned independently from the gateway.
|
|
5
|
+
|
|
6
|
+
## [0.1.0] - 2026-06-28
|
|
7
|
+
|
|
8
|
+
Initial alpha release.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `AuraClient` with a `responses.create()` resource (one-shot + streaming).
|
|
12
|
+
- Universal runtime support (Node 20+, browsers, Deno, Bun, Vercel Edge,
|
|
13
|
+
Cloudflare Workers) via global `fetch` + Web Streams — no Node-only deps.
|
|
14
|
+
- `AsyncIterable<StreamEvent>` streaming with back-pressure and SSE parsing.
|
|
15
|
+
- Typed error hierarchy (`AuraError` → `APIError` → `AuthenticationError` /
|
|
16
|
+
`BadRequestError` / `NotFoundError` / `RateLimitError`; plus
|
|
17
|
+
`APIConnectionError`, `APITimeoutError`) with `instanceof` support.
|
|
18
|
+
- Exponential-backoff retry with jitter; honors `Retry-After`; configurable;
|
|
19
|
+
off by default for streams.
|
|
20
|
+
- `onRequest` / `onResponse` / `onError` lifecycle hooks.
|
|
21
|
+
- Conversation threading via `previous_response_id`; tools via `functionTool`;
|
|
22
|
+
multi-tenant `user`; `compression` / `validation` / `consistency` blocks.
|
|
23
|
+
- Response helpers: `outputText`, `toolCalls`, `hasToolCalls`, `isComplete`,
|
|
24
|
+
`isFailed`.
|
|
25
|
+
- ESM + CJS builds with generated `.d.ts` (tsup).
|
package/README.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Aura LLM Gateway — TypeScript SDK
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/aura-llm)
|
|
4
|
+
|
|
5
|
+
TypeScript/JavaScript SDK for the [Aura LLM Gateway](https://aura-llm.dev) and
|
|
6
|
+
its Open Responses API. Universal (Node 20+, browsers, Deno, Bun, Vercel Edge,
|
|
7
|
+
Cloudflare Workers) — built on the global `fetch` and Web Streams, with no
|
|
8
|
+
Node-only dependencies. Streaming is exposed as an `AsyncIterable`, and errors
|
|
9
|
+
are a typed hierarchy you can `instanceof`.
|
|
10
|
+
|
|
11
|
+
This mirrors the [Python SDK](../python/) feature set. There is **one** client
|
|
12
|
+
(JS is async by default) — every call returns a `Promise`, and streaming
|
|
13
|
+
returns an `AsyncIterable`.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install aura-llm
|
|
19
|
+
# or: pnpm add aura-llm / yarn add aura-llm / bun add aura-llm
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { AuraClient, outputText } from 'aura-llm'
|
|
26
|
+
|
|
27
|
+
const client = new AuraClient({
|
|
28
|
+
apiKey: process.env.AURA_API_KEY, // or set AURA_API_KEY in the environment
|
|
29
|
+
baseUrl: 'http://localhost:8080', // or AURA_BASE_URL (default localhost:8080)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const response = await client.responses.create({
|
|
33
|
+
model: 'gpt-5.4-mini',
|
|
34
|
+
input: 'What is the capital of France?',
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
console.log(outputText(response))
|
|
38
|
+
// → The capital of France is Paris.
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Streaming
|
|
42
|
+
|
|
43
|
+
`create({ stream: true })` returns an `AsyncIterable<StreamEvent>` with full
|
|
44
|
+
back-pressure — you only pull events as fast as you consume them.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
const stream = await client.responses.create({
|
|
48
|
+
model: 'gpt-5.4-mini',
|
|
49
|
+
input: 'Tell me a story',
|
|
50
|
+
stream: true,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
for await (const event of stream) {
|
|
54
|
+
if (event.type === 'response.output_text.delta') {
|
|
55
|
+
process.stdout.write(event.delta)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
> Streaming requests are **not** retried — re-issuing a partial stream would
|
|
61
|
+
> replay events. Errors before the first byte still surface as typed errors.
|
|
62
|
+
|
|
63
|
+
## Conversation threading
|
|
64
|
+
|
|
65
|
+
Use `previous_response_id` (the canonical Open Responses mechanism):
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const r1 = await client.responses.create({ model: 'gpt-5.4-mini', input: 'My name is Alice.' })
|
|
69
|
+
const r2 = await client.responses.create({
|
|
70
|
+
model: 'gpt-5.4-mini',
|
|
71
|
+
input: 'What is my name?',
|
|
72
|
+
previous_response_id: r1.id,
|
|
73
|
+
})
|
|
74
|
+
console.log(outputText(r2)) // → Your name is Alice.
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Tools
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { functionTool, toolCalls } from 'aura-llm'
|
|
81
|
+
|
|
82
|
+
const response = await client.responses.create({
|
|
83
|
+
model: 'gpt-5.4-mini',
|
|
84
|
+
input: "What's the weather in Tokyo?",
|
|
85
|
+
tools: [
|
|
86
|
+
functionTool('get_weather', 'Get current weather', {
|
|
87
|
+
type: 'object',
|
|
88
|
+
properties: { city: { type: 'string' } },
|
|
89
|
+
required: ['city'],
|
|
90
|
+
}),
|
|
91
|
+
],
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
for (const call of toolCalls(response)) {
|
|
95
|
+
console.log(call.name, call.arguments)
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Multi-tenancy & config blocks
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
await client.responses.create({
|
|
103
|
+
model: 'gpt-5.4-mini',
|
|
104
|
+
input: 'Hello',
|
|
105
|
+
user: 'customer_42', // end-user cost tracking
|
|
106
|
+
compression: { strategy: 'toon', auto_select: true },
|
|
107
|
+
validation: { strategy: 'best_of_n', n: 3, min_confidence: 0.85 },
|
|
108
|
+
consistency: { style_profile: 'concise' },
|
|
109
|
+
})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Configuration
|
|
113
|
+
|
|
114
|
+
| Option | Default | Description |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| `apiKey` | `AURA_API_KEY` env | Bearer token |
|
|
117
|
+
| `baseUrl` | `AURA_BASE_URL` env / `http://localhost:8080` | Gateway URL |
|
|
118
|
+
| `timeout` | `60000` | Per-request timeout (ms); guards time-to-first-byte for streams |
|
|
119
|
+
| `maxRetries` | `2` | Retries for 408/429/5xx + network errors; `0` disables |
|
|
120
|
+
| `headers` | — | Extra headers merged into every request |
|
|
121
|
+
| `fetch` | global `fetch` | Custom fetch (edge runtimes / tests) |
|
|
122
|
+
| `onRequest` / `onResponse` / `onError` | — | Lifecycle hooks |
|
|
123
|
+
|
|
124
|
+
## Response helpers
|
|
125
|
+
|
|
126
|
+
Interfaces can't carry methods, so the Python `@property` accessors are
|
|
127
|
+
functions here:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import { outputText, toolCalls, hasToolCalls, isComplete, isFailed } from 'aura-llm'
|
|
131
|
+
|
|
132
|
+
outputText(response) // assistant text ('' if none)
|
|
133
|
+
toolCalls(response) // FunctionCallItem[]
|
|
134
|
+
hasToolCalls(response) // boolean
|
|
135
|
+
isComplete(response) // status === 'completed'
|
|
136
|
+
isFailed(response) // status === 'failed'
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Error handling
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { AuraError, RateLimitError, AuthenticationError } from 'aura-llm'
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
await client.responses.create({ model: 'gpt-5.4-mini', input: 'Hi' })
|
|
146
|
+
} catch (err) {
|
|
147
|
+
if (err instanceof RateLimitError) {
|
|
148
|
+
console.log(`Retry after ${err.retryAfter}s`)
|
|
149
|
+
} else if (err instanceof AuthenticationError) {
|
|
150
|
+
console.log('Check your API key')
|
|
151
|
+
} else if (err instanceof AuraError) {
|
|
152
|
+
console.log(err.status, err.code, err.message, err.requestId)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Hierarchy: `AuraError` → `APIError` → `AuthenticationError` (401) /
|
|
158
|
+
`BadRequestError` (400) / `NotFoundError` (404) / `RateLimitError` (429); plus
|
|
159
|
+
`APIConnectionError` (network) and `APITimeoutError` (timeout).
|
|
160
|
+
|
|
161
|
+
## Retry policy
|
|
162
|
+
|
|
163
|
+
- Retries on: 408, 429, 500, 502, 503, 504, and network errors.
|
|
164
|
+
- Never retries other 4xx, or streaming requests.
|
|
165
|
+
- Exponential backoff with jitter, capped at 30s; honors `Retry-After`.
|
|
166
|
+
|
|
167
|
+
## Edge runtimes
|
|
168
|
+
|
|
169
|
+
No Node-only APIs — works on Vercel Edge, Cloudflare Workers, Deno, and Bun
|
|
170
|
+
out of the box. Inject a custom `fetch` if your runtime needs it:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
const client = new AuraClient({ apiKey, fetch: myFetch })
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Environment variables
|
|
177
|
+
|
|
178
|
+
| Variable | Purpose |
|
|
179
|
+
|---|---|
|
|
180
|
+
| `AURA_API_KEY` | API key (used if `apiKey` not passed) |
|
|
181
|
+
| `AURA_BASE_URL` | Gateway base URL (used if `baseUrl` not passed) |
|
|
182
|
+
|
|
183
|
+
## Development
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
npm install
|
|
187
|
+
npm run build # tsup → ESM + CJS + .d.ts
|
|
188
|
+
npm test # vitest
|
|
189
|
+
npm run test:coverage
|
|
190
|
+
npm run typecheck
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Compatibility
|
|
194
|
+
|
|
195
|
+
Versioned independently from the gateway. This SDK targets the Open Responses
|
|
196
|
+
API as served by aura-proxy ≥ v0.13.
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
var AuraError = class extends Error {
|
|
5
|
+
/** Gateway error code (e.g. `invalid_model`), when present. */
|
|
6
|
+
code;
|
|
7
|
+
/** Offending request parameter, when the gateway reports one. */
|
|
8
|
+
param;
|
|
9
|
+
/** HTTP status code, when the error originated from a response. */
|
|
10
|
+
status;
|
|
11
|
+
/** Gateway request id for support/correlation, when present. */
|
|
12
|
+
requestId;
|
|
13
|
+
/** Raw parsed response body, for debugging. */
|
|
14
|
+
responseBody;
|
|
15
|
+
constructor(message, options = {}) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = this.constructor.name;
|
|
18
|
+
this.code = options.code;
|
|
19
|
+
this.param = options.param;
|
|
20
|
+
this.status = options.status;
|
|
21
|
+
this.requestId = options.requestId;
|
|
22
|
+
this.responseBody = options.responseBody;
|
|
23
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var APIError = class extends AuraError {
|
|
27
|
+
};
|
|
28
|
+
var AuthenticationError = class extends APIError {
|
|
29
|
+
constructor(message = "Invalid or missing API key", options = {}) {
|
|
30
|
+
super(message, { code: "authentication_error", status: 401, ...options });
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var BadRequestError = class extends APIError {
|
|
34
|
+
constructor(message, options = {}) {
|
|
35
|
+
super(message, { code: "invalid_request", status: 400, ...options });
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var NotFoundError = class extends APIError {
|
|
39
|
+
constructor(message, options = {}) {
|
|
40
|
+
super(message, { code: "not_found", status: 404, ...options });
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var RateLimitError = class extends APIError {
|
|
44
|
+
/** Seconds to wait before retrying, from the `Retry-After` header. */
|
|
45
|
+
retryAfter;
|
|
46
|
+
constructor(message = "Rate limit exceeded", options = {}) {
|
|
47
|
+
const { retryAfter, ...rest } = options;
|
|
48
|
+
super(message, { code: "rate_limit_exceeded", status: 429, ...rest });
|
|
49
|
+
this.retryAfter = retryAfter;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var APIConnectionError = class extends AuraError {
|
|
53
|
+
constructor(message = "Failed to connect to Aura API", options = {}) {
|
|
54
|
+
super(message, { code: "connection_error", ...options });
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var APITimeoutError = class extends AuraError {
|
|
58
|
+
constructor(message = "Request timed out", options = {}) {
|
|
59
|
+
super(message, { code: "timeout", ...options });
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
function errorFromResponse(status, body, headers) {
|
|
63
|
+
let code = "unknown_error";
|
|
64
|
+
let message = `HTTP ${status}`;
|
|
65
|
+
let param;
|
|
66
|
+
if (body && typeof body === "object") {
|
|
67
|
+
const err = body.error;
|
|
68
|
+
if (err && typeof err === "object") {
|
|
69
|
+
if (typeof err.code === "string") code = err.code;
|
|
70
|
+
if (typeof err.message === "string") message = err.message;
|
|
71
|
+
if (typeof err.param === "string") param = err.param;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const requestId = headers.get("x-request-id") ?? headers.get("x-aura-request-id") ?? void 0;
|
|
75
|
+
const base = { code, param, status, requestId, responseBody: body };
|
|
76
|
+
switch (status) {
|
|
77
|
+
case 401:
|
|
78
|
+
return new AuthenticationError(message, base);
|
|
79
|
+
case 400:
|
|
80
|
+
return new BadRequestError(message, base);
|
|
81
|
+
case 404:
|
|
82
|
+
return new NotFoundError(message, base);
|
|
83
|
+
case 429: {
|
|
84
|
+
const ra = headers.get("retry-after");
|
|
85
|
+
return new RateLimitError(message, {
|
|
86
|
+
...base,
|
|
87
|
+
retryAfter: ra ? Number.parseInt(ra, 10) : void 0
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
default:
|
|
91
|
+
return new APIError(message, base);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/types.ts
|
|
96
|
+
function functionTool(name, description, parameters) {
|
|
97
|
+
return {
|
|
98
|
+
type: "function",
|
|
99
|
+
function: { name, ...description ? { description } : {}, ...parameters ? { parameters } : {} }
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function outputText(response) {
|
|
103
|
+
for (const item of response.output) {
|
|
104
|
+
if (item.type === "message" && item.role === "assistant") {
|
|
105
|
+
return item.content.filter((c) => c.type === "text").map((c) => c.text).join("");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return "";
|
|
109
|
+
}
|
|
110
|
+
function toolCalls(response) {
|
|
111
|
+
return response.output.filter(
|
|
112
|
+
(item) => item.type === "function_call"
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
var hasToolCalls = (r) => toolCalls(r).length > 0;
|
|
116
|
+
var isComplete = (r) => r.status === "completed";
|
|
117
|
+
var isFailed = (r) => r.status === "failed";
|
|
118
|
+
var STREAM_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
119
|
+
"response.created",
|
|
120
|
+
"response.in_progress",
|
|
121
|
+
"response.completed",
|
|
122
|
+
"response.failed",
|
|
123
|
+
"response.output_item.added",
|
|
124
|
+
"response.output_item.done",
|
|
125
|
+
"response.output_text.delta",
|
|
126
|
+
"response.output_text.done",
|
|
127
|
+
"response.function_call.delta",
|
|
128
|
+
"response.function_call.done",
|
|
129
|
+
"error"
|
|
130
|
+
]);
|
|
131
|
+
var userMessage = (content) => ({ role: "user", content });
|
|
132
|
+
var assistantMessage = (content) => ({
|
|
133
|
+
role: "assistant",
|
|
134
|
+
content
|
|
135
|
+
});
|
|
136
|
+
var systemMessage = (content) => ({
|
|
137
|
+
role: "system",
|
|
138
|
+
content
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// src/streaming.ts
|
|
142
|
+
function parseSSEChunk(chunk) {
|
|
143
|
+
let eventType = null;
|
|
144
|
+
const dataLines = [];
|
|
145
|
+
for (const line of chunk.split("\n")) {
|
|
146
|
+
if (line.startsWith("event:")) {
|
|
147
|
+
eventType = line.slice(6).trim();
|
|
148
|
+
} else if (line.startsWith("data:")) {
|
|
149
|
+
dataLines.push(line.slice(5).trim());
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (dataLines.length === 0) return null;
|
|
153
|
+
const dataStr = dataLines.join("\n");
|
|
154
|
+
if (dataStr === "[DONE]") return null;
|
|
155
|
+
let data;
|
|
156
|
+
try {
|
|
157
|
+
data = JSON.parse(dataStr);
|
|
158
|
+
} catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const type = eventType ?? (typeof data?.type === "string" ? data.type : null);
|
|
162
|
+
if (!type || !STREAM_EVENT_TYPES.has(type)) return null;
|
|
163
|
+
return data;
|
|
164
|
+
}
|
|
165
|
+
async function* parseSSE(body) {
|
|
166
|
+
const reader = body.getReader();
|
|
167
|
+
const decoder = new TextDecoder();
|
|
168
|
+
let buffer = "";
|
|
169
|
+
try {
|
|
170
|
+
for (; ; ) {
|
|
171
|
+
const { done, value } = await reader.read();
|
|
172
|
+
if (done) break;
|
|
173
|
+
buffer += decoder.decode(value, { stream: true });
|
|
174
|
+
let idx;
|
|
175
|
+
while ((idx = buffer.indexOf("\n\n")) !== -1) {
|
|
176
|
+
const chunk = buffer.slice(0, idx);
|
|
177
|
+
buffer = buffer.slice(idx + 2);
|
|
178
|
+
const event = parseSSEChunk(chunk);
|
|
179
|
+
if (event) yield event;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const tail = (buffer + decoder.decode()).trim();
|
|
183
|
+
if (tail) {
|
|
184
|
+
const event = parseSSEChunk(tail);
|
|
185
|
+
if (event) yield event;
|
|
186
|
+
}
|
|
187
|
+
} finally {
|
|
188
|
+
reader.releaseLock();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/client.ts
|
|
193
|
+
var DEFAULT_BASE_URL = "http://localhost:8080";
|
|
194
|
+
var DEFAULT_TIMEOUT = 6e4;
|
|
195
|
+
var DEFAULT_MAX_RETRIES = 2;
|
|
196
|
+
var SDK_VERSION = "0.1.0";
|
|
197
|
+
var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
|
|
198
|
+
function readEnv(name) {
|
|
199
|
+
const proc = globalThis.process;
|
|
200
|
+
return proc?.env?.[name];
|
|
201
|
+
}
|
|
202
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
203
|
+
var Responses = class {
|
|
204
|
+
constructor(client) {
|
|
205
|
+
this.client = client;
|
|
206
|
+
}
|
|
207
|
+
client;
|
|
208
|
+
create(params) {
|
|
209
|
+
const { stream = false } = params;
|
|
210
|
+
const payload = this.buildPayload(params);
|
|
211
|
+
if (stream) {
|
|
212
|
+
return this.client._stream("/v1/responses", payload);
|
|
213
|
+
}
|
|
214
|
+
return this.client._request("POST", "/v1/responses", payload);
|
|
215
|
+
}
|
|
216
|
+
buildPayload(params) {
|
|
217
|
+
const { input, stream = false, ...rest } = params;
|
|
218
|
+
let inputItems;
|
|
219
|
+
if (typeof input === "string") {
|
|
220
|
+
inputItems = [{ role: "user", content: input }];
|
|
221
|
+
} else {
|
|
222
|
+
inputItems = input;
|
|
223
|
+
}
|
|
224
|
+
const payload = { ...rest, input: inputItems, stream };
|
|
225
|
+
for (const k of Object.keys(payload)) {
|
|
226
|
+
if (payload[k] === void 0) delete payload[k];
|
|
227
|
+
}
|
|
228
|
+
return payload;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
var AuraClient = class {
|
|
232
|
+
baseUrl;
|
|
233
|
+
timeout;
|
|
234
|
+
maxRetries;
|
|
235
|
+
responses;
|
|
236
|
+
apiKey;
|
|
237
|
+
headers;
|
|
238
|
+
fetchFn;
|
|
239
|
+
opts;
|
|
240
|
+
constructor(options = {}) {
|
|
241
|
+
this.apiKey = options.apiKey ?? readEnv("AURA_API_KEY");
|
|
242
|
+
this.baseUrl = (options.baseUrl ?? readEnv("AURA_BASE_URL") ?? DEFAULT_BASE_URL).replace(
|
|
243
|
+
/\/+$/,
|
|
244
|
+
""
|
|
245
|
+
);
|
|
246
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
247
|
+
this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
248
|
+
this.opts = options;
|
|
249
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
250
|
+
if (!fetchImpl) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
"No global fetch available. Pass a `fetch` implementation in AuraClientOptions."
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
this.fetchFn = fetchImpl;
|
|
256
|
+
this.headers = {
|
|
257
|
+
"Content-Type": "application/json",
|
|
258
|
+
"User-Agent": `aura-typescript/${SDK_VERSION}`,
|
|
259
|
+
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
|
|
260
|
+
...options.headers ?? {}
|
|
261
|
+
};
|
|
262
|
+
this.responses = new Responses(this);
|
|
263
|
+
}
|
|
264
|
+
/** Build an absolute URL + Headers for a request. */
|
|
265
|
+
prepare(path) {
|
|
266
|
+
return { url: `${this.baseUrl}${path}`, headers: new Headers(this.headers) };
|
|
267
|
+
}
|
|
268
|
+
/** Exponential backoff with jitter, capped at 30s; honors Retry-After. */
|
|
269
|
+
backoffMs(attempt, retryAfter) {
|
|
270
|
+
if (retryAfter && Number.isFinite(retryAfter)) return retryAfter * 1e3;
|
|
271
|
+
return Math.min(2 ** attempt * 1e3 + Math.random() * 1e3, 3e4);
|
|
272
|
+
}
|
|
273
|
+
/** Non-streaming JSON request with retry + typed error mapping. */
|
|
274
|
+
async _request(method, path, body) {
|
|
275
|
+
const { url, headers } = this.prepare(path);
|
|
276
|
+
let lastError;
|
|
277
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
278
|
+
const controller = new AbortController();
|
|
279
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
280
|
+
const started = Date.now();
|
|
281
|
+
this.opts.onRequest?.({ method, url, headers });
|
|
282
|
+
try {
|
|
283
|
+
const res = await this.fetchFn(url, {
|
|
284
|
+
method,
|
|
285
|
+
headers,
|
|
286
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
287
|
+
signal: controller.signal
|
|
288
|
+
});
|
|
289
|
+
this.opts.onResponse?.({ status: res.status, url }, Date.now() - started);
|
|
290
|
+
if (res.ok) {
|
|
291
|
+
return await res.json();
|
|
292
|
+
}
|
|
293
|
+
const parsed = await safeJson(res);
|
|
294
|
+
const err = errorFromResponse(res.status, parsed, res.headers);
|
|
295
|
+
if (RETRYABLE_STATUS.has(res.status) && attempt < this.maxRetries) {
|
|
296
|
+
lastError = err;
|
|
297
|
+
const retryAfter = "retryAfter" in err ? err.retryAfter : void 0;
|
|
298
|
+
await sleep(this.backoffMs(attempt, retryAfter));
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
this.opts.onError?.(err);
|
|
302
|
+
throw err;
|
|
303
|
+
} catch (e) {
|
|
304
|
+
const mapped = this.mapNetworkError(e);
|
|
305
|
+
if (mapped) {
|
|
306
|
+
if (attempt < this.maxRetries) {
|
|
307
|
+
lastError = mapped;
|
|
308
|
+
await sleep(this.backoffMs(attempt));
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
this.opts.onError?.(mapped);
|
|
312
|
+
throw mapped;
|
|
313
|
+
}
|
|
314
|
+
throw e;
|
|
315
|
+
} finally {
|
|
316
|
+
clearTimeout(timer);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
throw lastError ?? new APIConnectionError("Request failed after retries");
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Streaming request → AsyncIterable<StreamEvent>. Streams are NOT retried
|
|
323
|
+
* (re-issuing a partial stream would replay events); errors before the
|
|
324
|
+
* first byte still surface as typed errors.
|
|
325
|
+
*/
|
|
326
|
+
async _stream(path, body) {
|
|
327
|
+
const { url, headers } = this.prepare(path);
|
|
328
|
+
const controller = new AbortController();
|
|
329
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
330
|
+
const started = Date.now();
|
|
331
|
+
this.opts.onRequest?.({ method: "POST", url, headers });
|
|
332
|
+
let res;
|
|
333
|
+
try {
|
|
334
|
+
res = await this.fetchFn(url, {
|
|
335
|
+
method: "POST",
|
|
336
|
+
headers,
|
|
337
|
+
body: JSON.stringify(body),
|
|
338
|
+
signal: controller.signal
|
|
339
|
+
});
|
|
340
|
+
} catch (e) {
|
|
341
|
+
clearTimeout(timer);
|
|
342
|
+
const mapped = this.mapNetworkError(e);
|
|
343
|
+
if (mapped) {
|
|
344
|
+
this.opts.onError?.(mapped);
|
|
345
|
+
throw mapped;
|
|
346
|
+
}
|
|
347
|
+
throw e;
|
|
348
|
+
}
|
|
349
|
+
this.opts.onResponse?.({ status: res.status, url }, Date.now() - started);
|
|
350
|
+
if (!res.ok) {
|
|
351
|
+
clearTimeout(timer);
|
|
352
|
+
const parsed = await safeJson(res);
|
|
353
|
+
const err = errorFromResponse(res.status, parsed, res.headers);
|
|
354
|
+
this.opts.onError?.(err);
|
|
355
|
+
throw err;
|
|
356
|
+
}
|
|
357
|
+
if (!res.body) {
|
|
358
|
+
clearTimeout(timer);
|
|
359
|
+
throw new APIConnectionError("Streaming response had no body");
|
|
360
|
+
}
|
|
361
|
+
clearTimeout(timer);
|
|
362
|
+
return parseSSE(res.body);
|
|
363
|
+
}
|
|
364
|
+
/** Map a thrown fetch error to a typed AuraError, or null if not ours. */
|
|
365
|
+
mapNetworkError(e) {
|
|
366
|
+
if (e instanceof Error) {
|
|
367
|
+
if (e.name === "AbortError") return new APITimeoutError(`Request timed out: ${e.message}`);
|
|
368
|
+
if (e.name === "TypeError" || /fetch failed|network|ECONN|ENOTFOUND/i.test(e.message)) {
|
|
369
|
+
return new APIConnectionError(`Failed to connect: ${e.message}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
async function safeJson(res) {
|
|
376
|
+
try {
|
|
377
|
+
return await res.json();
|
|
378
|
+
} catch {
|
|
379
|
+
try {
|
|
380
|
+
return { error: { message: await res.text() } };
|
|
381
|
+
} catch {
|
|
382
|
+
return void 0;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
exports.APIConnectionError = APIConnectionError;
|
|
388
|
+
exports.APIError = APIError;
|
|
389
|
+
exports.APITimeoutError = APITimeoutError;
|
|
390
|
+
exports.AuraClient = AuraClient;
|
|
391
|
+
exports.AuraError = AuraError;
|
|
392
|
+
exports.AuthenticationError = AuthenticationError;
|
|
393
|
+
exports.BadRequestError = BadRequestError;
|
|
394
|
+
exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
|
|
395
|
+
exports.DEFAULT_MAX_RETRIES = DEFAULT_MAX_RETRIES;
|
|
396
|
+
exports.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT;
|
|
397
|
+
exports.NotFoundError = NotFoundError;
|
|
398
|
+
exports.RateLimitError = RateLimitError;
|
|
399
|
+
exports.Responses = Responses;
|
|
400
|
+
exports.STREAM_EVENT_TYPES = STREAM_EVENT_TYPES;
|
|
401
|
+
exports.assistantMessage = assistantMessage;
|
|
402
|
+
exports.functionTool = functionTool;
|
|
403
|
+
exports.hasToolCalls = hasToolCalls;
|
|
404
|
+
exports.isComplete = isComplete;
|
|
405
|
+
exports.isFailed = isFailed;
|
|
406
|
+
exports.outputText = outputText;
|
|
407
|
+
exports.parseSSE = parseSSE;
|
|
408
|
+
exports.parseSSEChunk = parseSSEChunk;
|
|
409
|
+
exports.systemMessage = systemMessage;
|
|
410
|
+
exports.toolCalls = toolCalls;
|
|
411
|
+
exports.userMessage = userMessage;
|
|
412
|
+
//# sourceMappingURL=index.cjs.map
|
|
413
|
+
//# sourceMappingURL=index.cjs.map
|