@gajae-code/ai 0.1.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 +2644 -0
- package/README.md +1181 -0
- package/dist/types/api-registry.d.ts +30 -0
- package/dist/types/auth-broker/client.d.ts +66 -0
- package/dist/types/auth-broker/index.d.ts +5 -0
- package/dist/types/auth-broker/refresher.d.ts +25 -0
- package/dist/types/auth-broker/remote-store.d.ts +96 -0
- package/dist/types/auth-broker/server.d.ts +32 -0
- package/dist/types/auth-broker/types.d.ts +105 -0
- package/dist/types/auth-broker/wire-schemas.d.ts +412 -0
- package/dist/types/auth-gateway/http.d.ts +39 -0
- package/dist/types/auth-gateway/index.d.ts +3 -0
- package/dist/types/auth-gateway/server.d.ts +17 -0
- package/dist/types/auth-gateway/types.d.ts +115 -0
- package/dist/types/auth-storage.d.ts +641 -0
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/index.d.ts +49 -0
- package/dist/types/model-cache.d.ts +17 -0
- package/dist/types/model-manager.d.ts +62 -0
- package/dist/types/model-thinking.d.ts +71 -0
- package/dist/types/models.d.ts +12 -0
- package/dist/types/provider-details.d.ts +24 -0
- package/dist/types/provider-models/bundled-references.d.ts +4 -0
- package/dist/types/provider-models/descriptors.d.ts +48 -0
- package/dist/types/provider-models/google.d.ts +20 -0
- package/dist/types/provider-models/index.d.ts +5 -0
- package/dist/types/provider-models/ollama.d.ts +7 -0
- package/dist/types/provider-models/openai-compat.d.ts +237 -0
- package/dist/types/provider-models/special.d.ts +16 -0
- package/dist/types/providers/amazon-bedrock.d.ts +36 -0
- package/dist/types/providers/anthropic-messages-server-schema.d.ts +450 -0
- package/dist/types/providers/anthropic-messages-server.d.ts +17 -0
- package/dist/types/providers/anthropic.d.ts +188 -0
- package/dist/types/providers/aws-credentials.d.ts +43 -0
- package/dist/types/providers/aws-eventstream.d.ts +38 -0
- package/dist/types/providers/aws-sigv4.d.ts +55 -0
- package/dist/types/providers/azure-openai-responses.d.ts +15 -0
- package/dist/types/providers/cursor/gen/agent_pb.d.ts +13022 -0
- package/dist/types/providers/cursor.d.ts +42 -0
- package/dist/types/providers/error-message.d.ts +27 -0
- package/dist/types/providers/github-copilot-headers.d.ts +40 -0
- package/dist/types/providers/gitlab-duo.d.ts +27 -0
- package/dist/types/providers/google-auth.d.ts +24 -0
- package/dist/types/providers/google-gemini-cli.d.ts +72 -0
- package/dist/types/providers/google-gemini-headers.d.ts +18 -0
- package/dist/types/providers/google-shared.d.ts +163 -0
- package/dist/types/providers/google-types.d.ts +138 -0
- package/dist/types/providers/google-vertex.d.ts +7 -0
- package/dist/types/providers/google.d.ts +4 -0
- package/dist/types/providers/grammar.d.ts +1 -0
- package/dist/types/providers/kimi.d.ts +27 -0
- package/dist/types/providers/mock.d.ts +175 -0
- package/dist/types/providers/ollama.d.ts +6 -0
- package/dist/types/providers/openai-anthropic-shim.d.ts +31 -0
- package/dist/types/providers/openai-chat-server-schema.d.ts +814 -0
- package/dist/types/providers/openai-chat-server.d.ts +16 -0
- package/dist/types/providers/openai-codex/constants.d.ts +26 -0
- package/dist/types/providers/openai-codex/request-transformer.d.ts +49 -0
- package/dist/types/providers/openai-codex/response-handler.d.ts +17 -0
- package/dist/types/providers/openai-codex-responses.d.ts +67 -0
- package/dist/types/providers/openai-completions-compat.d.ts +25 -0
- package/dist/types/providers/openai-completions.d.ts +33 -0
- package/dist/types/providers/openai-responses-server-schema.d.ts +392 -0
- package/dist/types/providers/openai-responses-server.d.ts +17 -0
- package/dist/types/providers/openai-responses-shared.d.ts +89 -0
- package/dist/types/providers/openai-responses.d.ts +32 -0
- package/dist/types/providers/pi-native-client.d.ts +13 -0
- package/dist/types/providers/pi-native-server.d.ts +68 -0
- package/dist/types/providers/register-builtins.d.ts +31 -0
- package/dist/types/providers/synthetic.d.ts +26 -0
- package/dist/types/providers/transform-messages.d.ts +12 -0
- package/dist/types/providers/vision-guard.d.ts +8 -0
- package/dist/types/rate-limit-utils.d.ts +19 -0
- package/dist/types/stream.d.ts +24 -0
- package/dist/types/types.d.ts +746 -0
- package/dist/types/usage/claude.d.ts +3 -0
- package/dist/types/usage/gemini.d.ts +2 -0
- package/dist/types/usage/github-copilot.d.ts +7 -0
- package/dist/types/usage/google-antigravity.d.ts +2 -0
- package/dist/types/usage/kimi.d.ts +2 -0
- package/dist/types/usage/minimax-code.d.ts +2 -0
- package/dist/types/usage/openai-codex.d.ts +3 -0
- package/dist/types/usage/shared.d.ts +1 -0
- package/dist/types/usage/zai.d.ts +2 -0
- package/dist/types/usage.d.ts +258 -0
- package/dist/types/utils/abort.d.ts +19 -0
- package/dist/types/utils/anthropic-auth.d.ts +31 -0
- package/dist/types/utils/discovery/antigravity.d.ts +61 -0
- package/dist/types/utils/discovery/codex.d.ts +38 -0
- package/dist/types/utils/discovery/cursor.d.ts +23 -0
- package/dist/types/utils/discovery/gemini.d.ts +25 -0
- package/dist/types/utils/discovery/index.d.ts +4 -0
- package/dist/types/utils/discovery/openai-compatible.d.ts +72 -0
- package/dist/types/utils/event-stream.d.ts +28 -0
- package/dist/types/utils/fireworks-model-id.d.ts +10 -0
- package/dist/types/utils/foundry.d.ts +1 -0
- package/dist/types/utils/h2-fetch.d.ts +22 -0
- package/dist/types/utils/http-inspector.d.ts +31 -0
- package/dist/types/utils/idle-iterator.d.ts +67 -0
- package/dist/types/utils/json-parse.d.ts +10 -0
- package/dist/types/utils/oauth/alibaba-coding-plan.d.ts +18 -0
- package/dist/types/utils/oauth/anthropic.d.ts +22 -0
- package/dist/types/utils/oauth/api-key-login.d.ts +35 -0
- package/dist/types/utils/oauth/api-key-validation.d.ts +27 -0
- package/dist/types/utils/oauth/callback-server.d.ts +57 -0
- package/dist/types/utils/oauth/cerebras.d.ts +1 -0
- package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +18 -0
- package/dist/types/utils/oauth/cursor.d.ts +15 -0
- package/dist/types/utils/oauth/deepseek.d.ts +10 -0
- package/dist/types/utils/oauth/firepass.d.ts +1 -0
- package/dist/types/utils/oauth/fireworks.d.ts +1 -0
- package/dist/types/utils/oauth/github-copilot.d.ts +38 -0
- package/dist/types/utils/oauth/gitlab-duo.d.ts +3 -0
- package/dist/types/utils/oauth/google-antigravity.d.ts +11 -0
- package/dist/types/utils/oauth/google-gemini-cli.d.ts +10 -0
- package/dist/types/utils/oauth/google-oauth-shared.d.ts +28 -0
- package/dist/types/utils/oauth/huggingface.d.ts +19 -0
- package/dist/types/utils/oauth/index.d.ts +38 -0
- package/dist/types/utils/oauth/kagi.d.ts +17 -0
- package/dist/types/utils/oauth/kilo.d.ts +5 -0
- package/dist/types/utils/oauth/kimi.d.ts +21 -0
- package/dist/types/utils/oauth/litellm.d.ts +18 -0
- package/dist/types/utils/oauth/lm-studio.d.ts +17 -0
- package/dist/types/utils/oauth/minimax-code.d.ts +28 -0
- package/dist/types/utils/oauth/moonshot.d.ts +1 -0
- package/dist/types/utils/oauth/nanogpt.d.ts +1 -0
- package/dist/types/utils/oauth/nvidia.d.ts +18 -0
- package/dist/types/utils/oauth/ollama-cloud.d.ts +2 -0
- package/dist/types/utils/oauth/ollama.d.ts +18 -0
- package/dist/types/utils/oauth/openai-codex.d.ts +21 -0
- package/dist/types/utils/oauth/opencode.d.ts +18 -0
- package/dist/types/utils/oauth/parallel.d.ts +17 -0
- package/dist/types/utils/oauth/perplexity.d.ts +9 -0
- package/dist/types/utils/oauth/pkce.d.ts +8 -0
- package/dist/types/utils/oauth/qianfan.d.ts +17 -0
- package/dist/types/utils/oauth/qwen-portal.d.ts +19 -0
- package/dist/types/utils/oauth/synthetic.d.ts +1 -0
- package/dist/types/utils/oauth/tavily.d.ts +17 -0
- package/dist/types/utils/oauth/together.d.ts +1 -0
- package/dist/types/utils/oauth/types.d.ts +44 -0
- package/dist/types/utils/oauth/venice.d.ts +18 -0
- package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +18 -0
- package/dist/types/utils/oauth/vllm.d.ts +16 -0
- package/dist/types/utils/oauth/xiaomi.d.ts +19 -0
- package/dist/types/utils/oauth/zai.d.ts +18 -0
- package/dist/types/utils/oauth/zenmux.d.ts +1 -0
- package/dist/types/utils/overflow.d.ts +54 -0
- package/dist/types/utils/parse-bind.d.ts +23 -0
- package/dist/types/utils/provider-response.d.ts +3 -0
- package/dist/types/utils/retry-after.d.ts +3 -0
- package/dist/types/utils/retry.d.ts +26 -0
- package/dist/types/utils/schema/adapt.d.ts +24 -0
- package/dist/types/utils/schema/compatibility.d.ts +30 -0
- package/dist/types/utils/schema/dereference.d.ts +11 -0
- package/dist/types/utils/schema/draft.d.ts +10 -0
- package/dist/types/utils/schema/equality.d.ts +4 -0
- package/dist/types/utils/schema/fields.d.ts +49 -0
- package/dist/types/utils/schema/index.d.ts +13 -0
- package/dist/types/utils/schema/json-schema-validator.d.ts +12 -0
- package/dist/types/utils/schema/meta-validator.d.ts +2 -0
- package/dist/types/utils/schema/normalize.d.ts +93 -0
- package/dist/types/utils/schema/spill.d.ts +8 -0
- package/dist/types/utils/schema/stamps.d.ts +25 -0
- package/dist/types/utils/schema/types.d.ts +4 -0
- package/dist/types/utils/schema/wire.d.ts +54 -0
- package/dist/types/utils/schema/zod-decontaminate.d.ts +31 -0
- package/dist/types/utils/sse-debug.d.ts +10 -0
- package/dist/types/utils/tool-call-healing.d.ts +71 -0
- package/dist/types/utils/tool-choice.d.ts +50 -0
- package/dist/types/utils/validation.d.ts +17 -0
- package/dist/types/utils.d.ts +28 -0
- package/package.json +146 -0
- package/src/api-registry.ts +96 -0
- package/src/auth-broker/client.ts +358 -0
- package/src/auth-broker/index.ts +5 -0
- package/src/auth-broker/refresher.ts +127 -0
- package/src/auth-broker/remote-store.ts +623 -0
- package/src/auth-broker/server.ts +644 -0
- package/src/auth-broker/types.ts +127 -0
- package/src/auth-broker/wire-schemas.ts +200 -0
- package/src/auth-gateway/http.ts +194 -0
- package/src/auth-gateway/index.ts +3 -0
- package/src/auth-gateway/server.ts +717 -0
- package/src/auth-gateway/types.ts +134 -0
- package/src/auth-storage.ts +4104 -0
- package/src/cli.ts +262 -0
- package/src/index.ts +54 -0
- package/src/model-cache.ts +129 -0
- package/src/model-manager.ts +450 -0
- package/src/model-thinking.ts +691 -0
- package/src/models.json +73853 -0
- package/src/models.json.d.ts +9 -0
- package/src/models.ts +56 -0
- package/src/prompts/turn-aborted-guidance.md +4 -0
- package/src/provider-details.ts +90 -0
- package/src/provider-models/bundled-references.ts +38 -0
- package/src/provider-models/descriptors.ts +308 -0
- package/src/provider-models/google.ts +91 -0
- package/src/provider-models/index.ts +5 -0
- package/src/provider-models/ollama.ts +153 -0
- package/src/provider-models/openai-compat.ts +2275 -0
- package/src/provider-models/special.ts +67 -0
- package/src/providers/amazon-bedrock.ts +849 -0
- package/src/providers/anthropic-messages-server-schema.ts +229 -0
- package/src/providers/anthropic-messages-server.ts +677 -0
- package/src/providers/anthropic.ts +2696 -0
- package/src/providers/aws-credentials.ts +501 -0
- package/src/providers/aws-eventstream.ts +185 -0
- package/src/providers/aws-sigv4.ts +218 -0
- package/src/providers/azure-openai-responses.ts +337 -0
- package/src/providers/cursor/gen/agent_pb.ts +15274 -0
- package/src/providers/cursor/proto/agent.proto +3526 -0
- package/src/providers/cursor/proto/buf.gen.yaml +6 -0
- package/src/providers/cursor/proto/buf.yaml +17 -0
- package/src/providers/cursor.ts +2561 -0
- package/src/providers/error-message.ts +21 -0
- package/src/providers/github-copilot-headers.ts +140 -0
- package/src/providers/gitlab-duo.ts +372 -0
- package/src/providers/google-auth.ts +252 -0
- package/src/providers/google-gemini-cli.ts +795 -0
- package/src/providers/google-gemini-headers.ts +41 -0
- package/src/providers/google-shared.ts +902 -0
- package/src/providers/google-types.ts +167 -0
- package/src/providers/google-vertex.ts +88 -0
- package/src/providers/google.ts +41 -0
- package/src/providers/grammar.ts +70 -0
- package/src/providers/kimi.ts +52 -0
- package/src/providers/mock.ts +500 -0
- package/src/providers/ollama.ts +544 -0
- package/src/providers/openai-anthropic-shim.ts +138 -0
- package/src/providers/openai-chat-server-schema.ts +243 -0
- package/src/providers/openai-chat-server.ts +628 -0
- package/src/providers/openai-codex/constants.ts +43 -0
- package/src/providers/openai-codex/request-transformer.ts +161 -0
- package/src/providers/openai-codex/response-handler.ts +81 -0
- package/src/providers/openai-codex-responses.ts +2598 -0
- package/src/providers/openai-completions-compat.ts +279 -0
- package/src/providers/openai-completions.ts +1853 -0
- package/src/providers/openai-responses-server-schema.ts +290 -0
- package/src/providers/openai-responses-server.ts +1183 -0
- package/src/providers/openai-responses-shared.ts +800 -0
- package/src/providers/openai-responses.ts +621 -0
- package/src/providers/pi-native-client.ts +228 -0
- package/src/providers/pi-native-server.ts +210 -0
- package/src/providers/register-builtins.ts +412 -0
- package/src/providers/synthetic.ts +50 -0
- package/src/providers/transform-messages.ts +309 -0
- package/src/providers/vision-guard.ts +31 -0
- package/src/rate-limit-utils.ts +84 -0
- package/src/stream.ts +895 -0
- package/src/types.ts +884 -0
- package/src/usage/claude.ts +431 -0
- package/src/usage/gemini.ts +250 -0
- package/src/usage/github-copilot.ts +421 -0
- package/src/usage/google-antigravity.ts +201 -0
- package/src/usage/kimi.ts +271 -0
- package/src/usage/minimax-code.ts +31 -0
- package/src/usage/openai-codex.ts +503 -0
- package/src/usage/shared.ts +10 -0
- package/src/usage/zai.ts +247 -0
- package/src/usage.ts +183 -0
- package/src/utils/abort.ts +51 -0
- package/src/utils/anthropic-auth.ts +87 -0
- package/src/utils/discovery/antigravity.ts +261 -0
- package/src/utils/discovery/codex.ts +371 -0
- package/src/utils/discovery/cursor.ts +306 -0
- package/src/utils/discovery/gemini.ts +248 -0
- package/src/utils/discovery/index.ts +4 -0
- package/src/utils/discovery/openai-compatible.ts +224 -0
- package/src/utils/event-stream.ts +142 -0
- package/src/utils/fireworks-model-id.ts +30 -0
- package/src/utils/foundry.ts +8 -0
- package/src/utils/h2-fetch.ts +60 -0
- package/src/utils/http-inspector.ts +176 -0
- package/src/utils/idle-iterator.ts +250 -0
- package/src/utils/json-parse.ts +148 -0
- package/src/utils/oauth/alibaba-coding-plan.ts +59 -0
- package/src/utils/oauth/anthropic.ts +200 -0
- package/src/utils/oauth/api-key-login.ts +87 -0
- package/src/utils/oauth/api-key-validation.ts +92 -0
- package/src/utils/oauth/callback-server.ts +276 -0
- package/src/utils/oauth/cerebras.ts +16 -0
- package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
- package/src/utils/oauth/cursor.ts +157 -0
- package/src/utils/oauth/deepseek.ts +53 -0
- package/src/utils/oauth/firepass.ts +24 -0
- package/src/utils/oauth/fireworks.ts +15 -0
- package/src/utils/oauth/github-copilot.ts +362 -0
- package/src/utils/oauth/gitlab-duo.ts +123 -0
- package/src/utils/oauth/google-antigravity.ts +200 -0
- package/src/utils/oauth/google-gemini-cli.ts +256 -0
- package/src/utils/oauth/google-oauth-shared.ts +110 -0
- package/src/utils/oauth/huggingface.ts +62 -0
- package/src/utils/oauth/index.ts +444 -0
- package/src/utils/oauth/kagi.ts +47 -0
- package/src/utils/oauth/kilo.ts +87 -0
- package/src/utils/oauth/kimi.ts +254 -0
- package/src/utils/oauth/litellm.ts +47 -0
- package/src/utils/oauth/lm-studio.ts +38 -0
- package/src/utils/oauth/minimax-code.ts +78 -0
- package/src/utils/oauth/moonshot.ts +16 -0
- package/src/utils/oauth/nanogpt.ts +15 -0
- package/src/utils/oauth/nvidia.ts +70 -0
- package/src/utils/oauth/oauth.html +199 -0
- package/src/utils/oauth/ollama-cloud.ts +28 -0
- package/src/utils/oauth/ollama.ts +47 -0
- package/src/utils/oauth/openai-codex.ts +299 -0
- package/src/utils/oauth/opencode.ts +49 -0
- package/src/utils/oauth/parallel.ts +46 -0
- package/src/utils/oauth/perplexity.ts +206 -0
- package/src/utils/oauth/pkce.ts +18 -0
- package/src/utils/oauth/qianfan.ts +58 -0
- package/src/utils/oauth/qwen-portal.ts +60 -0
- package/src/utils/oauth/synthetic.ts +16 -0
- package/src/utils/oauth/tavily.ts +46 -0
- package/src/utils/oauth/together.ts +16 -0
- package/src/utils/oauth/types.ts +94 -0
- package/src/utils/oauth/venice.ts +59 -0
- package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
- package/src/utils/oauth/vllm.ts +40 -0
- package/src/utils/oauth/xiaomi.ts +137 -0
- package/src/utils/oauth/zai.ts +60 -0
- package/src/utils/oauth/zenmux.ts +15 -0
- package/src/utils/overflow.ts +137 -0
- package/src/utils/parse-bind.ts +54 -0
- package/src/utils/provider-response.ts +30 -0
- package/src/utils/retry-after.ts +110 -0
- package/src/utils/retry.ts +54 -0
- package/src/utils/schema/CONSTRAINTS.md +164 -0
- package/src/utils/schema/adapt.ts +36 -0
- package/src/utils/schema/compatibility.ts +435 -0
- package/src/utils/schema/dereference.ts +98 -0
- package/src/utils/schema/draft.ts +341 -0
- package/src/utils/schema/equality.ts +97 -0
- package/src/utils/schema/fields.ts +190 -0
- package/src/utils/schema/index.ts +13 -0
- package/src/utils/schema/json-schema-validator.ts +577 -0
- package/src/utils/schema/meta-validator.ts +167 -0
- package/src/utils/schema/normalize.ts +1588 -0
- package/src/utils/schema/spill.ts +43 -0
- package/src/utils/schema/stamps.ts +97 -0
- package/src/utils/schema/types.ts +11 -0
- package/src/utils/schema/wire.ts +213 -0
- package/src/utils/schema/zod-decontaminate.ts +331 -0
- package/src/utils/sse-debug.ts +289 -0
- package/src/utils/tool-call-healing.ts +271 -0
- package/src/utils/tool-choice.ts +99 -0
- package/src/utils/validation.ts +1019 -0
- package/src/utils.ts +166 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for the gjc auth-broker server.
|
|
3
|
+
*
|
|
4
|
+
* Used by {@link RemoteAuthCredentialStore} (snapshot pulls) and by
|
|
5
|
+
* `gjc auth-broker status` (liveness checks). All endpoints except
|
|
6
|
+
* `/v1/healthz` require a bearer token.
|
|
7
|
+
*/
|
|
8
|
+
import { readSseEvents } from "@gajae-code/utils";
|
|
9
|
+
import type { ZodType, infer as zInfer } from "zod/v4";
|
|
10
|
+
import type { AuthCredential } from "../auth-storage";
|
|
11
|
+
import type {
|
|
12
|
+
CredentialDisableRequest,
|
|
13
|
+
CredentialDisableResponse,
|
|
14
|
+
CredentialRefreshResponse,
|
|
15
|
+
CredentialUploadRequest,
|
|
16
|
+
CredentialUploadResponse,
|
|
17
|
+
HealthzResponse,
|
|
18
|
+
SnapshotResponse,
|
|
19
|
+
SnapshotStreamEvent,
|
|
20
|
+
UsageResponse,
|
|
21
|
+
} from "./types";
|
|
22
|
+
import {
|
|
23
|
+
credentialDisableResponseSchema,
|
|
24
|
+
credentialRefreshResponseSchema,
|
|
25
|
+
credentialUploadResponseSchema,
|
|
26
|
+
healthzResponseSchema,
|
|
27
|
+
snapshotResponseSchema,
|
|
28
|
+
snapshotStreamEventSchema,
|
|
29
|
+
usageResponseSchema,
|
|
30
|
+
} from "./wire-schemas";
|
|
31
|
+
|
|
32
|
+
export interface AuthBrokerClientOptions {
|
|
33
|
+
/** Base URL (e.g. `https://broker.tailnet:8765`). Trailing slashes are trimmed. */
|
|
34
|
+
url: string;
|
|
35
|
+
/** Bearer token used for everything except `healthz`. */
|
|
36
|
+
token: string;
|
|
37
|
+
/** Per-request timeout in milliseconds. Default 10s. */
|
|
38
|
+
timeoutMs?: number;
|
|
39
|
+
/** Retry connection errors this many times. Default 1. */
|
|
40
|
+
maxRetries?: number;
|
|
41
|
+
/** Override fetch (used in tests). Default global `fetch`. */
|
|
42
|
+
fetchImpl?: typeof fetch;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class AuthBrokerError extends Error {
|
|
46
|
+
readonly status: number | undefined;
|
|
47
|
+
readonly body: string | undefined;
|
|
48
|
+
constructor(message: string, opts: { status?: number; body?: string; cause?: unknown } = {}) {
|
|
49
|
+
super(message, { cause: opts.cause });
|
|
50
|
+
this.name = "AuthBrokerError";
|
|
51
|
+
this.status = opts.status;
|
|
52
|
+
this.body = opts.body;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Thrown when a broker responds 404 to `GET /v1/snapshot/stream` — old
|
|
58
|
+
* brokers that predate the SSE endpoint. Callers (`RemoteAuthCredentialStore`)
|
|
59
|
+
* detect this sentinel to fall back to long-polling permanently.
|
|
60
|
+
*/
|
|
61
|
+
export class AuthBrokerStreamUnsupportedError extends AuthBrokerError {
|
|
62
|
+
constructor(message = "Auth broker does not support /v1/snapshot/stream") {
|
|
63
|
+
super(message, { status: 404 });
|
|
64
|
+
this.name = "AuthBrokerStreamUnsupportedError";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface FetchSnapshotOptions {
|
|
69
|
+
ifGenerationGt?: number;
|
|
70
|
+
waitMs?: number;
|
|
71
|
+
signal?: AbortSignal;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type FetchSnapshotResult =
|
|
75
|
+
| { status: 200; snapshot: SnapshotResponse; generation: number }
|
|
76
|
+
| { status: 304; generation: number };
|
|
77
|
+
|
|
78
|
+
function parseGenerationTag(header: string | null): number | undefined {
|
|
79
|
+
if (!header) return undefined;
|
|
80
|
+
let value = header.trim();
|
|
81
|
+
if (value.startsWith("W/")) value = value.slice(2).trim();
|
|
82
|
+
if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
|
|
83
|
+
value = value.slice(1, -1);
|
|
84
|
+
}
|
|
85
|
+
const generation = Number(value);
|
|
86
|
+
if (!Number.isInteger(generation) || generation < 0) return undefined;
|
|
87
|
+
return generation;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
91
|
+
const DEFAULT_MAX_RETRIES = 1;
|
|
92
|
+
|
|
93
|
+
export class AuthBrokerClient {
|
|
94
|
+
readonly #baseUrl: string;
|
|
95
|
+
readonly #token: string;
|
|
96
|
+
readonly #timeoutMs: number;
|
|
97
|
+
readonly #maxRetries: number;
|
|
98
|
+
readonly #fetch: typeof fetch;
|
|
99
|
+
|
|
100
|
+
constructor(opts: AuthBrokerClientOptions) {
|
|
101
|
+
this.#baseUrl = opts.url.replace(/\/+$/, "");
|
|
102
|
+
this.#token = opts.token;
|
|
103
|
+
this.#timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
104
|
+
this.#maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
105
|
+
this.#fetch = opts.fetchImpl ?? fetch;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
healthz(signal?: AbortSignal): Promise<HealthzResponse> {
|
|
109
|
+
return this.#request("GET", "/v1/healthz", { schema: healthzResponseSchema, auth: false, signal });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async fetchSnapshot(opts: FetchSnapshotOptions = {}): Promise<FetchSnapshotResult> {
|
|
113
|
+
return this.#fetchSnapshotResult(opts);
|
|
114
|
+
}
|
|
115
|
+
async #fetchSnapshotResult(opts: FetchSnapshotOptions): Promise<FetchSnapshotResult> {
|
|
116
|
+
const query = new URLSearchParams();
|
|
117
|
+
if (opts.waitMs !== undefined) query.set("wait", String(opts.waitMs));
|
|
118
|
+
const path = `/v1/snapshot${query.size > 0 ? `?${query.toString()}` : ""}`;
|
|
119
|
+
const headers: Record<string, string> = {};
|
|
120
|
+
if (opts.ifGenerationGt !== undefined) headers["If-None-Match"] = `"${opts.ifGenerationGt}"`;
|
|
121
|
+
const timeoutMs =
|
|
122
|
+
opts.waitMs !== undefined && opts.waitMs > 0 ? Math.max(this.#timeoutMs, opts.waitMs + 1000) : undefined;
|
|
123
|
+
const response = await this.#fetchRaw("GET", path, {
|
|
124
|
+
auth: true,
|
|
125
|
+
headers,
|
|
126
|
+
signal: opts.signal,
|
|
127
|
+
timeoutMs,
|
|
128
|
+
});
|
|
129
|
+
const etagGeneration = parseGenerationTag(response.headers.get("etag"));
|
|
130
|
+
if (response.status === 304) {
|
|
131
|
+
return { status: 304, generation: etagGeneration ?? opts.ifGenerationGt ?? 0 };
|
|
132
|
+
}
|
|
133
|
+
const text = await response.text();
|
|
134
|
+
const raw = this.#parseJson(text, response.status);
|
|
135
|
+
const validated = snapshotResponseSchema.safeParse(raw);
|
|
136
|
+
if (!validated.success) {
|
|
137
|
+
throw new AuthBrokerError("Auth broker response failed schema validation", {
|
|
138
|
+
status: response.status,
|
|
139
|
+
body: validated.error.message,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const snapshot = validated.data as SnapshotResponse;
|
|
143
|
+
return { status: 200, snapshot, generation: etagGeneration ?? snapshot.generation };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Subscribe to the broker's SSE snapshot stream. The first frame is always
|
|
148
|
+
* a full `snapshot`; subsequent frames are `entry` upserts / refreshes or
|
|
149
|
+
* `removed` deletes. Caller controls lifecycle via `opts.signal`.
|
|
150
|
+
*
|
|
151
|
+
* Throws {@link AuthBrokerStreamUnsupportedError} when the broker responds
|
|
152
|
+
* 404 — older brokers predate this endpoint and the caller should fall back
|
|
153
|
+
* to long-polling for the remainder of its lifetime.
|
|
154
|
+
*/
|
|
155
|
+
async *openSnapshotStream(opts: { signal?: AbortSignal } = {}): AsyncGenerator<SnapshotStreamEvent> {
|
|
156
|
+
const url = `${this.#baseUrl}/v1/snapshot/stream`;
|
|
157
|
+
const headers: Record<string, string> = {
|
|
158
|
+
Accept: "text/event-stream",
|
|
159
|
+
Authorization: `Bearer ${this.#token}`,
|
|
160
|
+
};
|
|
161
|
+
if (opts.signal?.aborted) {
|
|
162
|
+
throw new AuthBrokerError("Auth broker request aborted", { cause: opts.signal.reason });
|
|
163
|
+
}
|
|
164
|
+
// No timeout: this connection is intentionally long-lived. Caller's signal
|
|
165
|
+
// is the only cancel path.
|
|
166
|
+
const response = await this.#fetch(url, { method: "GET", headers, signal: opts.signal });
|
|
167
|
+
if (response.status === 404) {
|
|
168
|
+
// Drain the body so the socket can be reused; tiny payload.
|
|
169
|
+
await response.text().catch(() => {});
|
|
170
|
+
throw new AuthBrokerStreamUnsupportedError();
|
|
171
|
+
}
|
|
172
|
+
if (!response.ok) {
|
|
173
|
+
const text = await response.text().catch(() => "");
|
|
174
|
+
throw new AuthBrokerError(`Auth broker stream failed: ${response.status} ${response.statusText}`, {
|
|
175
|
+
status: response.status,
|
|
176
|
+
body: text,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (!response.body) {
|
|
180
|
+
throw new AuthBrokerError("Auth broker stream response had no body", { status: response.status });
|
|
181
|
+
}
|
|
182
|
+
const contentType = response.headers.get("content-type")?.toLowerCase();
|
|
183
|
+
if (contentType?.split(";", 1)[0].trim() !== "text/event-stream") {
|
|
184
|
+
await response.body.cancel().catch(() => {});
|
|
185
|
+
throw new AuthBrokerError("Auth broker stream returned non-SSE response", {
|
|
186
|
+
status: response.status,
|
|
187
|
+
body: contentType ?? "",
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let sawFirstEvent = false;
|
|
192
|
+
for await (const sse of readSseEvents(response.body, opts.signal)) {
|
|
193
|
+
if (sse.event === null && sse.data === "") continue; // keepalive comment frames
|
|
194
|
+
let parsed: unknown;
|
|
195
|
+
try {
|
|
196
|
+
parsed = JSON.parse(sse.data);
|
|
197
|
+
} catch (err) {
|
|
198
|
+
throw new AuthBrokerError("Auth broker stream returned malformed JSON", {
|
|
199
|
+
body: sse.data,
|
|
200
|
+
cause: err,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
const validated = snapshotStreamEventSchema.safeParse(parsed);
|
|
204
|
+
if (!validated.success) {
|
|
205
|
+
throw new AuthBrokerError("Auth broker stream event failed schema validation", {
|
|
206
|
+
body: validated.error.message,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
const event = validated.data;
|
|
210
|
+
if (!sawFirstEvent) {
|
|
211
|
+
sawFirstEvent = true;
|
|
212
|
+
if (event.kind !== "snapshot") {
|
|
213
|
+
throw new AuthBrokerError("Auth broker stream did not start with snapshot", { body: sse.data });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
yield event;
|
|
217
|
+
}
|
|
218
|
+
if (!opts.signal?.aborted) {
|
|
219
|
+
throw new AuthBrokerError(
|
|
220
|
+
sawFirstEvent
|
|
221
|
+
? "Auth broker stream ended unexpectedly"
|
|
222
|
+
: "Auth broker stream ended before initial snapshot",
|
|
223
|
+
{ status: response.status },
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
fetchUsage(signal?: AbortSignal): Promise<UsageResponse> {
|
|
229
|
+
// Validates the envelope (`generatedAt`, `reports[].provider`, `limits`,
|
|
230
|
+
// `metadata`) but leaves provider-specific extension fields permissive so
|
|
231
|
+
// the broker can ship new shapes ahead of the client. `raw` is accepted
|
|
232
|
+
// but normally stripped by the broker before send.
|
|
233
|
+
return this.#request("GET", "/v1/usage", { schema: usageResponseSchema, signal }) as Promise<UsageResponse>;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async refreshCredential(id: number, signal?: AbortSignal): Promise<CredentialRefreshResponse> {
|
|
237
|
+
return this.#request("POST", `/v1/credential/${id}/refresh`, {
|
|
238
|
+
schema: credentialRefreshResponseSchema,
|
|
239
|
+
signal,
|
|
240
|
+
}) as Promise<CredentialRefreshResponse>;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async disableCredential(id: number, cause: string, signal?: AbortSignal): Promise<CredentialDisableResponse> {
|
|
244
|
+
const body: CredentialDisableRequest = { cause };
|
|
245
|
+
return this.#request("POST", `/v1/credential/${id}/disable`, {
|
|
246
|
+
body,
|
|
247
|
+
schema: credentialDisableResponseSchema,
|
|
248
|
+
signal,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async uploadCredential(
|
|
253
|
+
provider: string,
|
|
254
|
+
credential: AuthCredential,
|
|
255
|
+
signal?: AbortSignal,
|
|
256
|
+
): Promise<CredentialUploadResponse> {
|
|
257
|
+
const body: CredentialUploadRequest = { provider, credential };
|
|
258
|
+
return this.#request("POST", "/v1/credential", {
|
|
259
|
+
body,
|
|
260
|
+
schema: credentialUploadResponseSchema,
|
|
261
|
+
signal,
|
|
262
|
+
}) as Promise<CredentialUploadResponse>;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async #request<TSchema extends ZodType>(
|
|
266
|
+
method: "GET" | "POST",
|
|
267
|
+
path: string,
|
|
268
|
+
opts: { schema: TSchema; auth?: boolean; body?: unknown; signal?: AbortSignal },
|
|
269
|
+
): Promise<zInfer<TSchema>> {
|
|
270
|
+
const response = await this.#fetchRaw(method, path, opts);
|
|
271
|
+
const text = await response.text();
|
|
272
|
+
const raw = this.#parseJson(text, response.status);
|
|
273
|
+
const validated = opts.schema.safeParse(raw);
|
|
274
|
+
if (!validated.success) {
|
|
275
|
+
throw new AuthBrokerError("Auth broker response failed schema validation", {
|
|
276
|
+
status: response.status,
|
|
277
|
+
body: validated.error.message,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
return validated.data;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
#parseJson(text: string, status: number): unknown {
|
|
284
|
+
try {
|
|
285
|
+
return text.length === 0 ? null : JSON.parse(text);
|
|
286
|
+
} catch (parseError) {
|
|
287
|
+
throw new AuthBrokerError("Auth broker returned malformed JSON", {
|
|
288
|
+
status,
|
|
289
|
+
body: text,
|
|
290
|
+
cause: parseError,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async #fetchRaw(
|
|
296
|
+
method: "GET" | "POST",
|
|
297
|
+
path: string,
|
|
298
|
+
opts: {
|
|
299
|
+
auth?: boolean;
|
|
300
|
+
body?: unknown;
|
|
301
|
+
signal?: AbortSignal;
|
|
302
|
+
headers?: Record<string, string>;
|
|
303
|
+
timeoutMs?: number;
|
|
304
|
+
},
|
|
305
|
+
): Promise<Response> {
|
|
306
|
+
const auth = opts.auth ?? true;
|
|
307
|
+
const url = `${this.#baseUrl}${path}`;
|
|
308
|
+
const headers: Record<string, string> = { Accept: "application/json", ...(opts.headers ?? {}) };
|
|
309
|
+
if (auth) headers.Authorization = `Bearer ${this.#token}`;
|
|
310
|
+
let payload: string | undefined;
|
|
311
|
+
if (opts.body !== undefined) {
|
|
312
|
+
payload = JSON.stringify(opts.body);
|
|
313
|
+
headers["Content-Type"] = "application/json";
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Fast-fail when the caller's signal is already aborted — avoids spinning
|
|
317
|
+
// up a fetch + timer that the first `await` would just abort anyway.
|
|
318
|
+
if (opts.signal?.aborted) {
|
|
319
|
+
throw new AuthBrokerError("Auth broker request aborted", { cause: opts.signal.reason });
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
let lastError: unknown;
|
|
323
|
+
for (let attempt = 0; attempt <= this.#maxRetries; attempt += 1) {
|
|
324
|
+
const timeoutSignal = AbortSignal.timeout(opts.timeoutMs ?? this.#timeoutMs);
|
|
325
|
+
const signal = opts.signal ? AbortSignal.any([opts.signal, timeoutSignal]) : timeoutSignal;
|
|
326
|
+
try {
|
|
327
|
+
const response = await this.#fetch(url, {
|
|
328
|
+
method,
|
|
329
|
+
headers,
|
|
330
|
+
body: payload,
|
|
331
|
+
signal,
|
|
332
|
+
});
|
|
333
|
+
if (!response.ok && response.status !== 304) {
|
|
334
|
+
const text = await response.text();
|
|
335
|
+
throw new AuthBrokerError(`Auth broker request failed: ${response.status} ${response.statusText}`, {
|
|
336
|
+
status: response.status,
|
|
337
|
+
body: text,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
return response;
|
|
341
|
+
} catch (error) {
|
|
342
|
+
lastError = error;
|
|
343
|
+
// Caller-driven abort wins over retry — the caller said stop.
|
|
344
|
+
if (opts.signal?.aborted) {
|
|
345
|
+
throw new AuthBrokerError("Auth broker request aborted", { cause: opts.signal.reason });
|
|
346
|
+
}
|
|
347
|
+
if (error instanceof AuthBrokerError && error.status !== undefined) {
|
|
348
|
+
// HTTP errors (4xx/5xx) don't retry — caller knows what to do.
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
351
|
+
if (attempt >= this.#maxRetries) break;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
throw new AuthBrokerError(`Auth broker request failed after ${this.#maxRetries + 1} attempt(s)`, {
|
|
355
|
+
cause: lastError,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background OAuth refresh loop for the auth-broker server.
|
|
3
|
+
*
|
|
4
|
+
* Iterates active OAuth credentials at `refreshIntervalMs` cadence, refreshing
|
|
5
|
+
* any whose `expires - Date.now() < refreshSkewMs`. Refresh single-flight
|
|
6
|
+
* lives in {@link AuthStorage} so manual and background refreshes share the
|
|
7
|
+
* same upstream attempt.
|
|
8
|
+
* Definitively-failed credentials (invalid_grant / 401 not from network blip)
|
|
9
|
+
* are disabled via {@link AuthStorage.disableCredentialById} so the next
|
|
10
|
+
* snapshot pull surfaces a clean delete on the client.
|
|
11
|
+
*/
|
|
12
|
+
import { logger } from "@gajae-code/utils";
|
|
13
|
+
import type { AuthStorage } from "../auth-storage";
|
|
14
|
+
import { DEFAULT_REFRESH_INTERVAL_MS, DEFAULT_REFRESH_SKEW_MS } from "./types";
|
|
15
|
+
|
|
16
|
+
export interface AuthBrokerRefresherOptions {
|
|
17
|
+
storage: AuthStorage;
|
|
18
|
+
/** Refresh credentials expiring within this window. Default 5 min. */
|
|
19
|
+
refreshSkewMs?: number;
|
|
20
|
+
/** Loop cadence. Default 60s. */
|
|
21
|
+
refreshIntervalMs?: number;
|
|
22
|
+
/** Override clock (tests). */
|
|
23
|
+
now?: () => number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const INVALID_GRANT_REGEX = /invalid_grant|invalid_token|revoked|unauthorized|expired.*refresh|refresh.*expired/i;
|
|
27
|
+
const TRANSIENT_REGEX = /timeout|network|fetch failed|ECONNREFUSED/i;
|
|
28
|
+
const HTTP_401_403_REGEX = /\b(401|403)\b/;
|
|
29
|
+
|
|
30
|
+
function isDefinitiveFailure(errorMsg: string): boolean {
|
|
31
|
+
if (INVALID_GRANT_REGEX.test(errorMsg)) return true;
|
|
32
|
+
if (HTTP_401_403_REGEX.test(errorMsg) && !TRANSIENT_REGEX.test(errorMsg)) return true;
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface AuthBrokerRefresherSchedule {
|
|
37
|
+
enabled: boolean;
|
|
38
|
+
intervalMs: number;
|
|
39
|
+
skewMs: number;
|
|
40
|
+
nextSweepAt: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class AuthBrokerRefresher {
|
|
44
|
+
readonly #storage: AuthStorage;
|
|
45
|
+
readonly #refreshSkewMs: number;
|
|
46
|
+
readonly #refreshIntervalMs: number;
|
|
47
|
+
readonly #now: () => number;
|
|
48
|
+
#timer: NodeJS.Timeout | undefined;
|
|
49
|
+
#running = false;
|
|
50
|
+
#nextSweepAt: number;
|
|
51
|
+
constructor(opts: AuthBrokerRefresherOptions) {
|
|
52
|
+
this.#storage = opts.storage;
|
|
53
|
+
this.#refreshSkewMs = opts.refreshSkewMs ?? DEFAULT_REFRESH_SKEW_MS;
|
|
54
|
+
this.#refreshIntervalMs = opts.refreshIntervalMs ?? DEFAULT_REFRESH_INTERVAL_MS;
|
|
55
|
+
this.#now = opts.now ?? Date.now;
|
|
56
|
+
this.#nextSweepAt = this.#now();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
start(): void {
|
|
60
|
+
if (this.#timer !== undefined) return;
|
|
61
|
+
// Refresh sweep is best-effort; kick once immediately so freshly-booted
|
|
62
|
+
// brokers don't hand out near-expired tokens for the first interval.
|
|
63
|
+
this.#nextSweepAt = this.#now();
|
|
64
|
+
void this.tick();
|
|
65
|
+
this.#timer = setInterval(() => {
|
|
66
|
+
void this.tick();
|
|
67
|
+
}, this.#refreshIntervalMs);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
stop(): void {
|
|
71
|
+
if (this.#timer !== undefined) {
|
|
72
|
+
clearInterval(this.#timer);
|
|
73
|
+
this.#timer = undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getSchedule(): AuthBrokerRefresherSchedule {
|
|
78
|
+
return {
|
|
79
|
+
enabled: true,
|
|
80
|
+
intervalMs: this.#refreshIntervalMs,
|
|
81
|
+
skewMs: this.#refreshSkewMs,
|
|
82
|
+
nextSweepAt: this.#nextSweepAt,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Run one sweep. Exposed for tests. */
|
|
87
|
+
async tick(): Promise<void> {
|
|
88
|
+
if (this.#running) return;
|
|
89
|
+
this.#running = true;
|
|
90
|
+
this.#nextSweepAt = this.#now();
|
|
91
|
+
try {
|
|
92
|
+
await this.#storage.reload();
|
|
93
|
+
const snapshot = this.#storage.exportSnapshot();
|
|
94
|
+
const now = this.#now();
|
|
95
|
+
const deadline = now + this.#refreshSkewMs;
|
|
96
|
+
const targets: number[] = [];
|
|
97
|
+
for (const entry of snapshot.credentials) {
|
|
98
|
+
if (entry.credential.type !== "oauth") continue;
|
|
99
|
+
const expires = entry.credential.expires;
|
|
100
|
+
if (typeof expires !== "number" || !Number.isFinite(expires)) continue;
|
|
101
|
+
if (expires > deadline) continue;
|
|
102
|
+
targets.push(entry.id);
|
|
103
|
+
}
|
|
104
|
+
await Promise.all(targets.map(id => this.#refreshOne(id)));
|
|
105
|
+
} finally {
|
|
106
|
+
this.#running = false;
|
|
107
|
+
this.#nextSweepAt = this.#now() + this.#refreshIntervalMs;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async #refreshOne(id: number): Promise<void> {
|
|
112
|
+
try {
|
|
113
|
+
await this.#storage.refreshCredentialById(id);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
const errorMsg = String(error);
|
|
116
|
+
if (isDefinitiveFailure(errorMsg)) {
|
|
117
|
+
logger.warn("auth-broker refresh failed definitively; disabling credential", {
|
|
118
|
+
id,
|
|
119
|
+
error: errorMsg,
|
|
120
|
+
});
|
|
121
|
+
this.#storage.disableCredentialById(id, `auth-broker refresh failed: ${errorMsg}`);
|
|
122
|
+
} else {
|
|
123
|
+
logger.debug("auth-broker refresh failed (transient)", { id, error: errorMsg });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|