@theokit/agents 7.3.1 → 7.4.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/dist/{agent-handle-tgyu8J7q.d.ts → agent-handle-BX4oFqfb.d.ts} +10 -10
- package/dist/auth.d.ts +68 -66
- package/dist/auth.js +106 -98
- package/dist/auth.js.map +1 -1
- package/dist/{bridge-entry-ofFpOe-j.d.ts → bridge-entry-Z8GjrwH4.d.ts} +85 -84
- package/dist/bridge.d.ts +1 -1
- package/dist/bridge.js +1 -2
- package/dist/{chunk-CVDPGEJF.js → chunk-FJA4QQLZ.js} +191 -246
- package/dist/chunk-FJA4QQLZ.js.map +1 -0
- package/dist/{chunk-7ECCHM4N.js → chunk-PHUNONZT.js} +523 -1053
- package/dist/chunk-PHUNONZT.js.map +1 -0
- package/dist/client-react.d.ts +1 -1
- package/dist/client-react.js +3 -9
- package/dist/client-react.js.map +1 -1
- package/dist/client.d.ts +3 -3
- package/dist/client.js +1 -2
- package/dist/index.d.ts +82 -50
- package/dist/index.js +161 -394
- package/dist/index.js.map +1 -1
- package/dist/interactive.js +6 -3
- package/dist/interactive.js.map +1 -1
- package/dist/persistence.js +0 -2
- package/dist/persistence.js.map +1 -1
- package/dist/pty.js +7 -3
- package/dist/pty.js.map +1 -1
- package/dist/sandbox.js +26 -3
- package/dist/sandbox.js.map +1 -1
- package/dist/testing.js +3 -21
- package/dist/testing.js.map +1 -1
- package/dist/tools.js +55 -3
- package/dist/tools.js.map +1 -1
- package/package.json +12 -12
- package/LICENSE +0 -201
- package/dist/chunk-7ECCHM4N.js.map +0 -1
- package/dist/chunk-CVDPGEJF.js.map +0 -1
- package/dist/chunk-Z4QWC7IK.js +0 -7
- package/dist/chunk-Z4QWC7IK.js.map +0 -1
|
@@ -85,20 +85,20 @@ interface InProcessTransportOptions {
|
|
|
85
85
|
* `sendMessages` promise — whereas `HttpTransport` throws from `sendMessages` on a non-2xx response.
|
|
86
86
|
*/
|
|
87
87
|
/**
|
|
88
|
-
*
|
|
88
|
+
* A parked approval was discarded because the turn ended with no decision.
|
|
89
89
|
*
|
|
90
|
-
* M92 —
|
|
91
|
-
*
|
|
90
|
+
* M92 — typed on purpose. Before, the promise simply **never** settled and the SDK tool call hung;
|
|
91
|
+
* `resolve(false)` would be worse still, because it is indistinguishable from "the user denied".
|
|
92
92
|
*/
|
|
93
93
|
declare class ApprovalAbortedError extends Error {
|
|
94
94
|
readonly approvalId: string;
|
|
95
|
-
constructor(approvalId: string,
|
|
95
|
+
constructor(approvalId: string, reason: string);
|
|
96
96
|
}
|
|
97
97
|
declare class InProcessTransport implements AgentTransport {
|
|
98
98
|
#private;
|
|
99
99
|
constructor(options: InProcessTransportOptions);
|
|
100
|
-
/**
|
|
101
|
-
get
|
|
100
|
+
/** How many approvals are parked. Exists so the test can prove the eviction. */
|
|
101
|
+
get pending(): number;
|
|
102
102
|
sendMessages(options: Parameters<WireTransport['sendMessages']>[0]): Promise<ReadableStream<WireChunk>>;
|
|
103
103
|
reconnectToStream(): Promise<ReadableStream<WireChunk> | null>;
|
|
104
104
|
approve(approvalId: string, decision: ApprovalDecision): Promise<void>;
|
|
@@ -181,14 +181,14 @@ interface AgentClientState {
|
|
|
181
181
|
* framework-agnostic, it is unit-tested without a DOM.
|
|
182
182
|
*/
|
|
183
183
|
/**
|
|
184
|
-
* M92 —
|
|
184
|
+
* M92 — client options. Additive: without them, the behaviour is exactly as before.
|
|
185
185
|
*/
|
|
186
186
|
interface AgentClientOptions {
|
|
187
187
|
/**
|
|
188
|
-
*
|
|
188
|
+
* Coalescing window in ms. `0` or absent = emit per token delta (the pre-M92 behaviour).
|
|
189
189
|
*
|
|
190
|
-
* Opt-in
|
|
191
|
-
*
|
|
190
|
+
* Opt-in on purpose: this is a published package, and changing the emit frequency by default would
|
|
191
|
+
* change the observable behaviour of anyone counting emits or depending on first-token latency.
|
|
192
192
|
*/
|
|
193
193
|
readonly emitIntervalMs?: number;
|
|
194
194
|
}
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OAuthProviderConfig, CredentialStoreConfig, ResolvedCredential, ensureFreshCredential, OpenAIDeviceConfig, openaiDeviceLogin, OAuthTokens, DeviceDeps } from '@theokit/sdk/auth';
|
|
2
|
-
export { CredentialError, CredentialStoreConfig, DeviceCodeGrant, DeviceDeps, DeviceOAuthConfig, OAuthProviderConfig, OAuthTokens, OpenAIDeviceConfig, ResolveCredentialOptions, ResolvedCredential, authFilePath, credentialHome, deviceLogin, openaiDeviceLogin, pollDeviceToken, readAuthFile, readStoredOAuth, requestDeviceCode, writeCredential } from '@theokit/sdk/auth';
|
|
2
|
+
export { CredentialError, CredentialStoreConfig, DeviceCodeGrant, DeviceDeps, DeviceOAuthConfig, OAuthProviderConfig, OAuthTokens, OpenAIDeviceConfig, ResolveCredentialOptions, ResolvedCredential, authFilePath, credentialHome, deviceLogin, ensureFreshCredential, extractAccountId, openaiDeviceLogin, persistOAuthTokens, pollDeviceToken, readAuthFile, readStoredOAuth, refreshOAuthTokens, requestDeviceCode, writeCredential } from '@theokit/sdk/auth';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* M60 — `AuthProvider`, the OO contract that unifies the SDK's free OAuth-lifecycle functions
|
|
@@ -31,16 +31,17 @@ declare class AuthProvider {
|
|
|
31
31
|
* thread through. Returns the fresh `ResolvedCredential` — never logs the rotated token.
|
|
32
32
|
*/
|
|
33
33
|
ensureFresh(resolved: ResolvedCredential, deps: EnsureFreshHttpDeps, env?: Record<string, string | undefined>): Promise<ResolvedCredential>;
|
|
34
|
-
/**
|
|
35
|
-
private static readonly
|
|
34
|
+
/** In-flight refresh per store path — the key is the file, not the instance. */
|
|
35
|
+
private static readonly refreshInFlight;
|
|
36
36
|
/**
|
|
37
|
-
*
|
|
37
|
+
* The refresh itself, serialized across PROCESSES and with a re-read.
|
|
38
38
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
39
|
+
* The re-read is not a detail: without it the lock merely serializes, and the second process
|
|
40
|
+
* decides using the state it read BEFORE waiting — refreshing again and invalidating the token the
|
|
41
|
+
* first one just wrote. It is classic double-checked locking, and it is what the two-process test
|
|
42
|
+
* catches.
|
|
42
43
|
*/
|
|
43
|
-
private
|
|
44
|
+
private refreshUnderLock;
|
|
44
45
|
/**
|
|
45
46
|
* Run the headless OpenAI device-login flow. Delegates to `openaiDeviceLogin` (which JWT-extracts the
|
|
46
47
|
* account id). `deviceConfig` is passed per-call because it is a distinct endpoint set from the
|
|
@@ -55,60 +56,61 @@ declare class AuthProvider {
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
|
-
* M111 — device auth plug-and-play:
|
|
59
|
-
*
|
|
59
|
+
* M111 — device auth plug-and-play: a provider is an object with LABELLED methods, and a login fits
|
|
60
|
+
* in one call.
|
|
60
61
|
*
|
|
61
|
-
* ##
|
|
62
|
+
* ## The problem, measured
|
|
62
63
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* `
|
|
67
|
-
*
|
|
68
|
-
*
|
|
64
|
+
* M110 made the RFC 8628 device flow cross this layer. It did not touch the ergonomics: to
|
|
65
|
+
* authenticate against Codex, the consumer had to know that **two** device-flow shapes exist, copy a
|
|
66
|
+
* `clientId` and three OpenAI URLs into its own code, assemble `{ fetch, sleep, now }`, call
|
|
67
|
+
* `deviceLogin` and **remember** to call `persist` — and forgetting the last one costs a full OAuth
|
|
68
|
+
* round-trip that stores nothing. The `AuthProvider` docblock instructed exactly that:
|
|
69
|
+
* *"the caller persists them via `AuthProvider.persist`"*.
|
|
69
70
|
*
|
|
70
|
-
* ##
|
|
71
|
+
* ## The design came from measuring three peers, and it refuted the original proposal
|
|
71
72
|
*
|
|
72
|
-
* - **`codex`** — `codex-rs/login/src/device_code_auth.rs:234`
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* - **`opencode`** —
|
|
76
|
-
* providers
|
|
77
|
-
*
|
|
78
|
-
* - **
|
|
79
|
-
*
|
|
80
|
-
* —
|
|
81
|
-
*
|
|
82
|
-
*
|
|
73
|
+
* - **`codex`** — `codex-rs/login/src/device_code_auth.rs:234` has `run_device_code_login`, which
|
|
74
|
+
* returns `()`: **nothing** comes out for the caller to persist, and the two granular halves stay
|
|
75
|
+
* public. `loginWithDevice` copies that shape.
|
|
76
|
+
* - **`opencode`** — every provider is an object with `methods: [{ label, type, authorize }]`, and
|
|
77
|
+
* three providers written by different authors converge on **3 labelled methods** each. The label
|
|
78
|
+
* is what the UI shows: it turns a protocol choice into a choice between readable phrases.
|
|
79
|
+
* - **REJECTED — a `kind` discriminant.** None of the three discriminates protocol by field. The
|
|
80
|
+
* measurement that closes the case: in `opencode`, Codex's browser and headless methods carry the
|
|
81
|
+
* **same** `type: 'oauth'` — so `type` classifies the **kind of credential**, not the protocol. A
|
|
82
|
+
* `kind` with internal dispatch would be a `switch`, exactly the defect this milestone removes
|
|
83
|
+
* from the consumer. Here, each method points at **its own** function.
|
|
83
84
|
*
|
|
84
|
-
* ##
|
|
85
|
+
* ## Why the public identity lives HERE
|
|
85
86
|
*
|
|
86
|
-
* `codex`
|
|
87
|
-
* **
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
87
|
+
* `codex` exports `CLIENT_ID` from the crate that implements the flow (`login/src/lib.rs:32`) and
|
|
88
|
+
* the CLI **imports** it; `opencode` declares it inside the plugin. The two arrived at the same
|
|
89
|
+
* place independently — and with the same value the consumer had copied. As long as it lived in the
|
|
90
|
+
* consumer, every project wanting Codex would copy four public constants: a DRY violation across the
|
|
91
|
+
* boundary, with two owners of the same fact.
|
|
91
92
|
*/
|
|
92
93
|
/**
|
|
93
|
-
*
|
|
94
|
+
* A labelled way of obtaining a credential within a provider.
|
|
94
95
|
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
96
|
+
* A DISCRIMINATED UNION, not an optional `authorize?` field. With an optional field,
|
|
97
|
+
* `{ label, type: 'oauth' }` would be representable — an OAuth method that cannot authorize,
|
|
98
|
+
* detected only at runtime, in the middle of the user's login. It is the alternative M110 already
|
|
99
|
+
* rejected in writing when it refused "one type with optional fields": it would make an invalid
|
|
100
|
+
* config representable and move detection out of the compiler
|
|
99
101
|
* para o runtime.
|
|
100
102
|
*/
|
|
101
103
|
type AuthMethod = {
|
|
102
|
-
/**
|
|
104
|
+
/** What the interface shows the user. The piece that makes the flow choosable without knowing the protocol. */
|
|
103
105
|
readonly label: string;
|
|
104
106
|
readonly type: 'oauth';
|
|
105
|
-
/**
|
|
107
|
+
/** THIS method's function. No discriminant: the method points at its own, not at a `switch`. */
|
|
106
108
|
readonly authorize: (deps: DeviceDeps, hooks: PromptHooks) => Promise<OAuthTokens>;
|
|
107
109
|
} | {
|
|
108
110
|
readonly label: string;
|
|
109
111
|
readonly type: 'api';
|
|
110
112
|
};
|
|
111
|
-
/**
|
|
113
|
+
/** What the consumer wires into its UI to show the code the user types on the other device. */
|
|
112
114
|
interface PromptHooks {
|
|
113
115
|
onPrompt: (p: {
|
|
114
116
|
userCode: string;
|
|
@@ -116,50 +118,50 @@ interface PromptHooks {
|
|
|
116
118
|
expiresIn?: number;
|
|
117
119
|
}) => void;
|
|
118
120
|
}
|
|
119
|
-
/**
|
|
121
|
+
/** An authentication provider: public identity + the labelled ways of authenticating with it. */
|
|
120
122
|
interface DeviceAuthProvider {
|
|
121
123
|
readonly name: string;
|
|
122
124
|
readonly oauth: OAuthProviderConfig;
|
|
123
125
|
readonly methods: readonly AuthMethod[];
|
|
124
126
|
}
|
|
125
127
|
/**
|
|
126
|
-
*
|
|
127
|
-
* `CLIENT_ID_OVERRIDE_ENV_VAR` (`login/src/lib.rs:32-33`).
|
|
128
|
-
*
|
|
129
|
-
* escape
|
|
128
|
+
* Environment override for `clientId` — adopted from `codex`, which exports `CLIENT_ID` **and**
|
|
129
|
+
* `CLIENT_ID_OVERRIDE_ENV_VAR` (`login/src/lib.rs:32-33`). It dissolves the false dilemma between a
|
|
130
|
+
* fixed constant (inflexible) and a mandatory parameter (which hands the copy back to the consumer):
|
|
131
|
+
* a default in the package, an escape for whoever needs one.
|
|
130
132
|
*/
|
|
131
133
|
declare const CODEX_CLIENT_ID_ENV_VAR = "THEOKIT_CODEX_CLIENT_ID";
|
|
132
134
|
/**
|
|
133
|
-
*
|
|
135
|
+
* The Codex provider, assembled and **frozen**.
|
|
134
136
|
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
137
|
+
* Frozen because it is a public identity SHARED across the process: a consumer that mutated it
|
|
138
|
+
* would change everyone else's login. `Object.freeze` is shallow, so the method list is frozen
|
|
139
|
+
* separately — without that, `CODEX_PROVIDER.methods.push(...)` would go through.
|
|
138
140
|
*/
|
|
139
141
|
declare const CODEX_PROVIDER: DeviceAuthProvider;
|
|
140
142
|
/**
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
143
|
+
* Facade options. `deps` and `env` travel together in one object rather than as two positional
|
|
144
|
+
* parameters: both are optional and rarely used, and six positionals is a signature callers get
|
|
145
|
+
* wrong in silence (the monorepo lint enforces 5 as the ceiling — the ceiling exists for this reason).
|
|
144
146
|
*/
|
|
145
147
|
interface LoginWithDeviceOptions {
|
|
146
|
-
/**
|
|
148
|
+
/** I/O injection for tests. Omitted, it uses the real `fetch`/`setTimeout`/`Date.now`. */
|
|
147
149
|
readonly deps?: Partial<DeviceDeps>;
|
|
148
|
-
/**
|
|
150
|
+
/** The environment the store reads to resolve the credential directory. */
|
|
149
151
|
readonly env?: Record<string, string | undefined>;
|
|
150
152
|
}
|
|
151
153
|
/**
|
|
152
|
-
*
|
|
153
|
-
* **
|
|
154
|
+
* Authorizes **and** persists, in one call. Returns where the credential landed and the account it
|
|
155
|
+
* was attributed to — **never** token material.
|
|
154
156
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* (`deviceLogin` / `persist`)
|
|
158
|
-
*
|
|
157
|
+
* The shape comes from `run_device_code_login` (`codex`), which returns `()`: if nothing comes out
|
|
158
|
+
* for the caller, there is no step it can forget. The two halves stay public on `AuthProvider`
|
|
159
|
+
* (`deviceLogin` / `persist`) for whoever needs the granularity — the same choice `codex` makes by
|
|
160
|
+
* keeping `request_device_code` and `complete_device_code_login` public alongside the facade.
|
|
159
161
|
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
162
|
+
* It delegates verbatim: `method.authorize` runs the flow and `AuthProvider.persist` writes. Copying
|
|
163
|
+
* the sequence instead of calling it would create a second oracle over the same fact, and two oracles
|
|
164
|
+
* diverge on the first fix applied to only one side.
|
|
163
165
|
*/
|
|
164
166
|
declare function loginWithDevice(provider: DeviceAuthProvider, method: AuthMethod, store: CredentialStoreConfig, hooks: PromptHooks, opts?: LoginWithDeviceOptions): Promise<{
|
|
165
167
|
path: string;
|
package/dist/auth.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
__name
|
|
3
|
-
} from "./chunk-Z4QWC7IK.js";
|
|
4
|
-
|
|
5
1
|
// src/auth/auth-provider.ts
|
|
6
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
authFilePath,
|
|
4
|
+
ensureFreshCredential,
|
|
5
|
+
openaiDeviceLogin,
|
|
6
|
+
persistOAuthTokens,
|
|
7
|
+
readStoredOAuth
|
|
8
|
+
} from "@theokit/sdk/auth";
|
|
7
9
|
import { withFileLock } from "@theokit/sdk/persistence";
|
|
8
10
|
var RefreshFailure = class extends Error {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
transitorio;
|
|
13
|
-
constructor(message, transitorio) {
|
|
14
|
-
super(message), this.transitorio = transitorio;
|
|
11
|
+
constructor(message, transient) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.transient = transient;
|
|
15
14
|
this.name = "RefreshFailure";
|
|
16
15
|
}
|
|
16
|
+
transient;
|
|
17
17
|
};
|
|
18
|
-
var
|
|
18
|
+
var TRANSIENT_REASONS = [
|
|
19
19
|
"ETIMEDOUT",
|
|
20
20
|
"ECONNRESET",
|
|
21
21
|
"ECONNREFUSED",
|
|
@@ -23,107 +23,119 @@ var MOTIVOS_TRANSITORIOS = [
|
|
|
23
23
|
"ENOTFOUND",
|
|
24
24
|
"AbortError"
|
|
25
25
|
];
|
|
26
|
-
function
|
|
27
|
-
const
|
|
28
|
-
if (/invalid_grant|invalid_request|unauthorized_client/i.test(
|
|
29
|
-
return new RefreshFailure(
|
|
26
|
+
function classifyRefreshFailure(err) {
|
|
27
|
+
const text = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
|
|
28
|
+
if (/invalid_grant|invalid_request|unauthorized_client/i.test(text)) {
|
|
29
|
+
return new RefreshFailure(
|
|
30
|
+
"the refresh token is no longer valid \u2014 log in again. Retrying does not change the outcome.",
|
|
31
|
+
false
|
|
32
|
+
);
|
|
30
33
|
}
|
|
31
|
-
const
|
|
32
|
-
return new RefreshFailure(
|
|
34
|
+
const transient = TRANSIENT_REASONS.some((m) => text.includes(m)) || /\b(5\d{2})\b|timeout|network/i.test(text);
|
|
35
|
+
return new RefreshFailure(
|
|
36
|
+
transient ? "transient failure refreshing the credential" : "failure refreshing the credential",
|
|
37
|
+
transient
|
|
38
|
+
);
|
|
33
39
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return Math.round(base * (0.75 + aleatorio() * 0.5));
|
|
40
|
+
function waitWithJitter(attempt, baseMs = 200, random = Math.random) {
|
|
41
|
+
const base = baseMs * 2 ** attempt;
|
|
42
|
+
return Math.round(base * (0.75 + random() * 0.5));
|
|
38
43
|
}
|
|
39
|
-
__name(esperaComJitter, "esperaComJitter");
|
|
40
44
|
var AuthProvider = class _AuthProvider {
|
|
41
|
-
static {
|
|
42
|
-
__name(this, "AuthProvider");
|
|
43
|
-
}
|
|
44
|
-
config;
|
|
45
|
-
store;
|
|
46
45
|
constructor(config, store) {
|
|
47
46
|
this.config = config;
|
|
48
47
|
this.store = store;
|
|
49
48
|
}
|
|
49
|
+
config;
|
|
50
|
+
store;
|
|
50
51
|
/**
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
* Refresh a resolved credential if stale. Delegates to `ensureFreshCredential` with the held
|
|
53
|
+
* `config` + `store`; `env` (for reading the store's env overrides) + the `{ fetch, now }` HTTP deps
|
|
54
|
+
* thread through. Returns the fresh `ResolvedCredential` — never logs the rotated token.
|
|
55
|
+
*/
|
|
55
56
|
async ensureFresh(resolved, deps, env) {
|
|
56
57
|
if (resolved.kind !== "oauth") {
|
|
57
|
-
return ensureFreshCredential(resolved, {
|
|
58
|
-
config: this.config,
|
|
59
|
-
store: this.store,
|
|
60
|
-
env
|
|
61
|
-
}, deps);
|
|
58
|
+
return ensureFreshCredential(resolved, { config: this.config, store: this.store, env }, deps);
|
|
62
59
|
}
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
if (
|
|
66
|
-
const
|
|
67
|
-
_AuthProvider.
|
|
60
|
+
const filePath = authFilePath(this.store, env);
|
|
61
|
+
const inFlight = _AuthProvider.refreshInFlight.get(filePath);
|
|
62
|
+
if (inFlight !== void 0) return inFlight;
|
|
63
|
+
const promise = this.refreshUnderLock(filePath, resolved, deps, env);
|
|
64
|
+
_AuthProvider.refreshInFlight.set(filePath, promise);
|
|
68
65
|
try {
|
|
69
|
-
return await
|
|
66
|
+
return await promise;
|
|
70
67
|
} finally {
|
|
71
|
-
_AuthProvider.
|
|
68
|
+
_AuthProvider.refreshInFlight.delete(filePath);
|
|
72
69
|
}
|
|
73
70
|
}
|
|
74
|
-
/**
|
|
75
|
-
static
|
|
71
|
+
/** In-flight refresh per store path — the key is the file, not the instance. */
|
|
72
|
+
static refreshInFlight = /* @__PURE__ */ new Map();
|
|
76
73
|
/**
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
74
|
+
* The refresh itself, serialized across PROCESSES and with a re-read.
|
|
75
|
+
*
|
|
76
|
+
* The re-read is not a detail: without it the lock merely serializes, and the second process
|
|
77
|
+
* decides using the state it read BEFORE waiting — refreshing again and invalidating the token the
|
|
78
|
+
* first one just wrote. It is classic double-checked locking, and it is what the two-process test
|
|
79
|
+
* catches.
|
|
80
|
+
*/
|
|
81
|
+
refreshUnderLock(filePath, resolved, deps, env) {
|
|
82
|
+
return withFileLock(filePath, async () => {
|
|
85
83
|
const doDisco = readStoredOAuth(this.store, env);
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
expiresAt: doDisco.expires
|
|
90
|
-
} : resolved;
|
|
91
|
-
const MAX_TENTATIVAS = 3;
|
|
92
|
-
for (let tentativa = 0; ; tentativa++) {
|
|
84
|
+
const current = doDisco !== void 0 ? { ...resolved, apiKey: doDisco.access, expiresAt: doDisco.expires } : resolved;
|
|
85
|
+
const MAX_ATTEMPTS = 3;
|
|
86
|
+
for (let attempt = 0; ; attempt++) {
|
|
93
87
|
try {
|
|
94
|
-
return await ensureFreshCredential(
|
|
95
|
-
|
|
96
|
-
store: this.store,
|
|
97
|
-
|
|
98
|
-
|
|
88
|
+
return await ensureFreshCredential(
|
|
89
|
+
current,
|
|
90
|
+
{ config: this.config, store: this.store, env },
|
|
91
|
+
deps
|
|
92
|
+
);
|
|
99
93
|
} catch (err) {
|
|
100
|
-
const
|
|
101
|
-
if (!
|
|
102
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
94
|
+
const failure = classifyRefreshFailure(err);
|
|
95
|
+
if (!failure.transient || attempt >= MAX_ATTEMPTS - 1) throw failure;
|
|
96
|
+
await new Promise((resolve) => setTimeout(resolve, waitWithJitter(attempt)));
|
|
103
97
|
}
|
|
104
98
|
}
|
|
105
99
|
});
|
|
106
100
|
}
|
|
107
101
|
/**
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
102
|
+
* Run the headless OpenAI device-login flow. Delegates to `openaiDeviceLogin` (which JWT-extracts the
|
|
103
|
+
* account id). `deviceConfig` is passed per-call because it is a distinct endpoint set from the
|
|
104
|
+
* refresh `config`. Returns `OAuthTokens` — the caller persists them via {@link AuthProvider.persist}.
|
|
105
|
+
*/
|
|
112
106
|
deviceLogin(deviceConfig, deps, hooks) {
|
|
113
107
|
return openaiDeviceLogin(deviceConfig, deps, hooks);
|
|
114
108
|
}
|
|
115
109
|
/**
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
110
|
+
* Persist freshly-obtained tokens through the held `store`. Delegates to `persistOAuthTokens` and
|
|
111
|
+
* returns the credential-file path (never the token). `env` selects the store's home override.
|
|
112
|
+
*/
|
|
119
113
|
persist(provider, tokens, env) {
|
|
120
114
|
return persistOAuthTokens(provider, tokens, this.store, env);
|
|
121
115
|
}
|
|
122
116
|
};
|
|
123
117
|
|
|
124
118
|
// src/auth-entry.ts
|
|
125
|
-
import {
|
|
126
|
-
|
|
119
|
+
import {
|
|
120
|
+
authFilePath as authFilePath2,
|
|
121
|
+
CredentialError,
|
|
122
|
+
credentialHome,
|
|
123
|
+
readAuthFile,
|
|
124
|
+
readStoredOAuth as readStoredOAuth2,
|
|
125
|
+
writeCredential
|
|
126
|
+
} from "@theokit/sdk/auth";
|
|
127
|
+
import {
|
|
128
|
+
deviceLogin,
|
|
129
|
+
openaiDeviceLogin as openaiDeviceLogin3,
|
|
130
|
+
pollDeviceToken,
|
|
131
|
+
requestDeviceCode
|
|
132
|
+
} from "@theokit/sdk/auth";
|
|
133
|
+
import {
|
|
134
|
+
ensureFreshCredential as ensureFreshCredential2,
|
|
135
|
+
extractAccountId,
|
|
136
|
+
persistOAuthTokens as persistOAuthTokens2,
|
|
137
|
+
refreshOAuthTokens
|
|
138
|
+
} from "@theokit/sdk/auth";
|
|
127
139
|
|
|
128
140
|
// src/auth/device-provider.ts
|
|
129
141
|
import { openaiDeviceLogin as openaiDeviceLogin2 } from "@theokit/sdk/auth";
|
|
@@ -135,12 +147,7 @@ var CODEX_OAUTH = {
|
|
|
135
147
|
clientId: process.env[CODEX_CLIENT_ID_ENV_VAR] ?? CODEX_CLIENT_ID,
|
|
136
148
|
authorizeEndpoint: `${CODEX_ISSUER}/oauth/authorize`,
|
|
137
149
|
tokenEndpoint: `${CODEX_ISSUER}/oauth/token`,
|
|
138
|
-
scopes: [
|
|
139
|
-
"openid",
|
|
140
|
-
"profile",
|
|
141
|
-
"email",
|
|
142
|
-
"offline_access"
|
|
143
|
-
],
|
|
150
|
+
scopes: ["openid", "profile", "email", "offline_access"],
|
|
144
151
|
redirectUri: `${CODEX_ISSUER}/deviceauth/callback`
|
|
145
152
|
};
|
|
146
153
|
var CODEX_DEVICE = {
|
|
@@ -156,9 +163,9 @@ var CODEX_PROVIDER = Object.freeze({
|
|
|
156
163
|
Object.freeze({
|
|
157
164
|
label: "ChatGPT Pro/Plus (headless device code)",
|
|
158
165
|
type: "oauth",
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
authorize:
|
|
166
|
+
// Points at OpenAI's variant. An RFC 8628 provider would point at `deviceLogin`, and that is
|
|
167
|
+
// how the two shapes coexist with no discriminant.
|
|
168
|
+
authorize: (deps, hooks) => openaiDeviceLogin2(CODEX_DEVICE, deps, hooks)
|
|
162
169
|
}),
|
|
163
170
|
Object.freeze({
|
|
164
171
|
label: "Manually enter API Key",
|
|
@@ -173,27 +180,24 @@ function comDefaults(deps) {
|
|
|
173
180
|
now: deps?.now ?? Date.now
|
|
174
181
|
};
|
|
175
182
|
}
|
|
176
|
-
__name(comDefaults, "comDefaults");
|
|
177
183
|
async function loginWithDevice(provider, method, store, hooks, opts = {}) {
|
|
178
184
|
if (provider.methods.length === 0) {
|
|
179
|
-
throw new TypeError(
|
|
185
|
+
throw new TypeError(
|
|
186
|
+
`provider "${provider.name}" declares no authentication method \u2014 there is nothing to choose`
|
|
187
|
+
);
|
|
180
188
|
}
|
|
181
189
|
if (!provider.methods.includes(method)) {
|
|
182
|
-
throw new TypeError(`
|
|
190
|
+
throw new TypeError(`method "${method.label}" does not belong to provider "${provider.name}"`);
|
|
183
191
|
}
|
|
184
192
|
if (method.type !== "oauth") {
|
|
185
|
-
throw new TypeError(
|
|
193
|
+
throw new TypeError(
|
|
194
|
+
`method "${method.label}" is an api-key method, not a device method \u2014 use the api-key path`
|
|
195
|
+
);
|
|
186
196
|
}
|
|
187
197
|
const tokens = await method.authorize(comDefaults(opts.deps), hooks);
|
|
188
198
|
const path = new AuthProvider(provider.oauth, store).persist(provider.name, tokens, opts.env);
|
|
189
|
-
return tokens.accountId === void 0 ? {
|
|
190
|
-
path
|
|
191
|
-
} : {
|
|
192
|
-
path,
|
|
193
|
-
accountId: tokens.accountId
|
|
194
|
-
};
|
|
199
|
+
return tokens.accountId === void 0 ? { path } : { path, accountId: tokens.accountId };
|
|
195
200
|
}
|
|
196
|
-
__name(loginWithDevice, "loginWithDevice");
|
|
197
201
|
export {
|
|
198
202
|
AuthProvider,
|
|
199
203
|
CODEX_CLIENT_ID_ENV_VAR,
|
|
@@ -202,11 +206,15 @@ export {
|
|
|
202
206
|
authFilePath2 as authFilePath,
|
|
203
207
|
credentialHome,
|
|
204
208
|
deviceLogin,
|
|
209
|
+
ensureFreshCredential2 as ensureFreshCredential,
|
|
210
|
+
extractAccountId,
|
|
205
211
|
loginWithDevice,
|
|
206
212
|
openaiDeviceLogin3 as openaiDeviceLogin,
|
|
213
|
+
persistOAuthTokens2 as persistOAuthTokens,
|
|
207
214
|
pollDeviceToken,
|
|
208
215
|
readAuthFile,
|
|
209
216
|
readStoredOAuth2 as readStoredOAuth,
|
|
217
|
+
refreshOAuthTokens,
|
|
210
218
|
requestDeviceCode,
|
|
211
219
|
writeCredential
|
|
212
220
|
};
|
package/dist/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/auth/auth-provider.ts","../src/auth-entry.ts","../src/auth/device-provider.ts"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/auth/auth-provider.ts","../src/auth-entry.ts","../src/auth/device-provider.ts"],"mappings":"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","names":["authFilePath","readStoredOAuth","openaiDeviceLogin","ensureFreshCredential","persistOAuthTokens","openaiDeviceLogin","openaiDeviceLogin"]}
|