@ diagrid/ 代理核心
在Diagrid AI代理适配器后共享的 Dapr 工作流运行时间.
应用程序代码通常安装适配器(例如.
@diagrid/agent-mastra)而不是这个包
直接说 即时直接安装 写入适配器 - 这是
软件包,您执行的两个基础类.
住在这里的东西
| 模块 | 导出 | 作用 |
|---|---|---|
agent/ | agentWorkflow, (中文(简体) ), invokeModelActivity,, , , , , , , , | 耐用剂循环 ——管弦乐手,活动与活动再试,框架-不可知 |
agent/ | agentWorkflowInputSchema,, , , , , , , , | 任何跨越工作流程界限的 Zod 计划 |
workflow/ | BaseWorkflowRunner | 运行时间生命周期、犬类工作流程命名、状态/终止/清洗、优雅关闭 |
workflow/ | buildWorkflowName, (中文(简体) ), sanitizeAgentName | 跨语言命名合同(dapr.<framework>.<AgentName>.workflow(中文(简体) ) |
mapping/ | BaseAgentMapper, (中文(简体) ), AgentMapper | 框架延长点 将本地代理映射到注册元数据 |
metadata/ | agentMetadataRecordSchema,, , , , , , , , | 代理登记记录的Zod计划 |
state/ | DaprStateStore | JSON 串行化包装器, 覆盖一个 Dapr 状态仓库组件 |
state/ | DaprAgentCheckpointer | 对话-记忆检查站;与 python-ai,每个适配器前缀 |
pubsub/ | DaprPubSub | 代理生命周期事件出版商 |
telemetry/ | setupTelemetry, (中文(简体) ), getTracer | OTLP/gRPC追踪——a 无 -op 除非 OTEL_EXPORTER_OTLP_ENDPOINT (或配置)已经设定 |
identity/ | oauthMiddleware, (中文(简体) ), oauthPlugin,, , , , , , , , | 已核实的来电者 ——JWKS支持的JWT验证,快取和快取适配器 |
workflow/dapr | Dapr 工作流程类型 | 只有类型再导出,所以适配器从不依赖 @dapr/dapr 本身 |
为什么代理循环是在这里而不是在适配器
它开始于马斯特拉适配器,一次回顾使后果变得显而易见:
它提到一个框架。 一个消息,一个工具呼叫,一个模型转动,重试
政策和检查站密钥布局是相同的形状,以SDK生产为准
因此适配器 #2 会复制整个持久循环,包括
容易误入歧途的变体(管弦乐手必须是
async function*; 计程车必须坐 外部 复试区域
钟必须来自 getCurrentUtcDateTime()) (中文(简体) ). 两份漂流,和
流出无声.
真正的每个框架都很小:一步一步地推动SDK 工具执行停留在检查的活动内,并读取本地代理 其元数据的结构.
写入适配器
执行两件事:
import {
BaseAgentMapper,
BaseWorkflowRunner,
SupportedFrameworks,
type AgentMapper,
type AgentMetadataRecord,
type SupportedFramework,
type WorkflowRuntime,
} from '@diagrid/agent-core';
class MyFrameworkMapper extends BaseAgentMapper {
readonly framework: SupportedFramework = SupportedFrameworks.MY_FRAMEWORK;
// Async by contract: frameworks commonly hide an agent's config behind async
// accessors (Mastra's `Agent.listTools()` returns a Promise), and a sync
// mapper would silently report empty tools for every real agent.
async mapAgentMetadata(agent: unknown): Promise<AgentMetadataRecord> {
// Read the framework-native agent, then hand the partial record to
// `this.finalize(...)`, which fills defaults, derives the workflow name
// and validates the result.
return this.finalize({/* … */});
}
}
class MyFrameworkRunner extends BaseWorkflowRunner {
readonly #mapper = new MyFrameworkMapper();
get mapper(): AgentMapper {
return this.#mapper;
}
protected registerWorkflowComponents(runtime: WorkflowRuntime): void {
runtime.registerWorkflowWithName(this.workflowName, myAgentWorkflow);
runtime.registerActivityWithName('…', myActivity);
}
}
CI警卫执行的两条规则:
- 导入框架 SDK 作为对等方, 从不依赖 . 读取本地代理
结构上,框架不会出现在运行时间图中。 读取配置
通过框架 访问者 (单位:千美元)
getInstructions(), (中文(简体) ),listTools()(没有) 它的平地属性——一个真正的实例通常使这些特性保持私人性质,而且 仅限属性的映射器通过每个基于固定的测试而无任何报告 对于真正的特工. - 从这里导入 Dapr 类型, 不从
@dapr/dapr。 。 。 . 一个包拥有 SDK 版本; 见src/workflow/dapr.ts为什么.
然后添加一个 ADAPTERS 输入
tests/guards/cross-framework-imports.test.ts - 它失败,直到你这样做.
遥测
setupTelemetry() 返回时 undefined 当没有配置 OTLP 端点时,以及
这是成功,而不是出错——这是正常的局部路径. getTracer()
总是可以安全拨打: 没有供应商注册 OTel API 手拿回 a
没有追踪器,所以仪器 从来不需要分辨是否在追踪.
最终点的解析优先性是明确的配置-然后
OTEL_EXPORTER_OTLP_ENDPOINT,与 enabled: false 电路短路.
信号路径后缀( S)/v1/traces由于GRPC出口商想要
一个赤 host:port。 。 。 .
身份
经核实的来电人和半个出门电话
diagrid.identity 在Python SDK(英语:Python SDK)中. 一行的应用程序代码可以买入
一半;外出的一半没有花费.
import express from 'express';
import { getVerifiedUser, oauthMiddleware } from '@diagrid/agent-core/express';
const app = express();
app.use(oauthMiddleware({ scopes: ['agent.invoke'] }));
app.post('/invoke', (req, res) => {
res.json({ caller: getVerifiedUser(req)?.subject });
});
以相同的选项快捷:
import Fastify from 'fastify';
import { getVerifiedUser, oauthPlugin } from '@diagrid/agent-core/fastify';
const app = Fastify();
await app.register(oauthPlugin, { scopes: ['agent.invoke'] });
app.post('/invoke', (request) => ({
caller: getVerifiedUser(request)?.subject,
}));
快取和快取是可选的对等点,每个适配器都是自己的
子路径入口 : @diagrid/agent-core/express 财务报告和审定财务报表
@diagrid/agent-core/fastify安装您所服务的任何一种;
安装两个都不行,而且包筒也解决不了问题
运行时间,而不是在类型检查时,再出口的枪管会在那里
手执 TS2307 输入到任何类型中 skipLibCheck。 。 。 .
tests/guards/cross-framework-imports.test.ts 保持这条线.
经核实的呼叫器读取 getVerifiedUser(req) 而不是从
req.user。 。 。 . user 是应用程序的插槽——护照的,典型的——和a
库中声称将在运行时覆盖应用程序自己的用户
与 @types/passport'在编译时声明它. 活猫
SDK同样区分 request.state.diagrid_user。 。 。 .
出门的半个电话除了 fetch 你造过一次.
createIdentityFetch() 手背一个普通的 fetch- 形状的功能,所以
app会发出一个普通的呼叫, 并且从不组装身份标题本身 :
import { createIdentityFetch } from '@diagrid/agent-core';
// Once, at module scope. The token is read per call, not captured here.
const identityFetch = createIdentityFetch();
export async function search(query: string): Promise<Response> {
return identityFetch('https://mcp.example.com/tools/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query }),
});
}
一个功能而不是客户类 因为这就是MCP SDK,
生成了 API 客户端, 大多数 HTTP 库接受: pass identityFetch
无论什么习俗 fetch 它的下方的电话载着
来电.
有三个属性是值得知道的, 因为它们是原因不 用手组装标题:
- 信使在呼叫时宣读. 一个长寿,共享
identityFetch实值 安全:每个同时提出的申请都携带自己的呼号。 标题 当函数构建时, 将会发送任何用户 电流. - 取出头前取出. 请求从不包含 特性当前上下文不存在,无论设置是什么。 户外a 经核实的请求——预定的、酒吧/分公司或克龙触发器——标题是 完全省略而不是发送为空,呼叫开始 而不是抛出.
- 信物只写在您给的来源上. 重定向到另一个
原产地放弃;没有此选项, a
Location被传唤者选择的 将呼叫器的代号交给任何主机 一个例外是 同宿主升级自http端口为 80 到https在港口443。 执行 意思是链子是走在这里而不是走在里面fetch,所以随行Response报告redirected === false。 。 。 .
自己传 fetch 作为 createIdentityFetch({ fetch }) {\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}给你打包
已经存在——追踪,回想,测试双层——上面有分层的身份.
对于一个你完全无法替代的客户来说, attachIdentityHeaders(request) 这是
与单请求截取器相同的行为
原地警卫不能从那里掩护.
范围检查 hasScope(user, scope) - 自由活动兄弟会
has_scope 在 Python SDK 中和 HasScope 在爪哇语和.NET中。 这是一个
函数而不是方法,因为 VerifiedUser 是普通的形状,所以
继续研究从工作流程有效载荷、缓存或固定装置中恢复的一个:
import express from 'express';
import { hasScope } from '@diagrid/agent-core';
import { getVerifiedUser } from '@diagrid/agent-core/express';
const app = express();
app.post('/admin', (req, res) => {
const user = getVerifiedUser(req);
if (!user || !hasScope(user, 'agent.admin')) {
res.status(403).json({ error: 'oauth.missing_scope' });
return;
}
res.json({ ok: true });
});
发现核查坐标,以便: issuer + 键
jwksUri - 本地侧车 /v1.0/metadata (单位:千美元)DAPR_HTTP_PORT- 遥控器
侧车 /v1.0/metadata (单位:千美元)DAPR_HTTP_ENDPOINT,这就是如何 diagrid dev run 到达催化装置的侧车,无任何回路) •
DIAGRID_DP_SENTRY_ISSUER。本地到远程之前,所以部署在集群中
应用程序继续从回回回调中响应. 单一的清晰字段缩小
不管是哪一个源答案,而不是替换它,所以只给一个
audience 仍然让我们找到发行商 issuer
由于JWKS URI 本身就足够了 issuer + 键
/jwks.jsonA级 jwks_uri 侧车广告被采用 以获得
价值,但只有当发行人 广告是 发行人实际上
解决了:绝不能用外国发行人的钥匙来核实被钉住的发行人
设置。 何时 DAPR_API_TOKEN 设置为
遥控探测器载着它 dapr-api-token过一个非..https 终点是
仍然派遣,并警告说,它行走在晴朗的地方.
整个过程中没有关闭——401个是来电者的坏消息或丢失的信号,403个是
缺少范围, 当关键材料或配置是服务器的问题时为 503 .
每个拒绝都回答 {"error":"<code>"} 下级 Cache-Control: no-store, (中文(简体) ),
和Diagrid SDK的代码完全一样 他们的十人,在
顺序 OAuthErrorCodes 声明:
| 状态 | 代码 | 何时 |
|---|---|---|
| 401 (中文(简体) ) | oauth.missing_token | 没有 X-Diagrid-User-Token 请求必须满足. |
| 第503条 | oauth.not_configured | 无法建立校验器:没有提供源代码,或者JWKS URI无法使用. |
| 第503条 | oauth.verifier_unavailable | 无法获取密钥集, 或没有匹配符号的密钥 . |
| 401 (中文(简体) ) | oauth.expired | exp 已经过去,超过120年代的时钟. |
| 401 (中文(简体) ) | oauth.invalid_issuer | iss 不匹配已解决的发行者. |
| 401 (中文(简体) ) | oauth.invalid_audience | aud 不匹配决心的受众. |
| 401 (中文(简体) ) | oauth.invalid_signature | 签名不对照 JWKS 密钥进行校验 . |
| 401 (中文(简体) ) | oauth.decode_error | 代号完全不是很好的JWS. |
| 401 (中文(简体) ) | oauth.invalid_token | 任何其他索赔或算法失败——缺失 exp编号 :iss编号 :sub,一个 alg 在许可名单之外. |
| 403 (韩语) | oauth.missing_scope | 证物已经核实,但并非每个范围 scopes 问取来. |
VerifiedUser.scopes 已按正常排序,没有重复,以
联 合 国 scp 编号 : scope 编号 : scopes 使用的指使牌及其排列顺序
他们。 范围是一组:一个符号碰巧列出它们的顺序不是
和每个Diagrid SDK报告相同同义词的顺序
因为回声 user.scopes 进入JSON的身体是最常见的事情
一个处理者会处理他们.
requireAuth 处理无案,别的事 默认时左转
联 合 国 true,请求没有 X-Diagrid-User-Token 被拒绝 401
oauth.missing_token; 设置为 false,该请求通过时没有
已核实的呼叫器附后。 一个标志 实值 总是被验证
总是被拒之门外.
但是,JWKS URI已经解决了,它必须是 https。拒绝使用
第503条 oauth.not_configured,因为密钥集是整个信任根:a
重写路径攻击者 http 反应取代了自己的键并
之后的每次签名检查都证实了他的签名. 循环主机是
免责——这是本地侧车发布其密钥的地方,主机是
解析而非图案匹配,所以DNS名称类似 127.evil.example 实值
没有. allowInsecureJwks: true 选择离开任何地方, 它放松平坦
http 仅限: 选入纯文本对读取签名密钥没有任何影响
从一个文件系统,所以 file:// URI在国旗设定时拒绝。 一个JWKS
无法解析的URI被同样拒绝 在建设时间
而不是在每一个请求中,这是一个配置错误,而不是降级.
许可证
业务来源许可证1.1 2026年 — Present Diagrid Inc.