Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
66fbdb1
feat(coding-agent): add extension creation eval
christianklotz Jul 25, 2026
73c1696
fix(coding-agent): use focused eval harness
christianklotz Jul 25, 2026
4b8acca
fix(coding-agent): simplify eval normalization
christianklotz Jul 25, 2026
a0ac81c
fix(coding-agent): distinguish eval invariants
christianklotz Jul 25, 2026
cee5ff7
ref: remove openclaw reference from readme
mitsuhiko Jul 26, 2026
3cd3916
chore: approve contributors from issue #7132
github-actions[bot] Jul 27, 2026
5f9d025
chore: approve contributors from issue #7143
github-actions[bot] Jul 27, 2026
f08f58f
fix(coding-agent): run coding-agent tests offline by default (#7031)
tmustier Jul 27, 2026
60f6a80
feat(ai): add GitHub Copilot Claude Opus 5 support (#7158)
jay-aye-see-kay Jul 27, 2026
f9a4986
feat(ai): expose pending stop reason while streaming (#7151)
lucasmeijer Jul 27, 2026
61da9e2
feat(ai): add manual redirect URL fallback to OpenRouter OAuth login …
rgarcia Jul 27, 2026
c2275d6
fix(coding-agent): prevent duplicate messages on startup session swit…
yzhg1983 Jul 27, 2026
2efa728
fix(coding-agent): support concurrent user bash cancellation (#7103)
yzhg1983 Jul 27, 2026
8a97632
fix(coding-agent): keep eval scenarios in test cases
christianklotz Jul 27, 2026
9423204
fix(coding-agent): clarify eval harness errors
christianklotz Jul 27, 2026
95d1c60
fix(coding-agent): simplify multi-step eval input
christianklotz Jul 27, 2026
af19a9e
fix(coding-agent): simplify extension eval prompt
christianklotz Jul 27, 2026
99e3401
feat: auth print (#7168)
cristinaponcela Jul 27, 2026
b56a35f
fix(coding-agent): project typed eval outputs
christianklotz Jul 27, 2026
b0864a6
fix(coding-agent): preserve eval run diagnostics
christianklotz Jul 27, 2026
a597371
Merge pull request #7117 from earendil-works/feat/coding-agent-evals
christianklotz Jul 27, 2026
e8f9c07
docs(agent): durable AgentHarness design (harness.md)
badlogic Jul 27, 2026
c820aa2
Merge branch 'design/durable-agent-harness'
badlogic Jul 27, 2026
2903063
chore: approve contributors from issue #7171
github-actions[bot] Jul 28, 2026
04b1525
feat(extensions): expose ctx.scopedModels to extensions (#7191)
pungggi Jul 28, 2026
b63403a
fix(ai): prefer configured Bedrock profile over ambient AWS keys (#7176)
christianbasch Jul 28, 2026
063fb96
fix(coding-agent): isolate autoload-disabled package test from real h…
tmustier Jul 28, 2026
b6fb91e
fix(coding-agent): include scoped models in TUI extension context (#7…
cristinaponcela Jul 28, 2026
66eead6
fix(coding-agent): preserve resource metadata after extension resourc…
davidbrai Jul 28, 2026
5d548ae
fix: rpc bash no longer bypass user_bash (#7214)
cristinaponcela Jul 28, 2026
f1ea6c0
fix(coding-agent): reset model selector selection to top row when fil…
christianbasch Jul 28, 2026
0563a7c
fix(coding-agent): clean up failed git installs (#7210)
haoqixu Jul 28, 2026
2fe21b4
fix(ai): send max_tokens for Z.AI providers (#7174)
HyeokjaeLee Jul 28, 2026
cefa40e
***WIP it's a PoC *** fix(coding-agent): guard tree navigation during…
tmustier Jul 28, 2026
f145195
fix(coding-agent): build-check-test (#7206)
haoqixu Jul 28, 2026
0d008b7
fix(coding-agent): show tool expansion status
davidbrai Jul 28, 2026
47ca25f
Revert "fix(coding-agent): build-check-test (#7206)"
davidbrai Jul 28, 2026
fdbedca
docs(agent): add refs to harness design
badlogic Jul 28, 2026
027a584
feat(ai): add per-request fetch injection
mitsuhiko Jul 28, 2026
0c32e83
fix(coding-agent): enable streaming usage for llama.cpp provider (#7258)
SteveImmanuel Jul 29, 2026
e9e86e1
chore: approve contributors from issue #7224
github-actions[bot] Jul 29, 2026
a5db0e4
chore: approve contributors from issue #7219
github-actions[bot] Jul 29, 2026
7796481
chore: approve contributors from issue #7217
github-actions[bot] Jul 29, 2026
fb4ecd6
fix(tui): shorten image fallback paths and clamp width (#7262)
badlogic Jul 29, 2026
f9476a6
fix(ai): update TypeBox nullable array validation (#7243)
petrroll Jul 29, 2026
cced6a2
fix(coding-agent): stop loading AGENTS.md twice in nested git worktre…
arajkumar Jul 29, 2026
23cb385
fix(ai): preserve google providers raw stop reason
davidbrai Jul 29, 2026
926eb15
fix(ai): preserve raw Anthropic stop reasons
davidbrai Jul 29, 2026
637737c
fix(ai): preserve raw Bedrock stop reasons
davidbrai Jul 29, 2026
fe1c9b6
fix(ai): preserve raw OpenAI completion stop reasons
davidbrai Jul 29, 2026
e5ef8d0
fix(ai): preserve raw OpenAI response statuses
davidbrai Jul 29, 2026
5a53f08
fix(ai): preserve raw Mistral stop reasons
davidbrai Jul 29, 2026
4f0437e
docs(agent): harness design v2 (harness-v2.md)
badlogic Jul 29, 2026
4c1a0b9
fix(ai): use Qwen thinking controls for token plan reasoning models
davidbrai Jul 29, 2026
bff5ab7
fix(coding-agent): show system prompt files in startup context (#7266)
davidbrai Jul 29, 2026
5a2539a
update error messages copy to be more generic
davidbrai Jul 29, 2026
d7b0263
Merge pull request #7272 from earendil-works/preserve-original-stop-r…
davidbrai Jul 29, 2026
21f579a
chore: approve contributors from issue #7160
github-actions[bot] Jul 29, 2026
3423918
fix(ai): preserve function arguments with empty custom payloads
mitsuhiko Jul 29, 2026
f0499a6
docs: audit changelogs since v0.82.1
mitsuhiko Jul 29, 2026
44b26c9
fix(tui): format image fallback changes
mitsuhiko Jul 29, 2026
845d6ff
Release v0.83.0
mitsuhiko Jul 29, 2026
71efc6f
Add [Unreleased] section for next cycle
mitsuhiko Jul 29, 2026
405f08f
Merge upstream/main v0.83.0
actions-user Jul 30, 2026
a8bd8fb
sync: record upstream pin 71efc6f
actions-user Jul 30, 2026
85e3725
docs(changelog): audit upstream 71efc6f
actions-user Jul 30, 2026
0ff737c
fix: repair upstream merge build
actions-user Jul 30, 2026
9e20a5f
fix: satisfy upstream merge check
actions-user Jul 30, 2026
4e9e561
fix: align upstream merge tests
actions-user Jul 30, 2026
1b755b8
fix: apply merge check formatting
actions-user Jul 30, 2026
053d956
docs: record upstream merge report
actions-user Jul 30, 2026
e0719a9
docs: clarify upstream merge report head
actions-user Jul 30, 2026
a7c1e42
chore: remove upstream agent report
actions-user Jul 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/APPROVED_CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -295,3 +295,17 @@ christianbasch pr
cpacker pr

rgarcia pr

renaudhartert-db pr

HyeokjaeLee pr

arajkumar pr

brianstanley pr

scruffymongrel pr

SI-RUI-ZHANG pr

sunnyyoung pr
6 changes: 3 additions & 3 deletions .github/upstream.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"repo": "badlogic/pi-mono",
"tag": "v0.82.1",
"sha": "5bc1c2c0a6f07e00e8c240304182f213ab8d311f",
"synced_at": "2026-07-25T21:29:48Z"
"tag": "v0.83.0",
"sha": "71efc6f0c1909874ec8c944637a9ae7fc0e2d508",
"synced_at": "2026-07-30T03:38:57Z"
}
1,655 changes: 1,655 additions & 0 deletions packages/agent/docs/harness-v2.md

Large diffs are not rendered by default.

2,320 changes: 2,320 additions & 0 deletions packages/agent/docs/harness.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/agent/src/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export function streamProxy(model: Model<any>, context: Context, options: ProxyS
// Initialize the partial message that we'll build up from events
const partial: AssistantMessage = {
role: "assistant",
stopReason: "stop",
stopReason: "pending",
content: [],
api: model.api,
provider: model.provider,
Expand Down
15 changes: 15 additions & 0 deletions packages/ai/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,25 @@

### Added

- Added per-request `fetch` injection for supported text and image provider transports; Google adapters reject non-global implementations rather than silently bypassing them.
- Added Claude Opus 5 support for the GitHub Copilot provider, routing through the Anthropic Messages API with adaptive thinking, 1M context, and the Copilot `minimal` thinking-level override ([#7158](https://github.com/earendil-works/pi/pull/7158) by [@jay-aye-see-kay](https://github.com/jay-aye-see-kay)).
- Added the `"pending"` stop reason for partial streaming messages ([#7151](https://github.com/earendil-works/pi/pull/7151) by [@lucasmeijer](https://github.com/lucasmeijer)).
- Added `AssistantMessage.rawStopReason` and populated it across Google, Anthropic, Amazon Bedrock, Mistral, and OpenAI streams; unmapped terminal reasons now surface as provider errors instead of successful stops ([#7272](https://github.com/earendil-works/pi/pull/7272)).
- Added manual redirect URL and authorization-code entry to OpenRouter OAuth login for remote and headless environments ([#7114](https://github.com/earendil-works/pi/pull/7114) by [@rgarcia](https://github.com/rgarcia)).
- Added `AuthResolutionOverrides.minOAuthValidityMs` so callers can require and refresh OAuth credentials with a minimum remaining validity ([#7168](https://github.com/earendil-works/pi/pull/7168)).

### Changed

- Changed stored OAuth credentials to refresh when less than five minutes of validity remain instead of waiting until expiration ([#7168](https://github.com/earendil-works/pi/pull/7168)).

### Fixed

- Fixed compiled validation of nullable array tool arguments with the inherited TypeBox validation update ([#7243](https://github.com/earendil-works/pi/pull/7243) by [@petrroll](https://github.com/petrroll)).
- Fixed Qwen Token Plan reasoning models to send their service-specific thinking controls and supported reasoning-effort levels ([#6951](https://github.com/earendil-works/pi/issues/6951), [#6998](https://github.com/earendil-works/pi/issues/6998)).
- Fixed Z.AI providers and compatible custom endpoints to send output limits through `max_tokens`, which those endpoints honor ([#7174](https://github.com/earendil-works/pi/pull/7174) by [@HyeokjaeLee](https://github.com/HyeokjaeLee)).
- Fixed explicitly configured Amazon Bedrock profiles being overridden by ambient AWS access keys ([#7176](https://github.com/earendil-works/pi/pull/7176) by [@christianbasch](https://github.com/christianbasch)).
- Fixed malformed OpenAI-compatible tool-call deltas with both a valid `function` payload and an empty `custom` object discarding the function arguments ([#7288](https://github.com/earendil-works/pi/pull/7288) by [@sunnyyoung](https://github.com/sunnyyoung)).

### Removed

## [2026.7.29-6] - 2026-07-29
Expand Down
3 changes: 2 additions & 1 deletion packages/ai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -874,7 +874,8 @@ for await (const event of s) {

Every `AssistantMessage` includes a `stopReason` field that indicates how the generation ended:

- `"stop"` - Normal completion, the model finished its response
- `"pending"` - Only present in partial messages when we do not know what the stop reason will be
- `"stop"` - This is the final message the model will produce this turn
- `"length"` - Output hit the maximum token limit
- `"toolUse"` - Model is calling tools and expects tool results
- `"error"` - An error occurred during generation
Expand Down
75 changes: 49 additions & 26 deletions packages/ai/scripts/generate-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,33 @@ const DEEPSEEK_V4_THINKING_LEVEL_MAP = {
high: "high",
max: "max",
} as const;
const QWEN_TOKEN_PLAN_HIGH_MAX_THINKING_LEVEL_MAP = {
minimal: null,
low: null,
medium: null,
high: "high",
xhigh: null,
max: "max",
} as const;
const QWEN_TOKEN_PLAN_QWEN38_THINKING_LEVEL_MAP = {
minimal: null,
low: "low",
medium: "medium",
high: null,
xhigh: "xhigh",
max: null,
} as const;
const QWEN_TOKEN_PLAN_REASONING_EFFORT_UNSUPPORTED_MODEL_IDS = new Set([
"MiniMax-M2.5",
"deepseek-v3.2",
"kimi-k2.5",
"kimi-k2.6",
"kimi-k2.7-code",
"qwen3.6-flash",
"qwen3.6-plus",
"qwen3.7-max",
"qwen3.7-plus",
]);

const KIMI_K3_THINKING_LEVEL_MAP = {
off: null,
Expand Down Expand Up @@ -430,6 +457,7 @@ const GITHUB_COPILOT_EXTENDED_CONTEXT_MODELS = new Set([
"claude-opus-4.6",
"claude-opus-4.7",
"claude-opus-4.8",
"claude-opus-5",
"claude-sonnet-4.6",
"claude-sonnet-5",
"gpt-5.3-codex",
Expand All @@ -442,6 +470,7 @@ const GITHUB_COPILOT_EXTENDED_CONTEXT_MODELS = new Set([
const GITHUB_COPILOT_THINKING_LEVEL_OVERRIDES = {
"claude-opus-4.7": { minimal: "low" },
"claude-opus-4.8": { minimal: "low" },
"claude-opus-5": { minimal: "low" },
"claude-sonnet-4.6": { minimal: "low", max: "max" },
} satisfies Record<string, NonNullable<Model<Api>["thinkingLevelMap"]>>;

Expand Down Expand Up @@ -630,7 +659,7 @@ function detectOpenAICompletionsCompat(model: Model<"openai-completions">): Open
isAntLing;

const useMaxTokens =
baseUrl.includes("chutes.ai") || isMoonshot || isCloudflareAiGateway || isTogether || isNvidia || isAntLing;
baseUrl.includes("chutes.ai") || isMoonshot || isCloudflareAiGateway || isTogether || isNvidia || isAntLing || isZai;

const isGrok = provider === "xai" || baseUrl.includes("api.x.ai");
const isDeepSeek = provider === "deepseek" || baseUrl.includes("deepseek.com");
Expand Down Expand Up @@ -2047,6 +2076,7 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
thinkingFormat: "qwen",
supportsDeveloperRole: false,
supportsStore: false,
supportsReasoningEffort: true,
};
const qwenTokenPlanVariants = [
{
Expand All @@ -2068,14 +2098,25 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
for (const [modelId, model] of Object.entries(providerModels)) {
const m = model as ModelsDevModel;
if (m.tool_call !== true) continue;
const supportsReasoningEffort = !QWEN_TOKEN_PLAN_REASONING_EFFORT_UNSUPPORTED_MODEL_IDS.has(modelId);

models.push({
id: modelId,
name: m.name || modelId,
api: "openai-completions",
provider,
baseUrl,
compat: qwenTokenPlanCompat,
compat: supportsReasoningEffort
? qwenTokenPlanCompat
: { ...qwenTokenPlanCompat, supportsReasoningEffort: false },
...(supportsReasoningEffort
? {
thinkingLevelMap:
modelId === "qwen3.8-max-preview"
? QWEN_TOKEN_PLAN_QWEN38_THINKING_LEVEL_MAP
: QWEN_TOKEN_PLAN_HIGH_MAX_THINKING_LEVEL_MAP,
}
: {}),
reasoning: m.reasoning === true,
input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
cost: {
Expand Down Expand Up @@ -2576,7 +2617,12 @@ async function generateModels() {
allModels.push(...antLingModels);

for (const candidate of allModels) {
if (candidate.api === "openai-completions" && candidate.id.includes("deepseek-v4")) {
if (
candidate.api === "openai-completions" &&
candidate.id.includes("deepseek-v4") &&
candidate.provider !== "qwen-token-plan" &&
candidate.provider !== "qwen-token-plan-cn"
) {
const preservesNativeReasoningEffort = candidate.provider === "openrouter" || candidate.provider === "opencode";
candidate.compat = {
...candidate.compat,
Expand Down Expand Up @@ -2719,29 +2765,6 @@ async function generateModels() {
});
}

// Add qwen3.8-max-preview to Qwen Token Plan providers until models.dev includes it
for (const qwenTpProvider of ["qwen-token-plan", "qwen-token-plan-cn"] as const) {
if (!allModels.some((m) => m.provider === qwenTpProvider && m.id === "qwen3.8-max-preview")) {
const baseUrl =
qwenTpProvider === "qwen-token-plan"
? "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1"
: "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1";
allModels.push({
id: "qwen3.8-max-preview",
name: "Qwen3.8 Max Preview",
api: "openai-completions",
provider: qwenTpProvider,
baseUrl,
compat: { thinkingFormat: "qwen", supportsDeveloperRole: false, supportsStore: false } satisfies OpenAICompletionsCompat,
reasoning: true,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 1000000,
maxTokens: 65536,
});
}
}

// Add "auto" alias for openrouter/auto
if (!allModels.some(m => m.provider === "openrouter" && m.id === "auto")) {
allModels.push({
Expand Down
21 changes: 18 additions & 3 deletions packages/ai/src/api/anthropic-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {
Message,
Model,
ProviderEnv,
ProviderHeaders,
ProviderNativeContent,
SimpleStreamOptions,
StopReason,
Expand Down Expand Up @@ -1199,7 +1200,7 @@ export const stream: StreamFunction<"anthropic-messages", AnthropicOptions> = (
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "stop",
stopReason: "pending",
timestamp: Date.now(),
};

Expand Down Expand Up @@ -1243,6 +1244,7 @@ export const stream: StreamFunction<"anthropic-messages", AnthropicOptions> = (
options?.interleavedThinking ?? true,
shouldUseFineGrainedToolStreamingBeta(model, context),
optionsHeaders,
options?.fetch,
copilotDynamicHeaders,
cacheSessionId,
options?.env,
Expand Down Expand Up @@ -1489,6 +1491,7 @@ export const stream: StreamFunction<"anthropic-messages", AnthropicOptions> = (
}
} else if (event.type === "message_delta") {
if (event.delta.stop_reason) {
output.rawStopReason = event.delta.stop_reason;
const stopReasonResult = mapStopReason(event.delta.stop_reason, event.delta.stop_details);
output.stopReason = stopReasonResult.stopReason;
if (stopReasonResult.errorMessage) {
Expand Down Expand Up @@ -1537,6 +1540,9 @@ export const stream: StreamFunction<"anthropic-messages", AnthropicOptions> = (
applyServerFallbackAbort(output, serverFallbackReceipt);
}

if (output.stopReason === "pending") {
throw new Error("Anthropic stream ended without a stop reason");
}
if (output.stopReason === "aborted" || output.stopReason === "error") {
throw new Error(output.errorMessage || "An unknown error occurred");
}
Expand Down Expand Up @@ -1710,7 +1716,8 @@ function createClient(
apiKey: string | undefined,
interleavedThinking: boolean,
useFineGrainedToolStreamingBeta: boolean,
optionsHeaders?: Record<string, string>,
optionsHeaders?: ProviderHeaders,
fetch?: typeof globalThis.fetch,
dynamicHeaders?: Record<string, string>,
sessionId?: string,
env?: ProviderEnv,
Expand All @@ -1731,6 +1738,7 @@ function createClient(
authToken: null,
baseURL: resolveCloudflareBaseUrl(model, env),
dangerouslyAllowBrowser: true,
fetch,
defaultHeaders: sanitizeAdaptiveThinkingHeaders(
model,
mergeHeaders(
Expand Down Expand Up @@ -1758,6 +1766,7 @@ function createClient(
authToken: apiKey,
baseURL: model.baseUrl,
dangerouslyAllowBrowser: true,
fetch,
defaultHeaders: sanitizeAdaptiveThinkingHeaders(
model,
mergeHeaders(
Expand All @@ -1783,6 +1792,7 @@ function createClient(
authToken: apiKey,
baseURL: model.baseUrl,
dangerouslyAllowBrowser: true,
fetch,
defaultHeaders: sanitizeAdaptiveThinkingHeaders(
model,
mergeHeaders(
Expand Down Expand Up @@ -1810,6 +1820,7 @@ function createClient(
authToken: null,
baseURL: model.baseUrl,
dangerouslyAllowBrowser: true,
fetch,
defaultHeaders: sanitizeAdaptiveThinkingHeaders(
model,
mergeHeaders(
Expand Down Expand Up @@ -2350,7 +2361,11 @@ function mapStopReason(
case "stop_sequence":
return { stopReason: "stop" }; // We don't supply stop sequences, so this should never happen
case "sensitive": // Content flagged by safety filters (not yet in SDK types)
return { stopReason: "error", stopDetails: { type: "sensitive" } };
return {
stopReason: "error",
errorMessage: "Provider stopped with: sensitive",
stopDetails: { type: "sensitive" },
};
default:
// Handle unknown stop reasons gracefully (API may add new values)
throw new Error(`Unhandled stop reason: ${reason}`);
Expand Down
6 changes: 5 additions & 1 deletion packages/ai/src/api/azure-openai-responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const stream: StreamFunction<"azure-openai-responses", AzureOpenAIRespons
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "stop",
stopReason: "pending",
timestamp: Date.now(),
};

Expand Down Expand Up @@ -132,6 +132,9 @@ export const stream: StreamFunction<"azure-openai-responses", AzureOpenAIRespons
throw new Error("Request was aborted");
}

if (output.stopReason === "pending") {
throw new Error("Azure OpenAI Responses stream ended without a stop reason");
}
if (output.stopReason === "aborted" || output.stopReason === "error") {
throw new Error("An unknown error occurred");
}
Expand Down Expand Up @@ -265,6 +268,7 @@ function createClient(model: Model<"azure-openai-responses">, apiKey: string, op
apiKey,
apiVersion,
dangerouslyAllowBrowser: true,
fetch: options?.fetch,
defaultHeaders: headers,
baseURL: baseUrl,
});
Expand Down
20 changes: 16 additions & 4 deletions packages/ai/src/api/bedrock-converse-stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,20 @@ export const stream: StreamFunction<"bedrock-converse-stream", BedrockOptions> =
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "stop",
stopReason: "pending",
timestamp: Date.now(),
};

const blocks = output.content as Block[];

// A profile explicitly configured through pi's auth flow (the `profile`
// option or scoped `AWS_PROFILE` on the stored credential's env) must win
// over ambient AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY. The SDK default
// chain already prefers a configured profile over env keys, but only when
// `credentials` is not set on the client config. See #6957.
const optionsProfile = options.profile || options.env?.AWS_PROFILE;
const config: BedrockRuntimeClientConfig = {
profile: options.profile || getProviderEnvValue("AWS_PROFILE", options.env),
profile: optionsProfile || getProviderEnvValue("AWS_PROFILE", options.env),
};
const configuredRegion = getConfiguredBedrockRegion(options);
const hasAmbientConfiguredProfile = Boolean(getProviderEnvValue("AWS_PROFILE"));
Expand Down Expand Up @@ -194,7 +200,7 @@ export const stream: StreamFunction<"bedrock-converse-stream", BedrockOptions> =
}

const credentials = getConfiguredBedrockCredentials(options.env);
if (!skipAuth && credentials) {
if (!skipAuth && credentials && !optionsProfile) {
config.credentials = credentials;
}

Expand Down Expand Up @@ -275,6 +281,7 @@ export const stream: StreamFunction<"bedrock-converse-stream", BedrockOptions> =
} else if (item.contentBlockStop) {
handleContentBlockStop(item.contentBlockStop, blocks, output, stream);
} else if (item.messageStop) {
output.rawStopReason = item.messageStop.stopReason;
const { stopReason, errorMessage } = mapStopReason(item.messageStop.stopReason);
output.stopReason = stopReason;
if (errorMessage) {
Expand All @@ -299,6 +306,9 @@ export const stream: StreamFunction<"bedrock-converse-stream", BedrockOptions> =
throw new Error("Request was aborted");
}

if (output.stopReason === "pending") {
throw new Error("Bedrock stream ended without a stop reason");
}
if (output.stopReason === "error" || output.stopReason === "aborted") {
throw new Error(output.errorMessage || "An unknown error occurred");
}
Expand Down Expand Up @@ -962,7 +972,9 @@ function mapStopReason(reason: string | undefined): { stopReason: StopReason; er
case BedrockStopReason.TOOL_USE:
return { stopReason: "toolUse" };
default:
return reason ? { stopReason: "error", errorMessage: reason } : { stopReason: "error" };
return reason
? { stopReason: "error", errorMessage: `Provider stopped with: ${reason}` }
: { stopReason: "error" };
}
}

Expand Down
Loading