@byokit/accounts 0.7.0 → 0.7.1
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 +4 -0
- package/README.md +9 -1
- package/dist/portable.d.ts +1 -1
- package/dist/portable.js +1 -1
- package/dist/responses.d.ts +14 -3
- package/dist/responses.js +35 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.7.1 (2026-09-30)
|
|
6
|
+
|
|
7
|
+
- FIX: a cut-off answer is now reported as cut off: `respond` throws `IncompleteError` with its reason and partial output, and notifies `onEvent`, instead of returning it as finished.
|
|
8
|
+
|
|
5
9
|
## 0.7.0 (2026-09-30)
|
|
6
10
|
|
|
7
11
|
- Add the portable `chatgptPlan` adapter for a host-validated official token-sharing session, checking
|
package/README.md
CHANGED
|
@@ -119,7 +119,7 @@ and [`examples/pwa`](../../examples/pwa) (browser sign-in).
|
|
|
119
119
|
| `memoryStore`, `fileStore`, `secureStore`, `browserStore`, `recordStore` | One store per person: in memory, a 0600 file (Node entry only), Keychain/Keystore, IndexedDB, or your own load and save |
|
|
120
120
|
| `offered`, `provider`, `PROVIDERS` | The catalogue: each provider's billing, terms status, reason and source |
|
|
121
121
|
| `billingWords`, `say`, `WORDS`, `signInError`, `failure`, `clock`, `callbackPage` | The plain sentences every app shows the same way (`words.json`), a time in words, and the page a browser sees after a sign-in |
|
|
122
|
-
| `respond`, `ResponseError`, `sseReader`, `limitResponse`, `isFunctionCall` | Ask ChatGPT's answers endpoint with a sign-in, with tools, pictures, thinking effort and an answer shape; the error with the words to show and the kind acted on |
|
|
122
|
+
| `respond`, `ResponseError`, `IncompleteError`, `sseReader`, `limitResponse`, `isFunctionCall` | Ask ChatGPT's answers endpoint with a sign-in, with tools, pictures, thinking effort and an answer shape; the error with the words to show and the kind acted on |
|
|
123
123
|
| `classify`, `REST_MS` | An error's kind (limit, overload, plan without this use, lapsed sign-in, network) and default rest times |
|
|
124
124
|
| `planOf`, `claims` | The ChatGPT plan and email behind a sign-in, from its own token |
|
|
125
125
|
| `deviceStart`, `devicePoll`, `credentialOf`, `portableEngine`, `PORTABLE` | The device-code flow, the sign-in built from a token answer, and the engine under `portable` |
|
|
@@ -217,6 +217,14 @@ member's sign-in, refreshed first when due, and returns the whole text (`onText`
|
|
|
217
217
|
returned completion is authoritative). A limit or a lapsed sign-in is acted on as `failed()` does, then thrown as a
|
|
218
218
|
`ResponseError` with the words to show and the kind acted on. Rules: [conformance fixtures](../../fixtures/README.md).
|
|
219
219
|
|
|
220
|
+
A cut-off answer always throws `IncompleteError` (a `ResponseError` with `kind: null`), with or without tools.
|
|
221
|
+
Its `reason` preserves the provider's `incomplete_details.reason`, including `max_output_tokens` and
|
|
222
|
+
`content_filter` (`unknown` when absent). Its `result` holds the partial `{ text, output }` for apps that want to
|
|
223
|
+
show it as unfinished. `onEvent` also receives `{ type: 'incomplete', reason }` before rejection; `onText` may
|
|
224
|
+
already have shown partial words. This covers `response.incomplete` events and `status: 'incomplete'` envelopes,
|
|
225
|
+
whether fetch streams SSE, buffers it, or returns JSON. The account stays signed in and is not put to rest.
|
|
226
|
+
Successful return values are unchanged.
|
|
227
|
+
|
|
220
228
|
The whole question passes through: `input` takes the turns so far (messages, with `input_image` where the person
|
|
221
229
|
attached a picture), `tools` and `tool_choice` take the app's own function tools and built-ins (including
|
|
222
230
|
`image_generation`), `reasoning.effort` how hard the model thinks, and `text` how long the answer is with the shape it
|
package/dist/portable.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { Accounts, planOf, portable, type AccountsOptions, type AuthHost, type L
|
|
|
2
2
|
export { PROVIDERS, offered, provider, type Billing, type Provider, type Terms } from './catalogue.ts';
|
|
3
3
|
export { PORTABLE, claims, credentialOf, devicePoll, deviceStart, portableEngine, type EngineOptions, type Poll } from './engine.ts';
|
|
4
4
|
export { REST_MS, classify, type Kind } from './limits.ts';
|
|
5
|
-
export { ResponseError, isFunctionCall, limitResponse, respond, sseReader, type Ask, type ResponseFunctionCall, type ResponseInputItem, type ResponseOutputItem, type ResponseOutputMessage, type ResponseReasoning, type ResponseResult, type ResponseStreamEvent, type ResponseText, type ResponseTextFormat, type ResponseTool, type ResponseToolChoice } from './responses.ts';
|
|
5
|
+
export { IncompleteError, ResponseError, isFunctionCall, limitResponse, respond, sseReader, type Ask, type ResponseFunctionCall, type ResponseInputItem, type ResponseOutputItem, type ResponseOutputMessage, type ResponseReasoning, type ResponseResult, type ResponseStreamEvent, type ResponseText, type ResponseTextFormat, type ResponseTool, type ResponseToolChoice } from './responses.ts';
|
|
6
6
|
export { browserStore, memoryStore, recordStore, secureStore, type SecureStoreLike } from './stores.ts';
|
|
7
7
|
export { WORDS, billingWords, callbackPage, clock, failure, say, signInError, type WordKey, type Why } from './words.ts';
|
|
8
8
|
export { chatgptPlan, UnsupportedAccountError, type ChatGPTPlanAccount, type ChatGPTPlanSession } from './chatgpt-plan.ts';
|
package/dist/portable.js
CHANGED
|
@@ -4,7 +4,7 @@ export { Accounts, planOf, portable } from "./accounts.js";
|
|
|
4
4
|
export { PROVIDERS, offered, provider } from "./catalogue.js";
|
|
5
5
|
export { PORTABLE, claims, credentialOf, devicePoll, deviceStart, portableEngine } from "./engine.js";
|
|
6
6
|
export { REST_MS, classify } from "./limits.js";
|
|
7
|
-
export { ResponseError, isFunctionCall, limitResponse, respond, sseReader } from "./responses.js";
|
|
7
|
+
export { IncompleteError, ResponseError, isFunctionCall, limitResponse, respond, sseReader } from "./responses.js";
|
|
8
8
|
export { browserStore, memoryStore, recordStore, secureStore } from "./stores.js";
|
|
9
9
|
export { WORDS, billingWords, callbackPage, clock, failure, say, signInError } from "./words.js";
|
|
10
10
|
export { chatgptPlan, UnsupportedAccountError } from "./chatgpt-plan.js";
|
package/dist/responses.d.ts
CHANGED
|
@@ -6,6 +6,12 @@ export declare class ResponseError extends Error {
|
|
|
6
6
|
until: number;
|
|
7
7
|
constructor(message: string, kind: Kind | null, until?: number);
|
|
8
8
|
}
|
|
9
|
+
/** An answer the provider cut off. Partial output is available, but never returned as a successful answer. */
|
|
10
|
+
export declare class IncompleteError extends ResponseError {
|
|
11
|
+
reason: string;
|
|
12
|
+
result: ResponseResult;
|
|
13
|
+
constructor(reason: string, result: ResponseResult);
|
|
14
|
+
}
|
|
9
15
|
/** A ChatGPT HTTP error as the kind, when to come back, and the message (fixtures/conformance/limit-responses.json). */
|
|
10
16
|
export declare function limitResponse(status: number, body: string, now?: number): {
|
|
11
17
|
kind: Kind | null;
|
|
@@ -129,6 +135,9 @@ export type ResponseResult = {
|
|
|
129
135
|
};
|
|
130
136
|
/** What streams besides the words: each text piece, each tool call as it builds and lands, and each output item. */
|
|
131
137
|
export type ResponseStreamEvent = {
|
|
138
|
+
type: 'incomplete';
|
|
139
|
+
reason: string;
|
|
140
|
+
} | {
|
|
132
141
|
type: 'text_delta';
|
|
133
142
|
delta: string;
|
|
134
143
|
} | {
|
|
@@ -149,7 +158,8 @@ export type ResponseStreamEvent = {
|
|
|
149
158
|
* `result` for the text with every output item. `onEvent` sees each tool call and output item as it lands.
|
|
150
159
|
* Events split on any blank line (LF, CRLF or bare CR). A data line that is not JSON throws a ResponseError;
|
|
151
160
|
* a stream ending with nothing to show throws too. The text is the streamed deltas; the completed envelope
|
|
152
|
-
* only fills in when no deltas arrived. An error event throws a ResponseError.
|
|
161
|
+
* only fills in when no deltas arrived. An error event throws a ResponseError. An incomplete answer
|
|
162
|
+
* emits an incomplete event and throws IncompleteError with its reason and partial result. */
|
|
153
163
|
export declare function sseReader(onText?: (delta: string) => void, onEvent?: (event: ResponseStreamEvent) => void): {
|
|
154
164
|
push(chunk: string): void;
|
|
155
165
|
end(): string;
|
|
@@ -173,7 +183,7 @@ export type Ask = {
|
|
|
173
183
|
text?: ResponseText;
|
|
174
184
|
/** Each piece of the answer as it streams. */
|
|
175
185
|
onText?: (delta: string) => void;
|
|
176
|
-
/** Each tool call
|
|
186
|
+
/** Each text piece, tool call, output item, and incomplete answer notification. */
|
|
177
187
|
onEvent?: (event: ResponseStreamEvent) => void;
|
|
178
188
|
signal?: AbortSignal;
|
|
179
189
|
/** The app's own originator header value. Default: 'byokit'. */
|
|
@@ -187,7 +197,8 @@ type Access = {
|
|
|
187
197
|
fetch?: typeof fetch;
|
|
188
198
|
};
|
|
189
199
|
/** Ask ChatGPT with a signed-in token. `fetch`: pass one that streams (Expo's `expo/fetch`); any fetch works.
|
|
190
|
-
* Without `tools` the answer is the plain text, as before; with `tools` it is the text with every output item.
|
|
200
|
+
* Without `tools` the answer is the plain text, as before; with `tools` it is the text with every output item.
|
|
201
|
+
* Incomplete answers always throw IncompleteError and notify onEvent, including with tools. */
|
|
191
202
|
export declare function respond(o: Ask & Access & {
|
|
192
203
|
tools?: undefined;
|
|
193
204
|
}): Promise<string>;
|
package/dist/responses.js
CHANGED
|
@@ -16,6 +16,17 @@ export class ResponseError extends Error {
|
|
|
16
16
|
until;
|
|
17
17
|
constructor(message, kind, until = 0) { super(message); this.kind = kind; this.until = until; }
|
|
18
18
|
}
|
|
19
|
+
/** An answer the provider cut off. Partial output is available, but never returned as a successful answer. */
|
|
20
|
+
export class IncompleteError extends ResponseError {
|
|
21
|
+
reason;
|
|
22
|
+
result;
|
|
23
|
+
constructor(reason, result) {
|
|
24
|
+
super('ChatGPT cut off its answer before it was complete.', null);
|
|
25
|
+
this.name = 'IncompleteError';
|
|
26
|
+
this.reason = reason;
|
|
27
|
+
this.result = result;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
19
30
|
/** A ChatGPT HTTP error as the kind, when to come back, and the message (fixtures/conformance/limit-responses.json). */
|
|
20
31
|
export function limitResponse(status, body, now = Date.now()) {
|
|
21
32
|
let err = {};
|
|
@@ -41,10 +52,12 @@ export const isFunctionCall = (item) => isRecord(item) && item.type === 'functio
|
|
|
41
52
|
* `result` for the text with every output item. `onEvent` sees each tool call and output item as it lands.
|
|
42
53
|
* Events split on any blank line (LF, CRLF or bare CR). A data line that is not JSON throws a ResponseError;
|
|
43
54
|
* a stream ending with nothing to show throws too. The text is the streamed deltas; the completed envelope
|
|
44
|
-
* only fills in when no deltas arrived. An error event throws a ResponseError.
|
|
55
|
+
* only fills in when no deltas arrived. An error event throws a ResponseError. An incomplete answer
|
|
56
|
+
* emits an incomplete event and throws IncompleteError with its reason and partial result. */
|
|
45
57
|
export function sseReader(onText, onEvent) {
|
|
46
58
|
let buffer = '', text = '', completed;
|
|
47
59
|
let done = false, finished;
|
|
60
|
+
let incomplete;
|
|
48
61
|
const output = [];
|
|
49
62
|
const emitted = new Set();
|
|
50
63
|
const calls = new Map();
|
|
@@ -100,7 +113,7 @@ export function sseReader(onText, onEvent) {
|
|
|
100
113
|
}
|
|
101
114
|
land(e.item, e.output_index ?? 0);
|
|
102
115
|
}
|
|
103
|
-
if (e.type === 'response.completed' || e.type === 'response.incomplete') {
|
|
116
|
+
if (e.type === 'response.completed' || e.type === 'response.incomplete' || e.response?.status === 'incomplete') {
|
|
104
117
|
done = true;
|
|
105
118
|
if (Array.isArray(e.response?.output)) {
|
|
106
119
|
for (const [i, item] of e.response.output.entries())
|
|
@@ -108,6 +121,11 @@ export function sseReader(onText, onEvent) {
|
|
|
108
121
|
completed = e.response.output.flatMap((o) => o?.content ?? []).filter((c) => c?.type === 'output_text').map((c) => c.text ?? '').join('');
|
|
109
122
|
}
|
|
110
123
|
}
|
|
124
|
+
if ((e.type === 'response.incomplete' || e.response?.status === 'incomplete') && incomplete === undefined) {
|
|
125
|
+
const reason = typeof e.response?.incomplete_details?.reason === 'string' ? e.response.incomplete_details.reason : 'unknown';
|
|
126
|
+
incomplete = reason;
|
|
127
|
+
onEvent?.({ type: 'incomplete', reason });
|
|
128
|
+
}
|
|
111
129
|
const failed = e.type === 'error' ? e : e.type === 'response.failed' ? e.response?.error : undefined;
|
|
112
130
|
if (failed) {
|
|
113
131
|
const message = String(failed.message ?? 'Request failed');
|
|
@@ -133,12 +151,14 @@ export function sseReader(onText, onEvent) {
|
|
|
133
151
|
if (!done)
|
|
134
152
|
throw new ResponseError('ChatGPT stopped before completing its answer.', 'network');
|
|
135
153
|
const whole = text !== '' ? text : (completed ?? '');
|
|
136
|
-
if (whole === '' && output.length === 0)
|
|
154
|
+
if (incomplete === undefined && whole === '' && output.length === 0)
|
|
137
155
|
throw new ResponseError('ChatGPT stopped before completing its answer.', 'network');
|
|
138
156
|
if (text === '' && whole !== '')
|
|
139
157
|
onText?.(whole);
|
|
140
158
|
finished = { text: whole, output };
|
|
141
159
|
}
|
|
160
|
+
if (incomplete !== undefined)
|
|
161
|
+
throw new IncompleteError(incomplete, finished);
|
|
142
162
|
return finished;
|
|
143
163
|
};
|
|
144
164
|
return {
|
|
@@ -173,7 +193,18 @@ export async function respond(o) {
|
|
|
173
193
|
}
|
|
174
194
|
const reader = sseReader(o.onText, o.onEvent);
|
|
175
195
|
const body = res.body;
|
|
176
|
-
if (
|
|
196
|
+
if (res.headers?.get('content-type')?.includes('application/json')) {
|
|
197
|
+
let response;
|
|
198
|
+
try {
|
|
199
|
+
response = JSON.parse(await res.text());
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
throw new ResponseError("ChatGPT's answer could not be read.", null);
|
|
203
|
+
}
|
|
204
|
+
const type = isRecord(response) ? `response.${String(response.status)}` : '';
|
|
205
|
+
reader.push(`data: ${JSON.stringify({ type, response })}\n\n`);
|
|
206
|
+
}
|
|
207
|
+
else if (body?.getReader && typeof TextDecoder !== 'undefined') {
|
|
177
208
|
const r = body.getReader();
|
|
178
209
|
const decoder = new TextDecoder();
|
|
179
210
|
for (let c = await r.read(); !c.done; c = await r.read())
|