chainlesschain 0.166.132 → 0.166.135
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/README.md +44 -18
- package/package.json +1 -1
- package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
- package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
- package/src/data/changelog.json +26 -0
- package/src/gateways/ws/llm-chat-protocol.js +1 -1
- package/src/gateways/ws/personal-data-hub-protocol.js +27 -11
- package/src/gateways/ws/session-core-protocol.js +22 -17
- package/src/gateways/ws/session-protocol.js +19 -8
- package/src/gateways/ws/ws-agent-handler.js +30 -0
- package/src/lib/background-launch-profile.js +37 -1
- package/src/lib/chat-core.js +40 -35
- package/src/lib/coding-task-guidance.js +10 -9
- package/src/lib/context-memory-kernel/message-adapter.js +5 -1
- package/src/lib/evolution/agent-model-projection.js +0 -1
- package/src/lib/evolution/governed-hub-llm.js +5 -1
- package/src/lib/evolution/governed-hub-resolver.js +15 -6
- package/src/lib/evolution/governed-hub-skill.js +2 -0
- package/src/lib/evolution/optional-ui-model-turn.js +13 -2
- package/src/lib/llm-config-defaults.js +17 -5
- package/src/lib/llm-connection-config.js +10 -2
- package/src/lib/llm-connection-probe.js +5 -2
- package/src/lib/llm-connection-resolution.js +7 -4
- package/src/lib/llm-providers.js +24 -11
- package/src/lib/openai-responses.js +2 -1
- package/src/lib/personal-data-hub-wiring.js +69 -36
- package/src/lib/provider-protocol.js +35 -3
- package/src/lib/provider-stream.js +6 -4
- package/src/lib/runnable-provider.js +4 -0
- package/src/lib/ws-chat-handler.js +22 -9
- package/src/runtime/agent-core.js +182 -34
- package/src/runtime/headless-runner.js +3 -3
|
@@ -10,7 +10,10 @@ import {
|
|
|
10
10
|
normalizeProviderBaseUrl,
|
|
11
11
|
openAICompatibleBaseUrl,
|
|
12
12
|
} from "../lib/provider-protocol.js";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
BUILT_IN_PROVIDERS,
|
|
15
|
+
resolveProviderDefinition,
|
|
16
|
+
} from "../lib/llm-providers.js";
|
|
14
17
|
import { resolveLlmConnection } from "../lib/llm-connection-resolution.js";
|
|
15
18
|
/**
|
|
16
19
|
* Agent Core — transport-independent agentic logic
|
|
@@ -12122,11 +12125,65 @@ function getEffectiveToolDefinitions(options = {}) {
|
|
|
12122
12125
|
});
|
|
12123
12126
|
}
|
|
12124
12127
|
|
|
12128
|
+
function projectExactFileMutationToolSchemas(
|
|
12129
|
+
definitions,
|
|
12130
|
+
scope,
|
|
12131
|
+
externalToolExecutors,
|
|
12132
|
+
) {
|
|
12133
|
+
if (scope == null) return definitions;
|
|
12134
|
+
return definitions.map((tool) => {
|
|
12135
|
+
const name = tool?.function?.name;
|
|
12136
|
+
if (
|
|
12137
|
+
!["write_file", "edit_file", "edit_file_hashed"].includes(name) ||
|
|
12138
|
+
!AGENT_TOOLS.includes(tool) ||
|
|
12139
|
+
externalToolExecutors?.[name]
|
|
12140
|
+
) {
|
|
12141
|
+
return tool;
|
|
12142
|
+
}
|
|
12143
|
+
const parameters = tool.function.parameters;
|
|
12144
|
+
return {
|
|
12145
|
+
...tool,
|
|
12146
|
+
function: {
|
|
12147
|
+
...tool.function,
|
|
12148
|
+
parameters: {
|
|
12149
|
+
...parameters,
|
|
12150
|
+
properties: {
|
|
12151
|
+
...parameters.properties,
|
|
12152
|
+
path: {
|
|
12153
|
+
...parameters.properties.path,
|
|
12154
|
+
// Complete host-authorized set: never truncate this enum or
|
|
12155
|
+
// derive it from a read result. Normal tool context budgeting
|
|
12156
|
+
// sees the same schema that is sent to the provider.
|
|
12157
|
+
enum: [...scope.allowedPaths],
|
|
12158
|
+
description:
|
|
12159
|
+
"Use exactly one listed canonical repo-relative path. Read tools may return absolute paths; those are not valid mutation arguments in this run. Do not use an absolute path, ./ prefix, .. segment or backslash. This schema is guidance; all execution-time permission and file-identity checks still apply.",
|
|
12160
|
+
},
|
|
12161
|
+
},
|
|
12162
|
+
},
|
|
12163
|
+
},
|
|
12164
|
+
};
|
|
12165
|
+
});
|
|
12166
|
+
}
|
|
12167
|
+
|
|
12125
12168
|
function getProviderToolDefinitions(options = {}) {
|
|
12126
|
-
|
|
12169
|
+
const definitions = projectMcpToolDefinitions(
|
|
12127
12170
|
getEffectiveToolDefinitions(options),
|
|
12128
12171
|
options.externalToolExecutors,
|
|
12129
12172
|
);
|
|
12173
|
+
if (options.fileMutationScope == null) return definitions;
|
|
12174
|
+
if (options.exactToolNames !== true || options.hermeticExecution !== true) {
|
|
12175
|
+
throw new Error(
|
|
12176
|
+
"exact file mutation scope requires exactToolNames=true and hermeticExecution=true",
|
|
12177
|
+
);
|
|
12178
|
+
}
|
|
12179
|
+
const scope = normalizeExactFileMutationScope(options.fileMutationScope, {
|
|
12180
|
+
cwd: options.cwd || process.cwd(),
|
|
12181
|
+
});
|
|
12182
|
+
return projectExactFileMutationToolSchemas(
|
|
12183
|
+
definitions,
|
|
12184
|
+
scope,
|
|
12185
|
+
options.externalToolExecutors,
|
|
12186
|
+
);
|
|
12130
12187
|
}
|
|
12131
12188
|
|
|
12132
12189
|
const PROVIDER_REQUEST_ID_RE = /^[\x21-\x7e]{1,512}$/;
|
|
@@ -12540,7 +12597,7 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
12540
12597
|
}
|
|
12541
12598
|
|
|
12542
12599
|
// OpenAI-compatible providers
|
|
12543
|
-
const providerDefinition =
|
|
12600
|
+
const providerDefinition = resolveProviderDefinition(provider);
|
|
12544
12601
|
const url = openAICompatibleBaseUrl(provider, baseUrl);
|
|
12545
12602
|
|
|
12546
12603
|
if (!providerDefinition || !url) {
|
|
@@ -12555,7 +12612,8 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
12555
12612
|
|
|
12556
12613
|
const envKey = providerDefinition.apiKeyEnv;
|
|
12557
12614
|
const key = apiKey;
|
|
12558
|
-
if (!key
|
|
12615
|
+
if (!key && provider !== "custom")
|
|
12616
|
+
throw new Error(`${envKey} required for provider ${provider}`);
|
|
12559
12617
|
const effectiveModel = model || providerDefinition.models?.[0];
|
|
12560
12618
|
const modelProfile = resolveModelCapabilityProfile({
|
|
12561
12619
|
provider,
|
|
@@ -12613,34 +12671,69 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
12613
12671
|
);
|
|
12614
12672
|
}
|
|
12615
12673
|
|
|
12616
|
-
|
|
12617
|
-
|
|
12618
|
-
|
|
12619
|
-
|
|
12620
|
-
|
|
12621
|
-
|
|
12622
|
-
|
|
12674
|
+
// A JSON-body disconnect has produced no accepted output or executable
|
|
12675
|
+
// tools. Apply the same bounded transport retry and usage settlement used
|
|
12676
|
+
// by streaming; HTTP/provider verdicts and governed workflow calls retain
|
|
12677
|
+
// their existing no-replay rules.
|
|
12678
|
+
return await _retryStreamingChat(
|
|
12679
|
+
(attempt = 0) =>
|
|
12680
|
+
_withResponsesRequestDeadline(
|
|
12681
|
+
async (requestSignal) => {
|
|
12682
|
+
const requestId = supportsOpenAIRequestIdentity
|
|
12683
|
+
? _physicalProviderRequestId(providerRequestId, attempt)
|
|
12684
|
+
: null;
|
|
12685
|
+
const response = await providerFetch(`${url}/responses`, {
|
|
12686
|
+
method: "POST",
|
|
12687
|
+
headers: {
|
|
12688
|
+
"Content-Type": "application/json",
|
|
12689
|
+
Authorization: `Bearer ${key}`,
|
|
12690
|
+
...(requestId ? { "X-Client-Request-Id": requestId } : {}),
|
|
12691
|
+
},
|
|
12692
|
+
signal: requestSignal,
|
|
12693
|
+
body: JSON.stringify(responseBody),
|
|
12694
|
+
});
|
|
12695
|
+
if (!response.ok) {
|
|
12696
|
+
throw await createProviderResponseError(provider, response);
|
|
12697
|
+
}
|
|
12698
|
+
const retryMetadata = captureStreamRetryMetadata(response);
|
|
12699
|
+
try {
|
|
12700
|
+
const data = await response.json();
|
|
12701
|
+
const out = normalizeOpenAIResponsesResponse(data);
|
|
12702
|
+
if (out.usage)
|
|
12703
|
+
out.usage = preserveRequestedServiceTierEvidence(
|
|
12704
|
+
out.usage,
|
|
12705
|
+
options.serviceTier,
|
|
12706
|
+
);
|
|
12707
|
+
delete out.responseId;
|
|
12708
|
+
const providerReceipt = supportsOpenAIRequestIdentity
|
|
12709
|
+
? _openAIProviderRequestReceipt(response, data, requestId)
|
|
12710
|
+
: null;
|
|
12711
|
+
if (providerReceipt) out.providerReceipt = providerReceipt;
|
|
12712
|
+
return out;
|
|
12713
|
+
} catch (error) {
|
|
12714
|
+
attachStreamRetryMetadata(error, retryMetadata);
|
|
12715
|
+
throw error;
|
|
12716
|
+
}
|
|
12717
|
+
},
|
|
12718
|
+
signal,
|
|
12719
|
+
options.streamStallTimeoutMs,
|
|
12720
|
+
),
|
|
12721
|
+
{
|
|
12722
|
+
signal,
|
|
12723
|
+
retries: options.workflowEffectId ? 0 : undefined,
|
|
12724
|
+
strictRetryObserver: options.strictUsageTelemetry === true,
|
|
12725
|
+
...(typeof options.onStreamRetry === "function"
|
|
12726
|
+
? {
|
|
12727
|
+
onRetry: (attempt, error, telemetry) =>
|
|
12728
|
+
options.onStreamRetry(attempt, error, {
|
|
12729
|
+
...telemetry,
|
|
12730
|
+
provider,
|
|
12731
|
+
model: effectiveModel,
|
|
12732
|
+
}),
|
|
12733
|
+
}
|
|
12623
12734
|
: {}),
|
|
12624
12735
|
},
|
|
12625
|
-
|
|
12626
|
-
body: JSON.stringify(responseBody),
|
|
12627
|
-
});
|
|
12628
|
-
if (!response.ok) {
|
|
12629
|
-
throw await createProviderResponseError(provider, response);
|
|
12630
|
-
}
|
|
12631
|
-
const data = await response.json();
|
|
12632
|
-
const out = normalizeOpenAIResponsesResponse(data);
|
|
12633
|
-
if (out.usage)
|
|
12634
|
-
out.usage = preserveRequestedServiceTierEvidence(
|
|
12635
|
-
out.usage,
|
|
12636
|
-
options.serviceTier,
|
|
12637
|
-
);
|
|
12638
|
-
delete out.responseId;
|
|
12639
|
-
const providerReceipt = supportsOpenAIRequestIdentity
|
|
12640
|
-
? _openAIProviderRequestReceipt(response, data, providerRequestId)
|
|
12641
|
-
: null;
|
|
12642
|
-
if (providerReceipt) out.providerReceipt = providerReceipt;
|
|
12643
|
-
return out;
|
|
12736
|
+
);
|
|
12644
12737
|
}
|
|
12645
12738
|
|
|
12646
12739
|
// Real token streaming (--include-partial-messages) for every OpenAI-compatible
|
|
@@ -12695,7 +12788,7 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
12695
12788
|
method: "POST",
|
|
12696
12789
|
headers: {
|
|
12697
12790
|
"Content-Type": "application/json",
|
|
12698
|
-
Authorization: `Bearer ${key}
|
|
12791
|
+
...(key ? { Authorization: `Bearer ${key}` } : {}),
|
|
12699
12792
|
...(supportsOpenAIRequestIdentity && providerRequestId
|
|
12700
12793
|
? { "X-Client-Request-Id": providerRequestId }
|
|
12701
12794
|
: {}),
|
|
@@ -12989,6 +13082,36 @@ const STREAM_STALL_MS = 20000;
|
|
|
12989
13082
|
const STREAM_STALL_TIMEOUT_MS = 180000;
|
|
12990
13083
|
const _STREAM_STALL = Symbol("stream-stall");
|
|
12991
13084
|
|
|
13085
|
+
/** Bound the whole JSON attempt, including headers and a stalled body read. */
|
|
13086
|
+
async function _withResponsesRequestDeadline(
|
|
13087
|
+
operation,
|
|
13088
|
+
signal,
|
|
13089
|
+
configuredTimeout,
|
|
13090
|
+
) {
|
|
13091
|
+
const timeoutMs = configuredTimeout ?? STREAM_STALL_TIMEOUT_MS;
|
|
13092
|
+
if (timeoutMs === 0) return operation(signal);
|
|
13093
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 0 || timeoutMs > 2_147_483_647)
|
|
13094
|
+
throw new RangeError("Invalid Responses request timeout");
|
|
13095
|
+
const controller = new AbortController();
|
|
13096
|
+
const requestSignal = signal
|
|
13097
|
+
? AbortSignal.any([signal, controller.signal])
|
|
13098
|
+
: controller.signal;
|
|
13099
|
+
const timer = setTimeout(() => {
|
|
13100
|
+
const error = new Error(
|
|
13101
|
+
"OpenAI Responses request timed out before its complete response was received",
|
|
13102
|
+
);
|
|
13103
|
+
error.code = "ETIMEDOUT";
|
|
13104
|
+
controller.abort(error);
|
|
13105
|
+
}, timeoutMs);
|
|
13106
|
+
timer.unref?.();
|
|
13107
|
+
try {
|
|
13108
|
+
throwIfAborted(requestSignal);
|
|
13109
|
+
return await raceWithAbort(operation(requestSignal), requestSignal);
|
|
13110
|
+
} finally {
|
|
13111
|
+
clearTimeout(timer);
|
|
13112
|
+
}
|
|
13113
|
+
}
|
|
13114
|
+
|
|
12992
13115
|
/**
|
|
12993
13116
|
* Run a streaming chat attempt with bounded auto-retry on transient API
|
|
12994
13117
|
* connection drops (Claude-Code 2.1.181: "auto-retry for API connection drops
|
|
@@ -13118,7 +13241,9 @@ export async function* _iterateStreamWithStall(reader, opts = {}) {
|
|
|
13118
13241
|
// socket) and throw a retryable error the dispatch seam re-issues.
|
|
13119
13242
|
if (timeoutMs && elapsed >= timeoutMs) {
|
|
13120
13243
|
try {
|
|
13121
|
-
|
|
13244
|
+
// Cancellation itself can hang on a broken transport. Observe its
|
|
13245
|
+
// rejection, but never let it postpone the authoritative timeout.
|
|
13246
|
+
void Promise.resolve(reader.cancel()).catch(() => {});
|
|
13122
13247
|
} catch {
|
|
13123
13248
|
/* best-effort release — the throw below is what matters */
|
|
13124
13249
|
}
|
|
@@ -13315,7 +13440,7 @@ async function _chatOpenAIStreaming(
|
|
|
13315
13440
|
method: "POST",
|
|
13316
13441
|
headers: {
|
|
13317
13442
|
"Content-Type": "application/json",
|
|
13318
|
-
Authorization: `Bearer ${apiKey}
|
|
13443
|
+
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
13319
13444
|
...(providerRequestId
|
|
13320
13445
|
? { "X-Client-Request-Id": providerRequestId }
|
|
13321
13446
|
: {}),
|
|
@@ -13416,7 +13541,7 @@ async function _chatOpenAIResponsesStreaming(
|
|
|
13416
13541
|
const decoder = new TextDecoder();
|
|
13417
13542
|
let buf = "";
|
|
13418
13543
|
try {
|
|
13419
|
-
for await (const value of _iterateStreamWithStall(reader, {
|
|
13544
|
+
responses: for await (const value of _iterateStreamWithStall(reader, {
|
|
13420
13545
|
onStall,
|
|
13421
13546
|
stallMs,
|
|
13422
13547
|
stallTimeoutMs,
|
|
@@ -13427,6 +13552,22 @@ async function _chatOpenAIResponsesStreaming(
|
|
|
13427
13552
|
for (const line of lines) {
|
|
13428
13553
|
reduceOpenAIResponsesStreamLine(state, line, ui.onToken, ui.onThinking);
|
|
13429
13554
|
await ui.settle();
|
|
13555
|
+
if (state.terminalError) throw state.terminalError;
|
|
13556
|
+
if (state.normalized) {
|
|
13557
|
+
// response.completed/incomplete is authoritative. Some gateways
|
|
13558
|
+
// leave the HTTP stream alive after it; socket EOF is not required.
|
|
13559
|
+
// Observe an already-queued transport error (especially cancellation)
|
|
13560
|
+
// without waiting for a pending trailing read to finish.
|
|
13561
|
+
await Promise.race([reader.read(), Promise.resolve()]);
|
|
13562
|
+
throwIfAborted(signal);
|
|
13563
|
+
try {
|
|
13564
|
+
void Promise.resolve(reader.cancel()).catch(() => {});
|
|
13565
|
+
} catch {
|
|
13566
|
+
// The terminal response is already complete; cleanup is best effort.
|
|
13567
|
+
}
|
|
13568
|
+
buf = "";
|
|
13569
|
+
break responses;
|
|
13570
|
+
}
|
|
13430
13571
|
}
|
|
13431
13572
|
}
|
|
13432
13573
|
if (buf.trim()) {
|
|
@@ -13468,6 +13609,12 @@ async function _chatOpenAIResponsesStreaming(
|
|
|
13468
13609
|
);
|
|
13469
13610
|
if (providerReceipt) out.providerReceipt = providerReceipt;
|
|
13470
13611
|
return out;
|
|
13612
|
+
} finally {
|
|
13613
|
+
try {
|
|
13614
|
+
reader.releaseLock?.();
|
|
13615
|
+
} catch {
|
|
13616
|
+
// A cancelled or still-settling reader can reject lock release.
|
|
13617
|
+
}
|
|
13471
13618
|
}
|
|
13472
13619
|
const out = finalizeOpenAIResponsesStream(state);
|
|
13473
13620
|
if (out.usage)
|
|
@@ -15018,6 +15165,7 @@ export async function* agentLoop(messages, options) {
|
|
|
15018
15165
|
let effectiveToolOptions = hermeticExecution
|
|
15019
15166
|
? {
|
|
15020
15167
|
...options,
|
|
15168
|
+
fileMutationScope,
|
|
15021
15169
|
hostManagedToolPolicy: null,
|
|
15022
15170
|
hostManagedToolPolicyAuthority: null,
|
|
15023
15171
|
extraToolDefinitions: [],
|
|
@@ -467,15 +467,15 @@ export function resolveEnabledTools({ allowedTools, readOnly } = {}) {
|
|
|
467
467
|
return names;
|
|
468
468
|
}
|
|
469
469
|
|
|
470
|
-
/**
|
|
470
|
+
/** An absent CLI list is unrestricted; an explicit empty list permits none. */
|
|
471
471
|
export function parseToolList(value) {
|
|
472
|
-
if (
|
|
472
|
+
if (value === null || value === undefined) return null;
|
|
473
473
|
if (Array.isArray(value)) return value.flatMap((v) => parseToolList(v) || []);
|
|
474
474
|
const out = String(value)
|
|
475
475
|
.split(/[,\s]+/)
|
|
476
476
|
.map((s) => s.trim())
|
|
477
477
|
.filter(Boolean);
|
|
478
|
-
return out
|
|
478
|
+
return out;
|
|
479
479
|
}
|
|
480
480
|
|
|
481
481
|
/**
|