@oberik/sdk 0.54.0 → 0.56.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/cjs/index.js +106 -7
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.d.ts +59 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +106 -7
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -122,6 +122,28 @@ function renderCited(turn) {
|
|
|
122
122
|
out.push({ text: content.slice(at), citations: [] });
|
|
123
123
|
return out;
|
|
124
124
|
}
|
|
125
|
+
const PROJECT_SESSION_QUERY_KEYS = [
|
|
126
|
+
"q", "model", "status", "userRef", "since", "until", "includeSubagents", "limit", "offset",
|
|
127
|
+
];
|
|
128
|
+
/**
|
|
129
|
+
* Throw on an option key this library does not know.
|
|
130
|
+
*
|
|
131
|
+
* A dropped option is the worst of the three answers: `{ includeSubAgents: true }` — one
|
|
132
|
+
* capital away from the real name — returned a cheerful 200 with no delegates in it, which
|
|
133
|
+
* reads as "there are no delegates" rather than as "you spelled it wrong" (OBE-147). A
|
|
134
|
+
* typo in an options bag is not something a type system catches for a caller in plain JS,
|
|
135
|
+
* and it is exactly the caller who has no compiler to tell them.
|
|
136
|
+
*/
|
|
137
|
+
function refuseUnknownKeys(opts, known, where) {
|
|
138
|
+
// `object` rather than `Record<string, unknown>`: an interface without an index
|
|
139
|
+
// signature is not assignable to the latter, so typing it that way makes the one call
|
|
140
|
+
// site fail to compile — which is exactly the caller this exists for.
|
|
141
|
+
const unknown = Object.keys(opts ?? {}).filter((k) => !known.includes(k));
|
|
142
|
+
if (unknown.length) {
|
|
143
|
+
throw new TypeError(`${where}: unknown option${unknown.length > 1 ? "s" : ""} ` +
|
|
144
|
+
`${unknown.map((k) => `'${k}'`).join(", ")}. Valid options: ${known.join(", ")}.`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
125
147
|
/** Wrap the stream's `done` promise into a thenable handle: `await handle` yields
|
|
126
148
|
* the ChatDone, and `.done`/`.cancel`/`.disconnect`/`.runId` remain available. */
|
|
127
149
|
function makeStreamHandle(done, extra) {
|
|
@@ -426,13 +448,28 @@ class AgentApiError extends Error {
|
|
|
426
448
|
* an intermediary, which is replaced by the same summary as `message`. Keeping four
|
|
427
449
|
* kilobytes of someone else's markup here helped nobody and buried the status. */
|
|
428
450
|
detail;
|
|
429
|
-
|
|
451
|
+
/** The run that failed, when the server said which.
|
|
452
|
+
*
|
|
453
|
+
* A failed `chat.send` used to carry neither this nor {@link sessionId}, so the reason
|
|
454
|
+
* the server stores for the turn — the only durable answer to "why did this fail" —
|
|
455
|
+
* could not be looked up by the person who hit it. `chat.stream` had both all along;
|
|
456
|
+
* this is the same handle on the blocking path (OBE-142). */
|
|
457
|
+
runId;
|
|
458
|
+
/** The conversation the failed turn belongs to, when the server said which.
|
|
459
|
+
*
|
|
460
|
+
* Present even when the turn failed before producing anything: the session exists from
|
|
461
|
+
* the moment the turn is prepared, and whatever it wrote is readable with
|
|
462
|
+
* `ai.chat.sessions.messages(err.sessionId)`. */
|
|
463
|
+
sessionId;
|
|
464
|
+
constructor(status, detail, ids) {
|
|
430
465
|
const described = describeDetail(status, detail);
|
|
431
466
|
super(described);
|
|
432
467
|
this.name = "AgentApiError";
|
|
433
468
|
this.status = status;
|
|
434
469
|
this.detail =
|
|
435
470
|
typeof detail === "string" && looksLikeHtml(detail) ? described : detail;
|
|
471
|
+
this.runId = ids?.runId;
|
|
472
|
+
this.sessionId = ids?.sessionId;
|
|
436
473
|
}
|
|
437
474
|
}
|
|
438
475
|
exports.AgentApiError = AgentApiError;
|
|
@@ -1607,15 +1644,32 @@ class AgentFramework {
|
|
|
1607
1644
|
}
|
|
1608
1645
|
if (!res.ok) {
|
|
1609
1646
|
let detail = await res.text();
|
|
1647
|
+
// Alongside `detail`, when the server said which turn this was about. Read here
|
|
1648
|
+
// because this is the one place an API error is built from a response — every route's
|
|
1649
|
+
// refusal passes through it, so a route that starts saying which run failed needs no
|
|
1650
|
+
// change on this side.
|
|
1651
|
+
let ids;
|
|
1610
1652
|
try {
|
|
1611
|
-
|
|
1612
|
-
if (
|
|
1613
|
-
|
|
1653
|
+
const body = JSON.parse(detail);
|
|
1654
|
+
if (body && typeof body === "object") {
|
|
1655
|
+
const b = body;
|
|
1656
|
+
if ("detail" in b)
|
|
1657
|
+
detail = b.detail;
|
|
1658
|
+
else
|
|
1659
|
+
detail = body;
|
|
1660
|
+
const runId = typeof b.run_id === "string" ? b.run_id : undefined;
|
|
1661
|
+
const sessionId = typeof b.session_id === "string" ? b.session_id : undefined;
|
|
1662
|
+
if (runId || sessionId)
|
|
1663
|
+
ids = { runId, sessionId };
|
|
1664
|
+
}
|
|
1665
|
+
else {
|
|
1666
|
+
detail = body;
|
|
1667
|
+
}
|
|
1614
1668
|
}
|
|
1615
1669
|
catch {
|
|
1616
1670
|
/* leave as text */
|
|
1617
1671
|
}
|
|
1618
|
-
throw new AgentApiError(res.status, detail);
|
|
1672
|
+
throw new AgentApiError(res.status, detail, ids);
|
|
1619
1673
|
}
|
|
1620
1674
|
return res;
|
|
1621
1675
|
}
|
|
@@ -2658,7 +2712,13 @@ class AgentFramework {
|
|
|
2658
2712
|
// This used to be a bare message, so a stream that died at minute
|
|
2659
2713
|
// eighteen threw away the answer AND the only handle on where the rest
|
|
2660
2714
|
// of it had been written.
|
|
2661
|
-
|
|
2715
|
+
// The server's sentence, unprefixed. This read `"server: " + detail`, so
|
|
2716
|
+
// every streamed failure reached the customer as
|
|
2717
|
+
// `server: the upstream endpoint answered 408 …` — a lowercase field name in
|
|
2718
|
+
// front of the first word they read, and the one difference between the two
|
|
2719
|
+
// paths' otherwise identical message (OBE-140). `AgentStreamError` already
|
|
2720
|
+
// says where it came from; the label said it again, worse.
|
|
2721
|
+
throw new AgentStreamError(ev.data.detail, {
|
|
2662
2722
|
code: ev.data.code,
|
|
2663
2723
|
// The status the same refusal carries on `POST /chat` — 429 for a spend
|
|
2664
2724
|
// cap or a rate limit. Without it a streaming caller had no number to
|
|
@@ -3278,7 +3338,46 @@ class OberikProject {
|
|
|
3278
3338
|
};
|
|
3279
3339
|
/** Conversations, and what was said in them. */
|
|
3280
3340
|
sessions = {
|
|
3281
|
-
|
|
3341
|
+
/**
|
|
3342
|
+
* Conversations on this project, newest first.
|
|
3343
|
+
*
|
|
3344
|
+
* A PAGE, and typed as one. This declared `Promise<unknown[]>` and returned
|
|
3345
|
+
* `{items, has_more, next_offset}`, so a TypeScript caller wrote `.length` and got
|
|
3346
|
+
* `undefined` — no type error, no exception, and a census that counted sessions
|
|
3347
|
+
* reported zero (OBE-147). The declared type is the envelope it has always been.
|
|
3348
|
+
*
|
|
3349
|
+
* It also forwarded nothing at all, which made its own paging unreachable: the
|
|
3350
|
+
* response carried `has_more` and `next_offset`, the two fields whose only purpose is
|
|
3351
|
+
* to be fed back in, and there was nowhere to feed them. Past 100 sessions the tail
|
|
3352
|
+
* was unreachable through this client.
|
|
3353
|
+
*
|
|
3354
|
+
* `includeSubagents` is the one that cost somebody an hour: a delegate's working
|
|
3355
|
+
* conversation is excluded by default (correct — a sidebar built from here grew a row
|
|
3356
|
+
* per delegate), and with nothing forwarded, the admin-scope client an operator audits
|
|
3357
|
+
* with was the one that could not ask for them.
|
|
3358
|
+
*/
|
|
3359
|
+
list: async (opts = {}) => {
|
|
3360
|
+
// `async`, so a bad option REJECTS rather than throwing synchronously out of a
|
|
3361
|
+
// promise-returning method — `list({…}).catch(…)` would not have caught that.
|
|
3362
|
+
refuseUnknownKeys(opts, PROJECT_SESSION_QUERY_KEYS, "sessions.list");
|
|
3363
|
+
// Built the way `documents.list` two methods down builds its own: this client's
|
|
3364
|
+
// `request` takes a body, not a query, and passing `{query: …}` to it sends a JSON
|
|
3365
|
+
// body on a GET and no query string at all — which is quietly what "forwards
|
|
3366
|
+
// nothing" would have looked like if the query had been added the obvious way.
|
|
3367
|
+
const wire = {
|
|
3368
|
+
q: opts.q,
|
|
3369
|
+
model: opts.model,
|
|
3370
|
+
status: opts.status,
|
|
3371
|
+
user_ref: opts.userRef,
|
|
3372
|
+
since: opts.since,
|
|
3373
|
+
until: opts.until,
|
|
3374
|
+
include_subagents: opts.includeSubagents,
|
|
3375
|
+
limit: opts.limit,
|
|
3376
|
+
offset: opts.offset,
|
|
3377
|
+
};
|
|
3378
|
+
const qs = new URLSearchParams(Object.entries(wire).filter(([, v]) => v != null).map(([k, v]) => [k, String(v)])).toString();
|
|
3379
|
+
return this.request("GET", `/sessions${qs ? `?${qs}` : ""}`);
|
|
3380
|
+
},
|
|
3282
3381
|
messages: (sessionId) => this.request("GET", `/sessions/${sessionId}/messages`),
|
|
3283
3382
|
};
|
|
3284
3383
|
/** Scheduled work this project's end-users have created. */
|