@oh-my-pi/pi-ai 16.2.1 → 16.2.2
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 +27 -0
- package/dist/types/auth-gateway/server.d.ts +0 -19
- package/dist/types/auth-retry.d.ts +2 -10
- package/dist/types/auth-storage.d.ts +1 -2
- package/dist/types/dialect/demotion.d.ts +22 -0
- package/dist/types/dialect/index.d.ts +2 -0
- package/dist/types/error/abort.d.ts +14 -0
- package/dist/types/error/auth-classify.d.ts +16 -0
- package/dist/types/error/auth.d.ts +27 -0
- package/dist/types/error/aws.d.ts +23 -0
- package/dist/types/error/classes.d.ts +102 -0
- package/dist/types/error/finalize.d.ts +39 -0
- package/dist/types/error/flags.d.ts +70 -0
- package/dist/types/error/format.d.ts +20 -0
- package/dist/types/error/gateway.d.ts +20 -0
- package/dist/types/error/index.d.ts +13 -0
- package/dist/types/error/oauth.d.ts +43 -0
- package/dist/types/error/provider.d.ts +42 -0
- package/dist/types/{rate-limit-utils.d.ts → error/rate-limit.d.ts} +14 -1
- package/dist/types/error/retryable.d.ts +27 -0
- package/dist/types/error/validation.d.ts +32 -0
- package/dist/types/index.d.ts +1 -3
- package/dist/types/providers/amazon-bedrock.d.ts +0 -5
- package/dist/types/providers/anthropic-client.d.ts +2 -16
- package/dist/types/providers/anthropic.d.ts +6 -1
- package/dist/types/providers/aws-eventstream.d.ts +2 -1
- package/dist/types/providers/cursor.d.ts +8 -7
- package/dist/types/providers/google-gemini-cli.d.ts +0 -5
- package/dist/types/providers/google-shared.d.ts +0 -5
- package/dist/types/providers/ollama.d.ts +0 -5
- package/dist/types/providers/openai-codex/request-transformer.d.ts +2 -2
- package/dist/types/providers/openai-codex/response-handler.d.ts +1 -1
- package/dist/types/providers/openai-codex-responses.d.ts +2 -2
- package/dist/types/providers/openai-responses-server-schema.d.ts +6 -0
- package/dist/types/providers/openai-responses-wire.d.ts +1 -1
- package/dist/types/providers/openai-responses.d.ts +1 -0
- package/dist/types/providers/openai-shared.d.ts +8 -6
- package/dist/types/providers/pi-native-client.d.ts +0 -9
- package/dist/types/registry/oauth/xai-oauth.d.ts +0 -9
- package/dist/types/types.d.ts +6 -0
- package/dist/types/utils/block-symbols.d.ts +20 -0
- package/dist/types/utils/openai-http.d.ts +4 -8
- package/dist/types/utils/retry.d.ts +2 -14
- package/dist/types/utils/thinking-loop.d.ts +3 -1
- package/package.json +8 -4
- package/src/api-registry.ts +3 -1
- package/src/auth-broker/discover.ts +7 -2
- package/src/auth-broker/remote-store.ts +8 -7
- package/src/auth-gateway/server.ts +27 -115
- package/src/auth-retry.ts +9 -21
- package/src/auth-storage.ts +34 -49
- package/src/dialect/demotion.ts +31 -0
- package/src/dialect/factory.ts +0 -2
- package/src/dialect/index.ts +2 -0
- package/src/dialect/owned-stream.ts +0 -1
- package/src/dialect/thinking.ts +22 -10
- package/src/error/abort.ts +18 -0
- package/src/error/auth-classify.ts +30 -0
- package/src/error/auth.ts +48 -0
- package/src/error/aws.ts +31 -0
- package/src/error/classes.ts +186 -0
- package/src/error/finalize.ts +69 -0
- package/src/error/flags.ts +486 -0
- package/src/error/format.ts +36 -0
- package/src/error/gateway.ts +96 -0
- package/src/error/index.ts +13 -0
- package/src/error/oauth.ts +58 -0
- package/src/error/provider.ts +56 -0
- package/src/{rate-limit-utils.ts → error/rate-limit.ts} +9 -3
- package/src/error/retryable.ts +70 -0
- package/src/error/validation.ts +44 -0
- package/src/index.ts +1 -3
- package/src/providers/amazon-bedrock.ts +61 -57
- package/src/providers/anthropic-client.ts +13 -41
- package/src/providers/anthropic-messages-server.ts +9 -2
- package/src/providers/anthropic.ts +84 -147
- package/src/providers/aws-credentials.ts +41 -11
- package/src/providers/aws-eventstream.ts +12 -21
- package/src/providers/azure-openai-responses.ts +27 -17
- package/src/providers/cursor.ts +59 -53
- package/src/providers/devin.ts +28 -20
- package/src/providers/gitlab-duo-workflow.ts +30 -10
- package/src/providers/gitlab-duo.ts +22 -6
- package/src/providers/google-auth.ts +15 -5
- package/src/providers/google-gemini-cli.ts +46 -60
- package/src/providers/google-shared.ts +40 -38
- package/src/providers/google-vertex.ts +3 -2
- package/src/providers/google.ts +5 -1
- package/src/providers/mock.ts +10 -7
- package/src/providers/ollama.ts +34 -29
- package/src/providers/openai-chat-server.ts +2 -1
- package/src/providers/openai-codex/request-transformer.ts +13 -12
- package/src/providers/openai-codex/response-handler.ts +1 -1
- package/src/providers/openai-codex-responses.ts +931 -1012
- package/src/providers/openai-completions.ts +46 -36
- package/src/providers/openai-responses-server-schema.ts +3 -0
- package/src/providers/openai-responses-server.ts +82 -30
- package/src/providers/openai-responses-wire.ts +1 -1
- package/src/providers/openai-responses.ts +46 -22
- package/src/providers/openai-shared.ts +118 -51
- package/src/providers/pi-native-client.ts +12 -18
- package/src/providers/pi-native-server.ts +9 -6
- package/src/providers/register-builtins.ts +5 -2
- package/src/providers/transform-messages.ts +31 -63
- package/src/registry/alibaba-coding-plan.ts +6 -5
- package/src/registry/api-key-login.ts +4 -3
- package/src/registry/api-key-validation.ts +4 -3
- package/src/registry/cloudflare-ai-gateway.ts +4 -3
- package/src/registry/coreweave.ts +2 -1
- package/src/registry/deepseek.ts +2 -1
- package/src/registry/google-antigravity.ts +2 -1
- package/src/registry/google-gemini-cli.ts +2 -1
- package/src/registry/kagi.ts +4 -3
- package/src/registry/kilo.ts +32 -10
- package/src/registry/litellm.ts +4 -3
- package/src/registry/llama-cpp.ts +3 -2
- package/src/registry/lm-studio.ts +3 -2
- package/src/registry/nvidia.ts +7 -7
- package/src/registry/oauth/anthropic.ts +23 -6
- package/src/registry/oauth/callback-server.ts +5 -3
- package/src/registry/oauth/cursor.ts +18 -4
- package/src/registry/oauth/devin.ts +14 -3
- package/src/registry/oauth/github-copilot.ts +21 -10
- package/src/registry/oauth/gitlab-duo-workflow.ts +15 -3
- package/src/registry/oauth/gitlab-duo.ts +30 -6
- package/src/registry/oauth/google-antigravity.ts +14 -5
- package/src/registry/oauth/google-gemini-cli.ts +24 -7
- package/src/registry/oauth/google-oauth-shared.ts +6 -3
- package/src/registry/oauth/index.ts +19 -8
- package/src/registry/oauth/kimi.ts +40 -10
- package/src/registry/oauth/openai-codex.ts +26 -11
- package/src/registry/oauth/opencode.ts +4 -3
- package/src/registry/oauth/perplexity.ts +33 -11
- package/src/registry/oauth/xai-oauth.ts +62 -18
- package/src/registry/oauth/xiaomi.ts +21 -9
- package/src/registry/ollama-cloud.ts +5 -4
- package/src/registry/ollama.ts +3 -2
- package/src/registry/parallel.ts +4 -3
- package/src/registry/qwen-portal.ts +4 -3
- package/src/registry/tavily.ts +4 -3
- package/src/registry/vercel-ai-gateway.ts +4 -3
- package/src/registry/vllm.ts +3 -2
- package/src/stream.ts +79 -24
- package/src/types.ts +6 -0
- package/src/usage/claude.ts +2 -1
- package/src/usage/github-copilot.ts +4 -3
- package/src/usage/google-antigravity.ts +2 -1
- package/src/utils/abort.ts +4 -2
- package/src/utils/block-symbols.ts +32 -0
- package/src/utils/event-stream.ts +15 -3
- package/src/utils/http-inspector.ts +4 -4
- package/src/utils/idle-iterator.ts +6 -5
- package/src/utils/openai-http.ts +11 -46
- package/src/utils/parse-bind.ts +7 -5
- package/src/utils/proxy.ts +2 -1
- package/src/utils/retry.ts +7 -23
- package/src/utils/schema/normalize.ts +3 -2
- package/src/utils/thinking-loop.ts +15 -11
- package/src/utils/validation.ts +4 -3
- package/dist/types/dialect/pi.d.ts +0 -9
- package/dist/types/errors.d.ts +0 -24
- package/dist/types/utils/overflow.d.ts +0 -55
- package/src/dialect/pi.md +0 -55
- package/src/dialect/pi.ts +0 -600
- package/src/errors.ts +0 -32
- package/src/utils/overflow.ts +0 -140
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
2
2
|
import { type } from "arktype";
|
|
3
3
|
import { captureRequestHeaders, resolvePromptCacheKey } from "../auth-gateway/http";
|
|
4
|
+
import * as AIError from "../error";
|
|
4
5
|
import type {
|
|
5
6
|
AssistantMessage,
|
|
6
7
|
AssistantMessageEventStream,
|
|
@@ -293,7 +294,7 @@ function deriveCacheRetention(data: {
|
|
|
293
294
|
export function parseRequest(body: unknown, headers?: Headers): ParsedRequest {
|
|
294
295
|
const data = anthropicMessagesRequestSchema(body);
|
|
295
296
|
if (data instanceof type.errors) {
|
|
296
|
-
throw new
|
|
297
|
+
throw new AIError.ValidationError(`anthropic-messages: ${data.summary}`);
|
|
297
298
|
}
|
|
298
299
|
|
|
299
300
|
const now = Date.now();
|
|
@@ -457,7 +458,13 @@ function encodeUsage(message: AssistantMessage): Record<string, unknown> {
|
|
|
457
458
|
|
|
458
459
|
export function encodeResponse(message: AssistantMessage, requestedModelId: string): Record<string, unknown> {
|
|
459
460
|
if (message.stopReason === "error" || message.stopReason === "aborted") {
|
|
460
|
-
throw new
|
|
461
|
+
throw new AIError.ProviderResponseError(
|
|
462
|
+
message.errorMessage ?? `anthropic-messages: upstream ${message.stopReason}`,
|
|
463
|
+
{
|
|
464
|
+
provider: "anthropic",
|
|
465
|
+
kind: "output",
|
|
466
|
+
},
|
|
467
|
+
);
|
|
461
468
|
}
|
|
462
469
|
return {
|
|
463
470
|
id: message.responseId ?? newMessageId(),
|
|
@@ -9,17 +9,15 @@ import { isAnthropicOAuthToken } from "@oh-my-pi/pi-catalog/utils";
|
|
|
9
9
|
import { parseGitHubCopilotApiKey } from "@oh-my-pi/pi-catalog/wire/github-copilot";
|
|
10
10
|
import {
|
|
11
11
|
$env,
|
|
12
|
-
extractHttpStatusFromError,
|
|
13
12
|
getInstallId,
|
|
14
13
|
isEnoent,
|
|
15
|
-
isRetryableError,
|
|
16
|
-
isUnexpectedSocketCloseMessage,
|
|
17
14
|
logger,
|
|
18
15
|
parseJsonWithRepair,
|
|
19
16
|
parseStreamingJsonThrottled,
|
|
20
17
|
readSseEvents,
|
|
21
18
|
} from "@oh-my-pi/pi-utils";
|
|
22
|
-
import {
|
|
19
|
+
import { renderDemotedThinking } from "../dialect/demotion";
|
|
20
|
+
import * as AIError from "../error";
|
|
23
21
|
import { getEnvApiKey, OUTPUT_FALLBACK_BUFFER } from "../stream";
|
|
24
22
|
import type {
|
|
25
23
|
Api,
|
|
@@ -48,13 +46,18 @@ import type {
|
|
|
48
46
|
import { resolveServiceTier } from "../types";
|
|
49
47
|
import { isRecord, normalizeSystemPrompts, normalizeToolCallId, resolveCacheRetention } from "../utils";
|
|
50
48
|
import { createAbortSourceTracker } from "../utils/abort";
|
|
49
|
+
import {
|
|
50
|
+
clearStreamingPartialJson,
|
|
51
|
+
kStreamingBlockIndex,
|
|
52
|
+
kStreamingLastParseLen,
|
|
53
|
+
kStreamingPartialJson,
|
|
54
|
+
} from "../utils/block-symbols";
|
|
51
55
|
import { withEmptyCompletionRetry } from "../utils/empty-completion-retry";
|
|
52
56
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
53
57
|
import { isFoundryEnabled } from "../utils/foundry";
|
|
54
|
-
import { finalizeErrorMessage, type RawHttpRequestDump
|
|
58
|
+
import { finalizeErrorMessage, type RawHttpRequestDump } from "../utils/http-inspector";
|
|
55
59
|
import { getStreamFirstEventTimeoutMs, getStreamIdleTimeoutMs, iterateWithIdleTimeout } from "../utils/idle-iterator";
|
|
56
60
|
import { notifyProviderResponse } from "../utils/provider-response";
|
|
57
|
-
import { isCopilotTransientModelError } from "../utils/retry";
|
|
58
61
|
import { COMBINATOR_KEYS, NO_STRICT, toolWireSchema } from "../utils/schema";
|
|
59
62
|
import { spillToDescription } from "../utils/schema/spill";
|
|
60
63
|
import { createSdkStreamRequestOptions } from "../utils/sdk-stream-timeout";
|
|
@@ -384,38 +387,6 @@ export function clearAnthropicFastModeFallback(
|
|
|
384
387
|
}
|
|
385
388
|
}
|
|
386
389
|
|
|
387
|
-
function isAnthropicStrictGrammarTooLargeError(error: unknown): boolean {
|
|
388
|
-
if (extractHttpStatusFromError(error) !== 400) return false;
|
|
389
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
390
|
-
const isStrictGrammarTooLarge = /compiled grammar/i.test(message) && /too large/i.test(message);
|
|
391
|
-
const isSchemaCompilationTooComplex =
|
|
392
|
-
/schema/i.test(message) && /too complex/i.test(message) && /compil/i.test(message);
|
|
393
|
-
return /invalid_request_error/i.test(message) && (isStrictGrammarTooLarge || isSchemaCompilationTooComplex);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
export function isAnthropicFastModeUnsupportedError(error: unknown): boolean {
|
|
397
|
-
const status = extractHttpStatusFromError(error);
|
|
398
|
-
if (status !== 400 && status !== 429) return false;
|
|
399
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
400
|
-
// 400 invalid_request_error — model doesn't accept `speed` at all.
|
|
401
|
-
// Observed: "'claude-opus-4-5-20251101' does not support the `speed` parameter."
|
|
402
|
-
// Stay tolerant of phrasing drift ("is not supported", quoted vs backticked field).
|
|
403
|
-
if (
|
|
404
|
-
status === 400 &&
|
|
405
|
-
/invalid_request_error/i.test(message) &&
|
|
406
|
-
/\bspeed\b/i.test(message) &&
|
|
407
|
-
/not support/i.test(message)
|
|
408
|
-
) {
|
|
409
|
-
return true;
|
|
410
|
-
}
|
|
411
|
-
// 429 rate_limit_error — account lacks the extra-usage entitlement fast mode requires.
|
|
412
|
-
// Observed: "Extra usage is required for fast mode."
|
|
413
|
-
if (status === 429 && /rate_limit_error/i.test(message) && /fast mode/i.test(message)) {
|
|
414
|
-
return true;
|
|
415
|
-
}
|
|
416
|
-
return false;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
390
|
function hasStrictAnthropicTools(params: MessageCreateParamsStreaming): boolean {
|
|
420
391
|
return params.tools?.some(tool => tool.strict === true) ?? false;
|
|
421
392
|
}
|
|
@@ -1228,7 +1199,7 @@ function resolvePemValue(value: string | undefined, name: string): string | unde
|
|
|
1228
1199
|
return fs.readFileSync(trimmed, "utf8");
|
|
1229
1200
|
} catch (error) {
|
|
1230
1201
|
if (isEnoent(error)) {
|
|
1231
|
-
throw new
|
|
1202
|
+
throw new AIError.ValidationError(`${name} path does not exist: ${trimmed}`);
|
|
1232
1203
|
}
|
|
1233
1204
|
throw error;
|
|
1234
1205
|
}
|
|
@@ -1249,7 +1220,9 @@ function resolveFoundryTlsOptions(model: Model<"anthropic-messages">): FoundryTl
|
|
|
1249
1220
|
const key = resolvePemValue($env.CLAUDE_CODE_CLIENT_KEY, "CLAUDE_CODE_CLIENT_KEY");
|
|
1250
1221
|
|
|
1251
1222
|
if ((cert && !key) || (!cert && key)) {
|
|
1252
|
-
throw new
|
|
1223
|
+
throw new AIError.ConfigurationError(
|
|
1224
|
+
"Both CLAUDE_CODE_CLIENT_CERT and CLAUDE_CODE_CLIENT_KEY must be set for mTLS.",
|
|
1225
|
+
);
|
|
1253
1226
|
}
|
|
1254
1227
|
|
|
1255
1228
|
const options: FoundryTlsOptions = {};
|
|
@@ -1339,14 +1312,15 @@ function createAnthropicSseStreamError(data: string): Error {
|
|
|
1339
1312
|
const errorType = typeof parsed?.error?.type === "string" ? parsed.error.type : undefined;
|
|
1340
1313
|
const message = typeof parsed?.error?.message === "string" ? parsed.error.message : undefined;
|
|
1341
1314
|
if (message) {
|
|
1342
|
-
return new
|
|
1315
|
+
return new AIError.ProviderResponseError(
|
|
1343
1316
|
errorType ? `Anthropic stream error (${errorType}): ${message}` : `Anthropic stream error: ${message}`,
|
|
1317
|
+
{ provider: "anthropic", kind: "output" },
|
|
1344
1318
|
);
|
|
1345
1319
|
}
|
|
1346
1320
|
} catch {
|
|
1347
1321
|
// Not a JSON envelope; fall through to the raw payload.
|
|
1348
1322
|
}
|
|
1349
|
-
return new
|
|
1323
|
+
return new AIError.ProviderResponseError(data, { provider: "anthropic", kind: "output" });
|
|
1350
1324
|
}
|
|
1351
1325
|
|
|
1352
1326
|
async function* iterateAnthropicEvents(
|
|
@@ -1355,7 +1329,7 @@ async function* iterateAnthropicEvents(
|
|
|
1355
1329
|
onSseEvent?: AnthropicOptions["onSseEvent"],
|
|
1356
1330
|
): AsyncGenerator<AnthropicStreamEvent> {
|
|
1357
1331
|
if (!response.body) {
|
|
1358
|
-
throw new
|
|
1332
|
+
throw new AIError.AnthropicStreamEnvelopeError("Attempted to iterate over an Anthropic response with no body");
|
|
1359
1333
|
}
|
|
1360
1334
|
|
|
1361
1335
|
let sawMessageStart = false;
|
|
@@ -1444,7 +1418,7 @@ async function getAnthropicStreamResponse(
|
|
|
1444
1418
|
const { data, response, request_id } = await request.withResponse();
|
|
1445
1419
|
return { events: data, response, requestId: request_id, recordsRawSseEvents: false };
|
|
1446
1420
|
}
|
|
1447
|
-
throw new
|
|
1421
|
+
throw new AIError.AnthropicStreamEnvelopeError("Anthropic SDK request did not expose a stream response");
|
|
1448
1422
|
}
|
|
1449
1423
|
|
|
1450
1424
|
async function* observeDecodedAnthropicSdkEvents(
|
|
@@ -1461,23 +1435,9 @@ async function* observeDecodedAnthropicSdkEvents(
|
|
|
1461
1435
|
|
|
1462
1436
|
const PROVIDER_MAX_RETRIES = 10;
|
|
1463
1437
|
|
|
1464
|
-
/** Transient stream corruption errors where the response was truncated mid-JSON. */
|
|
1465
|
-
function isTransientStreamParseError(error: unknown): boolean {
|
|
1466
|
-
if (!(error instanceof Error)) return false;
|
|
1467
|
-
return /unterminated string|unexpected end of json input|unexpected end of data|unexpected eof|end of file|eof while parsing|truncated/i.test(
|
|
1468
|
-
error.message,
|
|
1469
|
-
);
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
const ANTHROPIC_STREAM_ENVELOPE_ERROR_PREFIX = "Anthropic stream envelope error:";
|
|
1473
|
-
|
|
1474
|
-
function createAnthropicStreamEnvelopeError(message: string): Error {
|
|
1475
|
-
return new Error(`${ANTHROPIC_STREAM_ENVELOPE_ERROR_PREFIX} ${message}`);
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
1438
|
/**
|
|
1479
1439
|
* Log a malformed-stream-envelope anomaly without aborting the turn. The strict
|
|
1480
|
-
* parser would `throw
|
|
1440
|
+
* parser would `throw new AnthropicStreamEnvelopeError(...)` here; we instead
|
|
1481
1441
|
* surface a warning and let the caller skip the offending event (or finalize what
|
|
1482
1442
|
* already streamed) so a non-conforming endpoint degrades to best-effort content
|
|
1483
1443
|
* rather than failing the request.
|
|
@@ -1492,49 +1452,18 @@ function shouldIgnoreAnthropicPreambleEvent(eventType: unknown): boolean {
|
|
|
1492
1452
|
return !ANTHROPIC_MESSAGE_EVENTS.has(eventType);
|
|
1493
1453
|
}
|
|
1494
1454
|
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
function isProviderRetryableStreamEnvelopeError(error: unknown): boolean {
|
|
1504
|
-
if (!(error instanceof Error)) return false;
|
|
1505
|
-
return /stream event order|before message_start/i.test(error.message);
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
|
-
function isAnthropicTransientTransportMessage(message: string): boolean {
|
|
1509
|
-
return message.includes("tls: bad record mac") || message.includes("type=server_error");
|
|
1510
|
-
}
|
|
1511
|
-
|
|
1455
|
+
/**
|
|
1456
|
+
* Whether an Anthropic (or Copilot-over-Anthropic) stream error should be
|
|
1457
|
+
* retried. The classification lives in {@link AIError.isProviderRetryableError};
|
|
1458
|
+
* this wrapper injects the Copilot-specific `model_not_supported` transient
|
|
1459
|
+
* check, which the error module must not import directly.
|
|
1460
|
+
*/
|
|
1512
1461
|
export function isProviderRetryableError(error: unknown, provider?: string): boolean {
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
// Retrying the same key with the provider's seconds-scale backoff never
|
|
1519
|
-
// helps; these are owned by the credential-rotation layer (auth-gateway /
|
|
1520
|
-
// `streamSimple` a/b/c policy), so surface them immediately instead of
|
|
1521
|
-
// burning the retry budget here.
|
|
1522
|
-
if (isUsageLimitError(error.message)) return false;
|
|
1523
|
-
const status = extractHttpStatusFromError(error);
|
|
1524
|
-
if (status !== undefined && status >= 400 && status < 500 && status !== 408 && status !== 429) return false;
|
|
1525
|
-
const msg = error.message.toLowerCase();
|
|
1526
|
-
if (
|
|
1527
|
-
isUnexpectedSocketCloseMessage(msg) ||
|
|
1528
|
-
isAnthropicTransientTransportMessage(msg) ||
|
|
1529
|
-
/rate.?limit|too many requests|overloaded|service.?unavailable|internal_error|server_error|bad record mac|stream error.*received from peer|1302|timed?\s*out while waiting for the first event|timeout waiting for first/i.test(
|
|
1530
|
-
msg,
|
|
1531
|
-
) ||
|
|
1532
|
-
isTransientStreamParseError(error) ||
|
|
1533
|
-
isProviderRetryableStreamEnvelopeError(error)
|
|
1534
|
-
) {
|
|
1535
|
-
return true;
|
|
1536
|
-
}
|
|
1537
|
-
return isRetryableError(error);
|
|
1462
|
+
return AIError.isProviderRetryableError(error, {
|
|
1463
|
+
provider,
|
|
1464
|
+
isProviderTransient:
|
|
1465
|
+
provider === "github-copilot" ? (err): boolean => AIError.isCopilotTransientModelError(err) : undefined,
|
|
1466
|
+
});
|
|
1538
1467
|
}
|
|
1539
1468
|
|
|
1540
1469
|
const THINKING_ENVELOPE_OPEN = "<thinking>";
|
|
@@ -1609,7 +1538,7 @@ const streamAnthropicOnce = (
|
|
|
1609
1538
|
const stream = new AssistantMessageEventStream();
|
|
1610
1539
|
|
|
1611
1540
|
(async () => {
|
|
1612
|
-
const startTime =
|
|
1541
|
+
const startTime = performance.now();
|
|
1613
1542
|
let firstTokenTime: number | undefined;
|
|
1614
1543
|
|
|
1615
1544
|
const output: AssistantMessage = {
|
|
@@ -1772,15 +1701,14 @@ const streamAnthropicOnce = (
|
|
|
1772
1701
|
| ThinkingContent
|
|
1773
1702
|
| RedactedThinkingContent
|
|
1774
1703
|
| TextContent
|
|
1775
|
-
| (ToolCall & {
|
|
1776
|
-
) & {
|
|
1704
|
+
| (ToolCall & { [kStreamingPartialJson]: string; [kStreamingLastParseLen]?: number })
|
|
1705
|
+
) & { [kStreamingBlockIndex]: number };
|
|
1777
1706
|
const idleTimeoutMs = options?.streamIdleTimeoutMs ?? getStreamIdleTimeoutMs();
|
|
1778
1707
|
const firstEventTimeoutMs = options?.streamFirstEventTimeoutMs ?? getStreamFirstEventTimeoutMs(idleTimeoutMs);
|
|
1779
1708
|
const requestTimeoutMs =
|
|
1780
1709
|
firstEventTimeoutMs !== undefined && firstEventTimeoutMs > 0 ? firstEventTimeoutMs : undefined;
|
|
1781
1710
|
const blocks = output.content as Block[];
|
|
1782
1711
|
const finalizeStreamBlock = (block: Block, contentIndex: number): void => {
|
|
1783
|
-
delete (block as { index?: number }).index;
|
|
1784
1712
|
if (block.type === "text") {
|
|
1785
1713
|
stream.push({ type: "text_end", contentIndex, content: block.text, partial: output });
|
|
1786
1714
|
} else if (block.type === "thinking") {
|
|
@@ -1792,7 +1720,9 @@ const streamAnthropicOnce = (
|
|
|
1792
1720
|
stream.push({ type: "thinking_end", contentIndex, content: block.thinking, partial: output });
|
|
1793
1721
|
} else if (block.type === "toolCall") {
|
|
1794
1722
|
const finalJson =
|
|
1795
|
-
block.
|
|
1723
|
+
block[kStreamingPartialJson].length > 0
|
|
1724
|
+
? block[kStreamingPartialJson]
|
|
1725
|
+
: JSON.stringify(block.arguments ?? {});
|
|
1796
1726
|
try {
|
|
1797
1727
|
block.arguments = parseJsonWithRepair(finalJson) as ToolCall["arguments"];
|
|
1798
1728
|
} catch (parseError) {
|
|
@@ -1814,8 +1744,7 @@ const streamAnthropicOnce = (
|
|
|
1814
1744
|
};
|
|
1815
1745
|
}
|
|
1816
1746
|
}
|
|
1817
|
-
|
|
1818
|
-
delete (block as { lastParseLen?: number }).lastParseLen;
|
|
1747
|
+
clearStreamingPartialJson(block);
|
|
1819
1748
|
stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial: output });
|
|
1820
1749
|
}
|
|
1821
1750
|
};
|
|
@@ -1824,8 +1753,12 @@ const streamAnthropicOnce = (
|
|
|
1824
1753
|
// Provider-level transport/rate-limit failures: only before any streamed content starts.
|
|
1825
1754
|
// Malformed envelopes/JSON: only before replay-unsafe text/tool events are visible on this stream.
|
|
1826
1755
|
let providerRetryAttempt = 0;
|
|
1827
|
-
const firstEventTimeoutAbortError = new
|
|
1828
|
-
|
|
1756
|
+
const firstEventTimeoutAbortError = new AIError.StreamTimeoutError(
|
|
1757
|
+
"Anthropic stream timed out while waiting for the first event",
|
|
1758
|
+
);
|
|
1759
|
+
const idleTimeoutAbortError = new AIError.StreamTimeoutError(
|
|
1760
|
+
"Anthropic stream stalled while waiting for the next event",
|
|
1761
|
+
);
|
|
1829
1762
|
while (true) {
|
|
1830
1763
|
activeAbortTracker = createAbortSourceTracker(options?.signal);
|
|
1831
1764
|
const { requestSignal } = activeAbortTracker;
|
|
@@ -1945,7 +1878,7 @@ const streamAnthropicOnce = (
|
|
|
1945
1878
|
if (shouldIgnoreAnthropicPreambleEvent(event.type)) {
|
|
1946
1879
|
continue;
|
|
1947
1880
|
}
|
|
1948
|
-
throw
|
|
1881
|
+
throw new AIError.AnthropicStreamEnvelopeError(`received ${event.type} before message_start`);
|
|
1949
1882
|
}
|
|
1950
1883
|
|
|
1951
1884
|
if (event.type === "content_block_start") {
|
|
@@ -1971,13 +1904,13 @@ const streamAnthropicOnce = (
|
|
|
1971
1904
|
reportAnthropicEnvelopeAnomaly("content_block_start missing content_block payload");
|
|
1972
1905
|
continue;
|
|
1973
1906
|
}
|
|
1974
|
-
if (!firstTokenTime) firstTokenTime =
|
|
1907
|
+
if (!firstTokenTime) firstTokenTime = performance.now();
|
|
1975
1908
|
if (event.content_block.type === "text") {
|
|
1976
1909
|
streamedReplayUnsafeContent = true;
|
|
1977
1910
|
const block: Block = {
|
|
1978
1911
|
type: "text",
|
|
1979
1912
|
text: "",
|
|
1980
|
-
|
|
1913
|
+
[kStreamingBlockIndex]: event.index,
|
|
1981
1914
|
};
|
|
1982
1915
|
output.content.push(block);
|
|
1983
1916
|
const contentIndex = output.content.length - 1;
|
|
@@ -1993,7 +1926,7 @@ const streamAnthropicOnce = (
|
|
|
1993
1926
|
type: "thinking",
|
|
1994
1927
|
thinking: "",
|
|
1995
1928
|
thinkingSignature: "",
|
|
1996
|
-
|
|
1929
|
+
[kStreamingBlockIndex]: event.index,
|
|
1997
1930
|
};
|
|
1998
1931
|
output.content.push(block);
|
|
1999
1932
|
const contentIndex = output.content.length - 1;
|
|
@@ -2008,7 +1941,7 @@ const streamAnthropicOnce = (
|
|
|
2008
1941
|
const block: Block = {
|
|
2009
1942
|
type: "redactedThinking",
|
|
2010
1943
|
data: event.content_block.data,
|
|
2011
|
-
|
|
1944
|
+
[kStreamingBlockIndex]: event.index,
|
|
2012
1945
|
};
|
|
2013
1946
|
output.content.push(block);
|
|
2014
1947
|
openBlocks.set(event.index, {
|
|
@@ -2026,8 +1959,8 @@ const streamAnthropicOnce = (
|
|
|
2026
1959
|
model.compat.escapeBuiltinToolNames,
|
|
2027
1960
|
),
|
|
2028
1961
|
arguments: event.content_block.input ?? {},
|
|
2029
|
-
|
|
2030
|
-
|
|
1962
|
+
[kStreamingPartialJson]: "",
|
|
1963
|
+
[kStreamingBlockIndex]: event.index,
|
|
2031
1964
|
};
|
|
2032
1965
|
output.content.push(block);
|
|
2033
1966
|
const contentIndex = output.content.length - 1;
|
|
@@ -2090,11 +2023,14 @@ const streamAnthropicOnce = (
|
|
|
2090
2023
|
continue;
|
|
2091
2024
|
}
|
|
2092
2025
|
streamedReplayUnsafeContent = true;
|
|
2093
|
-
block
|
|
2094
|
-
const throttled = parseStreamingJsonThrottled(
|
|
2026
|
+
block[kStreamingPartialJson] += event.delta.partial_json;
|
|
2027
|
+
const throttled = parseStreamingJsonThrottled(
|
|
2028
|
+
block[kStreamingPartialJson],
|
|
2029
|
+
block[kStreamingLastParseLen] ?? 0,
|
|
2030
|
+
);
|
|
2095
2031
|
if (throttled) {
|
|
2096
2032
|
block.arguments = throttled.value;
|
|
2097
|
-
block
|
|
2033
|
+
block[kStreamingLastParseLen] = throttled.parsedLen;
|
|
2098
2034
|
}
|
|
2099
2035
|
stream.push({
|
|
2100
2036
|
type: "toolcall_delta",
|
|
@@ -2197,10 +2133,10 @@ const streamAnthropicOnce = (
|
|
|
2197
2133
|
throw firstEventTimeoutError;
|
|
2198
2134
|
}
|
|
2199
2135
|
if (activeAbortTracker.wasCallerAbort()) {
|
|
2200
|
-
throw new
|
|
2136
|
+
throw new AIError.AbortError();
|
|
2201
2137
|
}
|
|
2202
2138
|
if (!sawEvent || !sawMessageStart) {
|
|
2203
|
-
throw
|
|
2139
|
+
throw new AIError.AnthropicStreamEnvelopeError("stream ended before message_start");
|
|
2204
2140
|
}
|
|
2205
2141
|
if (!sawMessageStop) {
|
|
2206
2142
|
reportAnthropicEnvelopeAnomaly("stream ended before message_stop");
|
|
@@ -2218,7 +2154,10 @@ const streamAnthropicOnce = (
|
|
|
2218
2154
|
}
|
|
2219
2155
|
|
|
2220
2156
|
if (output.stopReason === "aborted" || output.stopReason === "error") {
|
|
2221
|
-
throw new
|
|
2157
|
+
throw new AIError.ProviderResponseError(output.errorMessage ?? "An unknown error occurred", {
|
|
2158
|
+
provider: model.provider,
|
|
2159
|
+
kind: "output",
|
|
2160
|
+
});
|
|
2222
2161
|
}
|
|
2223
2162
|
break;
|
|
2224
2163
|
} catch (streamError) {
|
|
@@ -2227,7 +2166,7 @@ const streamAnthropicOnce = (
|
|
|
2227
2166
|
!disableStrictTools &&
|
|
2228
2167
|
firstTokenTime === undefined &&
|
|
2229
2168
|
hasStrictAnthropicTools(params) &&
|
|
2230
|
-
|
|
2169
|
+
AIError.isGrammarError(streamFailure)
|
|
2231
2170
|
) {
|
|
2232
2171
|
// Log-only: the retried turn must not carry an errorMessage on
|
|
2233
2172
|
// success (consumers treat its presence as failure).
|
|
@@ -2254,7 +2193,7 @@ const streamAnthropicOnce = (
|
|
|
2254
2193
|
!dropFastMode &&
|
|
2255
2194
|
resolveServiceTier(options?.serviceTier, model.provider) === "priority" &&
|
|
2256
2195
|
firstTokenTime === undefined &&
|
|
2257
|
-
|
|
2196
|
+
AIError.isFastModeUnsupported(streamFailure)
|
|
2258
2197
|
) {
|
|
2259
2198
|
logger.debug("anthropic: fast mode unsupported, retrying without speed", {
|
|
2260
2199
|
model: model.id,
|
|
@@ -2276,7 +2215,7 @@ const streamAnthropicOnce = (
|
|
|
2276
2215
|
continue;
|
|
2277
2216
|
}
|
|
2278
2217
|
const isTransientEnvelopeFailure =
|
|
2279
|
-
isTransientStreamParseError(streamFailure) ||
|
|
2218
|
+
AIError.isTransientStreamParseError(streamFailure) || AIError.isStreamEnvelopeError(streamFailure);
|
|
2280
2219
|
const isLocalIdleTimeout =
|
|
2281
2220
|
streamFailure === idleTimeoutAbortError ||
|
|
2282
2221
|
(streamFailure instanceof Error && streamFailure.message === idleTimeoutAbortError.message);
|
|
@@ -2299,7 +2238,9 @@ const streamAnthropicOnce = (
|
|
|
2299
2238
|
// 429/529-style failures: retrying sooner than the server asked is a
|
|
2300
2239
|
// guaranteed failure that just burns the retry budget.
|
|
2301
2240
|
const headerDelayMs =
|
|
2302
|
-
streamFailure instanceof
|
|
2241
|
+
streamFailure instanceof Error && streamFailure instanceof AnthropicApiError
|
|
2242
|
+
? retryDelayFromHeaders(streamFailure.headers)
|
|
2243
|
+
: undefined;
|
|
2303
2244
|
const delayMs = headerDelayMs !== undefined ? Math.max(headerDelayMs, backoffDelayMs) : backoffDelayMs;
|
|
2304
2245
|
if (options?.providerRetryWait) {
|
|
2305
2246
|
await options.providerRetryWait(delayMs, options.signal);
|
|
@@ -2316,7 +2257,7 @@ const streamAnthropicOnce = (
|
|
|
2316
2257
|
firstTokenTime = undefined;
|
|
2317
2258
|
}
|
|
2318
2259
|
}
|
|
2319
|
-
output.duration =
|
|
2260
|
+
output.duration = performance.now() - startTime;
|
|
2320
2261
|
if (firstTokenTime) output.ttft = firstTokenTime - startTime;
|
|
2321
2262
|
if (dropFastMode && resolveServiceTier(options?.serviceTier, model.provider) === "priority") {
|
|
2322
2263
|
output.disabledFeatures = [...(output.disabledFeatures ?? []), "priority"];
|
|
@@ -2325,23 +2266,19 @@ const streamAnthropicOnce = (
|
|
|
2325
2266
|
stream.end();
|
|
2326
2267
|
} catch (error) {
|
|
2327
2268
|
for (const block of output.content) {
|
|
2328
|
-
|
|
2329
|
-
delete (block as { partialJson?: string }).partialJson;
|
|
2330
|
-
delete (block as { lastParseLen?: number }).lastParseLen;
|
|
2269
|
+
if (block.type === "toolCall") clearStreamingPartialJson(block);
|
|
2331
2270
|
}
|
|
2332
|
-
const
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
}
|
|
2344
|
-
output.duration = Date.now() - startTime;
|
|
2271
|
+
const result = await AIError.finalize(error, {
|
|
2272
|
+
api: model.api,
|
|
2273
|
+
provider: model.provider,
|
|
2274
|
+
abortTracker: activeAbortTracker,
|
|
2275
|
+
rawRequestDump,
|
|
2276
|
+
});
|
|
2277
|
+
output.stopReason = result.stopReason;
|
|
2278
|
+
output.errorStatus = result.status;
|
|
2279
|
+
output.errorId = result.id;
|
|
2280
|
+
output.errorMessage = result.message;
|
|
2281
|
+
output.duration = performance.now() - startTime;
|
|
2345
2282
|
if (firstTokenTime) output.ttft = firstTokenTime - startTime;
|
|
2346
2283
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
2347
2284
|
stream.end();
|
|
@@ -2631,7 +2568,7 @@ function ensureMaxTokensForThinking(params: MessageCreateParamsStreaming, maxAll
|
|
|
2631
2568
|
|
|
2632
2569
|
const clampedBudget = raisedMaxTokens - OUTPUT_FALLBACK_BUFFER;
|
|
2633
2570
|
if (clampedBudget <= 0) {
|
|
2634
|
-
throw new
|
|
2571
|
+
throw new AIError.ConfigurationError(
|
|
2635
2572
|
`Anthropic thinking budget requires max_tokens greater than ${OUTPUT_FALLBACK_BUFFER}; got ${raisedMaxTokens}`,
|
|
2636
2573
|
);
|
|
2637
2574
|
}
|
|
@@ -3238,7 +3175,7 @@ export function convertAnthropicMessages(
|
|
|
3238
3175
|
if (block.thinking.trim().length === 0) continue;
|
|
3239
3176
|
blocks.push({
|
|
3240
3177
|
type: "text",
|
|
3241
|
-
text: block.thinking
|
|
3178
|
+
text: renderDemotedThinking(model.id, block.thinking),
|
|
3242
3179
|
});
|
|
3243
3180
|
continue;
|
|
3244
3181
|
}
|
|
@@ -3260,7 +3197,7 @@ export function convertAnthropicMessages(
|
|
|
3260
3197
|
} else {
|
|
3261
3198
|
blocks.push({
|
|
3262
3199
|
type: "text",
|
|
3263
|
-
text: block.thinking
|
|
3200
|
+
text: renderDemotedThinking(model.id, block.thinking),
|
|
3264
3201
|
});
|
|
3265
3202
|
}
|
|
3266
3203
|
} else {
|
|
@@ -23,6 +23,7 @@ import * as fs from "node:fs";
|
|
|
23
23
|
import * as os from "node:os";
|
|
24
24
|
import * as path from "node:path";
|
|
25
25
|
import { $env, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
26
|
+
import * as AIError from "../error";
|
|
26
27
|
import type { FetchImpl } from "../types";
|
|
27
28
|
import { raceWithSignal } from "../utils/abort";
|
|
28
29
|
import type { AwsCredentials } from "./aws-sigv4";
|
|
@@ -111,9 +112,10 @@ async function resolveFresh(
|
|
|
111
112
|
if (imdsCreds) return imdsCreds;
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
throw new
|
|
115
|
+
throw new AIError.AwsCredentialsError(
|
|
115
116
|
`Unable to resolve AWS credentials. Set AWS_ACCESS_KEY_ID+AWS_SECRET_ACCESS_KEY, ` +
|
|
116
117
|
`or configure profile '${profile}' in ~/.aws/credentials (or ~/.aws/config for SSO).`,
|
|
118
|
+
"resolution",
|
|
117
119
|
);
|
|
118
120
|
}
|
|
119
121
|
|
|
@@ -245,11 +247,17 @@ async function readSsoCredentials(
|
|
|
245
247
|
|
|
246
248
|
const token = await loadSsoCachedToken(startUrl, sessionName);
|
|
247
249
|
if (!token?.accessToken) {
|
|
248
|
-
throw new
|
|
250
|
+
throw new AIError.AwsCredentialsError(
|
|
251
|
+
`AWS SSO token for ${startUrl} not found in ~/.aws/sso/cache. Run 'aws sso login' first.`,
|
|
252
|
+
"sso-token-missing",
|
|
253
|
+
);
|
|
249
254
|
}
|
|
250
255
|
const expiresAt = token.expiresAt ? Date.parse(token.expiresAt) : Number.POSITIVE_INFINITY;
|
|
251
256
|
if (Number.isFinite(expiresAt) && expiresAt <= Date.now()) {
|
|
252
|
-
throw new
|
|
257
|
+
throw new AIError.AwsCredentialsError(
|
|
258
|
+
`AWS SSO token for ${startUrl} has expired. Run 'aws sso login' to refresh.`,
|
|
259
|
+
"sso-token-expired",
|
|
260
|
+
);
|
|
253
261
|
}
|
|
254
262
|
|
|
255
263
|
const url =
|
|
@@ -263,13 +271,20 @@ async function readSsoCredentials(
|
|
|
263
271
|
});
|
|
264
272
|
if (!response.ok) {
|
|
265
273
|
const body = await response.text().catch(() => "");
|
|
266
|
-
throw new
|
|
274
|
+
throw new AIError.AwsCredentialsError(
|
|
275
|
+
`AWS SSO GetRoleCredentials failed: ${response.status} ${body.slice(0, 200)}`,
|
|
276
|
+
"sso-role",
|
|
277
|
+
);
|
|
267
278
|
}
|
|
268
279
|
const json = (await response.json()) as {
|
|
269
280
|
roleCredentials?: { accessKeyId: string; secretAccessKey: string; sessionToken: string; expiration: number };
|
|
270
281
|
};
|
|
271
282
|
const role = json.roleCredentials;
|
|
272
|
-
if (!role)
|
|
283
|
+
if (!role)
|
|
284
|
+
throw new AIError.AwsCredentialsError(
|
|
285
|
+
"AWS SSO GetRoleCredentials: missing roleCredentials in response",
|
|
286
|
+
"sso-role",
|
|
287
|
+
);
|
|
273
288
|
|
|
274
289
|
// region is honored at the caller; we only consume defaultRegion to keep the
|
|
275
290
|
// param wired for symmetry with other resolution paths.
|
|
@@ -359,23 +374,32 @@ async function readCredentialProcess(
|
|
|
359
374
|
]);
|
|
360
375
|
if (exitCode !== 0) {
|
|
361
376
|
const tail = stderr.trim().slice(-512) || stdout.trim().slice(-512) || "(no output)";
|
|
362
|
-
throw new
|
|
377
|
+
throw new AIError.AwsCredentialsError(
|
|
378
|
+
`AWS credential_process for profile '${profile}' exited ${exitCode}: ${tail}`,
|
|
379
|
+
"credential-process",
|
|
380
|
+
);
|
|
363
381
|
}
|
|
364
382
|
|
|
365
383
|
let parsed: CredentialProcessEnvelope;
|
|
366
384
|
try {
|
|
367
385
|
parsed = JSON.parse(stdout) as CredentialProcessEnvelope;
|
|
368
386
|
} catch (err) {
|
|
369
|
-
throw new
|
|
387
|
+
throw new AIError.AwsCredentialsError(
|
|
388
|
+
`AWS credential_process for profile '${profile}' did not emit valid JSON: ${String(err)}`,
|
|
389
|
+
"credential-process",
|
|
390
|
+
{ cause: err },
|
|
391
|
+
);
|
|
370
392
|
}
|
|
371
393
|
if (parsed.Version !== 1) {
|
|
372
|
-
throw new
|
|
394
|
+
throw new AIError.AwsCredentialsError(
|
|
373
395
|
`AWS credential_process for profile '${profile}' returned unsupported Version ${parsed.Version ?? "<missing>"}; expected 1.`,
|
|
396
|
+
"credential-process",
|
|
374
397
|
);
|
|
375
398
|
}
|
|
376
399
|
if (!parsed.AccessKeyId || !parsed.SecretAccessKey) {
|
|
377
|
-
throw new
|
|
400
|
+
throw new AIError.AwsCredentialsError(
|
|
378
401
|
`AWS credential_process for profile '${profile}' returned envelope without AccessKeyId/SecretAccessKey.`,
|
|
402
|
+
"credential-process",
|
|
379
403
|
);
|
|
380
404
|
}
|
|
381
405
|
|
|
@@ -397,7 +421,10 @@ async function readCredentialProcess(
|
|
|
397
421
|
function buildCredentialProcessArgv(profile: string, command: string): string[] {
|
|
398
422
|
const tokens = tokenizeCredentialProcessCommand(command);
|
|
399
423
|
if (tokens.length === 0) {
|
|
400
|
-
throw new
|
|
424
|
+
throw new AIError.AwsCredentialsError(
|
|
425
|
+
`AWS credential_process for profile '${profile}' is empty.`,
|
|
426
|
+
"credential-process",
|
|
427
|
+
);
|
|
401
428
|
}
|
|
402
429
|
if (process.platform === "win32" && isBatchScript(tokens[0])) {
|
|
403
430
|
return ["cmd.exe", "/d", "/s", "/c", command];
|
|
@@ -479,7 +506,10 @@ export function tokenizeCredentialProcessCommand(cmd: string): string[] {
|
|
|
479
506
|
current += ch;
|
|
480
507
|
}
|
|
481
508
|
if (mode !== "normal") {
|
|
482
|
-
throw new
|
|
509
|
+
throw new AIError.AwsCredentialsError(
|
|
510
|
+
"AWS credential_process command has an unterminated quote.",
|
|
511
|
+
"credential-process",
|
|
512
|
+
);
|
|
483
513
|
}
|
|
484
514
|
if (hasToken) tokens.push(current);
|
|
485
515
|
return tokens;
|