Files
Playa/tests/Volcanic.AgenticCore.Tests/Support/StubExtensionHost.cs
EugeneTes 6ab98a8155 fix(telegram): address review findings
- A speech or download timeout (TaskCanceledException nobody asked for) is a failure
  with the user notice, not a silent shutdown; OpenAiSpeechToText maps it to
  "the request timed out."; UpdateRouter only treats the caller's cancellation as shutdown.
- send: WorkspacePaths resolves the real path component by component (realpath),
  closing the directory-link-inside-target bypass; `.` reports a directory.
- Image documents are attached inline only when their magic bytes are JPEG/PNG/GIF/WebP,
  under the detected type.
- Audio goes to STT as an ASCII audio.<ext> name.
- MCP Browser section mentions send only when it is active.
- RunInOrderAsync takes a cancellation token; queued work is skipped once disposal gives up;
  SendReplyAsync stays private behind ReplyInOrderAsync.
- Speech HttpClient handler recycles pooled connections; BuiltInTools.Send();
  IExtensionHost.Channel is abstract; WorkspacePaths in its own file.
- Tests: timeouts, symlink chains (Linux), sniffing, channel delivery failures,
  queued-work cancellation, gated voice ordering. Spec and docs updated.
2026-09-26 22:35:20 +02:00

44 lines
2.7 KiB
C#

using System.Text.Json;
using Volcanic.AgenticCore.Abstractions.Extensions;
using Volcanic.AgenticCore.Abstractions.Messages;
using Volcanic.AgenticCore.Abstractions.Providers;
using Volcanic.AgenticCore.Abstractions.Sessions;
using Volcanic.AgenticCore.Abstractions.Ui;
using Volcanic.AgenticCore.Features.Extensions;
using Volcanic.AgenticCore.Features.Models;
namespace Volcanic.AgenticCore.Tests.Support;
public sealed class StubExtensionHost : IExtensionHost
{
public List<string> Calls { get; } = [];
public string Cwd { get; init; } = Path.GetTempPath();
public string SessionId => "session-1";
public string Scope { get; init; } = "test-scope";
public IUserInterface Ui { get; init; } = NullUserInterface.Instance;
public IChatChannel? Channel { get; init; }
public IReadOnlySessionManager SessionManager => throw new NotSupportedException();
public IModelRegistry ModelRegistry { get; init; } = new ModelRegistry(new ApiKeyResolver(null, _ => null));
public Model? Model { get; set; }
public ThinkingLevel ThinkingLevel { get; set; } = ThinkingLevel.Off;
public TimeZoneInfo TimeZone { get; init; } = TimeZoneInfo.Utc;
public string? TimeZoneId { get; init; }
public CancellationToken Signal => CancellationToken.None;
public bool IsIdle() => true;
public void Abort() => Calls.Add("abort");
public bool HasPendingMessages() => false;
public ContextUsage? GetContextUsage() => null;
public string GetSystemPrompt() => "prompt";
public void RequestCompaction(CompactOptions? options) => Calls.Add("compact");
public Task WaitForIdleAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task<SwitchResult> NewSessionAsync(NewSessionOptions? options, CancellationToken cancellationToken) => Task.FromResult(new SwitchResult(false));
public Task<SwitchResult> SwitchSessionAsync(string sessionId, CancellationToken cancellationToken) => Task.FromResult(new SwitchResult(false));
public void SendMessage(CustomMessage message, MessageDelivery delivery, bool triggerTurn) => Calls.Add($"send:{message.CustomType}:{delivery}:{triggerTurn}");
public void SendUserMessage(string text, IReadOnlyList<ImageContent>? images, MessageDelivery delivery) => Calls.Add($"user:{text}:{delivery}");
public void AppendEntry(string customType, JsonElement? data) => Calls.Add($"entry:{customType}");
public Task<bool> SetModelAsync(Model model, CancellationToken cancellationToken) { Model = model; return Task.FromResult(true); }
public void SetThinkingLevel(ThinkingLevel level) => ThinkingLevel = level;
public string? GetSessionName() => "name";
public void SetSessionName(string? name) => Calls.Add($"name:{name}");
}