@bitkyc08/opencodex 2.82.0 → 2.83.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/gui/dist/assets/{App-Ds_qL7o8.js → App-Ba8uG696.js} +4 -4
- package/gui/dist/assets/{Tray-BqHF5Trd.js → Tray-IImbNPRy.js} +1 -1
- package/gui/dist/assets/{index-D3FBCNiP.js → index-BHRZdJuv.js} +12 -12
- package/gui/dist/assets/{quota-summary-DTBC8n7-.js → quota-summary-DiJO6sYY.js} +1 -1
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/cursor/catalog.ts +2 -2
- package/src/adapters/cursor/effort-map.ts +1 -1
- package/src/adapters/devin/live-models.ts +2 -2
- package/src/adapters/google-truncation.ts +22 -0
- package/src/adapters/google.ts +7 -3
- package/src/adapters/ollama-native.ts +18 -14
- package/src/adapters/openai-responses/passthrough.ts +2 -0
- package/src/adapters/openai-responses/plaintext-reasoning-notice.ts +85 -0
- package/src/adapters/openai-responses/reasoning.ts +11 -1
- package/src/bridge/errors.ts +7 -1
- package/src/claude/context-windows.ts +3 -3
- package/src/claude/desktop-picker.ts +1 -1
- package/src/cli/account-auth.ts +20 -12
- package/src/cli/resolve.ts +1 -1
- package/src/clients/config-export/model-metadata.ts +16 -0
- package/src/clients/config-export/zcode-store.ts +46 -11
- package/src/clients/config-export/zcode.ts +3 -5
- package/src/clients/config-export.ts +1 -1
- package/src/codex/auth-api/pool-mode-gate.ts +4 -5
- package/src/codex/auth-context.ts +8 -3
- package/src/codex/catalog/native-models.ts +1 -1
- package/src/codex/catalog/provider-models.ts +25 -0
- package/src/codex/desktop-app/darwin.ts +1 -1
- package/src/codex/desktop-app/handoff.ts +1 -1
- package/src/codex/desktop-app/linux.ts +1 -1
- package/src/codex/desktop-app/lock.ts +1 -1
- package/src/codex/desktop-app/types.ts +1 -1
- package/src/codex/desktop-app/windows.ts +1 -1
- package/src/codex/desktop-app-restart.ts +1 -1
- package/src/codex/main-account.ts +6 -7
- package/src/generated/compatibility-version.json +91 -75
- package/src/grok/inject.ts +245 -221
- package/src/grok/reset-coupon-ledger.ts +223 -57
- package/src/grok/reset-coupons.ts +13 -6
- package/src/integrations/cursor-detect.ts +1 -1
- package/src/lib/errors.ts +25 -0
- package/src/lib/redact.ts +2 -2
- package/src/oauth/index.ts +1 -1
- package/src/oauth/meta-muse-device.ts +1 -1
- package/src/oauth/types.ts +1 -1
- package/src/providers/anthropic-fast.ts +1 -1
- package/src/providers/anthropic-reset-grant-ledger.ts +1 -1
- package/src/providers/devin-cli-authmode-migration.ts +1 -1
- package/src/providers/devin-provider-merge-migration.ts +1 -1
- package/src/providers/github-copilot-auto.ts +169 -0
- package/src/providers/model-presets.ts +1 -1
- package/src/providers/muse-subscription-usage.ts +1 -1
- package/src/providers/registry/entries-core.ts +6 -6
- package/src/providers/registry/entries-extended.ts +4 -0
- package/src/providers/registry/model-seeds.ts +3 -3
- package/src/providers/xai-fast-model.ts +1 -1
- package/src/server/inbound-body-admission.ts +24 -7
- package/src/server/index/link-listener.ts +30 -17
- package/src/server/index/serve-options.ts +8 -2
- package/src/server/index.ts +1 -1
- package/src/server/lifecycle.ts +31 -4
- package/src/server/management/grok-coupon-routes.ts +277 -52
- package/src/server/management/link-routes.ts +42 -20
- package/src/server/management/route-registry.ts +2 -2
- package/src/server/relay-eager.ts +2 -0
- package/src/server/relay.ts +18 -7
- package/src/server/request-log-failure-attribution.ts +3 -0
- package/src/server/request-log-terminal-status.ts +186 -0
- package/src/server/request-log.ts +15 -118
- package/src/server/responses/adapter-continuation.ts +9 -1
- package/src/server/responses/adapter-dispatch.ts +65 -6
- package/src/server/responses/compact.ts +3 -2
- package/src/server/responses/core-opaque-recovery.ts +5 -5
- package/src/server/responses/core.ts +6 -6
- package/src/server/responses/empty-completion-guard.ts +18 -2
- package/src/server/responses/jev-model-invoke.ts +1 -1
- package/src/server/responses/passthrough-dispatch.ts +115 -60
- package/src/server/responses/passthrough-execution.ts +3 -3
- package/src/server/responses/request-transport.ts +125 -5
- package/src/server/responses/run-turn-execution.ts +13 -5
- package/src/server/responses/sidecar-execution.ts +5 -0
- package/src/server/responses/spend-storage-error.ts +33 -0
- package/src/types/request.ts +5 -0
- package/src/usage/expected-prices.ts +1 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import {
|
|
1
|
+
import { closeSync, fsyncSync, openSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { atomicWriteFileStreamed } from "../config/atomic-write";
|
|
4
|
+
import { withConfigMutationLockSync } from "../config/mutation-lock";
|
|
4
5
|
import { getConfigDir } from "../config/paths";
|
|
5
6
|
|
|
6
|
-
export type GrokResetCouponOperationKind = "execute" | "replay" | "identity-mismatch" | "capacity";
|
|
7
|
+
export type GrokResetCouponOperationKind = "execute" | "replay" | "identity-mismatch" | "token-mismatch" | "capacity";
|
|
7
8
|
|
|
8
9
|
export interface GrokResetCouponOperationIdentity {
|
|
9
10
|
accountId: string;
|
|
@@ -16,6 +17,12 @@ export interface GrokResetCouponOperationRecord {
|
|
|
16
17
|
operationId: string;
|
|
17
18
|
accountId?: string;
|
|
18
19
|
tokenId?: string;
|
|
20
|
+
/** Token expiry (ms) captured at claim time as journal metadata. Remaining
|
|
21
|
+
* availability or expiry cannot establish this operation's outcome. */
|
|
22
|
+
tokenValidityEnd?: number;
|
|
23
|
+
/** When the attempt was marked (ms) — set only on an "attempted" replay so
|
|
24
|
+
* the route can defer inspection while the attempt may still be in flight. */
|
|
25
|
+
attemptedAt?: number;
|
|
19
26
|
code?: string;
|
|
20
27
|
settledAt?: number;
|
|
21
28
|
}
|
|
@@ -23,14 +30,18 @@ export interface GrokResetCouponOperationRecord {
|
|
|
23
30
|
interface GrokResetCouponOperationState {
|
|
24
31
|
accountId: string;
|
|
25
32
|
tokenId?: string;
|
|
26
|
-
|
|
33
|
+
// "attempted" sits between open and settled: the spend call may have fired,
|
|
34
|
+
// so the operation must never execute again. Its replay carries no code —
|
|
35
|
+
// the route reconciles it against upstream instead of trusting a status.
|
|
36
|
+
status: "open" | "attempted" | "settled" | "failed";
|
|
27
37
|
code?: string;
|
|
38
|
+
tokenValidityEnd?: number;
|
|
28
39
|
createdAt: number;
|
|
29
40
|
updatedAt: number;
|
|
30
41
|
}
|
|
31
42
|
|
|
32
43
|
interface GrokResetCouponLedger {
|
|
33
|
-
version:
|
|
44
|
+
version: 2;
|
|
34
45
|
operations: Record<string, GrokResetCouponOperationState>;
|
|
35
46
|
}
|
|
36
47
|
|
|
@@ -40,95 +51,249 @@ export function grokCouponJournalPath(customDir?: string): string {
|
|
|
40
51
|
}
|
|
41
52
|
|
|
42
53
|
function readGrokCouponLedger(filePath: string): GrokResetCouponLedger {
|
|
43
|
-
|
|
44
|
-
|
|
54
|
+
let raw: string;
|
|
55
|
+
try { raw = readFileSync(filePath, "utf-8"); }
|
|
56
|
+
catch (error) {
|
|
57
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return { version: 2, operations: {} };
|
|
58
|
+
throw new Error("Grok coupon ledger is unavailable; existing records were preserved.");
|
|
45
59
|
}
|
|
46
60
|
try {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
const parsed = JSON.parse(raw) as Omit<GrokResetCouponLedger, "version"> & { version: unknown };
|
|
62
|
+
if (!parsed || (parsed.version !== 1 && parsed.version !== 2) || !parsed.operations
|
|
63
|
+
|| typeof parsed.operations !== "object" || Array.isArray(parsed.operations)) throw new Error();
|
|
64
|
+
for (const op of Object.values(parsed.operations)) {
|
|
65
|
+
if (!op || typeof op.accountId !== "string" || !op.accountId
|
|
66
|
+
|| !["open", "attempted", "settled", "failed"].includes(op.status)
|
|
67
|
+
|| !Number.isFinite(op.createdAt) || !Number.isFinite(op.updatedAt)
|
|
68
|
+
|| (op.tokenId !== undefined && typeof op.tokenId !== "string")
|
|
69
|
+
|| (op.code !== undefined && typeof op.code !== "string")
|
|
70
|
+
|| (op.tokenValidityEnd !== undefined && !Number.isFinite(op.tokenValidityEnd))) throw new Error();
|
|
71
|
+
}
|
|
72
|
+
if (parsed.version === 1) {
|
|
73
|
+
for (const op of Object.values(parsed.operations)) {
|
|
74
|
+
// Old opens (and transport failures recorded as failed) may already
|
|
75
|
+
// have dispatched. Only version-two opens prove pre-dispatch state.
|
|
76
|
+
if (op.status === "open" || (op.status === "failed" && op.code === "redeem_failed")) {
|
|
77
|
+
op.status = "attempted";
|
|
78
|
+
delete op.code;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { version: 2, operations: parsed.operations };
|
|
52
83
|
} catch {
|
|
53
|
-
|
|
84
|
+
throw new Error("Grok coupon ledger is unreadable; existing records were preserved.");
|
|
54
85
|
}
|
|
55
86
|
}
|
|
56
87
|
|
|
57
88
|
function writeGrokCouponLedger(filePath: string, ledger: GrokResetCouponLedger, now = Date.now()): void {
|
|
58
|
-
// Prune
|
|
89
|
+
// Prune every record past the 30-day window — including "open" and
|
|
90
|
+
// "attempted". Idempotency retention is explicitly bounded to 30 days;
|
|
91
|
+
// operations are not retry authorities after that window.
|
|
59
92
|
const retentionCutoff = now - 30 * 24 * 60 * 60_000;
|
|
60
93
|
ledger.operations = Object.fromEntries(
|
|
61
|
-
Object.entries(ledger.operations).filter(
|
|
62
|
-
([, op]) => op.status === "open" || op.updatedAt > retentionCutoff,
|
|
63
|
-
),
|
|
94
|
+
Object.entries(ledger.operations).filter(([, op]) => op.updatedAt > retentionCutoff),
|
|
64
95
|
);
|
|
65
|
-
|
|
96
|
+
atomicWriteFileStreamed(filePath, descriptor => writeFileSync(descriptor, JSON.stringify(ledger, null, 2)), {
|
|
97
|
+
afterRename: target => {
|
|
98
|
+
// The shared streamed writer flushes the temp. A spend claim must also
|
|
99
|
+
// refuse on a POSIX directory-sync failure before dispatch, rather than
|
|
100
|
+
// relying on that writer's best-effort parent sync. Windows has no
|
|
101
|
+
// portable directory descriptor and retains the writer's platform contract.
|
|
102
|
+
if (process.platform === "win32") return;
|
|
103
|
+
const descriptor = openSync(dirname(target), "r");
|
|
104
|
+
try { fsyncSync(descriptor); }
|
|
105
|
+
finally { closeSync(descriptor); }
|
|
106
|
+
},
|
|
107
|
+
});
|
|
66
108
|
}
|
|
67
109
|
|
|
68
110
|
const MAX_GROK_RESET_COUPON_OPERATION_IDS = 256;
|
|
69
111
|
|
|
112
|
+
/** Resolve an omitted-account retry from its durable identity, within retention. */
|
|
113
|
+
export function getGrokResetCouponOperationAccountId(
|
|
114
|
+
operationId: string,
|
|
115
|
+
now = Date.now(),
|
|
116
|
+
journalPath?: string,
|
|
117
|
+
): string | undefined {
|
|
118
|
+
return withConfigMutationLockSync(() => {
|
|
119
|
+
const record = readGrokCouponLedger(journalPath ?? grokCouponJournalPath()).operations[operationId];
|
|
120
|
+
if (!record || record.updatedAt <= now - 30 * 24 * 60 * 60_000) return undefined;
|
|
121
|
+
return record.accountId;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
70
125
|
export function openGrokResetCouponOperation(
|
|
71
126
|
identity: GrokResetCouponOperationIdentity,
|
|
72
127
|
now = Date.now(),
|
|
73
128
|
journalPath?: string,
|
|
74
129
|
): GrokResetCouponOperationRecord {
|
|
75
|
-
|
|
76
|
-
|
|
130
|
+
// Read→decide→write under the shared config mutation lock: two concurrent
|
|
131
|
+
// coupon requests on the same home must not lose an operation record — the
|
|
132
|
+
// ledger exists precisely to make an irreversible spend replay-safe.
|
|
133
|
+
return withConfigMutationLockSync(() => {
|
|
134
|
+
const filePath = journalPath ?? grokCouponJournalPath();
|
|
135
|
+
const ledger = readGrokCouponLedger(filePath);
|
|
77
136
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
137
|
+
// Prune BEFORE the capacity check: expiry is enforced on write, so a ledger
|
|
138
|
+
// full of stale records would otherwise reject every new operation forever
|
|
139
|
+
// (the prune path inside writeGrokCouponLedger is never reached when all
|
|
140
|
+
// openings are rejected).
|
|
141
|
+
const retentionCutoff = now - 30 * 24 * 60 * 60_000;
|
|
142
|
+
ledger.operations = Object.fromEntries(
|
|
143
|
+
Object.entries(ledger.operations).filter(([, op]) => op.updatedAt > retentionCutoff),
|
|
144
|
+
);
|
|
81
145
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
146
|
+
const existing = ledger.operations[identity.operationId];
|
|
147
|
+
if (existing) {
|
|
148
|
+
if (existing.accountId !== identity.accountId) {
|
|
149
|
+
return { kind: "identity-mismatch", operationId: identity.operationId };
|
|
150
|
+
}
|
|
151
|
+
if (identity.tokenId !== undefined && existing.tokenId !== undefined && existing.tokenId !== identity.tokenId) {
|
|
152
|
+
return { kind: "token-mismatch", operationId: identity.operationId };
|
|
153
|
+
}
|
|
154
|
+
if (existing.status !== "open") {
|
|
155
|
+
// Non-open: replay the recorded outcome. "attempted" records carry no
|
|
156
|
+
// code — the caller must reconcile them against upstream, never re-run.
|
|
157
|
+
return {
|
|
158
|
+
kind: "replay",
|
|
159
|
+
operationId: identity.operationId,
|
|
160
|
+
accountId: existing.accountId,
|
|
161
|
+
tokenId: existing.tokenId,
|
|
162
|
+
tokenValidityEnd: existing.tokenValidityEnd,
|
|
163
|
+
attemptedAt: existing.status === "attempted" ? existing.updatedAt : undefined,
|
|
164
|
+
code: existing.code,
|
|
165
|
+
settledAt: existing.updatedAt,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
90
168
|
return {
|
|
91
|
-
kind: "
|
|
169
|
+
kind: "execute",
|
|
92
170
|
operationId: identity.operationId,
|
|
93
171
|
accountId: existing.accountId,
|
|
94
172
|
tokenId: existing.tokenId,
|
|
95
|
-
code: existing.code,
|
|
96
|
-
settledAt: existing.updatedAt,
|
|
97
173
|
};
|
|
98
174
|
}
|
|
175
|
+
|
|
176
|
+
if (Object.keys(ledger.operations).length >= MAX_GROK_RESET_COUPON_OPERATION_IDS) {
|
|
177
|
+
return { kind: "capacity", operationId: identity.operationId };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
ledger.operations[identity.operationId] = {
|
|
181
|
+
accountId: identity.accountId,
|
|
182
|
+
...(identity.tokenId === undefined ? {} : { tokenId: identity.tokenId }),
|
|
183
|
+
status: "open",
|
|
184
|
+
createdAt: now,
|
|
185
|
+
updatedAt: now,
|
|
186
|
+
};
|
|
187
|
+
writeGrokCouponLedger(filePath, ledger, now);
|
|
99
188
|
return {
|
|
100
189
|
kind: "execute",
|
|
101
190
|
operationId: identity.operationId,
|
|
102
|
-
accountId:
|
|
103
|
-
tokenId:
|
|
191
|
+
accountId: identity.accountId,
|
|
192
|
+
tokenId: identity.tokenId,
|
|
104
193
|
};
|
|
105
|
-
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
106
196
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
197
|
+
/**
|
|
198
|
+
* Flip an open operation to `attempted` BEFORE the upstream spend call. The
|
|
199
|
+
* write happens while nothing is irreversible yet: if it throws, the caller
|
|
200
|
+
* aborts without spending; if a later settle write dies instead, the
|
|
201
|
+
* operation still refuses blind re-execution — the next open returns a
|
|
202
|
+
* code-less replay the route resolves via the upstream remaining-resets list.
|
|
203
|
+
*/
|
|
204
|
+
export function markGrokResetCouponAttempt(
|
|
205
|
+
operationId: string,
|
|
206
|
+
tokenId: string,
|
|
207
|
+
now = Date.now(),
|
|
208
|
+
journalPath?: string,
|
|
209
|
+
tokenValidityEnd?: number,
|
|
210
|
+
): boolean {
|
|
211
|
+
return withConfigMutationLockSync(() => {
|
|
212
|
+
const filePath = journalPath ?? grokCouponJournalPath();
|
|
213
|
+
const ledger = readGrokCouponLedger(filePath);
|
|
214
|
+
const existing = ledger.operations[operationId];
|
|
215
|
+
if (!existing || existing.status !== "open"
|
|
216
|
+
|| (existing.tokenId !== undefined && existing.tokenId !== tokenId)) return false;
|
|
217
|
+
existing.status = "attempted";
|
|
218
|
+
existing.tokenId = tokenId;
|
|
219
|
+
if (tokenValidityEnd !== undefined) existing.tokenValidityEnd = tokenValidityEnd;
|
|
220
|
+
existing.updatedAt = now;
|
|
221
|
+
writeGrokCouponLedger(filePath, ledger, now);
|
|
222
|
+
return true;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
type GrokResetCouponSettlement = { operationId: string; accountId: string; code: string } & (
|
|
227
|
+
| { tokenId?: string; status: "failed"; expectedStatus: "open" }
|
|
228
|
+
| { tokenId: string; status: "success"; expectedStatus: "attempted" }
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
type GrokResetCouponPreflightResult =
|
|
232
|
+
| { kind: "recorded" | "changed" }
|
|
233
|
+
| { kind: "replay"; code: string; tokenId?: string; settledAt: number };
|
|
234
|
+
|
|
235
|
+
const UNRESOLVED_COUPON_CODES = new Set(["redeem_failed", "attempt_unresolved", "attempt_in_progress", "attempt_reconcile_failed", "operation_state_changed"]);
|
|
236
|
+
|
|
237
|
+
/** The durable definitive outcome for this account and token, if the operation has one. */
|
|
238
|
+
function terminalReplay(
|
|
239
|
+
ledger: GrokResetCouponLedger, operationId: string, accountId: string, tokenId: string | undefined,
|
|
240
|
+
): Extract<GrokResetCouponPreflightResult, { kind: "replay" }> | null {
|
|
241
|
+
const existing = ledger.operations[operationId];
|
|
242
|
+
if (!existing || existing.accountId !== accountId) return null;
|
|
243
|
+
if (tokenId !== undefined && existing.tokenId !== undefined && existing.tokenId !== tokenId) return null;
|
|
244
|
+
if ((existing.status !== "settled" && existing.status !== "failed") || !existing.code || UNRESOLVED_COUPON_CODES.has(existing.code)) return null;
|
|
245
|
+
return { kind: "replay", code: existing.code, tokenId: existing.tokenId, settledAt: existing.updatedAt };
|
|
121
246
|
}
|
|
122
247
|
|
|
248
|
+
/** Settle a refusal or inspect its winner without releasing the mutation transaction. */
|
|
249
|
+
export function settleGrokResetCouponPreflightRefusal(
|
|
250
|
+
settlement: Extract<GrokResetCouponSettlement, { status: "failed" }>,
|
|
251
|
+
now = Date.now(),
|
|
252
|
+
journalPath?: string,
|
|
253
|
+
): GrokResetCouponPreflightResult {
|
|
254
|
+
return withConfigMutationLockSync(() => {
|
|
255
|
+
const filePath = journalPath ?? grokCouponJournalPath();
|
|
256
|
+
const ledger = readGrokCouponLedger(filePath);
|
|
257
|
+
if (recordSettlement(ledger, filePath, settlement, now)) return { kind: "recorded" };
|
|
258
|
+
return terminalReplay(ledger, settlement.operationId, settlement.accountId, settlement.tokenId) ?? { kind: "changed" };
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* After a lost claim: the definitive winner to replay, or null while the
|
|
264
|
+
* operation is still unresolved. Terminal states never revert, so reading after
|
|
265
|
+
* the failed claim cannot replay an outcome that is later replaced.
|
|
266
|
+
*/
|
|
267
|
+
export function readGrokResetCouponTerminalReplay(
|
|
268
|
+
operationId: string, accountId: string, tokenId?: string, journalPath?: string,
|
|
269
|
+
): Extract<GrokResetCouponPreflightResult, { kind: "replay" }> | null {
|
|
270
|
+
return withConfigMutationLockSync(() => {
|
|
271
|
+
const filePath = journalPath ?? grokCouponJournalPath();
|
|
272
|
+
return terminalReplay(readGrokCouponLedger(filePath), operationId, accountId, tokenId);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** A preflight refusal cannot replace a claim or a terminal result. */
|
|
123
277
|
export function recordGrokResetCouponSettlement(
|
|
124
|
-
settlement:
|
|
278
|
+
settlement: GrokResetCouponSettlement,
|
|
125
279
|
now = Date.now(),
|
|
126
280
|
journalPath?: string,
|
|
127
|
-
):
|
|
128
|
-
|
|
129
|
-
|
|
281
|
+
): boolean {
|
|
282
|
+
return withConfigMutationLockSync(() => {
|
|
283
|
+
const filePath = journalPath ?? grokCouponJournalPath();
|
|
284
|
+
const ledger = readGrokCouponLedger(filePath);
|
|
285
|
+
return recordSettlement(ledger, filePath, settlement, now);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function recordSettlement(
|
|
290
|
+
ledger: GrokResetCouponLedger, filePath: string, settlement: GrokResetCouponSettlement, now: number,
|
|
291
|
+
): boolean {
|
|
130
292
|
const existing = ledger.operations[settlement.operationId];
|
|
131
|
-
if (!existing
|
|
293
|
+
if (!existing || existing.status !== settlement.expectedStatus
|
|
294
|
+
|| existing.accountId !== settlement.accountId
|
|
295
|
+
|| (settlement.tokenId !== undefined && existing.tokenId !== undefined && existing.tokenId !== settlement.tokenId)
|
|
296
|
+
|| (settlement.status === "success" && existing.tokenId !== settlement.tokenId)) return false;
|
|
132
297
|
|
|
133
298
|
existing.status = settlement.status === "success" ? "settled" : "failed";
|
|
134
299
|
existing.code = settlement.code;
|
|
@@ -136,4 +301,5 @@ export function recordGrokResetCouponSettlement(
|
|
|
136
301
|
existing.updatedAt = now;
|
|
137
302
|
|
|
138
303
|
writeGrokCouponLedger(filePath, ledger, now);
|
|
304
|
+
return true;
|
|
139
305
|
}
|
|
@@ -230,6 +230,17 @@ function buildGrokHeaders(accessToken: string): Record<string, string> {
|
|
|
230
230
|
/**
|
|
231
231
|
* Reads available Grok reset tokens for the authenticated xAI account.
|
|
232
232
|
*/
|
|
233
|
+
function assertConfirmedCouponStatus(decoded: ReturnType<typeof decodeGrpcWebResponse>): void {
|
|
234
|
+
const rawStatus = decoded.trailers?.metadata["grpc-status"];
|
|
235
|
+
// The shared decoder defaults a missing/malformed status to zero. Coupon
|
|
236
|
+
// availability and an irreversible redemption need affirmative confirmation.
|
|
237
|
+
if (rawStatus === undefined || !/^\d+$/.test(rawStatus) || !Number.isSafeInteger(Number(rawStatus))) {
|
|
238
|
+
throw new Error("Grok coupon response has no confirmed gRPC status");
|
|
239
|
+
}
|
|
240
|
+
const status = Number(rawStatus);
|
|
241
|
+
if (status !== 0) throw new GrpcWebError(status, decoded.statusMessage ?? "Unknown gRPC error");
|
|
242
|
+
}
|
|
243
|
+
|
|
233
244
|
export async function getGrokRemainingResets(options: GetRemainingResetsOptions): Promise<{ tokens: GrokResetCoupon[] }> {
|
|
234
245
|
const fetchImpl = options.fetchFn ?? globalThis.fetch;
|
|
235
246
|
const endpoint = options.endpoint ?? GROK_GET_REMAINING_RESETS_ENDPOINT;
|
|
@@ -251,9 +262,7 @@ export async function getGrokRemainingResets(options: GetRemainingResetsOptions)
|
|
|
251
262
|
const rawBytes = new Uint8Array(await res.arrayBuffer());
|
|
252
263
|
const decoded = decodeGrpcWebResponse(rawBytes);
|
|
253
264
|
|
|
254
|
-
|
|
255
|
-
throw new GrpcWebError(decoded.status, decoded.statusMessage ?? "Unknown gRPC error");
|
|
256
|
-
}
|
|
265
|
+
assertConfirmedCouponStatus(decoded);
|
|
257
266
|
|
|
258
267
|
if (decoded.messages.length === 0) {
|
|
259
268
|
return { tokens: [] };
|
|
@@ -285,9 +294,7 @@ export async function redeemGrokResetCoupon(options: RedeemResetOptions): Promis
|
|
|
285
294
|
const rawBytes = new Uint8Array(await res.arrayBuffer());
|
|
286
295
|
const decoded = decodeGrpcWebResponse(rawBytes);
|
|
287
296
|
|
|
288
|
-
|
|
289
|
-
throw new GrpcWebError(decoded.status, decoded.statusMessage ?? "Unknown gRPC error");
|
|
290
|
-
}
|
|
297
|
+
assertConfirmedCouponStatus(decoded);
|
|
291
298
|
|
|
292
299
|
return {
|
|
293
300
|
success: true,
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Detection is read-only and injectable: the proxy never writes anything into a Cursor
|
|
11
11
|
* install, its state database, or its keychain entries (the T20 exclusion in
|
|
12
|
-
* devlog/
|
|
12
|
+
* devlog/_fin/260822_senpi_cursor_transfer/090), and the tests run against a temp tree.
|
|
13
13
|
*/
|
|
14
14
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
15
15
|
import { homedir } from "node:os";
|
package/src/lib/errors.ts
CHANGED
|
@@ -17,6 +17,7 @@ export const ENCRYPTED_FUNCTION_OUTPUT_REJECTION =
|
|
|
17
17
|
* string for a client to be able to tell this apart from a provider rate limit.
|
|
18
18
|
*/
|
|
19
19
|
export const SEND_BUDGET_EXHAUSTED_CODE = "request_send_budget_exhausted";
|
|
20
|
+
export const SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE = "spend_ledger_storage_unavailable";
|
|
20
21
|
|
|
21
22
|
/** First nonblank string across the canonical upstream error paths, in priority order. */
|
|
22
23
|
export function upstreamErrorMessageFromPayload(payload: unknown): string | undefined {
|
|
@@ -357,6 +358,9 @@ export function isUpstreamResetReplayRefusedMessage(text: string): boolean {
|
|
|
357
358
|
|
|
358
359
|
export function classifyError(status: number, type: string, message: string): OcxErrorPayload {
|
|
359
360
|
const text = message.toLowerCase();
|
|
361
|
+
if (type === SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE) {
|
|
362
|
+
return { message, type: "server_error", code: SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE };
|
|
363
|
+
}
|
|
360
364
|
if (type === "previous_response_not_found") {
|
|
361
365
|
return { message, type: "invalid_request_error", code: "previous_response_not_found" };
|
|
362
366
|
}
|
|
@@ -639,6 +643,24 @@ export function adapterFailureFromMessage(message: string): { httpStatus: number
|
|
|
639
643
|
return { httpStatus, error };
|
|
640
644
|
}
|
|
641
645
|
|
|
646
|
+
/** Narrow structured classes carried through a synthesized bare-error terminal. */
|
|
647
|
+
export function recognizedUpstreamError(error: { type?: unknown; code?: unknown }):
|
|
648
|
+
{ type: string; code: string; httpStatus: 429 | 503 } | undefined {
|
|
649
|
+
const code = typeof error.code === "string" ? error.code : undefined;
|
|
650
|
+
// An explicit unknown code must not gain a recognized class from its type or copy.
|
|
651
|
+
if (code !== undefined && !["rate_limit_exceeded", "rate_limit_error", "server_is_overloaded", "overloaded_error"].includes(code)) return undefined;
|
|
652
|
+
if (code === "rate_limit_exceeded" || code === "rate_limit_error"
|
|
653
|
+
|| (code === undefined && error.type === "rate_limit_error")) {
|
|
654
|
+
return { type: "rate_limit_error", code: code ?? "rate_limit_exceeded", httpStatus: 429 };
|
|
655
|
+
}
|
|
656
|
+
if (code === "server_is_overloaded" || code === "overloaded_error"
|
|
657
|
+
|| (code === undefined && error.type === "overloaded_error")) {
|
|
658
|
+
return { type: error.type === "overloaded_error" ? "overloaded_error" : "server_error",
|
|
659
|
+
code: code ?? "server_is_overloaded", httpStatus: 503 };
|
|
660
|
+
}
|
|
661
|
+
return undefined;
|
|
662
|
+
}
|
|
663
|
+
|
|
642
664
|
/** Map a terminal Responses error object to the HTTP status we record in /api/logs. */
|
|
643
665
|
export function httpStatusFromTerminalError(error: {
|
|
644
666
|
type?: string;
|
|
@@ -646,10 +668,13 @@ export function httpStatusFromTerminalError(error: {
|
|
|
646
668
|
message?: string;
|
|
647
669
|
} | undefined): number {
|
|
648
670
|
if (!error) return 502;
|
|
671
|
+
if (error.code === SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE) return 503;
|
|
649
672
|
if (error.code === "client_closed_request" || error.code === "client_cancelled") return 499;
|
|
650
673
|
if (isCyberPolicyCode(error.code) || (error.message ? isCyberPolicyMessage(error.message) : false)) {
|
|
651
674
|
return 400;
|
|
652
675
|
}
|
|
676
|
+
const recognized = recognizedUpstreamError(error);
|
|
677
|
+
if (recognized) return recognized.httpStatus;
|
|
653
678
|
if (error.type === "rate_limit_error" || error.code === "rate_limit_exceeded") return 429;
|
|
654
679
|
if (error.type === "authentication_error" || error.code === "invalid_api_key") return 401;
|
|
655
680
|
if (
|
package/src/lib/redact.ts
CHANGED
|
@@ -7,7 +7,7 @@ export const REDACTED_SECRET = "[REDACTED]";
|
|
|
7
7
|
* credentials over an unsafe channel (e.g. plaintext non-loopback HTTP) rather than
|
|
8
8
|
* re-deriving a narrower local list.
|
|
9
9
|
*/
|
|
10
|
-
export const SENSITIVE_KEY_PATTERN = /^(?:authorization|proxy-authorization|cookie|set-cookie|set-cookie2|api[-_]?key|x-api-key|x-goog-api-key|x-amz-security-token|access[-_]?token|refresh[-_]?token|id[-_]?token|token|secret|client[-_]?secret|password|profile[-_]?arn|exa[-_]?api[-_]?key)$/i;
|
|
10
|
+
export const SENSITIVE_KEY_PATTERN = /^(?:authorization|proxy-authorization|cookie|set-cookie|set-cookie2|api[-_]?key|x-api-key|x-goog-api-key|x-amz-security-token|copilot[-_]?session[-_]?token|session[-_]?token|access[-_]?token|refresh[-_]?token|id[-_]?token|token|secret|client[-_]?secret|password|profile[-_]?arn|exa[-_]?api[-_]?key)$/i;
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Colon-labelled credential headers echoed back inside an error body
|
|
@@ -40,7 +40,7 @@ export const SENSITIVE_KEY_PATTERN = /^(?:authorization|proxy-authorization|cook
|
|
|
40
40
|
// Every letter position also accepts \u0001, the placeholder the fold emits for
|
|
41
41
|
// an unresolved HTML named reference: `authorⅈzation` is the label with one
|
|
42
42
|
// character we cannot name, and that is still the label.
|
|
43
|
-
const CREDENTIAL_HEADER_LABEL_RAW = "x-api-key|x-goog-api-key|x-amz-security-token|api[_-]?key|apiKey|exa[_-]?api[_-]?key|exaApiKey|access[_-]?token|accessToken|refresh[_-]?token|refreshToken|id[_-]?token|client[_-]?secret|clientSecret|authorization|proxy-authorization|cookie|set-cookie|password|secret|token";
|
|
43
|
+
const CREDENTIAL_HEADER_LABEL_RAW = "copilot[_-]?session[_-]?token|session[_-]?token|sessionToken|x-api-key|x-goog-api-key|x-amz-security-token|api[_-]?key|apiKey|exa[_-]?api[_-]?key|exaApiKey|access[_-]?token|accessToken|refresh[_-]?token|refreshToken|id[_-]?token|client[_-]?secret|clientSecret|authorization|proxy-authorization|cookie|set-cookie|password|secret|token";
|
|
44
44
|
|
|
45
45
|
const CREDENTIAL_HEADER_LABEL = CREDENTIAL_HEADER_LABEL_RAW
|
|
46
46
|
.replace(/(?<![\[\\])([A-Za-z])(?![\]\-])/g, "[$1\u0001]");
|
package/src/oauth/index.ts
CHANGED
|
@@ -360,7 +360,7 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderDef> = {
|
|
|
360
360
|
* Removed provider ids that still name a live successor.
|
|
361
361
|
*
|
|
362
362
|
* `devin-cli` was merged into `devin` (import-first login absorbed the CLI
|
|
363
|
-
* credential import; devlog/
|
|
363
|
+
* credential import; devlog/_fin/260913_devin_provider_merge). The id can
|
|
364
364
|
* still arrive here from a saved config row or a stored credential slot that
|
|
365
365
|
* the startup migration has not rekeyed yet, and from a user typing the old
|
|
366
366
|
* name at `ocx login`. It is deliberately NOT an OAUTH_PROVIDERS entry:
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* resulting account token into the `LLM|` Model API key our request path sends as a
|
|
7
7
|
* bearer.
|
|
8
8
|
*
|
|
9
|
-
* Protocol source: devlog/
|
|
9
|
+
* Protocol source: devlog/_fin/260912_muse_device_oauth/001_reference_measurements.md.
|
|
10
10
|
* It is second-party, not vendor documentation, so every response is parsed defensively
|
|
11
11
|
* and every failure names a kind instead of throwing a bare string.
|
|
12
12
|
*
|
package/src/oauth/types.ts
CHANGED
|
@@ -30,7 +30,7 @@ export interface KiroOAuthMetadata {
|
|
|
30
30
|
*
|
|
31
31
|
* The Model API is authenticated by the `LLM|` key in `access`; this token authenticates
|
|
32
32
|
* the Meta ACCOUNT and exists only to mint that key and to read subscription usage
|
|
33
|
-
* (devlog/
|
|
33
|
+
* (devlog/_fin/260912_muse_device_oauth/002 A). Keeping it out of `access` is what lets
|
|
34
34
|
* every request path stay unchanged.
|
|
35
35
|
*
|
|
36
36
|
* It must never be added to `OAuthAccountSummary` (src/oauth/index.ts:1803) or to
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* account rate limit and must keep flowing into the existing wait/rotation handling, so only an
|
|
13
13
|
* error message that names fast mode or the speed parameter, or a fast-pool header reporting
|
|
14
14
|
* zero remaining, counts. Live probe strings (2026-09-23,
|
|
15
|
-
* devlog/
|
|
15
|
+
* devlog/_fin/260923_anthropic_fast_speed/020_probe-evidence.md):
|
|
16
16
|
* 429 "Usage credits are required for fast mode."
|
|
17
17
|
* 400 "Fast mode is not enabled for your organization. ..."
|
|
18
18
|
* 400 "'<model>' does not support the `speed` parameter. ..."
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* ten minutes (`b5o = 600000`) and mints a new one afterwards; this journal keeps
|
|
8
8
|
* the same window.
|
|
9
9
|
*
|
|
10
|
-
* Rules (devlog/
|
|
10
|
+
* Rules (devlog/_fin/260923_claude_reset_grants/010_plan.md, "Ledger contract"):
|
|
11
11
|
* - Every read-modify-write runs synchronously inside a cross-process
|
|
12
12
|
* `BEGIN IMMEDIATE` lock on a sibling SQLite file; nothing awaits inside it,
|
|
13
13
|
* and the upstream claim always runs after the open record is on disk.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `devin-cli` used to be a local provider whose adapter spawned `devin acp`;
|
|
5
5
|
* it later became an account provider on the shared `devin` adapter, and the
|
|
6
6
|
* registry id itself has since merged into `devin` outright
|
|
7
|
-
* (devlog/
|
|
7
|
+
* (devlog/_fin/260913_devin_provider_merge).
|
|
8
8
|
*
|
|
9
9
|
* Only the adapter repair still lives here. The `authMode` half — rewriting
|
|
10
10
|
* `"local"` to `"oauth"` on the registry-id row — moved into
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Merge the retired `devin-cli` provider id into `devin` at startup
|
|
3
|
-
* (devlog/
|
|
3
|
+
* (devlog/_fin/260913_devin_provider_merge).
|
|
4
4
|
*
|
|
5
5
|
* The two registry entries were the same adapter, api-server, and credential
|
|
6
6
|
* shape — only the login path differed — so `devin-cli` was removed from the
|