@nylorun/harness 0.11.0-beta → 0.11.1-beta

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.1-beta
4
+
5
+ ### Patch Changes
6
+
7
+ - d27242c: Preserve opaque provider continuation metadata through assistant conversation history. Gemini tool calls now retain thought signatures when sending tool results back to the model, including signed empty text and reasoning blocks. Only the originating provider and model receive their signatures.
8
+
3
9
  ## 0.11.0-beta
4
10
 
5
11
  ### Minor Changes
@@ -3,7 +3,7 @@ import { copyJsonObject } from "../utils/immutable.js";
3
3
  import { preparedModel } from "./prepared.js";
4
4
  /** Translate a Harness call to the OpenAI Chat Completions request shape. */
5
5
  export function toChatCompletions(call) {
6
- const messages = call.prompt.map((item) => {
6
+ const messages = withoutProviderReasoning(call.prompt).map((item) => {
7
7
  if (item.kind === "instructions")
8
8
  return { role: "system", content: textOf(item) };
9
9
  if (item.kind === "tool-result")
@@ -93,7 +93,7 @@ export function chatCompletionsAdapter(send) {
93
93
  /** Translate a Harness call to the OpenAI Responses request shape. */
94
94
  export function toResponses(call) {
95
95
  const instructions = call.prompt.filter((item) => item.kind === "instructions").map(textOf);
96
- const input = call.prompt.flatMap((item) => {
96
+ const input = withoutProviderReasoning(call.prompt).flatMap((item) => {
97
97
  if (item.kind === "instructions")
98
98
  return [];
99
99
  if (item.kind === "tool-result")
@@ -201,7 +201,7 @@ export function toMessages(call, defaultMaxOutputTokens) {
201
201
  if (call.outputSchema !== undefined)
202
202
  throw new HarnessError("model.unsupported-output-schema", "Anthropic Messages output schemas require a custom prepared adapter");
203
203
  const instructions = call.prompt.filter((item) => item.kind === "instructions").map(textOf);
204
- const messages = call.prompt.flatMap((item) => {
204
+ const messages = withoutProviderReasoning(call.prompt).flatMap((item) => {
205
205
  if (item.kind === "instructions")
206
206
  return [];
207
207
  if (item.kind === "tool-result")
@@ -543,3 +543,14 @@ function invalidResponse(message, path, cause) {
543
543
  details: { path },
544
544
  });
545
545
  }
546
+ // These portable translators do not interpret another adapter's signed reasoning.
547
+ function withoutProviderReasoning(prompt) {
548
+ return prompt.flatMap((item) => {
549
+ if (item.kind !== "message" ||
550
+ item.role !== "assistant" ||
551
+ !item.content.some((part) => part.type === "reasoning"))
552
+ return [item];
553
+ const content = item.content.filter((part) => part.type !== "reasoning");
554
+ return content.length === 0 ? [] : [{ ...item, content }];
555
+ });
556
+ }
@@ -119,11 +119,11 @@ function normalizeBlock(value, index) {
119
119
  throw invalidCandidate(`Model output[${index}] must be an object`, `output[${index}]`);
120
120
  const block = value;
121
121
  if (block.type === "text" || block.type === "reasoning") {
122
- rejectUnknownKeys(value, ["type", "text"], `Model output[${index}]`);
122
+ rejectUnknownKeys(value, ["type", "text", "providerMetadata"], `Model output[${index}]`);
123
123
  const text = value.text;
124
124
  if (typeof text !== "string")
125
125
  throw invalidCandidate(`Model output[${index}].text must be a string`, `output[${index}].text`);
126
- return Object.freeze({ type: block.type, text });
126
+ return Object.freeze({ type: block.type, text, ...providerMetadata(value, index) });
127
127
  }
128
128
  if (block.type === "json") {
129
129
  rejectUnknownKeys(value, ["type", "value"], `Model output[${index}]`);
@@ -140,7 +140,7 @@ function normalizeBlock(value, index) {
140
140
  }
141
141
  }
142
142
  if (block.type === "tool-call") {
143
- rejectUnknownKeys(value, ["type", "id", "name", "args", "raw"], `Model output[${index}]`);
143
+ rejectUnknownKeys(value, ["type", "id", "name", "args", "raw", "providerMetadata"], `Model output[${index}]`);
144
144
  const raw = value;
145
145
  if (raw.id !== undefined && typeof raw.id !== "string")
146
146
  throw invalidCandidate(`Model output[${index}].id must be a string`, `output[${index}].id`);
@@ -159,6 +159,7 @@ function normalizeBlock(value, index) {
159
159
  throw invalidCandidate(`Model output[${index}].raw must be a string`, `output[${index}].raw`);
160
160
  return Object.freeze({
161
161
  type: "tool-call",
162
+ ...providerMetadata(value, index),
162
163
  id: typeof raw.id === "string" ? raw.id : "",
163
164
  name: typeof raw.name === "string" ? raw.name : "",
164
165
  args,
@@ -240,3 +241,14 @@ function isFiniteNumber(value) {
240
241
  function isNonNegativeInteger(value) {
241
242
  return typeof value === "number" && Number.isInteger(value) && value >= 0;
242
243
  }
244
+ function providerMetadata(value, index) {
245
+ const metadata = value.providerMetadata;
246
+ if (metadata === undefined)
247
+ return {};
248
+ try {
249
+ return { providerMetadata: copyJsonObject(metadata, "providerMetadata") };
250
+ }
251
+ catch (error) {
252
+ throw invalidCandidate(`Model output[${index}].providerMetadata must be a JSON object`, `output[${index}].providerMetadata`, error);
253
+ }
254
+ }
@@ -246,7 +246,7 @@ function validateSeedCandidate(value, index) {
246
246
  fail(`Candidate output ${blockIndex} at transcript ${index} must be an object`);
247
247
  const block = value;
248
248
  if (block.type === "text" || block.type === "reasoning") {
249
- exactKeys(block, ["type", "text"], `Candidate output ${blockIndex}`);
249
+ exactKeys(block, ["type", "text", "providerMetadata"], `Candidate output ${blockIndex}`);
250
250
  if (typeof block.text !== "string")
251
251
  fail(`Candidate output ${blockIndex} text must be a string`);
252
252
  return;
@@ -257,7 +257,7 @@ function validateSeedCandidate(value, index) {
257
257
  return;
258
258
  }
259
259
  if (block.type === "tool-call") {
260
- exactKeys(block, ["type", "id", "name", "args", "raw"], `Candidate output ${blockIndex}`);
260
+ exactKeys(block, ["type", "id", "name", "args", "raw", "providerMetadata"], `Candidate output ${blockIndex}`);
261
261
  requiredString(block.id, `Candidate output ${blockIndex} id`);
262
262
  requiredString(block.name, `Candidate output ${blockIndex} name`);
263
263
  if (block.raw !== undefined && typeof block.raw !== "string")
@@ -38,6 +38,9 @@ export function canonicalizeOutput(output) {
38
38
  id: call.id,
39
39
  name: call.name,
40
40
  args: call.args,
41
+ ...(block.providerMetadata === undefined
42
+ ? {}
43
+ : { providerMetadata: block.providerMetadata }),
41
44
  ...(block.raw === undefined ? {} : { raw: block.raw }),
42
45
  });
43
46
  })),
@@ -64,8 +64,9 @@ function projectEntry(entry) {
64
64
  }
65
65
  if (entry.kind === "candidate") {
66
66
  const content = entry.candidate.output.flatMap((block) => {
67
- if (block.type === "text")
68
- return [textPart(block.text)];
67
+ if (block.type === "text" ||
68
+ (block.type === "reasoning" && block.providerMetadata !== undefined))
69
+ return [Object.freeze({ ...block })];
69
70
  if (block.type === "json")
70
71
  return [textPart(JSON.stringify(block.value))];
71
72
  if (block.type === "tool-call")
@@ -75,6 +76,9 @@ function projectEntry(entry) {
75
76
  id: block.id,
76
77
  name: block.name,
77
78
  args: copyJson(block.args),
79
+ ...(block.providerMetadata === undefined
80
+ ? {}
81
+ : { providerMetadata: copyJson(block.providerMetadata) }),
78
82
  }),
79
83
  ];
80
84
  return [];
@@ -9,14 +9,17 @@ export interface ModelToolCall {
9
9
  export type ModelOutputBlock = {
10
10
  readonly type: "text";
11
11
  readonly text: string;
12
+ readonly providerMetadata?: JsonObject;
12
13
  } | {
13
14
  readonly type: "reasoning";
14
15
  readonly text: string;
16
+ readonly providerMetadata?: JsonObject;
15
17
  } | {
16
18
  readonly type: "json";
17
19
  readonly value: JsonValue;
18
20
  } | {
19
21
  readonly type: "tool-call";
22
+ readonly providerMetadata?: JsonObject;
20
23
  readonly id: string;
21
24
  readonly name: string;
22
25
  readonly args: JsonObject;
@@ -117,14 +120,20 @@ export interface ModelRequest {
117
120
  readonly outputSchema?: JsonObject;
118
121
  }
119
122
  export type PromptContentPart = {
123
+ readonly type: "reasoning";
124
+ readonly text: string;
125
+ readonly providerMetadata?: JsonObject;
126
+ } | {
120
127
  readonly type: "text";
121
128
  readonly text: string;
129
+ readonly providerMetadata?: JsonObject;
122
130
  } | {
123
131
  readonly type: "media";
124
132
  readonly mediaType: string;
125
133
  readonly reference: JsonValue;
126
134
  } | {
127
135
  readonly type: "tool-call";
136
+ readonly providerMetadata?: JsonObject;
128
137
  readonly id: string;
129
138
  readonly name: string;
130
139
  readonly args: JsonObject;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nylorun/harness",
3
- "version": "0.11.0-beta",
3
+ "version": "0.11.1-beta",
4
4
  "description": "Nylorun's TypeScript agent runtime. See github.com/nylorun/harness.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",