@byfriends/sdk 0.2.5 → 0.3.0

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/dist/index.mjs CHANGED
@@ -8375,47 +8375,202 @@ function resolveAuthBackedClient(state, auth, build) {
8375
8375
  return build(auth);
8376
8376
  }
8377
8377
  //#endregion
8378
- //#region ../kosong/src/providers/anthropic.ts
8378
+ //#region ../kosong/src/providers/base-chat-provider.ts
8379
8379
  /**
8380
- * Normalize an Anthropic `stop_reason` string to the unified
8381
- * {@link FinishReason} enum.
8380
+ * Abstract base implementing the SDK-agnostic ChatProvider boilerplate.
8382
8381
  *
8383
- * Source: `message.stop_reason` (non-stream) or the last `message_delta`
8384
- * event's `delta.stop_reason` (stream).
8382
+ * Subclasses must implement:
8383
+ * - `generate(...)` — the streaming/dispatch loop (protocol-specific)
8384
+ * - `createRawClient(auth, defaultHeaders)` — `new OpenAI(...)` / `new Anthropic(...)` / etc.
8385
+ * - `thinkingEffort` getter — per-provider effort mapping
8386
+ * - `getCapability(model?)` — per-provider capability registry lookup
8387
+ * - `withThinking(effort)` — per-provider thinking configuration
8388
+ *
8389
+ * Subclasses inherit: `_clone`, `withGenerationKwargs`, `modelName`,
8390
+ * `modelParameters`, and the `_createClient` shell.
8385
8391
  */
8386
- function normalizeAnthropicStopReason(raw) {
8387
- if (raw === null || raw === void 0) return {
8388
- finishReason: null,
8389
- rawFinishReason: null
8390
- };
8391
- switch (raw) {
8392
- case "end_turn":
8393
- case "stop_sequence": return {
8394
- finishReason: "completed",
8395
- rawFinishReason: raw
8396
- };
8397
- case "max_tokens": return {
8398
- finishReason: "truncated",
8399
- rawFinishReason: raw
8400
- };
8401
- case "tool_use": return {
8402
- finishReason: "tool_calls",
8403
- rawFinishReason: raw
8392
+ var BaseChatProvider = class {
8393
+ _model;
8394
+ _generationKwargs;
8395
+ _apiKey;
8396
+ _baseUrl;
8397
+ _defaultHeaders;
8398
+ _client;
8399
+ _clientFactory;
8400
+ constructor(_model, _generationKwargs, _apiKey = void 0, _baseUrl = "", _defaultHeaders = void 0, _client = void 0, _clientFactory = void 0) {
8401
+ this._model = _model;
8402
+ this._generationKwargs = _generationKwargs;
8403
+ this._apiKey = _apiKey;
8404
+ this._baseUrl = _baseUrl;
8405
+ this._defaultHeaders = _defaultHeaders;
8406
+ this._client = _client;
8407
+ this._clientFactory = _clientFactory;
8408
+ }
8409
+ get modelName() {
8410
+ return this._model;
8411
+ }
8412
+ get modelParameters() {
8413
+ return {
8414
+ model: this._model,
8415
+ ...this._generationKwargs
8404
8416
  };
8405
- case "pause_turn": return {
8406
- finishReason: "paused",
8407
- rawFinishReason: raw
8417
+ }
8418
+ /**
8419
+ * Return a shallow copy of this provider with `kwargs` merged into the
8420
+ * generation-keyword bag. The clone shares transport state (client) with
8421
+ * the original; only `_generationKwargs` is deep-copied.
8422
+ */
8423
+ withGenerationKwargs(kwargs) {
8424
+ const clone = this._clone();
8425
+ clone._generationKwargs = {
8426
+ ...clone._generationKwargs,
8427
+ ...kwargs
8408
8428
  };
8409
- case "refusal": return {
8410
- finishReason: "filtered",
8411
- rawFinishReason: raw
8429
+ return clone;
8430
+ }
8431
+ /**
8432
+ * Shallow clone preserving prototype and instance state, with a fresh
8433
+ * `_generationKwargs` copy. Subclasses with extra clone-time cleanup
8434
+ * (e.g. resetting a lazy `_files` cache) override and call `super._clone()`
8435
+ * then apply their cleanup.
8436
+ */
8437
+ _clone() {
8438
+ const clone = Object.assign(Object.create(Object.getPrototypeOf(this)), this);
8439
+ clone._generationKwargs = { ...this._generationKwargs };
8440
+ return clone;
8441
+ }
8442
+ /**
8443
+ * Resolve the SDK client for the current request, using cached/client-factory
8444
+ * auth resolution. Delegates the actual SDK construction to
8445
+ * {@link createRawClient}. The provider name passed to `requireProviderApiKey`
8446
+ * is the subclass's `name`.
8447
+ */
8448
+ _createClient(auth) {
8449
+ return resolveAuthBackedClient({
8450
+ cachedClient: this._client,
8451
+ clientFactory: this._clientFactory
8452
+ }, auth, (a) => {
8453
+ const defaultHeaders = mergeRequestHeaders(this._defaultHeaders, a?.headers);
8454
+ return this.createRawClient({
8455
+ apiKey: requireProviderApiKey(this.name, a, this._apiKey),
8456
+ headers: defaultHeaders
8457
+ }, defaultHeaders);
8458
+ });
8459
+ }
8460
+ };
8461
+ //#endregion
8462
+ //#region ../kosong/src/providers/base-streamed-message.ts
8463
+ var BaseStreamedMessage = class {
8464
+ _id = null;
8465
+ _usage = null;
8466
+ _finishReason = null;
8467
+ _rawFinishReason = null;
8468
+ _iter;
8469
+ get id() {
8470
+ return this._id;
8471
+ }
8472
+ get usage() {
8473
+ return this._usage;
8474
+ }
8475
+ get finishReason() {
8476
+ return this._finishReason;
8477
+ }
8478
+ get rawFinishReason() {
8479
+ return this._rawFinishReason;
8480
+ }
8481
+ async *[Symbol.asyncIterator]() {
8482
+ this._iter ??= this._buildIter();
8483
+ yield* this._iter;
8484
+ }
8485
+ };
8486
+ //#endregion
8487
+ //#region ../kosong/src/providers/provider-common.ts
8488
+ /**
8489
+ * Build a finish-reason normalizer from a per-provider raw-string → FinishReason table.
8490
+ *
8491
+ * Mirrors the shape of the per-adapter `normalizeXxxFinishReason` functions:
8492
+ * - `null` / `undefined` raw → `{ finishReason: null, rawFinishReason: null }`
8493
+ * - raw present and in the table → mapped FinishReason, raw echoed back
8494
+ * - raw present but not in the table → `'other'`, raw echoed back
8495
+ *
8496
+ * The returned function is stateless and safe to call repeatedly.
8497
+ */
8498
+ function makeFinishReasonNormalizer(mapping) {
8499
+ return (raw) => {
8500
+ if (raw === null || raw === void 0) return {
8501
+ finishReason: null,
8502
+ rawFinishReason: null
8412
8503
  };
8413
- default: return {
8414
- finishReason: "other",
8504
+ return {
8505
+ finishReason: mapping[raw] ?? "other",
8415
8506
  rawFinishReason: raw
8416
8507
  };
8417
- }
8508
+ };
8418
8509
  }
8510
+ /**
8511
+ * Build the four-field `TokenUsage` from already-parsed per-provider numbers,
8512
+ * applying the `inputOther = total - cached` formula shared by OpenAI-style and
8513
+ * Google providers (which expose only a total prompt count and a cached subset).
8514
+ *
8515
+ * `inputOther` is clamped to ≥ 0 when `cached` exceeds `total` (defensive — a
8516
+ * provider should never report more cached than total, but we never emit a
8517
+ * negative usage field). Anthropic is excluded: it reports a real
8518
+ * `inputCacheCreation` field that does not fit this formula.
8519
+ */
8520
+ function extractCacheUsage(total, cached, output) {
8521
+ return {
8522
+ inputOther: Math.max(0, total - cached),
8523
+ output,
8524
+ inputCacheRead: cached,
8525
+ inputCacheCreation: 0
8526
+ };
8527
+ }
8528
+ const NETWORK_RE$1 = /network|connection|connect|disconnect/i;
8529
+ const TIMEOUT_RE$1 = /timed?\s*out|timeout|deadline/i;
8530
+ /**
8531
+ * Convert a raw thrown value into a kosong `ChatProviderError` using the
8532
+ * shared message-based classification ladder:
8533
+ *
8534
+ * 1. already a `ChatProviderError` → returned as-is (identity)
8535
+ * 2. `status` provided → `normalizeAPIStatusError` (status + message + requestId)
8536
+ * 3. message matches `TIMEOUT_RE` → `APITimeoutError`
8537
+ * 4. message matches `NETWORK_RE` or any `extraNetworkMatchers`, or the value
8538
+ * is a `TypeError` matching `extraTypeErrorMatch` → `APIConnectionError`
8539
+ * 5. otherwise → `ChatProviderError` wrapping the message
8540
+ *
8541
+ * Provider adapters that recognize SDK-specific error classes (e.g. OpenAI's
8542
+ * `APIConnectionTimeoutError`, Google's `GoogleApiError`) should unwrap them
8543
+ * into `(message, status?, requestId?)` before calling this function. The
8544
+ * SDK-class detection itself is provider-specific and stays in the adapter.
8545
+ */
8546
+ function convertProviderError(error, opts = {}) {
8547
+ if (error instanceof ChatProviderError) return error;
8548
+ const message = error instanceof Error ? error.message : String(error);
8549
+ if (typeof opts.status === "number") return normalizeAPIStatusError(opts.status, message, opts.requestId);
8550
+ if (TIMEOUT_RE$1.test(message)) return new APITimeoutError(message);
8551
+ if (NETWORK_RE$1.test(message)) return new APIConnectionError$3(message);
8552
+ if (opts.extraNetworkMatchers?.some((re) => re.test(message))) return new APIConnectionError$3(message);
8553
+ if (opts.extraTypeErrorMatch !== void 0 && error instanceof TypeError && message.includes(opts.extraTypeErrorMatch)) return new APIConnectionError$3(message);
8554
+ if (error instanceof Error) return new ChatProviderError(`Error: ${message}`);
8555
+ return new ChatProviderError(`Error: ${String(error)}`);
8556
+ }
8557
+ //#endregion
8558
+ //#region ../kosong/src/providers/anthropic.ts
8559
+ /**
8560
+ * Normalize an Anthropic `stop_reason` string to the unified
8561
+ * {@link FinishReason} enum.
8562
+ *
8563
+ * Source: `message.stop_reason` (non-stream) or the last `message_delta`
8564
+ * event's `delta.stop_reason` (stream).
8565
+ */
8566
+ const normalizeAnthropicStopReason = makeFinishReasonNormalizer({
8567
+ end_turn: "completed",
8568
+ stop_sequence: "completed",
8569
+ max_tokens: "truncated",
8570
+ tool_use: "tool_calls",
8571
+ pause_turn: "paused",
8572
+ refusal: "filtered"
8573
+ });
8419
8574
  const INTERLEAVED_THINKING_BETA = "interleaved-thinking-2025-05-14";
8420
8575
  const FAMILY_VERSION_RE = /(?:opus|sonnet|haiku)[.-](\d+)[.-](\d{1,2})(?!\d)/;
8421
8576
  const OPUS_VERSION_RE = /opus[.-](\d+)[.-](\d{1,2})(?!\d)/;
@@ -8729,35 +8884,23 @@ function convertAnthropicError(error) {
8729
8884
  if (error instanceof Error) return new ChatProviderError(`Error: ${error.message}`);
8730
8885
  return new ChatProviderError(`Error: ${String(error)}`);
8731
8886
  }
8732
- var AnthropicStreamedMessage = class {
8733
- _id = null;
8734
- _usage = {
8735
- inputOther: 0,
8736
- output: 0,
8737
- inputCacheRead: 0,
8738
- inputCacheCreation: 0
8739
- };
8740
- _finishReason = null;
8741
- _rawFinishReason = null;
8742
- _iter;
8887
+ var AnthropicStreamedMessage = class extends BaseStreamedMessage {
8888
+ _response;
8889
+ _isStream;
8743
8890
  constructor(response, isStream) {
8744
- if (isStream) this._iter = this._convertStreamResponse(response);
8745
- else this._iter = this._convertNonStreamResponse(response);
8746
- }
8747
- get id() {
8748
- return this._id;
8749
- }
8750
- get usage() {
8751
- return this._usage;
8752
- }
8753
- get finishReason() {
8754
- return this._finishReason;
8755
- }
8756
- get rawFinishReason() {
8757
- return this._rawFinishReason;
8891
+ super();
8892
+ this._response = response;
8893
+ this._isStream = isStream;
8894
+ this._usage = {
8895
+ inputOther: 0,
8896
+ output: 0,
8897
+ inputCacheRead: 0,
8898
+ inputCacheCreation: 0
8899
+ };
8758
8900
  }
8759
- async *[Symbol.asyncIterator]() {
8760
- yield* this._iter;
8901
+ _buildIter() {
8902
+ if (this._isStream) return this._convertStreamResponse(this._response);
8903
+ return this._convertNonStreamResponse(this._response);
8761
8904
  }
8762
8905
  _captureStopReason(raw) {
8763
8906
  const normalized = normalizeAnthropicStopReason(raw);
@@ -8765,11 +8908,14 @@ var AnthropicStreamedMessage = class {
8765
8908
  this._rawFinishReason = normalized.rawFinishReason;
8766
8909
  }
8767
8910
  _extractUsage(usage) {
8911
+ const inputTokens = usage.input_tokens ?? 0;
8912
+ const cacheRead = usage.cache_read_input_tokens ?? 0;
8913
+ const cacheCreation = usage.cache_creation_input_tokens ?? 0;
8768
8914
  this._usage = {
8769
- inputOther: usage.input_tokens ?? 0,
8915
+ inputOther: Math.max(0, inputTokens - cacheRead - cacheCreation),
8770
8916
  output: usage.output_tokens ?? 0,
8771
- inputCacheRead: usage.cache_read_input_tokens ?? 0,
8772
- inputCacheCreation: usage.cache_creation_input_tokens ?? 0
8917
+ inputCacheRead: cacheRead,
8918
+ inputCacheCreation: cacheCreation
8773
8919
  };
8774
8920
  }
8775
8921
  async *_convertNonStreamResponse(response) {
@@ -8894,9 +9040,16 @@ var AnthropicStreamedMessage = class {
8894
9040
  const deltaUsage = evt.usage;
8895
9041
  if (deltaUsage !== void 0) {
8896
9042
  if (typeof deltaUsage["output_tokens"] === "number") this._usage.output = deltaUsage["output_tokens"];
9043
+ const prevInputOther = this._usage.inputOther;
9044
+ const prevCacheRead = this._usage.inputCacheRead;
9045
+ const prevCacheCreation = this._usage.inputCacheCreation;
8897
9046
  if (typeof deltaUsage["cache_read_input_tokens"] === "number") this._usage.inputCacheRead = deltaUsage["cache_read_input_tokens"];
8898
9047
  if (typeof deltaUsage["cache_creation_input_tokens"] === "number") this._usage.inputCacheCreation = deltaUsage["cache_creation_input_tokens"];
8899
- if (typeof deltaUsage["input_tokens"] === "number") this._usage.inputOther = deltaUsage["input_tokens"];
9048
+ if (typeof deltaUsage["input_tokens"] === "number") this._usage.inputOther = Math.max(0, deltaUsage["input_tokens"] - this._usage.inputCacheRead - this._usage.inputCacheCreation);
9049
+ else {
9050
+ const totalInput = prevInputOther + prevCacheRead + prevCacheCreation;
9051
+ this._usage.inputOther = Math.max(0, totalInput - this._usage.inputCacheRead - this._usage.inputCacheCreation);
9052
+ }
8900
9053
  }
8901
9054
  const messageDeltaPayload = evt.delta;
8902
9055
  if (messageDeltaPayload !== void 0 && "stop_reason" in messageDeltaPayload) this._captureStopReason(messageDeltaPayload["stop_reason"]);
@@ -8907,31 +9060,22 @@ var AnthropicStreamedMessage = class {
8907
9060
  }
8908
9061
  }
8909
9062
  };
8910
- var AnthropicChatProvider = class {
9063
+ var AnthropicChatProvider = class AnthropicChatProvider extends BaseChatProvider {
8911
9064
  name = "anthropic";
8912
- _model;
8913
9065
  _stream;
8914
- _client;
8915
- _generationKwargs;
8916
9066
  _metadata;
8917
- _apiKey;
8918
- _baseUrl;
8919
- _defaultHeaders;
8920
- _clientFactory;
8921
9067
  constructor(options) {
8922
- this._model = options.model;
8923
- this._stream = options.stream ?? true;
8924
- this._metadata = options.metadata;
8925
- const apiKey = options.apiKey ?? process.env["ANTHROPIC_API_KEY"];
8926
- this._apiKey = apiKey === void 0 || apiKey.length === 0 ? void 0 : apiKey;
8927
- this._baseUrl = options.baseUrl;
8928
- this._defaultHeaders = options.defaultHeaders;
8929
- this._clientFactory = options.clientFactory;
8930
- this._client = this._apiKey === void 0 ? void 0 : this._buildClient(this._apiKey);
8931
- this._generationKwargs = {
9068
+ const apiKey = options.apiKey === void 0 || options.apiKey.length === 0 ? process.env["ANTHROPIC_API_KEY"] ?? void 0 : options.apiKey;
9069
+ const apiKeyResolved = apiKey === void 0 || apiKey.length === 0 ? void 0 : apiKey;
9070
+ const baseUrl = options.baseUrl;
9071
+ const client = apiKeyResolved === void 0 ? void 0 : AnthropicChatProvider.buildClient(apiKeyResolved, baseUrl, options.defaultHeaders);
9072
+ const generationKwargs = {
8932
9073
  max_tokens: resolveDefaultMaxTokens(options.model, options.defaultMaxTokens),
8933
9074
  betaFeatures: options.betaFeatures ?? [INTERLEAVED_THINKING_BETA]
8934
9075
  };
9076
+ super(options.model, generationKwargs, apiKeyResolved, baseUrl ?? "", options.defaultHeaders, client, options.clientFactory);
9077
+ this._stream = options.stream ?? true;
9078
+ this._metadata = options.metadata;
8935
9079
  }
8936
9080
  get modelName() {
8937
9081
  return this._model;
@@ -9024,24 +9168,25 @@ var AnthropicChatProvider = class {
9024
9168
  throw convertAnthropicError(error);
9025
9169
  }
9026
9170
  }
9027
- _createClient(auth) {
9028
- return resolveAuthBackedClient({
9029
- cachedClient: this._client,
9030
- clientFactory: this._clientFactory
9031
- }, auth, (a) => this._buildClient(requireProviderApiKey("AnthropicChatProvider", a, this._apiKey)));
9032
- }
9033
- _buildClient(apiKey) {
9171
+ static buildClient(apiKey, baseUrl, defaultHeaders) {
9034
9172
  return new Anthropic({
9035
9173
  apiKey,
9174
+ baseURL: baseUrl,
9175
+ defaultHeaders
9176
+ });
9177
+ }
9178
+ createRawClient(auth, defaultHeaders) {
9179
+ return new Anthropic({
9180
+ apiKey: auth.apiKey,
9036
9181
  baseURL: this._baseUrl,
9037
- defaultHeaders: this._defaultHeaders
9182
+ defaultHeaders
9038
9183
  });
9039
9184
  }
9040
9185
  withThinking(effort) {
9041
9186
  if (effort === "off") {
9042
9187
  let newBetas = [...this._generationKwargs.betaFeatures ?? []];
9043
9188
  newBetas = newBetas.filter((b) => b !== INTERLEAVED_THINKING_BETA);
9044
- const clone = this._withGenerationKwargs({
9189
+ const clone = this.withGenerationKwargs({
9045
9190
  thinking: { type: "disabled" },
9046
9191
  betaFeatures: newBetas
9047
9192
  });
@@ -9052,7 +9197,7 @@ var AnthropicChatProvider = class {
9052
9197
  if (effectiveEffort === "off") throw new Error("Non-off thinking effort unexpectedly clamped to off.");
9053
9198
  let newBetas = [...this._generationKwargs.betaFeatures ?? []];
9054
9199
  newBetas = newBetas.filter((b) => b !== INTERLEAVED_THINKING_BETA);
9055
- return this._withGenerationKwargs({
9200
+ return this.withGenerationKwargs({
9056
9201
  thinking: {
9057
9202
  type: "adaptive",
9058
9203
  display: "summarized"
@@ -9061,22 +9206,6 @@ var AnthropicChatProvider = class {
9061
9206
  betaFeatures: newBetas
9062
9207
  });
9063
9208
  }
9064
- withGenerationKwargs(kwargs) {
9065
- return this._withGenerationKwargs(kwargs);
9066
- }
9067
- _withGenerationKwargs(kwargs) {
9068
- const clone = this._clone();
9069
- clone._generationKwargs = {
9070
- ...clone._generationKwargs,
9071
- ...kwargs
9072
- };
9073
- return clone;
9074
- }
9075
- _clone() {
9076
- const clone = Object.assign(Object.create(Object.getPrototypeOf(this)), this);
9077
- clone._generationKwargs = { ...this._generationKwargs };
9078
- return clone;
9079
- }
9080
9209
  };
9081
9210
  //#endregion
9082
9211
  //#region ../../node_modules/.pnpm/retry@0.13.1/node_modules/retry/lib/retry_operation.js
@@ -37547,30 +37676,19 @@ function messagesToGoogleGenAIContents(messages) {
37547
37676
  }
37548
37677
  return contents;
37549
37678
  }
37550
- var GoogleGenAIStreamedMessage = class {
37551
- _id = null;
37552
- _usage = null;
37553
- _finishReason = null;
37554
- _rawFinishReason = null;
37555
- _iter;
37679
+ var GoogleGenAIStreamedMessage = class extends BaseStreamedMessage {
37680
+ _response;
37681
+ _isStream;
37682
+ _signal;
37556
37683
  constructor(response, isStream, signal) {
37557
- if (isStream) this._iter = this._convertStreamResponse(response, signal);
37558
- else this._iter = this._convertNonStreamResponse(response, signal);
37559
- }
37560
- get id() {
37561
- return this._id;
37562
- }
37563
- get usage() {
37564
- return this._usage;
37565
- }
37566
- get finishReason() {
37567
- return this._finishReason;
37568
- }
37569
- get rawFinishReason() {
37570
- return this._rawFinishReason;
37684
+ super();
37685
+ this._response = response;
37686
+ this._isStream = isStream;
37687
+ this._signal = signal;
37571
37688
  }
37572
- async *[Symbol.asyncIterator]() {
37573
- yield* this._iter;
37689
+ _buildIter() {
37690
+ if (this._isStream) return this._convertStreamResponse(this._response, this._signal);
37691
+ return this._convertNonStreamResponse(this._response, this._signal);
37574
37692
  }
37575
37693
  _captureFinishReason(response) {
37576
37694
  const candidates = response["candidates"];
@@ -37626,12 +37744,8 @@ var GoogleGenAIStreamedMessage = class {
37626
37744
  if (usageMetadata) {
37627
37745
  const promptTokenCount = typeof usageMetadata["promptTokenCount"] === "number" ? usageMetadata["promptTokenCount"] : 0;
37628
37746
  const cachedContentTokenCount = typeof usageMetadata["cachedContentTokenCount"] === "number" ? usageMetadata["cachedContentTokenCount"] : 0;
37629
- this._usage = {
37630
- inputOther: Math.max(promptTokenCount - cachedContentTokenCount, 0),
37631
- output: usageMetadata["candidatesTokenCount"] ?? 0,
37632
- inputCacheRead: cachedContentTokenCount,
37633
- inputCacheCreation: 0
37634
- };
37747
+ const output = usageMetadata["candidatesTokenCount"] ?? 0;
37748
+ this._usage = extractCacheUsage(promptTokenCount, cachedContentTokenCount, output);
37635
37749
  }
37636
37750
  }
37637
37751
  /** Extract response ID from a response chunk. */
@@ -37669,59 +37783,44 @@ var GoogleGenAIStreamedMessage = class {
37669
37783
  }
37670
37784
  }
37671
37785
  };
37672
- const NETWORK_RE$1 = /network|connection|connect|disconnect|fetch failed/i;
37673
- const TIMEOUT_RE$1 = /timed?\s*out|timeout|deadline/i;
37674
37786
  /**
37675
37787
  * Convert a Google GenAI SDK error (or raw Error) to a kosong `ChatProviderError`.
37676
37788
  */
37677
37789
  function convertGoogleGenAIError(error) {
37678
37790
  if (error instanceof ApiError) return normalizeAPIStatusError(error.status, error.message);
37679
- if (error instanceof Error) {
37680
- const msg = error.message;
37681
- if (TIMEOUT_RE$1.test(msg)) return new APITimeoutError(msg);
37682
- if (NETWORK_RE$1.test(msg) || error instanceof TypeError && msg.includes("fetch")) return new APIConnectionError$3(msg);
37683
- const statusCode = error.code;
37684
- if (typeof statusCode === "number") return normalizeAPIStatusError(statusCode, msg);
37685
- return new ChatProviderError(`GoogleGenAI error: ${msg}`);
37686
- }
37687
- return new ChatProviderError(`GoogleGenAI error: ${String(error)}`);
37791
+ const statusCode = error.code;
37792
+ if (error instanceof Error && typeof statusCode === "number") return normalizeAPIStatusError(statusCode, error.message);
37793
+ return convertProviderError(error, {
37794
+ extraNetworkMatchers: [/^fetch failed$/i],
37795
+ extraTypeErrorMatch: "fetch"
37796
+ });
37688
37797
  }
37689
- var GoogleGenAIChatProvider = class {
37798
+ var GoogleGenAIChatProvider = class GoogleGenAIChatProvider extends BaseChatProvider {
37690
37799
  name = "google_genai";
37691
- _model;
37692
- _client;
37693
- _generationKwargs;
37694
- _vertexai;
37695
37800
  _stream;
37696
- _apiKey;
37801
+ _vertexai;
37697
37802
  _project;
37698
37803
  _location;
37699
- _clientFactory;
37700
37804
  constructor(options) {
37701
- this._model = options.model;
37702
- this._vertexai = options.vertexai ?? false;
37703
- this._stream = options.stream ?? true;
37704
- this._generationKwargs = {};
37705
37805
  const apiKey = options.apiKey ?? process.env["GOOGLE_API_KEY"];
37706
- this._apiKey = apiKey === void 0 || apiKey.length === 0 ? void 0 : apiKey;
37806
+ const apiKeyResolved = apiKey === void 0 || apiKey.length === 0 ? void 0 : apiKey;
37807
+ const client = (options.vertexai ?? false) || apiKeyResolved !== void 0 ? GoogleGenAIChatProvider.buildClient(apiKeyResolved, options.vertexai ?? false, options.project, options.location) : void 0;
37808
+ super(options.model, {}, apiKeyResolved, "", void 0, client, options.clientFactory);
37809
+ this._stream = options.stream ?? true;
37810
+ this._vertexai = options.vertexai ?? false;
37707
37811
  this._project = options.project;
37708
37812
  this._location = options.location;
37709
- this._clientFactory = options.clientFactory;
37710
- this._client = this._vertexai || this._apiKey !== void 0 ? this._buildClient(this._apiKey) : void 0;
37711
37813
  }
37712
- _buildClient(apiKey) {
37814
+ static buildClient(apiKey, vertexai, project, location) {
37713
37815
  return new GoogleGenAI({
37714
37816
  apiKey,
37715
- ...this._vertexai ? {
37817
+ ...vertexai ? {
37716
37818
  vertexai: true,
37717
- project: this._project,
37718
- location: this._location
37819
+ project,
37820
+ location
37719
37821
  } : {}
37720
37822
  });
37721
37823
  }
37722
- get modelName() {
37723
- return this._model;
37724
- }
37725
37824
  get thinkingEffort() {
37726
37825
  const thinkingConfig = this._generationKwargs.thinking_config;
37727
37826
  if (thinkingConfig === void 0) return null;
@@ -37771,15 +37870,26 @@ var GoogleGenAIChatProvider = class {
37771
37870
  throw convertGoogleGenAIError(error);
37772
37871
  }
37773
37872
  }
37873
+ /**
37874
+ * Override the base auth-resolution path to preserve the Vertex AI
37875
+ * short-circuit: Vertex uses service credentials, not request-scoped keys
37876
+ * or headers, so `requireProviderApiKey` must not be enforced there.
37877
+ */
37774
37878
  _createClient(auth) {
37775
37879
  return resolveAuthBackedClient({
37776
37880
  cachedClient: this._client,
37777
37881
  clientFactory: this._clientFactory
37778
37882
  }, auth, (a) => {
37779
- if (this._vertexai) return this._buildClient(this._apiKey);
37780
- return this._buildClient(requireProviderApiKey("GoogleGenAIChatProvider", a, this._apiKey));
37883
+ if (this._vertexai) return GoogleGenAIChatProvider.buildClient(this._apiKey, this._vertexai, this._project, this._location);
37884
+ return this.createRawClient({
37885
+ apiKey: requireProviderApiKey("GoogleGenAIChatProvider", a, this._apiKey),
37886
+ headers: void 0
37887
+ }, void 0);
37781
37888
  });
37782
37889
  }
37890
+ createRawClient(auth, _defaultHeaders) {
37891
+ return GoogleGenAIChatProvider.buildClient(auth.apiKey, this._vertexai, this._project, this._location);
37892
+ }
37783
37893
  withThinking(effort) {
37784
37894
  const thinkingConfig = { include_thoughts: true };
37785
37895
  if (this._model.includes("gemini-3")) switch (effort) {
@@ -37821,19 +37931,6 @@ var GoogleGenAIChatProvider = class {
37821
37931
  }
37822
37932
  return this.withGenerationKwargs({ thinking_config: thinkingConfig });
37823
37933
  }
37824
- withGenerationKwargs(kwargs) {
37825
- const clone = this._clone();
37826
- clone._generationKwargs = {
37827
- ...clone._generationKwargs,
37828
- ...kwargs
37829
- };
37830
- return clone;
37831
- }
37832
- _clone() {
37833
- const clone = Object.assign(Object.create(Object.getPrototypeOf(this)), this);
37834
- clone._generationKwargs = { ...this._generationKwargs };
37835
- return clone;
37836
- }
37837
37934
  };
37838
37935
  //#endregion
37839
37936
  //#region ../kosong/src/providers/openai-compat-schema.ts
@@ -45165,35 +45262,13 @@ function extractUsage(usage) {
45165
45262
  * - `'content_filter'` → `'filtered'`
45166
45263
  * - any other non-null string → `'other'`
45167
45264
  */
45168
- function normalizeOpenAIFinishReason(raw) {
45169
- if (raw === null || raw === void 0) return {
45170
- finishReason: null,
45171
- rawFinishReason: null
45172
- };
45173
- switch (raw) {
45174
- case "stop": return {
45175
- finishReason: "completed",
45176
- rawFinishReason: raw
45177
- };
45178
- case "tool_calls":
45179
- case "function_call": return {
45180
- finishReason: "tool_calls",
45181
- rawFinishReason: raw
45182
- };
45183
- case "length": return {
45184
- finishReason: "truncated",
45185
- rawFinishReason: raw
45186
- };
45187
- case "content_filter": return {
45188
- finishReason: "filtered",
45189
- rawFinishReason: raw
45190
- };
45191
- default: return {
45192
- finishReason: "other",
45193
- rawFinishReason: raw
45194
- };
45195
- }
45196
- }
45265
+ const normalizeOpenAIFinishReason = makeFinishReasonNormalizer({
45266
+ stop: "completed",
45267
+ tool_calls: "tool_calls",
45268
+ function_call: "tool_calls",
45269
+ length: "truncated",
45270
+ content_filter: "filtered"
45271
+ });
45197
45272
  /**
45198
45273
  * Convert tool-role message content according to the chosen strategy.
45199
45274
  */
@@ -45481,30 +45556,19 @@ function extractUsageFromChunk(chunk) {
45481
45556
  if (choiceUsage !== null && choiceUsage !== void 0 && typeof choiceUsage === "object") return choiceUsage;
45482
45557
  return null;
45483
45558
  }
45484
- var OpenAICompletionsStreamedMessage = class {
45485
- _id = null;
45486
- _usage = null;
45487
- _finishReason = null;
45488
- _rawFinishReason = null;
45489
- _iter;
45559
+ var OpenAICompletionsStreamedMessage = class extends BaseStreamedMessage {
45560
+ _response;
45561
+ _isStream;
45562
+ _reasoningKey;
45490
45563
  constructor(response, isStream, reasoningKey) {
45491
- if (isStream) this._iter = this._convertStreamResponse(response, reasoningKey);
45492
- else this._iter = this._convertNonStreamResponse(response, reasoningKey);
45493
- }
45494
- get id() {
45495
- return this._id;
45496
- }
45497
- get usage() {
45498
- return this._usage;
45499
- }
45500
- get finishReason() {
45501
- return this._finishReason;
45502
- }
45503
- get rawFinishReason() {
45504
- return this._rawFinishReason;
45564
+ super();
45565
+ this._response = response;
45566
+ this._isStream = isStream;
45567
+ this._reasoningKey = reasoningKey;
45505
45568
  }
45506
- async *[Symbol.asyncIterator]() {
45507
- yield* this._iter;
45569
+ _buildIter() {
45570
+ if (this._isStream) return this._convertStreamResponse(this._response, this._reasoningKey);
45571
+ return this._convertNonStreamResponse(this._response, this._reasoningKey);
45508
45572
  }
45509
45573
  _captureFinishReason(raw) {
45510
45574
  const normalized = normalizeOpenAIFinishReason(raw);
@@ -45564,42 +45628,29 @@ var OpenAICompletionsStreamedMessage = class {
45564
45628
  }
45565
45629
  }
45566
45630
  };
45567
- var OpenAICompletionsChatProvider = class {
45631
+ var OpenAICompletionsChatProvider = class extends BaseChatProvider {
45568
45632
  name = "openai-completions";
45569
- _model;
45570
45633
  _stream;
45571
- _apiKey;
45572
- _baseUrl;
45573
- _defaultHeaders;
45574
- _generationKwargs;
45575
45634
  _thinkingEffortKey;
45576
45635
  _reasoningKey;
45577
45636
  _toolMessageConversion;
45578
- _client;
45579
- _clientFactory;
45580
45637
  _files;
45581
45638
  constructor(options) {
45582
- const apiKey = options.apiKey;
45583
- this._apiKey = apiKey === void 0 || apiKey.length === 0 ? void 0 : apiKey;
45584
- this._baseUrl = options.baseUrl ?? "";
45585
- this._defaultHeaders = options.defaultHeaders;
45586
- this._clientFactory = options.clientFactory;
45587
- this._model = options.model;
45639
+ const apiKey = options.apiKey === void 0 || options.apiKey.length === 0 ? void 0 : options.apiKey;
45640
+ const baseUrl = options.baseUrl ?? "";
45641
+ const generationKwargs = { ...options.generationKwargs };
45642
+ const client = apiKey === void 0 ? void 0 : new OpenAI({
45643
+ apiKey,
45644
+ baseURL: baseUrl,
45645
+ defaultHeaders: options.defaultHeaders
45646
+ });
45647
+ super(options.model, generationKwargs, apiKey, baseUrl, options.defaultHeaders, client, options.clientFactory);
45588
45648
  this._stream = options.stream ?? true;
45589
45649
  const normalizedThinkingEffortKey = options.thinkingEffortKey?.trim();
45590
45650
  this._thinkingEffortKey = normalizedThinkingEffortKey !== void 0 && normalizedThinkingEffortKey.length > 0 ? normalizedThinkingEffortKey : "reasoning_effort";
45591
45651
  const normalizedReasoningKey = options.reasoningKey?.trim();
45592
45652
  this._reasoningKey = normalizedReasoningKey !== void 0 && normalizedReasoningKey.length > 0 ? normalizedReasoningKey : void 0;
45593
- this._generationKwargs = { ...options.generationKwargs };
45594
45653
  this._toolMessageConversion = options.toolMessageConversion ?? null;
45595
- this._client = this._apiKey === void 0 ? void 0 : new OpenAI({
45596
- apiKey: this._apiKey,
45597
- baseURL: this._baseUrl,
45598
- defaultHeaders: this._defaultHeaders
45599
- });
45600
- }
45601
- get modelName() {
45602
- return this._model;
45603
45654
  }
45604
45655
  get thinkingEffort() {
45605
45656
  const customValue = this._generationKwargs[this._thinkingEffortKey];
@@ -45685,9 +45736,6 @@ var OpenAICompletionsChatProvider = class {
45685
45736
  const nextEffort = { [this._thinkingEffortKey]: reasoningEffort };
45686
45737
  return this._withGenerationKwargs(nextEffort).withExtraBody({ thinking });
45687
45738
  }
45688
- withGenerationKwargs(kwargs) {
45689
- return this._withGenerationKwargs(kwargs);
45690
- }
45691
45739
  withMaxCompletionTokens(maxCompletionTokens) {
45692
45740
  return this._withGenerationKwargs({ max_completion_tokens: maxCompletionTokens });
45693
45741
  }
@@ -45705,33 +45753,21 @@ var OpenAICompletionsChatProvider = class {
45705
45753
  };
45706
45754
  return this._withGenerationKwargs({ extra_body: merged });
45707
45755
  }
45708
- _createClient(auth) {
45709
- return resolveAuthBackedClient({
45710
- cachedClient: this._client,
45711
- clientFactory: this._clientFactory
45712
- }, auth, (a) => {
45713
- const defaultHeaders = mergeRequestHeaders(this._defaultHeaders, a?.headers);
45714
- return new OpenAI({
45715
- apiKey: requireProviderApiKey("OpenAICompletionsChatProvider", a, this._apiKey),
45716
- baseURL: this._baseUrl,
45717
- defaultHeaders
45718
- });
45719
- });
45720
- }
45721
45756
  _withGenerationKwargs(kwargs) {
45722
- const clone = this._clone();
45723
- clone._generationKwargs = {
45724
- ...clone._generationKwargs,
45725
- ...kwargs
45726
- };
45727
- return clone;
45757
+ return this.withGenerationKwargs(kwargs);
45728
45758
  }
45729
45759
  _clone() {
45730
- const clone = Object.assign(Object.create(Object.getPrototypeOf(this)), this);
45731
- clone._generationKwargs = { ...this._generationKwargs };
45760
+ const clone = super._clone();
45732
45761
  clone._files = void 0;
45733
45762
  return clone;
45734
45763
  }
45764
+ createRawClient(auth, defaultHeaders) {
45765
+ return new OpenAI({
45766
+ apiKey: auth.apiKey,
45767
+ baseURL: this._baseUrl,
45768
+ defaultHeaders
45769
+ });
45770
+ }
45735
45771
  };
45736
45772
  //#endregion
45737
45773
  //#region ../kosong/src/providers/openai-responses.ts
@@ -46039,30 +46075,17 @@ function convertTool(tool) {
46039
46075
  strict: false
46040
46076
  };
46041
46077
  }
46042
- var OpenAIResponsesStreamedMessage = class {
46043
- _id = null;
46044
- _usage = null;
46045
- _finishReason = null;
46046
- _rawFinishReason = null;
46047
- _iter;
46078
+ var OpenAIResponsesStreamedMessage = class extends BaseStreamedMessage {
46079
+ _response;
46080
+ _isStream;
46048
46081
  constructor(response, isStream) {
46049
- if (isStream) this._iter = this._convertStreamResponse(response);
46050
- else this._iter = this._convertNonStreamResponse(response);
46051
- }
46052
- get id() {
46053
- return this._id;
46054
- }
46055
- get usage() {
46056
- return this._usage;
46057
- }
46058
- get finishReason() {
46059
- return this._finishReason;
46060
- }
46061
- get rawFinishReason() {
46062
- return this._rawFinishReason;
46082
+ super();
46083
+ this._response = response;
46084
+ this._isStream = isStream;
46063
46085
  }
46064
- async *[Symbol.asyncIterator]() {
46065
- yield* this._iter;
46086
+ _buildIter() {
46087
+ if (this._isStream) return this._convertStreamResponse(this._response);
46088
+ return this._convertNonStreamResponse(this._response);
46066
46089
  }
46067
46090
  _captureFinishReasonFromResponse(response) {
46068
46091
  const status = readNullableStringField(response, "status");
@@ -46076,12 +46099,7 @@ var OpenAIResponsesStreamedMessage = class {
46076
46099
  const outputTokens = readNumberField(usage, "output_tokens") ?? 0;
46077
46100
  const details = readObjectField(usage, "input_tokens_details");
46078
46101
  const cached = details ? readNumberField(details, "cached_tokens") ?? 0 : 0;
46079
- this._usage = {
46080
- inputOther: inputTokens - cached,
46081
- output: outputTokens,
46082
- inputCacheRead: cached,
46083
- inputCacheCreation: 0
46084
- };
46102
+ this._usage = extractCacheUsage(inputTokens, cached, outputTokens);
46085
46103
  }
46086
46104
  async *_convertNonStreamResponse(response) {
46087
46105
  this._id = readStringField$2(response, "id") ?? null;
@@ -46244,34 +46262,22 @@ var OpenAIResponsesStreamedMessage = class {
46244
46262
  }
46245
46263
  }
46246
46264
  };
46247
- var OpenAIResponsesChatProvider = class {
46265
+ var OpenAIResponsesChatProvider = class OpenAIResponsesChatProvider extends BaseChatProvider {
46248
46266
  name = "openai-responses";
46249
- _model;
46250
46267
  _stream;
46251
- _apiKey;
46252
- _baseUrl;
46253
- _defaultHeaders;
46254
- _generationKwargs;
46255
46268
  _toolMessageConversion;
46256
- _client;
46257
46269
  _httpClient;
46258
- _clientFactory;
46259
46270
  constructor(options) {
46260
46271
  const apiKey = options.apiKey ?? process.env["OPENAI_API_KEY"];
46261
- this._apiKey = apiKey === void 0 || apiKey.length === 0 ? void 0 : apiKey;
46262
- this._baseUrl = options.baseUrl ?? "https://api.openai.com/v1";
46263
- this._defaultHeaders = options.defaultHeaders;
46264
- this._model = options.model;
46272
+ const apiKeyResolved = apiKey === void 0 || apiKey.length === 0 ? void 0 : apiKey;
46273
+ const baseUrl = options.baseUrl ?? "https://api.openai.com/v1";
46274
+ const generationKwargs = {};
46275
+ if (options.maxOutputTokens !== void 0) generationKwargs.max_output_tokens = options.maxOutputTokens;
46276
+ const client = apiKeyResolved === void 0 ? void 0 : OpenAIResponsesChatProvider.buildClient(apiKeyResolved, baseUrl, options.defaultHeaders, options.httpClient);
46277
+ super(options.model, generationKwargs, apiKeyResolved, baseUrl, options.defaultHeaders, client, options.clientFactory);
46265
46278
  this._stream = true;
46266
- this._generationKwargs = {};
46267
46279
  this._toolMessageConversion = options.toolMessageConversion ?? null;
46268
46280
  this._httpClient = options.httpClient;
46269
- this._clientFactory = options.clientFactory;
46270
- if (options.maxOutputTokens !== void 0) this._generationKwargs.max_output_tokens = options.maxOutputTokens;
46271
- this._client = this._apiKey === void 0 ? void 0 : this._buildClient(this._apiKey);
46272
- }
46273
- get modelName() {
46274
- return this._model;
46275
46281
  }
46276
46282
  get thinkingEffort() {
46277
46283
  return reasoningEffortToThinkingEffort(this._generationKwargs.reasoning_effort);
@@ -46330,41 +46336,18 @@ var OpenAIResponsesChatProvider = class {
46330
46336
  }
46331
46337
  withThinking(effort) {
46332
46338
  const reasoningEffort = thinkingEffortToReasoningEffort(effort, this._model);
46333
- const clone = this._clone();
46334
- clone._generationKwargs = {
46335
- ...clone._generationKwargs,
46336
- reasoning_effort: reasoningEffort
46337
- };
46338
- return clone;
46339
+ return this.withGenerationKwargs({ reasoning_effort: reasoningEffort });
46339
46340
  }
46340
- withGenerationKwargs(kwargs) {
46341
- const clone = this._clone();
46342
- clone._generationKwargs = {
46343
- ...clone._generationKwargs,
46344
- ...kwargs
46345
- };
46346
- return clone;
46347
- }
46348
- _clone() {
46349
- const clone = Object.assign(Object.create(Object.getPrototypeOf(this)), this);
46350
- clone._generationKwargs = { ...this._generationKwargs };
46351
- return clone;
46341
+ createRawClient(auth, defaultHeaders) {
46342
+ return OpenAIResponsesChatProvider.buildClient(auth.apiKey, this._baseUrl, defaultHeaders, this._httpClient);
46352
46343
  }
46353
- _createClient(auth) {
46354
- return resolveAuthBackedClient({
46355
- cachedClient: this._client,
46356
- clientFactory: this._clientFactory
46357
- }, auth, (a) => this._buildClient(requireProviderApiKey("OpenAIResponsesChatProvider", a, this._apiKey), a));
46358
- }
46359
- _buildClient(apiKey, auth) {
46360
- const clientOpts = {
46344
+ static buildClient(apiKey, baseURL, defaultHeaders, httpClient) {
46345
+ return new OpenAI({
46361
46346
  apiKey,
46362
- baseURL: this._baseUrl
46363
- };
46364
- const defaultHeaders = mergeRequestHeaders(this._defaultHeaders, auth?.headers);
46365
- if (defaultHeaders !== void 0) clientOpts["defaultHeaders"] = defaultHeaders;
46366
- if (this._httpClient !== void 0) clientOpts["httpClient"] = this._httpClient;
46367
- return new OpenAI(clientOpts);
46347
+ baseURL,
46348
+ ...defaultHeaders !== void 0 ? { defaultHeaders } : {},
46349
+ ...httpClient !== void 0 ? { httpClient } : {}
46350
+ });
46368
46351
  }
46369
46352
  };
46370
46353
  //#endregion
@@ -47542,17 +47525,17 @@ async function loadAgentsMd(kaos, workDir) {
47542
47525
  return true;
47543
47526
  };
47544
47527
  const home = kaos.gethome();
47545
- await collect(joinPath$2(kaos, home, ".byf", "AGENTS.md"));
47546
- const genericFiles = [joinPath$2(kaos, home, ".agents")].flatMap((dir) => ["AGENTS.md", "agents.md"].map((name) => joinPath$2(kaos, dir, name)));
47528
+ await collect(joinPath$1(kaos, home, ".byf", "AGENTS.md"));
47529
+ const genericFiles = [joinPath$1(kaos, home, ".agents")].flatMap((dir) => ["AGENTS.md", "agents.md"].map((name) => joinPath$1(kaos, dir, name)));
47547
47530
  for (const file of genericFiles) if (await collect(file)) break;
47548
47531
  for (const dir of dirs) {
47549
- await collect(joinPath$2(kaos, dir, ".byf", "AGENTS.md"));
47550
- for (const fileName of ["AGENTS.md", "agents.md"]) if (await collect(joinPath$2(kaos, dir, fileName))) break;
47532
+ await collect(joinPath$1(kaos, dir, ".byf", "AGENTS.md"));
47533
+ for (const fileName of ["AGENTS.md", "agents.md"]) if (await collect(joinPath$1(kaos, dir, fileName))) break;
47551
47534
  }
47552
47535
  return renderAgentFiles(discovered);
47553
47536
  }
47554
47537
  async function findProjectRoot$1(kaos, workDir) {
47555
- const path = pathMod$6(kaos);
47538
+ const path = pathMod$5(kaos);
47556
47539
  const initial = kaos.normpath(workDir);
47557
47540
  let current = initial;
47558
47541
  while (true) {
@@ -47563,7 +47546,7 @@ async function findProjectRoot$1(kaos, workDir) {
47563
47546
  }
47564
47547
  }
47565
47548
  function dirsRootToLeaf(kaos, workDir, projectRoot) {
47566
- const path = pathMod$6(kaos);
47549
+ const path = pathMod$5(kaos);
47567
47550
  const dirs = [];
47568
47551
  let current = kaos.normpath(workDir);
47569
47552
  while (true) {
@@ -47638,10 +47621,10 @@ function byteLength(text) {
47638
47621
  function annotationFor(path) {
47639
47622
  return `<!-- From: ${path} -->\n`;
47640
47623
  }
47641
- function joinPath$2(kaos, ...parts) {
47642
- return pathMod$6(kaos).join(...parts);
47624
+ function joinPath$1(kaos, ...parts) {
47625
+ return pathMod$5(kaos).join(...parts);
47643
47626
  }
47644
- function pathMod$6(kaos) {
47627
+ function pathMod$5(kaos) {
47645
47628
  return kaos.pathClass() === "win32" ? win32Path : posixPath;
47646
47629
  }
47647
47630
  //#endregion
@@ -54749,7 +54732,7 @@ const PROFILE_SOURCES = {
54749
54732
  "profile/default/agent.yaml": agent_default$1,
54750
54733
  "profile/default/coder.yaml": coder_default,
54751
54734
  "profile/default/explore.yaml": explore_default,
54752
- "profile/default/system.md": "You are BYF, an AI agent running on the user's computer. Your job is to help\nusers accomplish tasks by taking action — read, write, search, and execute to\nmake real changes on the user's system. Answer questions when asked; otherwise,\nact.\n\nWhen responding, use the same language as the user unless explicitly instructed\notherwise.\n\n{{ ROLE_ADDITIONAL }}\n\n# First Principles\n\nThink from first principles. Strip away assumptions and conventions; every\naction must be traceable to a verifiable fact — the actual file contents,\ncommand output, data, or the user's explicit words. When in doubt, read\nbefore guessing, ask before assuming, verify before claiming.\n\n# Tool Use\n\nUse tools only when the task requires them. If the request can be answered\nwithout reading files, running commands, or searching the web, reply in text\ndirectly. When a request is ambiguous, prefer action — the user can see your\noutput and correct course.\n\nCode that only appears in your text response is NOT saved to the file system\nand will not take effect. To create or modify files, use `Write` or `Edit`.\nTo run commands, use `Bash`.\n\n# Protocol\n\n<system> tags in user or tool messages provide supplementary context. Treat\nthem as background information.\n\n<system-reminder> tags are authoritative directives that override default\nbehavior. They are unrelated to the messages they appear in. Always comply.\n\n# Safety\n\nThe environment is not a sandbox — your actions immediately affect the user's\nsystem.\n\n- Stay within the working directory unless explicitly instructed otherwise.\n- Git operations are destructive and may affect remote repositories. Never\n execute git mutations unless explicitly asked; confirm each time.\n- Avoid installing or deleting anything outside the working directory. If\n necessary, ask for confirmation first.\n\n# Working Environment\n\n## Operating System\n\nYou are running on **{{ BYF_OS }}**. The Bash tool executes commands using **{{ BYF_SHELL }}**.\n{% if BYF_OS == \"Windows\" %}\n\nIMPORTANT: You are on Windows. The Bash tool runs through Git Bash, so use Unix shell syntax inside Bash commands — `/dev/null` not `NUL`, and forward slashes in paths. For file operations, always prefer the built-in tools (Read, Write, Edit, Glob, Grep) over Bash commands — they work reliably across all platforms.\n{% endif %}\n\n## Working Directory\n\nThe current working directory is `{{ BYF_WORK_DIR }}`. This should be considered as the project root if you are instructed to perform tasks on the project. Every file system operation will be relative to the working directory if you do not explicitly specify the absolute path. Tools may require absolute paths for some parameters, IF SO, YOU MUST use absolute paths for these parameters.\n{% if BYF_ADDITIONAL_DIRS_INFO %}\n\n## Additional Directories\n\nThe following directories have been added to the workspace. You can read, write, search, and glob files in these directories as part of your workspace scope.\n\n{{ BYF_ADDITIONAL_DIRS_INFO }}\n{% endif %}\n\n# Project Information\n\n`AGENTS.md` files contain project-specific context, styles, and conventions for agents. They may exist at different locations in the project — each file governs its directory and all subdirectories beneath it. Deeper files take precedence over parent files.\n\nIf instructions conflict:\n- `<system-reminder>` directives override all other instructions, including user messages.\n- Safety rules are hard constraints and must never be violated, even if a user message or AGENTS.md says otherwise.\n- Beyond those two, user messages > AGENTS.md > default system instructions.\n\n{% if BYF_AGENTS_MD_TOO_LONG %}\n> ⚠️ The merged AGENTS.md content exceeds 4,000 tokens. Consider compressing project instructions to reduce context usage.\n{% endif %}\n\nThe `AGENTS.md` instructions (merged from all applicable directories):\n\n`````````\n{{ BYF_AGENTS_MD }}\n`````````\n\nIf you modified anything mentioned in `AGENTS.md` files, update the corresponding files to keep them up-to-date.\n\n# Skills\n\nSkills are reusable capabilities. When a skill from the listing matches the user's request, you MUST call the `Skill` tool (not free-form text).\n\n{{ BYF_SKILLS }}\n"
54735
+ "profile/default/system.md": "You are BYF, an AI agent running on the user's computer. Your job is to help\nusers accomplish tasks by taking action — read, write, search, and execute to\nmake real changes on the user's system. Answer questions when asked; otherwise,\nact.\n\nWhen responding, use the same language as the user unless explicitly instructed\notherwise.\n\n{{ ROLE_ADDITIONAL }}\n\n# First Principles\n\nThink from first principles. Strip away assumptions and conventions; every\naction must be traceable to a verifiable fact — the actual file contents,\ncommand output, data, or the user's explicit words. When in doubt, read\nbefore guessing, ask before assuming, verify before claiming.\n\n# Tool Use\n\nUse tools only when the task requires them. If the request can be answered\nwithout reading files, running commands, or searching the web, reply in text\ndirectly. When a request is ambiguous, prefer action — the user can see your\noutput and correct course.\n\nCode that only appears in your text response is NOT saved to the file system\nand will not take effect. To create or modify files, use `Write` or `Edit`.\nTo run commands, use `Bash`.\n\n# Protocol\n\n<system> tags in user or tool messages provide supplementary context. Treat\nthem as background information.\n\n<system-reminder> tags are authoritative directives that override default\nbehavior. They are unrelated to the messages they appear in. Always comply.\n\n# Safety\n\nThe environment is not a sandbox — your actions immediately affect the user's\nsystem.\n\n- Stay within the working directory unless explicitly instructed otherwise.\n- Git operations are destructive and may affect remote repositories. Never\n execute git mutations unless explicitly asked; confirm each time.\n- Avoid installing or deleting anything outside the working directory. If\n necessary, ask for confirmation first.\n\n# Project Information\n\n`AGENTS.md` files contain project-specific context, styles, and conventions for agents. They may exist at different locations in the project — each file governs its directory and all subdirectories beneath it. Deeper files take precedence over parent files.\n\nIf instructions conflict:\n- `<system-reminder>` directives override all other instructions, including user messages.\n- Safety rules are hard constraints and must never be violated, even if a user message or AGENTS.md says otherwise.\n- Beyond those two, user messages > AGENTS.md > default system instructions.\n\n{% if BYF_AGENTS_MD_TOO_LONG %}\n> ⚠️ The merged AGENTS.md content exceeds 4,000 tokens. Consider compressing project instructions to reduce context usage.\n{% endif %}\n\nThe `AGENTS.md` instructions (merged from all applicable directories):\n\n`````````\n{{ BYF_AGENTS_MD }}\n`````````\n\nIf you modified anything mentioned in `AGENTS.md` files, update the corresponding files to keep them up-to-date.\n\n# Working Environment\n\n## Operating System\n\nYou are running on **{{ BYF_OS }}**. The Bash tool executes commands using **{{ BYF_SHELL }}**.\n{% if BYF_OS == \"Windows\" %}\n\nIMPORTANT: You are on Windows. The Bash tool runs through Git Bash, so use Unix shell syntax inside Bash commands — `/dev/null` not `NUL`, and forward slashes in paths. For file operations, always prefer the built-in tools (Read, Write, Edit, Glob, Grep) over Bash commands — they work reliably across all platforms.\n{% endif %}\n\n## Working Directory\n\nThe current working directory is `{{ BYF_WORK_DIR }}`. This should be considered as the project root if you are instructed to perform tasks on the project. Every file system operation will be relative to the working directory if you do not explicitly specify the absolute path. Tools may require absolute paths for some parameters, IF SO, YOU MUST use absolute paths for these parameters.\n{% if BYF_ADDITIONAL_DIRS_INFO %}\n\n## Additional Directories\n\nThe following directories have been added to the workspace. You can read, write, search, and glob files in these directories as part of your workspace scope.\n\n{{ BYF_ADDITIONAL_DIRS_INFO }}\n{% endif %}\n\n# Skills\n\nSkills are reusable capabilities. When a skill from the listing matches the user's request, you MUST call the `Skill` tool (not free-form text).\n\n{{ BYF_SKILLS }}\n"
54753
54736
  };
54754
54737
  const DEFAULT_INIT_PROMPT = init_default;
54755
54738
  const DEFAULT_AGENT_PROFILES = loadAgentProfilesFromSources([
@@ -55175,6 +55158,7 @@ var BackgroundProcessManager = class {
55175
55158
  const kind = opts?.kind;
55176
55159
  const taskId = generateTaskId(kind ?? "bash");
55177
55160
  const entry = {
55161
+ kind: "process",
55178
55162
  taskId,
55179
55163
  command,
55180
55164
  description,
@@ -55374,9 +55358,10 @@ var BackgroundProcessManager = class {
55374
55358
  entry.approvalReason = void 0;
55375
55359
  entry.stopRequested = true;
55376
55360
  entry.stopReason = stopReason;
55377
- try {
55361
+ if (entry.kind === "process") try {
55378
55362
  await entry.proc.kill("SIGTERM");
55379
55363
  } catch {}
55364
+ else entry.abort();
55380
55365
  let graceTimer;
55381
55366
  const graceful = await Promise.race([entry.lifecyclePromise.then(() => true, () => true), new Promise((resolve) => {
55382
55367
  graceTimer = setTimeout(() => {
@@ -55388,9 +55373,11 @@ var BackgroundProcessManager = class {
55388
55373
  await entry.persistWriteQueue;
55389
55374
  return this.toInfo(entry);
55390
55375
  }
55391
- if (!graceful && entry.proc.exitCode === null) try {
55392
- await entry.proc.kill("SIGKILL");
55393
- } catch {}
55376
+ if (!graceful) if (entry.kind === "process") {
55377
+ if (entry.proc.exitCode === null) try {
55378
+ await entry.proc.kill("SIGKILL");
55379
+ } catch {}
55380
+ } else entry.abort();
55394
55381
  if (TERMINAL_STATUSES.has(entry.status)) {
55395
55382
  await entry.persistWriteQueue;
55396
55383
  return this.toInfo(entry);
@@ -55448,32 +55435,15 @@ var BackgroundProcessManager = class {
55448
55435
  else this.assertCanRegister();
55449
55436
  const taskId = generateTaskId("agent");
55450
55437
  const entry = {
55438
+ kind: "promise",
55451
55439
  taskId,
55452
55440
  command: `[agent] ${description}`,
55453
55441
  description,
55454
55442
  timeoutMs: opts.timeoutMs,
55443
+ completion,
55444
+ abort: () => opts.abort?.(),
55455
55445
  agentId: opts.agentId ?? taskId,
55456
55446
  subagentType: opts.subagentType ?? "agent",
55457
- proc: {
55458
- stdin: {
55459
- write: () => false,
55460
- end: () => {}
55461
- },
55462
- stdout: {
55463
- setEncoding: () => {},
55464
- on: () => {}
55465
- },
55466
- stderr: {
55467
- setEncoding: () => {},
55468
- on: () => {}
55469
- },
55470
- pid: 0,
55471
- exitCode: null,
55472
- wait: () => completion.then(() => 0),
55473
- kill: async () => {
55474
- opts.abort?.();
55475
- }
55476
- },
55477
55447
  outputChunks: [],
55478
55448
  outputSizeBytes: 0,
55479
55449
  status: "running",
@@ -55649,7 +55619,7 @@ var BackgroundProcessManager = class {
55649
55619
  if (this.sessionDir !== void 0) await removeTask(this.sessionDir, taskId);
55650
55620
  }
55651
55621
  /**
55652
- * Persist the current state of a live ManagedProcess. Called from
55622
+ * Persist the current state of a live TaskEntry. Called from
55653
55623
  * `register()` and the lifecycle finally block. No-op unless attached.
55654
55624
  */
55655
55625
  persistLive(entry) {
@@ -55659,7 +55629,7 @@ var BackgroundProcessManager = class {
55659
55629
  task_id: entry.taskId,
55660
55630
  command: entry.command,
55661
55631
  description: entry.description,
55662
- pid: entry.proc.pid,
55632
+ pid: entry.kind === "process" ? entry.proc.pid : 0,
55663
55633
  started_at: entry.startedAt,
55664
55634
  ended_at: entry.endedAt,
55665
55635
  exit_code: entry.exitCode,
@@ -55705,7 +55675,7 @@ var BackgroundProcessManager = class {
55705
55675
  }
55706
55676
  observedExitCompletions() {
55707
55677
  const completions = [];
55708
- for (const entry of this.processes.values()) if (!TERMINAL_STATUSES.has(entry.status) && entry.proc.exitCode !== null) completions.push(entry.lifecyclePromise);
55678
+ for (const entry of this.processes.values()) if (entry.kind === "process" && !TERMINAL_STATUSES.has(entry.status) && entry.proc.exitCode !== null) completions.push(entry.lifecyclePromise);
55709
55679
  return completions;
55710
55680
  }
55711
55681
  toInfo(entry) {
@@ -55714,7 +55684,7 @@ var BackgroundProcessManager = class {
55714
55684
  command: entry.command,
55715
55685
  description: entry.description,
55716
55686
  status: entry.status,
55717
- pid: entry.proc.pid,
55687
+ pid: entry.kind === "process" ? entry.proc.pid : null,
55718
55688
  exitCode: entry.exitCode,
55719
55689
  startedAt: entry.startedAt,
55720
55690
  endedAt: entry.endedAt,
@@ -55762,7 +55732,7 @@ function infoToPersisted(info) {
55762
55732
  task_id: info.taskId,
55763
55733
  command: info.command,
55764
55734
  description: info.description,
55765
- pid: info.pid,
55735
+ pid: info.pid ?? 0,
55766
55736
  started_at: info.startedAt,
55767
55737
  ended_at: info.endedAt,
55768
55738
  exit_code: info.exitCode,
@@ -58617,14 +58587,14 @@ const SENSITIVE_DOT_VARIANT_SUFFIXES = [
58617
58587
  ];
58618
58588
  const SENSITIVE_DOT_VARIANT_SUFFIX_SET = new Set(SENSITIVE_DOT_VARIANT_SUFFIXES);
58619
58589
  const DEFAULT_PATH_CLASS$1 = path$8.sep === "\\" ? "win32" : "posix";
58620
- function pathMod$5(pathClass) {
58590
+ function pathMod$4(pathClass) {
58621
58591
  return pathClass === "win32" ? win32Path : posixPath;
58622
58592
  }
58623
58593
  function comparable(path, pathClass) {
58624
58594
  return pathClass === "win32" ? path.toLowerCase() : path;
58625
58595
  }
58626
58596
  function isSensitiveFile(path, pathClass = DEFAULT_PATH_CLASS$1) {
58627
- const mod = pathMod$5(pathClass);
58597
+ const mod = pathMod$4(pathClass);
58628
58598
  const comparableName = comparable(mod.basename(path), pathClass);
58629
58599
  const comparablePath = comparable(path, pathClass);
58630
58600
  if (ENV_EXEMPTIONS.has(comparableName)) return false;
@@ -58678,7 +58648,7 @@ var PathSecurityError = class extends Error {
58678
58648
  }
58679
58649
  };
58680
58650
  const DEFAULT_PATH_CLASS = path$8.sep === "\\" ? "win32" : "posix";
58681
- function pathMod$4(pathClass) {
58651
+ function pathMod$3(pathClass) {
58682
58652
  return pathClass === "win32" ? win32Path : posixPath;
58683
58653
  }
58684
58654
  function comparablePath(path, pathClass) {
@@ -58708,7 +58678,7 @@ function normalizeUserPath(path, pathClass = DEFAULT_PATH_CLASS) {
58708
58678
  function expandUserPath$1(path, homeDir, pathClass) {
58709
58679
  if (homeDir === void 0) return path;
58710
58680
  if (path === "~") return homeDir;
58711
- if (path.startsWith("~/") || pathClass === "win32" && path.startsWith("~\\")) return pathMod$4(pathClass).join(homeDir, path.slice(2));
58681
+ if (path.startsWith("~/") || pathClass === "win32" && path.startsWith("~\\")) return pathMod$3(pathClass).join(homeDir, path.slice(2));
58712
58682
  return path;
58713
58683
  }
58714
58684
  /**
@@ -58717,7 +58687,7 @@ function expandUserPath$1(path, homeDir, pathClass) {
58717
58687
  */
58718
58688
  function canonicalizePath(path, cwd, pathClass = DEFAULT_PATH_CLASS) {
58719
58689
  if (path === "") throw new PathSecurityError("PATH_INVALID", path, path, "Path cannot be empty");
58720
- const mod = pathMod$4(pathClass);
58690
+ const mod = pathMod$3(pathClass);
58721
58691
  const normalizedPath = normalizeUserPath(path, pathClass);
58722
58692
  if (pathClass === "win32" && isWin32DriveRelative(normalizedPath)) throw new PathSecurityError("PATH_INVALID", path, normalizedPath, `"${path}" is a drive-relative Windows path. Use an absolute path like C:\\path or a path relative to the working directory.`);
58723
58693
  if (!mod.isAbsolute(normalizedPath) && !mod.isAbsolute(cwd)) throw new PathSecurityError("PATH_INVALID", path, normalizedPath, `Cannot resolve "${path}" against non-absolute cwd "${cwd}".`);
@@ -58729,7 +58699,7 @@ function canonicalizePath(path, cwd, pathClass = DEFAULT_PATH_CLASS) {
58729
58699
  * on path-component boundaries. Both arguments must already be canonical.
58730
58700
  */
58731
58701
  function isWithinDirectory(candidate, base, pathClass = DEFAULT_PATH_CLASS) {
58732
- const mod = pathMod$4(pathClass);
58702
+ const mod = pathMod$3(pathClass);
58733
58703
  const comparableCandidate = comparablePath(candidate, pathClass);
58734
58704
  const comparableBase = comparablePath(base, pathClass);
58735
58705
  if (comparableCandidate === comparableBase) return true;
@@ -58755,7 +58725,7 @@ function relativeOutsideMessage(path, operation) {
58755
58725
  }
58756
58726
  function resolvePathAccess(path, cwd, config, options) {
58757
58727
  const pathClass = options.pathClass ?? DEFAULT_PATH_CLASS;
58758
- const mod = pathMod$4(pathClass);
58728
+ const mod = pathMod$3(pathClass);
58759
58729
  const expandedPath = expandUserPath$1(normalizeUserPath(path, pathClass), options.homeDir, pathClass);
58760
58730
  const rawIsAbsolute = mod.isAbsolute(expandedPath);
58761
58731
  const canonical = canonicalizePath(expandedPath, cwd, pathClass);
@@ -58910,11 +58880,11 @@ var EditTool = class {
58910
58880
  }
58911
58881
  }
58912
58882
  };
58913
- async function collectEntries$1(kaos, dirPath, maxWidth, pathClass) {
58883
+ async function collectEntries(kaos, dirPath, maxWidth, pathClass) {
58914
58884
  const all = [];
58915
58885
  try {
58916
58886
  for await (const fullPath of kaos.iterdir(dirPath)) {
58917
- const name = basename$2(fullPath, pathClass);
58887
+ const name = basename$1(fullPath, pathClass);
58918
58888
  let isDir = false;
58919
58889
  try {
58920
58890
  isDir = ((await kaos.stat(fullPath)).stMode & 61440) === 16384;
@@ -58941,11 +58911,11 @@ async function collectEntries$1(kaos, dirPath, maxWidth, pathClass) {
58941
58911
  readable: true
58942
58912
  };
58943
58913
  }
58944
- function pathMod$3(pathClass) {
58914
+ function pathMod$2(pathClass) {
58945
58915
  return pathClass === "win32" ? win32Path : posixPath;
58946
58916
  }
58947
- function basename$2(p, pathClass) {
58948
- return pathMod$3(pathClass).basename(p);
58917
+ function basename$1(p, pathClass) {
58918
+ return pathMod$2(pathClass).basename(p);
58949
58919
  }
58950
58920
  /**
58951
58921
  * Return a 2-level tree listing of `workDir` suitable for inclusion in a
@@ -58955,7 +58925,7 @@ function basename$2(p, pathClass) {
58955
58925
  async function listDirectory(kaos, workDir) {
58956
58926
  const lines = [];
58957
58927
  const pathClass = kaos.pathClass();
58958
- const { entries, total, readable } = await collectEntries$1(kaos, workDir, 30, pathClass);
58928
+ const { entries, total, readable } = await collectEntries(kaos, workDir, 30, pathClass);
58959
58929
  if (!readable) return "[not readable]";
58960
58930
  const remaining = total - entries.length;
58961
58931
  for (let i = 0; i < entries.length; i++) {
@@ -58967,7 +58937,7 @@ async function listDirectory(kaos, workDir) {
58967
58937
  if (isDir) {
58968
58938
  lines.push(`${connector}${name}/`);
58969
58939
  const childPrefix = isLast ? " " : "│ ";
58970
- const child = await collectEntries$1(kaos, joinPath$1(workDir, name, pathClass), 10, pathClass);
58940
+ const child = await collectEntries(kaos, joinPath(workDir, name, pathClass), 10, pathClass);
58971
58941
  if (!child.readable) {
58972
58942
  lines.push(`${childPrefix}└── [not readable]`);
58973
58943
  continue;
@@ -58986,8 +58956,8 @@ async function listDirectory(kaos, workDir) {
58986
58956
  if (remaining > 0) lines.push(`└── ... and ${String(remaining)} more entries`);
58987
58957
  return lines.length > 0 ? lines.join("\n") : "(empty directory)";
58988
58958
  }
58989
- function joinPath$1(parent, child, pathClass) {
58990
- return pathMod$3(pathClass).join(parent, child);
58959
+ function joinPath(parent, child, pathClass) {
58960
+ return pathMod$2(pathClass).join(parent, child);
58991
58961
  }
58992
58962
  //#endregion
58993
58963
  //#region ../agent-core/src/tools/builtin/file/glob.md
@@ -65095,7 +65065,7 @@ var write_default = "Overwrite or append to a file with content exactly as provi
65095
65065
  const S_IFMT$2 = 61440;
65096
65066
  /** File-type bits of a directory. */
65097
65067
  const S_IFDIR$1 = 16384;
65098
- function pathMod$2(pathClass) {
65068
+ function pathMod$1(pathClass) {
65099
65069
  return pathClass === "win32" ? win32Path : posixPath;
65100
65070
  }
65101
65071
  const WriteInputSchema = z.object({
@@ -65162,7 +65132,7 @@ var WriteTool = class {
65162
65132
  * skipped and the write proceeds, surfacing the real I/O error if any.
65163
65133
  */
65164
65134
  async checkParentDirectory(safePath) {
65165
- const parent = pathMod$2(this.kaos.pathClass()).dirname(safePath);
65135
+ const parent = pathMod$1(this.kaos.pathClass()).dirname(safePath);
65166
65136
  let stat;
65167
65137
  try {
65168
65138
  stat = await this.kaos.stat(parent);
@@ -66078,8 +66048,14 @@ function project(history, ephemeralInjections) {
66078
66048
  if (isBlockedUserPrompt(message)) return false;
66079
66049
  return !isTranscriptOnlyHookResult(message) && message.partial !== true && !(message.role === "assistant" && message.content.length === 0 && message.toolCalls.length === 0);
66080
66050
  }));
66081
- const injectionMessages = ephemeralInjections?.map((injection) => renderInjection(injection));
66082
- return injectionMessages ? [...injectionMessages, ...merged] : merged;
66051
+ if (!ephemeralInjections?.length) return merged;
66052
+ const afterSystemMsgs = ephemeralInjections.filter((injection) => !injection.position || injection.position === "after_system").map((injection) => renderInjection(injection));
66053
+ const beforeUserMsgs = ephemeralInjections.filter((injection) => injection.position === "before_user").map((injection) => renderInjection(injection));
66054
+ return [
66055
+ ...afterSystemMsgs,
66056
+ ...merged,
66057
+ ...beforeUserMsgs
66058
+ ];
66083
66059
  }
66084
66060
  function isTranscriptOnlyHookResult(message) {
66085
66061
  return message.origin?.kind === "hook_result" && TRANSCRIPT_ONLY_HOOK_RESULT_EVENTS.has(message.origin.event ?? "");
@@ -67069,7 +67045,15 @@ var ContextMemory = class {
67069
67045
  return this._history;
67070
67046
  }
67071
67047
  get messages() {
67072
- return project(this.history);
67048
+ return this.getMessages();
67049
+ }
67050
+ /**
67051
+ * Project history into provider-ready messages, optionally with
67052
+ * ephemeral injections (e.g. timestamp, permission mode) appended
67053
+ * at the `'before_user'` position.
67054
+ */
67055
+ getMessages(ephemeral) {
67056
+ return project(this.history, ephemeral);
67073
67057
  }
67074
67058
  applyObservationMasking(config) {
67075
67059
  const effectiveConfig = config ?? DEFAULT_MASKING_CONFIG;
@@ -67730,146 +67714,55 @@ var DynamicInjector = class {
67730
67714
  }
67731
67715
  };
67732
67716
  //#endregion
67733
- //#region ../agent-core/src/agent/injection/directory-tree.ts
67734
- const EXCLUDED_DIRS = new Set([
67735
- "node_modules",
67736
- ".git",
67737
- "dist",
67738
- "build",
67739
- ".next",
67740
- ".nuxt",
67741
- ".vite",
67742
- "target",
67743
- ".turbo",
67744
- "coverage",
67745
- ".cache",
67746
- ".DS_Store",
67747
- ".idea",
67748
- ".vscode",
67749
- "venv",
67750
- ".venv"
67751
- ]);
67752
- const HIDDEN_DIR_WHITELIST = new Set([
67753
- ".github",
67754
- ".byf",
67755
- ".agents",
67756
- ".changeset",
67757
- ".husky"
67758
- ]);
67759
- var DirectoryTreeInjector = class extends DynamicInjector {
67760
- injectionVariant = "directory_tree";
67761
- lastTree;
67762
- hasInjected = false;
67763
- capturedTimestamp;
67764
- async getInjection() {
67765
- const kaos = this.agent.runtime.kaos;
67766
- const workDir = this.agent.config.cwd || kaos.getcwd();
67767
- const tree = await buildTree(kaos, workDir);
67768
- if (this.hasInjected && tree === this.lastTree) return;
67769
- this.lastTree = tree;
67770
- this.hasInjected = true;
67771
- if (this.capturedTimestamp === void 0) this.capturedTimestamp = (/* @__PURE__ */ new Date()).toISOString();
67772
- return `Current working directory structure (${workDir}):\n${tree}\n\nThe current date and time in ISO format is \`${this.capturedTimestamp}\`. This is only a reference for you when searching the web or checking file modification time, etc. If you need the exact time, use Bash tool with proper command.`;
67773
- }
67774
- };
67775
- async function buildTree(kaos, workDir) {
67776
- const lines = [];
67777
- const pathClass = kaos.pathClass();
67778
- const { entries, total, readable } = await collectEntries(kaos, workDir, 30, pathClass);
67779
- if (!readable) return "[not readable]";
67780
- const remaining = total - entries.length;
67781
- for (let i = 0; i < entries.length; i++) {
67782
- const entry = entries[i];
67783
- if (entry === void 0) continue;
67784
- const { name, isDir } = entry;
67785
- const isLast = i === entries.length - 1 && remaining === 0;
67786
- const connector = isLast ? "└── " : "├── ";
67787
- if (isDir) {
67788
- lines.push(`${connector}${name}/`);
67789
- const childPrefix = isLast ? " " : "│ ";
67790
- const child = await collectEntries(kaos, joinPath(workDir, name, pathClass), 10, pathClass);
67791
- if (!child.readable) {
67792
- lines.push(`${childPrefix}└── [not readable]`);
67793
- continue;
67794
- }
67795
- const childRemaining = child.total - child.entries.length;
67796
- for (let j = 0; j < child.entries.length; j++) {
67797
- const ce = child.entries[j];
67798
- if (ce === void 0) continue;
67799
- const cConnector = j === child.entries.length - 1 && childRemaining === 0 ? "└── " : "├── ";
67800
- const suffix = ce.isDir ? "/" : "";
67801
- lines.push(`${childPrefix}${cConnector}${ce.name}${suffix}`);
67802
- }
67803
- if (childRemaining > 0) lines.push(`${childPrefix}└── ... and ${String(childRemaining)} more`);
67804
- } else lines.push(`${connector}${name}`);
67805
- }
67806
- if (remaining > 0) lines.push(`└── ... and ${String(remaining)} more entries`);
67807
- return lines.length > 0 ? lines.join("\n") : "(empty directory)";
67808
- }
67809
- async function collectEntries(kaos, dirPath, maxWidth, pathClass) {
67810
- const all = [];
67811
- try {
67812
- for await (const fullPath of kaos.iterdir(dirPath)) {
67813
- const name = basename$1(fullPath, pathClass);
67814
- if (shouldExclude(name)) continue;
67815
- let isDir = false;
67816
- try {
67817
- isDir = ((await kaos.stat(fullPath)).stMode & 61440) === 16384;
67818
- } catch {}
67819
- all.push({
67820
- name,
67821
- isDir
67822
- });
67823
- }
67824
- } catch {
67825
- return {
67826
- entries: [],
67827
- total: 0,
67828
- readable: false
67829
- };
67830
- }
67831
- all.sort((a, b) => {
67832
- if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
67833
- return a.name.localeCompare(b.name);
67834
- });
67835
- return {
67836
- entries: all.slice(0, maxWidth),
67837
- total: all.length,
67838
- readable: true
67839
- };
67840
- }
67841
- function shouldExclude(name) {
67842
- if (EXCLUDED_DIRS.has(name)) return true;
67843
- if (name.startsWith(".") && !HIDDEN_DIR_WHITELIST.has(name)) return true;
67844
- return false;
67845
- }
67846
- function pathMod$1(pathClass) {
67847
- return pathClass === "win32" ? win32Path : posixPath;
67848
- }
67849
- function basename$1(p, pathClass) {
67850
- return pathMod$1(pathClass).basename(p);
67851
- }
67852
- function joinPath(parent, child, pathClass) {
67853
- return pathMod$1(pathClass).join(parent, child);
67854
- }
67855
- //#endregion
67856
67717
  //#region ../agent-core/src/agent/injection/permission-mode.ts
67857
- const AUTO_MODE_ENTER_REMINDER = [
67718
+ const AUTO_MODE_REMINDER = [
67858
67719
  "Auto permission mode is active. Tool approvals will be handled automatically while this mode remains enabled.",
67859
67720
  " - Continue normally without pausing for approval prompts.",
67860
67721
  " - Do NOT call AskUserQuestion while auto mode is active. Make a reasonable decision and continue without asking the user."
67861
67722
  ].join("\n");
67862
- const AUTO_MODE_EXIT_REMINDER = ["Auto permission mode is no longer active. Tool approvals and permission checks are back to the current mode.", " - Continue normally, but expect approval prompts or denials when a tool requires them."].join("\n");
67723
+ /**
67724
+ * Ephemeral injector for permission mode state.
67725
+ *
67726
+ * Emits the current permission mode as an ephemeral injection placed at
67727
+ * the `'before_user'` position. Unlike the previous persistent approach
67728
+ * (which recorded transition events into history), the ephemeral approach
67729
+ * always reflects the current state — surviving compaction and avoiding
67730
+ * history pollution.
67731
+ *
67732
+ * Only auto mode produces an injection; in all other modes the absence
67733
+ * of a reminder signals that normal approval prompts apply.
67734
+ */
67863
67735
  var PermissionModeInjector = class extends DynamicInjector {
67864
67736
  injectionVariant = "permission_mode";
67865
- lastMode;
67866
- getInjection() {
67867
- const mode = this.agent.permission.mode;
67868
- const previousMode = this.lastMode;
67869
- if (mode === previousMode) return void 0;
67870
- this.lastMode = mode;
67871
- if (mode === "auto") return AUTO_MODE_ENTER_REMINDER;
67872
- if (previousMode === "auto") return AUTO_MODE_EXIT_REMINDER;
67737
+ getInjection() {}
67738
+ getEphemeral() {
67739
+ if (this.agent.permission.mode !== "auto") return [];
67740
+ return [{
67741
+ kind: "system_reminder",
67742
+ content: AUTO_MODE_REMINDER,
67743
+ position: "before_user"
67744
+ }];
67745
+ }
67746
+ };
67747
+ //#endregion
67748
+ //#region ../agent-core/src/agent/injection/timestamp.ts
67749
+ /**
67750
+ * Ephemeral injector that provides the current timestamp at request time.
67751
+ *
67752
+ * The timestamp is rendered fresh on every step (not frozen) and placed
67753
+ * at the `'before_user'` position so it never breaks the cached prefix.
67754
+ * This aligns with the prompt-cache best practice of keeping per-request
67755
+ * dynamic content out of the cacheable system-prompt blocks.
67756
+ */
67757
+ var TimestampInjector = class extends DynamicInjector {
67758
+ injectionVariant = "timestamp";
67759
+ getInjection() {}
67760
+ getEphemeral() {
67761
+ return [{
67762
+ kind: "system_reminder",
67763
+ content: `The current date and time in ISO format is \`${(/* @__PURE__ */ new Date()).toISOString()}\`. This is only a reference for you when searching the web or checking file modification time, etc. If you need the exact time, use Bash tool with proper command.`,
67764
+ position: "before_user"
67765
+ }];
67873
67766
  }
67874
67767
  };
67875
67768
  //#endregion
@@ -67879,11 +67772,20 @@ var InjectionManager = class {
67879
67772
  injectors;
67880
67773
  constructor(agent) {
67881
67774
  this.agent = agent;
67882
- this.injectors = [new PermissionModeInjector(agent), new DirectoryTreeInjector(agent)];
67775
+ this.injectors = [new PermissionModeInjector(agent), new TimestampInjector(agent)];
67883
67776
  }
67884
67777
  async inject() {
67885
67778
  for (const injector of this.injectors) await injector.inject();
67886
67779
  }
67780
+ getEphemeralInjections() {
67781
+ return this.injectors.flatMap((injector) => {
67782
+ try {
67783
+ return injector.getEphemeral?.() ?? [];
67784
+ } catch {
67785
+ return [];
67786
+ }
67787
+ });
67788
+ }
67887
67789
  onContextClear() {
67888
67790
  for (const injector of this.injectors) injector.onContextClear();
67889
67791
  }
@@ -83152,20 +83054,25 @@ const CACHE_BOUNDARY_MARKER = "__CACHE_BOUNDARY__";
83152
83054
  *
83153
83055
  * These headers mark natural breaks in the system prompt where cache boundaries should be placed:
83154
83056
  * - "# Project Information" marks the start of project-specific content
83057
+ * - "# Working Environment" marks the start of session-specific environment (OS, working directory)
83155
83058
  * - "# Skills" marks the start of session-specific skills listing
83156
83059
  */
83157
- const IMPLICIT_BOUNDARY_HEADERS = ["# Project Information", "# Skills"];
83060
+ const IMPLICIT_BOUNDARY_HEADERS = [
83061
+ "# Project Information",
83062
+ "# Working Environment",
83063
+ "# Skills"
83064
+ ];
83158
83065
  /**
83159
83066
  * Block names by position.
83160
83067
  *
83161
83068
  * - First block (before first marker): 'base'
83162
83069
  * - Last block (after last marker): 'sessionContext'
83163
- * - Intermediate blocks: Sequential names from 'projectInstructions', 'skillsListing', etc.
83070
+ * - Intermediate blocks: Sequential names from 'projectInstructions', 'workingEnvironment', etc.
83164
83071
  */
83165
83072
  const BLOCK_NAMES = [
83166
83073
  "base",
83167
83074
  "projectInstructions",
83168
- "skillsListing",
83075
+ "workingEnvironment",
83169
83076
  "sessionContext"
83170
83077
  ];
83171
83078
  /**
@@ -83989,8 +83896,8 @@ var TurnFlow = class {
83989
83896
  completionBudgetConfig
83990
83897
  }),
83991
83898
  buildMessages: () => {
83992
- const messages = this.agent.context.messages;
83993
- return applyCacheStaking(messages, { previousTurnMessageCount: this._previousTurnMessageCount });
83899
+ const ephemeral = this.agent.injection.getEphemeralInjections();
83900
+ return applyCacheStaking(this.agent.context.getMessages(ephemeral), { previousTurnMessageCount: this._previousTurnMessageCount });
83994
83901
  },
83995
83902
  dispatchEvent: this.buildDispatchEvent(turnId),
83996
83903
  tools: this.agent.tools.loopTools,
@@ -89065,7 +88972,7 @@ var SessionSubagentHost = class {
89065
88972
  const { id, agent } = await this.session.createAgent({
89066
88973
  type: "sub",
89067
88974
  generate: parent.rawGenerate
89068
- }, void 0, this.ownerAgentId);
88975
+ }, void 0, this.ownerAgentId, options.parentToolCallId);
89069
88976
  const controller = new AbortController();
89070
88977
  const unlinkAbortSignal = linkAbortSignal(options.signal, controller);
89071
88978
  this.activeChildren.set(id, {
@@ -89364,7 +89271,7 @@ var Session$1 = class {
89364
89271
  })) return;
89365
89272
  await Promise.all(Array.from(this.agents.values(), (agent) => agent.background.stopAll("Session closed")));
89366
89273
  }
89367
- async createAgent(config, profile, parentAgentId) {
89274
+ async createAgent(config, profile, parentAgentId, parentToolCallId) {
89368
89275
  await this.skillsReady;
89369
89276
  const type = config.type ?? "main";
89370
89277
  const id = type === "main" ? "main" : this.nextGeneratedAgentId();
@@ -89376,7 +89283,8 @@ var Session$1 = class {
89376
89283
  this.metadata.agents[id] = {
89377
89284
  homedir,
89378
89285
  type,
89379
- parentAgentId: parentAgentId ?? null
89286
+ parentAgentId: parentAgentId ?? null,
89287
+ parentToolCallId
89380
89288
  };
89381
89289
  this.writeMetadata();
89382
89290
  return {
@@ -127223,7 +127131,8 @@ async function resumeSessionResult(summary, session, warning) {
127223
127131
  usage,
127224
127132
  tools: await api.getTools({ agentId }),
127225
127133
  toolStore: agent.tools.storeData(),
127226
- background: agent.background.list(false)
127134
+ background: agent.background.list(false),
127135
+ parentToolCallId: session.metadata.agents[agentId]?.parentToolCallId
127227
127136
  };
127228
127137
  }
127229
127138
  return {
@@ -128300,6 +128209,9 @@ function firstNonEmptyString(source, keys) {
128300
128209
  }
128301
128210
  }
128302
128211
  async function fetchModels(baseUrl, apiKey, fetchImpl = fetch, signal) {
128212
+ return fetchOpenAICompatModels(baseUrl, apiKey, fetchImpl, signal);
128213
+ }
128214
+ async function fetchOpenAICompatModels(baseUrl, apiKey, fetchImpl = fetch, signal) {
128303
128215
  const res = await fetchImpl(`${baseUrl.replace(/\/+$/, "")}/models`, {
128304
128216
  headers: {
128305
128217
  Authorization: `Bearer ${apiKey}`,
@@ -128312,6 +128224,70 @@ async function fetchModels(baseUrl, apiKey, fetchImpl = fetch, signal) {
128312
128224
  if (!isRecord(payload) || !Array.isArray(payload["data"])) throw new Error(`Unexpected models response for ${baseUrl}.`);
128313
128225
  return payload["data"].map((item) => toModelInfo(item)).filter((item) => item !== void 0);
128314
128226
  }
128227
+ /**
128228
+ * Lists models from a provider using its native wire-type endpoint. Dispatches
128229
+ * per `type` so each protocol gets its correct auth header and response shape.
128230
+ * `openai-completions` and `openai_responses` share the OpenAI-compatible
128231
+ * `/models` endpoint; `anthropic` uses its native `x-api-key` endpoint with
128232
+ * pagination. Other types have dedicated native fetchers (added in
128233
+ * later slices).
128234
+ */
128235
+ async function fetchModelsByType(type, baseUrl, apiKey, fetchImpl = fetch, signal) {
128236
+ switch (type) {
128237
+ case "openai-completions":
128238
+ case "openai_responses": return fetchOpenAICompatModels(baseUrl, apiKey, fetchImpl, signal);
128239
+ case "anthropic": return fetchAnthropicModels(baseUrl, apiKey, fetchImpl, signal);
128240
+ default: throw new Error(`fetchModelsByType: unsupported provider type "${type}".`);
128241
+ }
128242
+ }
128243
+ /**
128244
+ * Anthropic native `/v1/models` listing. Uses `x-api-key` + `anthropic-version`
128245
+ * headers (not Bearer). Response is paginated via `has_more` + `last_id`;
128246
+ * follow pages by passing `?after_id=<last_id>`. Defensive guards: a page that
128247
+ * claims `has_more` but omits `last_id` stops pagination (returns what we have)
128248
+ * rather than looping forever; a hard cap (10 pages) also bounds the loop.
128249
+ */
128250
+ async function fetchAnthropicModels(baseUrl, apiKey, fetchImpl = fetch, signal) {
128251
+ const base = baseUrl.replace(/\/+$/, "");
128252
+ const ANTHROPIC_VERSION = "2023-06-01";
128253
+ const MAX_PAGES = 10;
128254
+ const collected = [];
128255
+ let afterId;
128256
+ for (let page = 0; page < MAX_PAGES; page += 1) {
128257
+ const res = await fetchImpl(afterId === void 0 ? `${base}/models` : `${base}/models?after_id=${encodeURIComponent(afterId)}`, {
128258
+ headers: {
128259
+ "x-api-key": apiKey,
128260
+ "anthropic-version": ANTHROPIC_VERSION,
128261
+ Accept: "application/json"
128262
+ },
128263
+ signal
128264
+ });
128265
+ if (!res.ok) throw new ProviderApiError(await readApiErrorMessage(res, `Failed to list models (HTTP ${res.status}).`), res.status);
128266
+ const payload = await res.json();
128267
+ if (!isRecord(payload) || !Array.isArray(payload["data"])) throw new Error(`Unexpected models response for ${baseUrl}.`);
128268
+ for (const item of payload["data"]) {
128269
+ const info = anthropicModelToInfo(item);
128270
+ if (info !== void 0) collected.push(info);
128271
+ }
128272
+ const hasMore = payload["has_more"] === true;
128273
+ const lastId = typeof payload["last_id"] === "string" ? payload["last_id"] : void 0;
128274
+ if (!hasMore || lastId === void 0 || lastId.length === 0) break;
128275
+ afterId = lastId;
128276
+ }
128277
+ return collected;
128278
+ }
128279
+ function anthropicModelToInfo(item) {
128280
+ if (!isRecord(item) || typeof item["id"] !== "string" || item["id"].length === 0) return;
128281
+ const displayName = item["display_name"];
128282
+ return {
128283
+ id: item["id"],
128284
+ contextLength: 2e5,
128285
+ supportsReasoning: true,
128286
+ supportsImageIn: true,
128287
+ supportsVideoIn: false,
128288
+ displayName: typeof displayName === "string" && displayName.length > 0 ? displayName : void 0
128289
+ };
128290
+ }
128315
128291
  function capabilitiesForModel(model) {
128316
128292
  const caps = /* @__PURE__ */ new Set();
128317
128293
  if (model.supportsReasoning) caps.add("thinking");
@@ -128325,7 +128301,7 @@ function applyProviderConfig(config, options) {
128325
128301
  const providerKey = options.name;
128326
128302
  const modelKey = `${providerKey}/${options.selectedModel.id}`;
128327
128303
  config.providers[providerKey] = {
128328
- type: "openai-completions",
128304
+ type: options.type ?? "openai-completions",
128329
128305
  baseUrl: options.baseUrl,
128330
128306
  apiKey: options.apiKey,
128331
128307
  thinkingEffortKey: options.selectedModel.reasoningEffortKey
@@ -128351,4 +128327,4 @@ function applyProviderConfig(config, options) {
128351
128327
  };
128352
128328
  }
128353
128329
  //#endregion
128354
- export { BYF_ERROR_INFO, ByfAuthFacade, ByfError, ByfHarness, CatalogFetchError, DEFAULT_CATALOG_URL, ErrorCodes, MCP_OAUTH_AUTHORIZATION_URL_TOOL_UPDATE, Session, applyCatalogProvider, applyProviderConfig, catalogBaseUrl, catalogIdMatchesModelId, catalogModelToAlias, catalogProviderModels, enrichWithCatalog, fetchCatalog, fetchModels, findCatalogModel, flushDiagnosticLogs, fromByfErrorPayload, __toESM as i, inferWireType, isByfError, loadBuiltInCatalog, log, __esmMin as n, __require as r, redact, resolveByfHome, resolveGlobalLogPath, __commonJSMin as t, toByfErrorPayload };
128330
+ export { BYF_ERROR_INFO, ByfAuthFacade, ByfError, ByfHarness, CatalogFetchError, DEFAULT_CATALOG_URL, ErrorCodes, MCP_OAUTH_AUTHORIZATION_URL_TOOL_UPDATE, Session, applyCatalogProvider, applyProviderConfig, catalogBaseUrl, catalogIdMatchesModelId, catalogModelToAlias, catalogProviderModels, enrichWithCatalog, fetchCatalog, fetchModels, fetchModelsByType, findCatalogModel, flushDiagnosticLogs, fromByfErrorPayload, __toESM as i, inferWireType, isByfError, loadBuiltInCatalog, log, __esmMin as n, __require as r, redact, resolveByfHome, resolveGlobalLogPath, __commonJSMin as t, toByfErrorPayload };