mcpField
S
sinch-functions-dotnet

sinch-functions-dotnet

github.com/sinch开发1156 次安装4 周前更新
代码仓库

关于 sinch-functions-dotnet

Sinch Functions C#/.NET Runtime 是面向使用 ASP.NET 控制器模式编写 Sinch 函数的开发者技能,支持语音通话、会话 Webhook 和自定义 HTTP 端点。运行时提供依赖注入并预配置 Sinch SDK 客户端,语音函数使用 Voice API v2。

主要功能

  1. 01ASP.NET 控制器模式
  2. 02语音、会话与 HTTP 控制器
  3. 03依赖注入
  4. 04预配置 Sinch SDK 客户端
  5. 05Voice API v2 通话处理

适用场景

  1. 01构建入站语音控制器
  2. 02处理 SMS 或 WhatsApp Webhook
  3. 03在函数中提供自定义 REST 端点

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/sinch/skills --skill sinch-functions-dotnet

安装后在 Claude Code 中输入/sinch-functions-dotnet确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
context-services.md
conversation.md
shared-policy.md
voice.md

详细介绍

Sinch Functions — C#/.NET 运行时

概述

Sinch Functions 目前处于 Beta 阶段:Beta 期间免费,正式发布前 API 可能会发生变化。

程序包:Sinch.Functions.Runtime(NuGet)。使用 C# 函数和 ASP.NET 控制器模式,通过依赖注入响应电话呼叫、处理对话 webhook,并提供自定义 HTTP 端点。

本技能介绍 Sinch.Functions.Runtime 0.3.17 及更高版本,其中语音处理程序是 OnIncoming(Call, CommandBuilder) 重写方法。早期版本使用返回 CallHandlers 的 Handlers 属性。该接口已移除,因此请勿生成相关代码。Context.Voice 所代表的语音 API v2,也是新函数的编写目标。未加版本号的名称始终表示当前 API:Context.Voice 是 SinchFunctions.Voice.V2.Client。由于 developers.sinch.com 尚无 Functions-on-v2 页面,以下 v2 章节优先介绍。本地已有的 Voice v1 控制器会继续正常运行,无需修改;请参阅 Voice v1 回调。

相关技能:

  • sinch-functions — 平台概述、概念和运行时选择
  • sinch-cli — 终端命令(sinch functions dev、sinch functions deploy 等)
  • Voice API v2 由 sinch-voice-api-v2 技能介绍;加载该技能以了解 REST 契约、SVAML v2 命令和服务配置。
  • sinch-functions-node — 使用 Node.js/TypeScript 实现的相同概念

Agent 指令

策略门控 sinch-shared-policy@5(sha256:4864cf0fa8d6): 以下策略摘要具有约束力,必须按原文执行。在实现或实时执行之前,每次对话都要阅读一次完整的 Sinch 共享策略;如果此精确 ID/版本/指纹已加载,则跳过;如果版本更新或指纹不同,则阅读。本技能的规范操作路由位于其 Agent 指令和链接部分。

Sinch 策略摘要(具有约束力):

  1. 每次对话加载一次共享策略;跳过具有相同 ID/版本/指纹的重复副本。
  2. 根据请求和工作区推断产品、语言、地区和环境;只有遇到真正的阻碍时,才提出一个综合性问题。优先使用官方 Sinch SDK,除非请求或工作区另有决定,或没有官方 SDK 支持该语言或操作。
  3. 获准生成代码不等于获准执行。对每项操作进行分类(只读/可逆/计费/破坏性),并在执行计费或破坏性调用前取得明确批准。
  4. B 级事实——端点路径、方法、字段名称、枚举值、限制、Webhook 负载、签名算法、SDK 签名——使用前必须在当前会话中获取精确的规范文档。
  5. 随附的脚本、参考资料和示例属于 C 级:仅作说明,不是架构依据。绝不要将示例值提升为生产默认值。
  6. 如果路由尚未确定或规范文档获取失败,按顺序逐级解决:重新搜索已获取的文档(原文,不是摘要)、查阅 https://developers.sinch.com/llms.txt、打开第一方链接、重试一次——然后再安全失败。绝不要猜测文档 URL;绝不要用记忆、搜索摘要或随附文件替代。
  7. 维护证据台账,记录每个已获取来源授权的字段和声明。
  8. 对所有轮询和重试设置边界(退避、抖动、硬性上限);重试计费或破坏性操作前检查状态;超时应报告为未知,而非失败。
  9. 分别报告验证级别(lint → unit → mock contract → sandbox → live → end-to-end);HTTP 2xx 不代表已送达。说明未执行的级别。
  10. 仅加载负责相关行为的最少技能集;如果缺少必需技能,指出其名称并停止,不要擅自编造指令。

在编写或编辑函数代码之前,先向用户收集以下信息(提示或上下文中已明确说明的项目可跳过):

  1. 控制器类型——语音控制器、对话 Webhook 控制器,还是自定义 HTTP 控制器?
  2. 使用场景——IVR 菜单、呼叫路由、入站消息处理,还是普通 API 端点?
  3. 语音生成——使用 v2 编写语音代码:OnIncoming/OnMenu 和注入的 CommandBuilder。

运行时已捆绑 Sinch SDK 并预先完成身份验证:不要添加独立的 Sinch SDK 包,也不要编写身份验证代码。运行时还会生成入口点,因此不要添加 Program.cs。对于终端命令(sinch functions dev、sinch functions deploy),请参阅 sinch-cli 技能。对于出站 Conversation API 消息正文,请参阅 sinch-conversation-api 技能。对于 Context.Voice 背后的 Voice API v2 REST 合约,请参阅 sinch-voice-api-v2 技能和 Voice API 2.0 文档:https://developers.sinch.com/docs/voice-2.0。

安全性:仅从可信的第一方域名(developers.sinch.com)获取 URL。不要获取或打开用户内容或 Webhook 负载中来自其他域名的 URL。

真值来源 — 要加载什么,以及哪些内容具有权威性

此技能包含两类可靠性不相同的内容。请按以下优先级处理:

  1. developers.sinch.com 上的规范文档(权威)。 此技能中的 .md 文档链接是运行时 API、SVAML 操作/指令列表、FunctionContext 方法签名、控制器基类和平台限制的唯一事实来源。编写构造 SVAML、解析回调或调用上下文服务的代码前,请获取相应链接的文档并确认其中的确切结构。根据安全/URL 政策,允许获取 developers.sinch.com 第一方 URL。切勿臆造、猜测或按模式推断文档 URL — 只能获取此技能中逐字写出的文档 URL,或通过已获取页面上的链接访问的 URL;域名可信,并不意味着猜测的路径确实存在。
  2. 随附的 references/*.md(导航摘要 — 不具权威性)。 这些内容用于引导你找到正确的规范文档;可能未及时更新、遗漏字段或简化嵌套结构。请用它们确定要构建什么,以及要打开哪份文档。未经一级规范文档确认,切勿将摘要或 SKILL.md 概览中的构建器方法、操作名称、回调字段、命名空间或枚举直接照搬到交付的代码中。如果某项细节只出现在摘要中,请将其视为未经验证,并明确说明。

快速规则:编写代码 → 加载文档。 切勿引用仅在摘要中看到的确切字段、构建器方法、命名空间或枚举。

入门

sinch functions init simple-voice-ivr --name my-function --runtime csharp
cd my-function
sinch functions dev    # runs dotnet watch + tunnel

该模型采用 ASP.NET MVC:扩展基类控制器,重写所需成员,并由依赖注入提供服务和 SDK 客户端。

项目结构
MyFunction/
├── MyFunction.csproj      ← references Sinch.Functions.Runtime
├── FunctionController.cs  ← voice handlers (extends SinchVoiceController)
├── Init.cs                ← optional: ISinchFunctionInit for DI and extra routes
├── appsettings.json       ← config (variables, not secrets)
├── sinch.json             ← project manifest
├── assets/                ← private files
└── public/                ← static files, served at /

目标框架:.NET 10。控制器和辅助工具位于 SinchFunctions.Utils;v2 通话类型位于 SinchFunctions.Voice.V2。模板附带一个 GlobalUsings.cs,其中导入了这两个命名空间,以及 SinchFunctions.AI.Realtime、SinchFunctions.Runtime、Microsoft.AspNetCore.Mvc、Microsoft.AspNetCore.Authorization 和 System.Text.Json。

入口点:一个继承 SinchVoiceController 的控制器类。无需 Program.cs — 运行时会自动发现你的 ISinchFunctionInit 和控制器,并为你启动 ASP.NET 管道。

using SinchFunctions.Utils;
using SinchFunctions.Voice.V2;

public class FunctionController(FunctionContext context, IConfiguration configuration, ILogger<FunctionController> logger)
    : SinchVoiceController(context, configuration, logger)
{
    protected override CallFlow OnIncoming(Call call, CommandBuilder builder) =>
        builder.Answer().Say("Thanks for calling.").Hangup();
}

核心概念

FunctionContext 和随附的 Sinch SDK

通过 DI 注入控制器和服务:

public class FunctionContext
{
    public IConfiguration Configuration { get; }
    public IFunctionCache Cache { get; }
    public IFunctionStorage Storage { get; }
    public IFunctionDatabase Database { get; }
    public ILogger Logger { get; }
    public Client Voice { get; }                          // SinchFunctions.Voice.V2.Client — always present
    public ISinchConversation? Conversation { get; }      // pre-authenticated when configured
    public ISinchSms? Sms { get; }                        // pre-authenticated when configured
    public ISinchNumbers? Numbers { get; }                // pre-authenticated when configured
    public ISinchVerificationClient? Verification { get; } // pre-authenticated when configured
}

(仅为摘要 — 实施前,请对照权威的 Function 上下文参考确认确切的属性名称和类型。)

官方的 Sinch NuGet 包已随附,并针对所有产品预先完成身份验证 — 你无需自行添加该包或编写身份验证代码。这五个客户端会使用平台注入的环境变量,在 Context 上随时可用;如果某个产品未设置凭据,其属性值将为 null(Context.Voice 除外,它始终存在)— 调用前请先检查:if (Context.Sms != null) await Context.Sms.Batches.Send(...)。请注意,这与 Node.js 运行时不同:它不会在 context 上预先配置 Verification 客户端。对于其他产品(Number Lookup、Fax、Elastic SIP Trunking 等),请使用相同的项目凭据自行构造相应产品的客户端 — 无需额外的 NuGet 包,因为 Sinch.Functions.Runtime 已包含对 Sinch 的 PackageReference:

using Sinch;

var sinch = new SinchClient(
    Context.Configuration["PROJECT_ID"]!,
    Context.Configuration["PROJECT_ID_API_KEY"]!,
    Context.Configuration["PROJECT_ID_API_SECRET"]!);

有关 SinchClient 构造函数,请参阅 sinch-sdks 技能;有关其 API,请参阅对应的产品技能(例如 sinch-number-lookup-api)。

控制器 — 选择哪个基类
如果你要构建……继承常见的命名空间导入
入站语音功能(电话呼入你的号码)SinchVoiceControllerSinchFunctions.Utils、SinchFunctions.Voice.V2
消息机器人(SMS、WhatsApp、RCS、Messenger、Viber)SinchConversationControllerSinchFunctions.Utils(辅助方法和 ConversationMessage)
与语音或消息功能并行的自定义 HTTP/REST 端点SinchController + [Route]/[HttpGet] 等Microsoft.AspNetCore.Mvc
ElevenLabs 对话初始化和通话后 WebhookAgentControllerSinchFunctions.Controllers

一个项目中可以有多个控制器——例如并列使用 FunctionController : SinchVoiceController 和 StatusController : SinchController 很常见。

将通话路由到函数

入站通话通过 Voice v2 服务到达函数。sinch functions init 会选择一个服务,并将其 ID 写入 appsettings.json 中的 VOICE_SERVICE_ID;随后,sinch functions deploy 会将该服务的 Webhook 指向已部署的函数。电话号码通过其 RTC 应用 ID 绑定到服务,而该 ID 就是服务 ID。

VOICE_SERVICE_ID 是 v2 函数的标记。VOICE_APPLICATION_KEY 是 v1 的标记。

通话生命周期

入站事件以 CloudEvents 的形式发送到函数根路径。SinchVoiceController 会将每个事件路由到一个虚方法,你只需重写要处理的事件对应的方法。未重写的方法会返回 CallFlow.None,即响应 204。

事件重写方法触发时机
call.incomingOnIncoming(Call call, CommandBuilder builder)入站通话呼入该服务上的号码
call.answeredOnAnswered(Call call, CommandBuilder builder)出站通话被接听
call.menuOnMenu(Call call, MenuInput? menu, CommandBuilder builder)菜单收集到输入
call.webhook.<name>OnWebhook(string name, Call call, CommandBuilder builder)Webhook 命令触发
call.hangup、call.failedOnCompleted(string eventName, Call call)通话结束,因此无法返回命令
其他事件OnOther(string eventName, Call call, CommandBuilder builder)忙线、AMD、录音及其他事件

每个方法都有对应的 ...Async 异步版本(OnIncomingAsync 返回 Task<CallFlow>),适用于需要先等待某项操作的处理程序。call 是已解析的通话对象,包含 CallId、FromNumber、ToNumber、Direction、CallResult 等信息。使用 menu?.MenuName 和 menu?.Input 读取菜单输入。处理程序可通过控制器自身的 Context 和 Request 属性访问缓存、存储和请求标头。

(仅为概要。实施前,请对照 Voice API 2.0 文档 https://developers.sinch.com/docs/voice-2.0 确认事件名称和负载字段(另请参阅 sinch-voice-api-v2 技能)。)

命令构建器

运行时会向每个处理程序传入一个已预先填入此次调用信息的 CommandBuilder。在其后链接命令并返回它。它会自行转换为 CallFlow,因此 .Build() 是可选的。切勿手写 JSON。

protected override CallFlow OnIncoming(Call call, CommandBuilder builder) =>
    builder.Answer().Say("Thanks for calling.").Hangup();
  • 连接呼叫方:使用 DialPhone、DialSip、DialStream、DialRelay 或 DialAgent。每个方法都会将呼叫方与新参与方桥接起来,并在任一方离开时同时挂断双方。这些方法都不会接听来电,因此请先调用 Answer()。BridgeToOptions 用于设置 Timeout、MaxDuration、From 和 OnNoAnswer。OnNoAnswer 可一次处理忙线、拒接、超时和失败情况。
  • 菜单:AddOption(digits, flow) 接受字面 DTMF(0-9、*、#),Match(pattern, flow) 接受正则表达式。Menu(name, new MenuOptions { ... }) 可通过对象构建相同的菜单。菜单会在通话中运行匹配的选项,因此 OnMenu 通常只需记录日志。
  • 其他命令:Say、Play、Answer、Hangup、Pause、StopMessages、Amd、Webhook、StartRecording、StopRecording 和 GotoMenu。较底层的 Dial、BridgeCall 和 BridgeTo 可用于自行管理桥接。有关通话中仍有效的一方的规则、处理程序/事件表、不明显的行为(AMD、Say+Hangup 的时序、Voice Relay 提供商的拼写)以及 IVR/Agent 移交示例,请参阅 references/voice.md。如需查看完整的 CommandBuilder 方法集,请使用 Sinch.Functions.Runtime NuGet 包中附带的 XML 文档(IntelliSense),或参阅 C# 运行时指南——本技能不维护方法目录。

(仅为摘要。实施前,请对照 https://developers.sinch.com/docs/voice-2.0 上的 Voice API 2.0 API 参考确认确切的方法集和参数形式(另请参阅 sinch-voice-api-v2 技能)。)

使用 Context.Voice 发起通话

Context.Voice 是 SinchFunctions.Voice.V2.Client,使用项目 Access Key 密钥对(PROJECT_ID_API_KEY / PROJECT_ID_API_SECRET)进行身份验证。它可以发起通话(CallAsync,以及用于将被叫方桥接到 WebSocket 的 CallWithStreamAsync/CallWithRelayAsync)、转接正在进行的通话(TransferToPhoneAsync(callId, number) 及其 SIP、stream、relay 和 agent 对应方法),并修改已接通的通话(PatchAsync)。

await Context.Voice.CallAsync("+15559876543", new DialOptions
{
    From = "+15551234567",
    OnAnswer = new CommandBuilder().Say("Your appointment is confirmed.").Hangup()
});

发起通话会产生费用。运行会发起通话的代码前,请先征得用户确认。

备用方案——Context.Voice.RawAsync。 对于此客户端未建模的通话创建请求格式,请使用 Context.Voice.Flows 构建流程,并通过 RawAsync(body) 原样发送;它仅支持通话创建(POST /calls),不支持其他 Voice API v2 端点。它已完成身份验证,无需编写身份验证样板代码。

var flow = Context.Voice.Flows.Call("+15559876543", new DialOptions { From = "+15551234567" });
await Context.Voice.RawAsync(flow.ToCallRequest());
AI 语音 Agent

实时 Agent 会将对话保留在你的函数中:在 Init.cs 中通过 services.AddRealtimeAgent<T>(agent => agent.UseDeepgram())(或 UseOpenAi/UseGrok/UseGemini/UseVoiceRelay)注册它,将其定义为一个继承 RealtimeAgent 且包含 [AgentTool] 方法的类,然后在 OnIncoming 中调用 agent.Connect(call, builder)。若要改用 SIP 接入 ElevenLabs Agent,请使用 builder.Answer().DialAgent(AgentProvider.ElevenLabs, number)。完整的演示示例以及工具/转录文本/API 密钥的详细信息,请参阅 realtime-*/voice-relay-agent 模板。

Webhook 签名

v2 事件由服务签名。每个事件都带有 Authorization: service <serviceId>:<signature> 和 x-timestamp,签名使用每个服务的密钥,基于原始请求体、内容类型、时间戳和路径生成。在 WebhookProtection 设置下,只要 VOICE_SERVICE_SECRET 中存有 Base64 密钥,VoiceV2WebhookValidator 就会验证签名。

Sinch 目前尚未提供服务密钥。在此之前,如果控制器已启用保护但未配置服务密钥,它会在每个进程中记录一条警告并继续处理 webhook;密钥设置后,验证会自动启用,无需更改代码。请保持保护开启;不要将其设为 never。

FunctionContext 服务 — 缓存、存储、数据库

每个控制器都可以使用 Context.Cache、Context.Storage 和 Context.Database 来持久化状态。缓存是带 TTL 的键值存储;Storage 是文件/Blob 存储(生产环境中由 S3 提供支持);Database 使用 SQLite,连接字符串可与 Microsoft.Data.Sqlite 或 Dapper 搭配使用。

// Cache — key-value with TTL (seconds)
await Context.Cache.Set($"call:{data.CallId}:cli", data.Cli, 3600);
var cli = await Context.Cache.Get<string>($"call:{data.CallId}:cli");

// Storage — file/blob
await Context.Storage.WriteAsync("reports/daily.json", JsonSerializer.Serialize(data));

// Database — SQLite
using var conn = new SqliteConnection(Context.Database.ConnectionString);

完整服务参考 — 包含所有方法、批量操作、流 I/O 和 Dapper 示例:请阅读 references/context-services.md。

依赖注入(ISinchFunctionInit)

注册自定义服务和中间件。无需 Program.cs。

public class FunctionInit : ISinchFunctionInit
{
    public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
    {
        services.AddScoped<ICustomerService, CustomerService>();
        services.AddHttpClient<IMyApiClient, MyApiClient>();
    }

    public void ConfigureApp(SinchWebApplication app)
    {
        app.LandingPageEnabled = true;
        app.MapGet("/custom", () => Results.Ok(new { status = "ok" }));
    }
}
对话 webhook(简介)

这里介绍的是函数专用的粘合逻辑 — webhook 路由和事件辅助方法。有关出站消息正文(渠道、模板、富卡片),请参阅 sinch-conversation-api 技能。

扩展 SinchConversationController,并仅重写你关心的事件。所有处理程序均为可选,默认返回 Ok()。

using SinchFunctions.Utils;
using Sinch.Conversation.Hooks;

public class MyBot(FunctionContext context, IConfiguration configuration, ILogger<MyBot> logger)
    : SinchConversationController(context, configuration, logger)
{
    public override async Task<IActionResult> MessageInbound(MessageInboundEvent callback)
    {
        var text = callback.GetText();
        if (text == "hello")
            await Context.Conversation!.Messages.Send(Reply(callback, "Hi there!"));
        return Ok();
    }
}

完整对话参考 — 包含所有重写方法、扩展辅助方法(GetText、GetMedia、GetChannel 等)、ConversationMessage 静态辅助方法和多渠道分发模式:请阅读 references/conversation.md。

自定义控制器
[Route("api")]
public class MyController(FunctionContext context, IConfiguration configuration, ILogger<MyController> logger)
    : SinchController(context, configuration, logger)
{
    [HttpGet("status")]
    public IActionResult GetStatus() => Ok(new { status = "healthy" });
}
使用 [Authorize] 保护控制器

在任何控制器操作上使用标准 ASP.NET [Authorize] 特性即可要求 Basic Auth。Voice 和 Conversation webhook 以及 /health 始终绕过身份验证。Webhook 通过签名验证进行检查,而 /health 用于响应平台的存活探测。

using Microsoft.AspNetCore.Authorization;

public class SecureController(FunctionContext context, IConfiguration configuration, ILogger<SecureController> logger)
    : SinchController(context, configuration, logger)
{
    [Authorize]
    [HttpPost("webhook")]
    public IActionResult Webhook() => Ok(new { received = "data" }); // no [Authorize] on an action leaves it public
}

可使用以下两组凭据:

  • 项目的 API 密钥和密文,会自动注入为 PROJECT_ID_API_KEY 和 PROJECT_ID_API_SECRET
  • ADMIN_USER 变量及对应的 ADMIN_PASSWORD 密钥,由你自行设置

浏览器会在 /login 显示内置登录页面,而不是 Basic Auth 提示框。[Authorize] 采用默认拒绝策略:无论本地还是已部署环境,未配置凭据时都无法通过身份验证。若要在本地测试受保护的路由,请使用 dotnet user-secrets 设置 ADMIN_USER/ADMIN_PASSWORD。public/ 下的文件始终允许匿名访问。(仅为摘要 — 实施前请对照权威指南 Protect your function 确认准确的变量名称。) 使用 curl 进行测试:

curl -u $API_KEY:$API_SECRET https://your-function-url/webhook

常见模式

  • 转接呼叫:builder.Answer().DialPhone("+15551234567", new BridgeToOptions { Timeout = 20, OnNoAnswer = new CommandBuilder().Say("Nobody is available.").Hangup() })。
  • IVR 菜单:在 OnIncoming 中使用 .Menu(name, m => m.Prompt(...).AddOption("1", c => ...))。在 OnMenu 中记录输入。
  • 接听前查询信息:重写 OnIncomingAsync,等待 Context.Cache、Context.Storage 或 HTTP 调用,然后返回 builder。
  • 发起外呼:Context.Voice.CallAsync(to, new DialOptions { From = ..., OnAnswer = new CommandBuilder()... })。
  • 转接进行中的呼叫:Context.Voice.TransferToPhoneAsync(callId, number)。
  • AI 语音 Agent:在 Init.cs 中使用 services.AddRealtimeAgent<T>(a => a.UseDeepgram()),创建一个继承自 RealtimeAgent 的类,并在 OnIncoming 中调用 agent.Connect(call, builder)。
  • 处理入站消息:创建一个继承自 SinchConversationController 的控制器,使用 SinchFunctions.Utils 辅助方法和 ConversationMessage。请参阅 references/conversation.md。
  • 自定义 HTTP 端点:创建一个继承自 SinchController 的控制器,并使用标准 [Route] / [HttpGet] 特性。添加 [Authorize] 以要求使用 Basic Auth。
  • 在呼叫之间持久化状态:使用 Context.Cache 存储带有 TTL 的短期键,使用 Context.Database 通过 Microsoft.Data.Sqlite 或 Dapper 为每个函数持久化 SQLite 数据。请参阅 references/context-services.md。

易错点与最佳实践

  • 编写语音代码时使用 v2,通过重写 OnIncoming 和其他 On* 方法实现,绝不要使用已移除的 Handlers/CallHandlers/Plan 接口。
  • 返回传入的构建器。 不要在处理程序中执行 new CommandBuilder()。注入的构建器了解当前通话,因此 DialPhone、DialAgent 和 agent.Connect 才能同时挂断双方。new CommandBuilder() 适用于没有关联通话的流程,例如 DialOptions.OnAnswer。
  • 处理程序无内容可返回时,返回 CallFlow.None,而不是 null。 这会返回 204。
  • 使用 DialPhone 等方法连接双方,不要使用原始 Dial。 挂断一方不会自动结束另一方,因此使用原始 Dial 后,另一方可能会一直保持连接并产生费用,直到达到最长通话时长。
  • 未指定版本的名称就是当前 API。 Context.Voice 是 SinchFunctions.Voice.V2.Client。不存在名为 VoiceV2 的类型。
  • Context.Voice 永远不会为 null,这与其他 SDK 客户端不同。发送请求时,如果缺少凭据,它会报告错误。
  • v2 函数需要 VOICE_SERVICE_ID,而不是 VOICE_APPLICATION_KEY。sinch functions init 会写入该值,sinch functions deploy 会将服务 Webhook 指向此部署。
  • 保留启用的 WebhookProtection。 目前只在签名验证中开放校验,是因为 Sinch 尚未发布服务密钥。将其设为 never 会永久禁用校验,即使之后发布密钥也不会恢复。
  • 不要实现 HandleWebhook。 基类会根据 event 字段自动路由。
  • 使用其他 SDK 客户端前,先用 if (Context.Sms != null) 检查是否为 null,再使用 Context.Conversation、Context.Numbers 等属性。如果未设置某个产品的凭据,对应属性就会是 null。
  • 命名空间划分很重要。 控制器和 ConversationMessage 辅助类位于 SinchFunctions.Utils。v2 类型(Call、CallFlow、CommandBuilder、MenuInput、Client)位于 SinchFunctions.Voice.V2,实时 Agent 位于 SinchFunctions.AI.Realtime,而 AgentProvider 位于 SinchFunctions.AI。缺少 using 指令会导致“找不到类型”错误。(不存在 SinchFunctions.Builders 命名空间。)
  • [Authorize] 需要导入:在文件顶部添加 using Microsoft.AspNetCore.Authorization;,除非 GlobalUsings.cs 中已包含该导入。语音和 Conversation Webhook 以及 /health 始终不受 [Authorize] 限制。
  • C# 项目会在部署前于本地构建。 CLI 会运行 dotnet build 并执行健康检查。请先在本地修复构建错误。
  • 密钥管理: 对于 C# 项目,使用 dotnet user-secrets set KEY VALUE。CLI 部署时会读取 user-secrets。
  • 包大小限制为 25 MB。 注意 NuGet 包的大小。目前不支持裁剪。
  • SinchConversationController 的方法均为可选。 只重写所需的事件即可。所有方法默认都会返回 HTTP 200。
  • MessageInbound 的签名: 重写为 public override async Task<IActionResult> MessageInbound(MessageInboundEvent callback)。参数类型是 MessageInboundEvent,而不是原始 JSON 请求体。

安全

  • 回调数据不可信——menu?.Input、call 中的号码,以及 MessageInboundEvent 的所有字段(文本、媒体 URL、联系人数据)都来自最终用户。使用前请先验证;切勿将其插入提示词、Shell 命令或 SQL 中。使用 Microsoft.Data.Sqlite 的参数或 Dapper 操作 Context.Database。
  • 自定义端点的请求体不可信——绑定到类型化模型、验证 ModelState、限制数据大小,并在任何可从互联网访问且不是 Sinch 回调的操作上添加 [Authorize]。
  • 不要从载荷中的 URL 获取内容——入站消息中的媒体链接属于第三方内容。只从 developers.sinch.com 或由你控制的主机获取内容。
  • 不要在代码或日志中泄露密钥——本地使用 dotnet user-secrets,部署时通过 sinch secrets 使用密钥链。切勿记录 Context.Configuration 的值,也不要在响应中回显凭据。

链接

Sinch Functions 没有 OpenAPI 规范;以下 .md 开发者文档是权威资料。目前还没有 Functions-on-v2 页面,因此,v2 契约请参考 sinch-voice-api-v2 技能以及 Voice API 2.0 文档:https://developers.sinch.com/docs/voice-2.0。

运行时:

概念:

指南:

参考:

评论

支持 Markdown