@opencode/ai 0.0.0-dev-20549 → 0.0.0-dev-20553

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.
@@ -675,9 +675,6 @@ const requireThinkingSignature = (request) => {
675
675
  return false;
676
676
  return true;
677
677
  };
678
- // Mid-conversation system messages became available with Opus 4.8 and version
679
- // 5 of the other supported Claude families. Treat later family versions as
680
- // compatible without assuming that every Anthropic Messages model is Claude.
681
678
  // Opus 4.8 and every Claude 5 model accept mid-conversation system messages; later versions inherit support.
682
679
  const supportsNativeSystemUpdates = (request) => {
683
680
  const version = claudeVersion(String(request.model.id));
@@ -687,23 +684,8 @@ const supportsNativeSystemUpdates = (request) => {
687
684
  return version.minor >= 8;
688
685
  return version.major >= 5;
689
686
  };
690
- const endsInServerToolUse = (message) => {
691
- const last = message.content.at(-1);
692
- return message.role === "assistant" && last?.type === "tool-call" && last.providerExecuted === true;
693
- };
694
- const canUseNativeSystemUpdate = (request, index) => {
695
- const previous = request.messages[index - 1];
696
- const next = request.messages[index + 1];
697
- // Vertex currently rejects/404s for a system message after local tool results,
698
- // so fold it into the user tool-result turn across continuations and history.
699
- if (request.model.route.id === "google-vertex-messages" && previous?.role === "tool")
700
- return false;
701
- return (previous !== undefined &&
702
- previous.role !== "system" &&
703
- (previous.role === "user" || previous.role === "tool" || endsInServerToolUse(previous)) &&
704
- next?.role !== "system" &&
705
- (next === undefined || next.role === "assistant"));
706
- };
687
+ // Native system messages must follow a user turn (tool results count) or a paused server-tool turn.
688
+ const acceptsNativeSystemAfter = (message) => message?.role === "user" || (message?.role === "assistant" && message.content.at(-1)?.type === "server_tool_use");
707
689
  const splitsLocalToolResults = (messages, index) => {
708
690
  const pending = new Set();
709
691
  for (const message of messages.slice(0, index)) {
@@ -727,9 +709,33 @@ const lowerNativeSystemUpdate = Effect.fnUntraced(function* (message, breakpoint
727
709
  })),
728
710
  };
729
711
  });
712
+ const lowerWrappedSystemUpdate = Effect.fnUntraced(function* (message, breakpoints) {
713
+ const part = yield* ProviderShared.wrappedSystemUpdate("Anthropic Messages", message);
714
+ return { type: "text", text: part.text, cache_control: cacheControl(breakpoints, part.cache) };
715
+ });
716
+ const appendToUserTurn = (messages, block) => {
717
+ const last = messages.at(-1);
718
+ if (last?.role === "user")
719
+ messages[messages.length - 1] = { role: "user", content: [...last.content, block] };
720
+ else
721
+ messages.push({ role: "user", content: [block] });
722
+ };
730
723
  const lowerMessages = Effect.fnUntraced(function* (request, breakpoints) {
731
724
  const messages = [];
732
725
  const providerMetadataKey = request.model.route.providerMetadataKey ?? String(request.model.provider);
726
+ // Text updates stay where they are unless a user turn follows them; then they move after the latest
727
+ // user turn, the nearest spot where Anthropic accepts a native system message.
728
+ const holdUpdates = supportsNativeSystemUpdates(request);
729
+ const held = [];
730
+ const releaseHeld = Effect.fnUntraced(function* () {
731
+ const native = acceptsNativeSystemAfter(messages.findLast((message) => message.role !== "system"));
732
+ for (const update of held.splice(0)) {
733
+ if (native)
734
+ messages.push(yield* lowerNativeSystemUpdate(update, breakpoints));
735
+ else
736
+ appendToUserTurn(messages, yield* lowerWrappedSystemUpdate(update, breakpoints));
737
+ }
738
+ });
733
739
  for (const [index, message] of request.messages.entries()) {
734
740
  if (message.role === "system") {
735
741
  const update = effortUpdate(message);
@@ -740,17 +746,10 @@ const lowerMessages = Effect.fnUntraced(function* (request, breakpoints) {
740
746
  }
741
747
  if (splitsLocalToolResults(request.messages, index))
742
748
  return yield* invalid("Anthropic Messages system updates cannot split a local tool call from its tool result");
743
- if (supportsNativeSystemUpdates(request) && canUseNativeSystemUpdate(request, index)) {
744
- messages.push(yield* lowerNativeSystemUpdate(message, breakpoints));
745
- continue;
746
- }
747
- const part = yield* ProviderShared.wrappedSystemUpdate("Anthropic Messages", message);
748
- const block = { type: "text", text: part.text, cache_control: cacheControl(breakpoints, part.cache) };
749
- const previous = messages.at(-1);
750
- if (previous?.role === "user")
751
- messages[messages.length - 1] = { role: "user", content: [...previous.content, block] };
749
+ if (holdUpdates)
750
+ held.push(message);
752
751
  else
753
- messages.push({ role: "user", content: [block] });
752
+ appendToUserTurn(messages, yield* lowerWrappedSystemUpdate(message, breakpoints));
754
753
  continue;
755
754
  }
756
755
  if (message.role === "user") {
@@ -826,8 +825,10 @@ const lowerMessages = Effect.fnUntraced(function* (request, breakpoints) {
826
825
  }
827
826
  return yield* invalid(`Anthropic Messages assistant messages only support text, reasoning, and tool-call content for now`);
828
827
  }
829
- if (content.length > 0)
830
- messages.push({ role: "assistant", content });
828
+ if (content.length === 0)
829
+ continue;
830
+ yield* releaseHeld();
831
+ messages.push({ role: "assistant", content });
831
832
  continue;
832
833
  }
833
834
  const content = [];
@@ -848,6 +849,7 @@ const lowerMessages = Effect.fnUntraced(function* (request, breakpoints) {
848
849
  else
849
850
  messages.push({ role: "user", content });
850
851
  }
852
+ yield* releaseHeld();
851
853
  return messages;
852
854
  });
853
855
  // TODO: Move per-model capability heuristics (`supportsEffortUpdates`, `supportsNativeSystemUpdates`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
- "version": "0.0.0-dev-20549",
3
+ "version": "0.0.0-dev-20553",
4
4
  "name": "@opencode/ai",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -34,7 +34,7 @@
34
34
  "devDependencies": {
35
35
  "@clack/prompts": "1.0.0-alpha.1",
36
36
  "@effect/platform-node": "4.0.0-rc.112",
37
- "@opencode/http-recorder": "0.0.0-dev-20549",
37
+ "@opencode/http-recorder": "0.0.0-dev-20553",
38
38
  "@tsconfig/bun": "1.0.9",
39
39
  "@types/bun": "1.4.0",
40
40
  "@typescript/native-preview": "7.0.0-dev.20251207.1",
@@ -44,7 +44,7 @@
44
44
  "@aws-sdk/credential-providers": "3.1057.0",
45
45
  "@smithy/eventstream-codec": "4.2.14",
46
46
  "@smithy/util-utf8": "4.2.2",
47
- "@opencode/schema": "0.0.0-dev-20549",
47
+ "@opencode/schema": "0.0.0-dev-20553",
48
48
  "aws4fetch": "1.0.20",
49
49
  "effect": "4.0.0-rc.112",
50
50
  "google-auth-library": "10.5.0"