fix(subagent): intersect model discovery with authorized catalog
This commit is contained in:
1 parent
0948cd9809
commit
7609189262
10 files changed
+102
-64
No files matched your search
+4
-4
@@ -9,14 +9,14 @@
|
||||
en: 3238dfb7db7dfdd8
|
||||
zh: ec3bdd9a9da0a321
|
||||
/agent-note-user-authorized-subagent-model-routes/decision:
|
||||
en: 499721e05ad15d9c
|
||||
zh: 2bcf5ade8910da8e
|
||||
en: 47b020441723cc06
|
||||
zh: 8394c32b711a0b30
|
||||
/agent-note-user-authorized-subagent-model-routes/alternatives-considered:
|
||||
en: e41c17226190ea8b
|
||||
zh: d3b1395fafe75f01
|
||||
/agent-note-user-authorized-subagent-model-routes/consequences:
|
||||
en: e57b48c7967a316d
|
||||
zh: 085fe2e57019c01c
|
||||
en: 1749cfbfda849692
|
||||
zh: 9925e638cd72fba6
|
||||
/agent-note-user-authorized-subagent-model-routes/related-decisions:
|
||||
en: 17fdfabedb673935
|
||||
zh: 496f4f85622d7d15
|
||||
+2
-2
@@ -14,7 +14,7 @@ The Host-owned `subagent-model-selection` settings section stores an explicit `e
|
||||
|
||||
A newly composed top-level Session snapshots the route list in `subagent/model-selection-policy` when the setting is enabled, before its first model request. Event presence means selection was enabled; the event does not store the global switch. Child Sessions inherit that exact list from their live parent, and resumed Sessions use the recorded event instead of current settings. Settings changes therefore affect only subsequently composed top-level Sessions. Without the event, including an explicitly empty restored Session, explicit selection remains restricted to the current parent route.
|
||||
|
||||
The fixed `list_subagent_models` schema does not enumerate the policy. At call time, it lists authorized registered providers and resolves their allowed exact model ids, including ids absent from advisory catalogs. Exact lookup requires authorization before resolving model metadata and all supported reasoning efforts. Without a recorded policy, discovery uses the parent Agent’s current effective route. The delegation executor independently checks any explicit provider, model, or effort selection against the same authorization before adapter preflight. A call with no selection fields retains configured or inherited routing.
|
||||
The fixed `list_subagent_models` schema does not enumerate the policy. At call time, it lists authorized registered providers and intersects their advertised model catalogs with the recorded route list. Exact lookup requires authorization before resolving model metadata and all supported reasoning efforts, including for ids absent from the catalog. Without a recorded policy, discovery resolves only the parent Agent’s current effective route, even if the catalog omits it. The delegation executor independently checks any explicit provider, model, or effort selection against the same authorization before adapter preflight. A call with no selection fields retains configured or inherited routing.
|
||||
|
||||
Model selection has no unrestricted static mode. The default-off Host setting authorizes alternative child routes; without it, the parent route and its supported efforts remain selectable. The primary spawn tool captures the setting once, while all capable delegation tools and shared discovery read the same Session policy. Fork defaults preserve the parent route, and its description warns about prefix recomputation when that route changes.
|
||||
|
||||
@@ -33,7 +33,7 @@ Model selection has no unrestricted static mode. The default-off Host setting au
|
||||
## Consequences
|
||||
|
||||
- New adapter registrations and newly advertised models do not expand user authorization.
|
||||
- Adapter removal can make provider discovery unavailable; provider model listings report exact-model resolution failures per item without discarding other results or deleting the saved route decision; an allowed model remains usable when its adapter accepts it even if the advisory catalog omits it.
|
||||
- Adapter removal or catalog lookup failure can make provider discovery unavailable without deleting the saved route decision; an allowed model remains usable when its adapter accepts it even if the advisory catalog omits it.
|
||||
- The allowlist itself consumes no parent-request tokens. Only a `list_subagent_models` result enters the transcript.
|
||||
- The policy event is log-only and is appended while an Agent is composed, before either SDK begins its run subscription. Shipped SDK profiles do not enable this Web-owned preference, so the event changes neither SDK's expected notifications or persisted-session output; package restore tests own its durable projection instead of fabricating an SDK composition solely to emit it.
|
||||
- Unit coverage pins settings validation, malformed durable values, Session sampling and inheritance, shared definitions, parent-route defaults, discovery restrictions, executor denial, and the settings UI’s staged writes and revision checks. The assembled Web scenario pins the real settings document and Plugins card flow.
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ Host 自有的 `subagent-model-selection` 设置 section 保存显式 `enabled`
|
||||
|
||||
设置启用时,新组合的顶层 Session 在首个模型请求前将路由列表记录为 `subagent/model-selection-policy`。事件存在表示选择已启用;事件不保存全局开关。子 Session 从在线父级继承同一精确列表,恢复的 Session 使用已记录事件而非当前设置。因此设置变更只影响之后组合的顶层 Session。没有该事件时,包括显式为空的恢复 Session,显式选择仍仅限当前父级路由。
|
||||
|
||||
固定的 `list_subagent_models` schema 不枚举策略。调用时,它列出已授权且已注册的提供方,并解析允许的精确模型 ID,包括建议性目录未列出的 ID。精确查询先要求授权,再解析模型元数据及全部支持的推理强度。没有已记录策略时,发现使用父 Agent 当前生效的路由。委派执行器会在适配器预检前独立校验显式提供方、模型或强度选择是否符合相同授权。没有选择字段的调用保留配置或继承的路由。
|
||||
固定的 `list_subagent_models` schema 不枚举策略。调用时,它列出已授权且已注册的提供方,并取其公布模型目录与已记录路由列表的交集。精确查询先要求授权,再解析模型元数据及全部支持的推理强度,也允许目录未列出的 ID。没有已记录策略时,发现只解析父 Agent 当前生效的路由,即使目录未列出它。委派执行器会在适配器预检前独立校验显式提供方、模型或强度选择是否符合相同授权。没有选择字段的调用保留配置或继承的路由。
|
||||
|
||||
模型选择没有无限制的静态模式。默认关闭的 Host 设置授权其他子级路由;没有该授权时,仍可选择父级路由及其支持的强度。主 spawn 工具一次性记录设置,所有支持选择的委派工具和共享发现均读取同一 Session 策略。fork 默认保留父级路由,其描述会提示改变路由时的前缀重算成本。
|
||||
|
||||
@@ -33,7 +33,7 @@ Host 自有的 `subagent-model-selection` 设置 section 保存显式 `enabled`
|
||||
## Consequences
|
||||
|
||||
- 新适配器注册和新公布模型不会扩大用户授权。
|
||||
- 适配器移除可能使提供方发现不可用;提供方模型列表逐项报告精确模型解析失败,不会丢弃其他结果或删除已存路由决定;只要适配器接受某个获准模型,即使建议性目录省略它,该模型仍可使用。
|
||||
- 适配器移除或目录查询失败可能使提供方发现不可用,但不会删除已存路由决定;只要适配器接受某个获准模型,即使建议性目录省略它,该模型仍可使用。
|
||||
- 允许列表本身不消耗父级请求 token。只有 `list_subagent_models` 结果进入 transcript。
|
||||
- 策略事件仅存在于日志,并在 Agent 组合期间、两套 SDK 开始订阅运行前追加。随附 SDK profile 不启用这项 Web 自有偏好,因此该事件不会改变任一 SDK 的预期通知或持久 Session 输出;其持久投影由包级恢复测试负责,不会为了发出该事件而虚构 SDK 组合。
|
||||
- 单元覆盖固定设置校验、异常持久值、Session 取样与继承、共享定义、父级路由默认值、发现限制、执行器拒绝,以及设置 UI 的暂存写入与 revision 检查。组装 Web 场景固定真实设置文档与 Plugins 设置卡流程。
|
||||
|
||||
@@ -66,8 +66,8 @@
|
||||
en: a4ca31b9cbacda6b
|
||||
zh: 91ac3e0784a14697
|
||||
/deepseek-ai-dsh-tool-subagent/model-experience/model-selection-and-discovery/what-the-model-sees:
|
||||
en: b2bafbccb0e10406
|
||||
zh: d93155da44a977e1
|
||||
en: be3c5b647252ddc7
|
||||
zh: efecd46ed7dd9a95
|
||||
/deepseek-ai-dsh-tool-subagent/model-experience/model-selection-and-discovery/token-effect:
|
||||
en: cfdfe3b1106c3b57
|
||||
zh: 3f421f2901d6f7ab
|
||||
|
||||
@@ -143,7 +143,7 @@ Prefix-stable while provider instances and their configuration are unchanged. Ad
|
||||
|
||||
#### What the model sees
|
||||
|
||||
The shared `list_subagent_models` definition reads the calling Session’s route policy, falling back to the current parent route when no policy is recorded. Calls reject while the optional `ctx.llm` service is unavailable. Discovery resolves only allowed routes, including exact model ids absent from advisory catalogs; unauthorized provider/model lookups fail before adapter access. Provider listings retain resolved models and report lookup errors beside each failed model; exact-model lookup failures and cancellation still reject the call. Execution independently enforces the same policy.
|
||||
The shared `list_subagent_models` definition reads the calling Session’s route policy, falling back to the current parent route when no policy is recorded. Calls reject while the optional `ctx.llm` service is unavailable. With a recorded policy, provider model lists intersect the advertised catalog with the Session’s authorized routes. Without one, they resolve only the current parent model, even if the catalog omits it. Exact-model queries accept authorized ids absent from the catalog; unauthorized provider/model lookups fail before adapter access. Catalog or exact-model lookup failures and cancellation reject the call. Execution independently enforces the same policy.
|
||||
|
||||
#### Token effect
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ kind: "package-reference"
|
||||
|
||||
#### 模型看到什么
|
||||
|
||||
共享的 `list_subagent_models` 定义读取调用 Session 的路由策略,没有已记录策略时使用当前父级路由。可选的 `ctx.llm` 服务不可用时,调用会被拒绝。发现只解析允许的路由,包括建议性目录未列出的精确模型 ID;未授权的提供方/模型查询会在访问适配器前失败。提供方列表保留成功解析的模型,并在每个失败模型旁显示查询错误;精确模型查询失败和取消仍会使调用被拒绝。执行过程独立强制同一策略。
|
||||
共享的 `list_subagent_models` 定义读取调用 Session 的路由策略,没有已记录策略时使用当前父级路由。可选的 `ctx.llm` 服务不可用时,调用会被拒绝。有已记录策略时,提供方模型列表取公布目录与 Session 授权路由的交集。没有策略时,只解析当前父级模型,即使目录未列出它。精确模型查询允许已授权但目录未列出的 ID;未授权的提供方/模型查询会在访问适配器前失败。目录或精确模型查询失败以及取消都会使调用被拒绝。执行过程独立强制同一策略。
|
||||
|
||||
#### Token 影响
|
||||
|
||||
|
||||
@@ -38,10 +38,20 @@ function modelLine(provider: string, model: { id: string; name: string; descript
|
||||
/** Read the requested provider, allowed models, or exact-model efforts. */
|
||||
async function listSubagentModels(
|
||||
ctx: Context,
|
||||
policy: ModelSelectionPolicy,
|
||||
policy: ModelSelectionPolicy | undefined,
|
||||
request: ListSubagentModelsRequest,
|
||||
signal: AbortSignal,
|
||||
exec: ToolRunContext,
|
||||
): Promise<string> {
|
||||
const selectionEnabled = policy !== undefined
|
||||
if (policy === undefined) {
|
||||
if (exec.agent === undefined) throw new Error('list_subagent_models requires a calling agent')
|
||||
const { provider, model } = parentAgentOptionsForDelegation(exec.agent)
|
||||
if (provider === undefined || model === undefined) {
|
||||
throw new Error('cannot discover child LLM routes without a parent provider and model')
|
||||
}
|
||||
policy = { routes: [{ provider, model }] }
|
||||
}
|
||||
const { signal } = exec
|
||||
const llm = ctx.get('llm')
|
||||
if (llm === undefined) {
|
||||
throw new Error('cannot discover child LLM routes because the `llm` service is unavailable')
|
||||
@@ -58,23 +68,19 @@ async function listSubagentModels(
|
||||
}
|
||||
if (request.provider.length === 0) throw new Error('`provider` must be non-empty')
|
||||
const allowedRoutes = policy.routes.filter(route => route.provider === request.provider)
|
||||
if (allowedRoutes.length === 0) {
|
||||
const [firstRoute] = allowedRoutes
|
||||
if (firstRoute === undefined) {
|
||||
throw new Error(`LLM provider "${request.provider}" is not allowed for this Session`)
|
||||
}
|
||||
const provider = registeredProvider(llm, policy, request.provider)
|
||||
if (request.model === undefined) {
|
||||
const lines = await Promise.all(allowedRoutes.map(async (route) => {
|
||||
let model
|
||||
try {
|
||||
model = await llm.resolveModelInfo(provider.id, route.model, signal)
|
||||
} catch (error) {
|
||||
signal.throwIfAborted()
|
||||
return `${provider.id}/${route.model} — Error: ${error instanceof Error ? error.message : String(error)}`
|
||||
}
|
||||
return modelLine(provider.id, model)
|
||||
}))
|
||||
const models = selectionEnabled
|
||||
? (await llm.listModels(provider.id)).filter(model => allowedRoutes.some(route => route.model === model.id))
|
||||
: [await llm.resolveModelInfo(provider.id, firstRoute.model, signal)]
|
||||
signal.throwIfAborted()
|
||||
return lines.join('\n')
|
||||
return models.length === 0
|
||||
? `(no advertised models for ${provider.id})`
|
||||
: models.map(model => modelLine(provider.id, model)).join('\n')
|
||||
}
|
||||
if (request.model.length === 0) throw new Error('`model` must be non-empty')
|
||||
if (!allowedRoutes.some(route => route.model === request.model)) {
|
||||
@@ -90,11 +96,11 @@ async function listSubagentModels(
|
||||
|
||||
/**
|
||||
* Register `list_subagent_models` in the owning composition.
|
||||
* Provider listings report lookup failures per model; cancellation rejects the call.
|
||||
* Provider listings intersect the catalog with authorization, or resolve only the parent model when selection is disabled.
|
||||
* @param ctx - Context whose tool registry owns the fixed discovery definition.
|
||||
* @param policyFor - Resolve the calling Session’s authorized routes.
|
||||
* @param policyFor - Read the calling Session’s authorized routes; undefined restricts discovery to its parent model.
|
||||
*/
|
||||
export function registerListSubagentModels(ctx: Context, policyFor: (exec: ToolRunContext) => ModelSelectionPolicy): void {
|
||||
export function registerListSubagentModels(ctx: Context, policyFor: (exec: ToolRunContext) => ModelSelectionPolicy | undefined): void {
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'list_subagent_models',
|
||||
description:
|
||||
@@ -118,7 +124,7 @@ export function registerListSubagentModels(ctx: Context, policyFor: (exec: ToolR
|
||||
render: (_args, result) => [{ type: 'text', text: result }],
|
||||
},
|
||||
execute(args, exec) {
|
||||
return listSubagentModels(ctx, policyFor(exec), args, exec.signal)
|
||||
return listSubagentModels(ctx, policyFor(exec), args, exec)
|
||||
},
|
||||
}))
|
||||
}
|
||||
@@ -137,11 +143,6 @@ export function apply(ctx: Context): void {
|
||||
registerListSubagentModels(ctx, (exec) => {
|
||||
if (exec.agent === undefined) throw new Error('list_subagent_models requires a calling agent')
|
||||
const routes = subagentModelSelectionPolicy(ctx.sessionProjections, exec.agent.session)
|
||||
if (routes !== undefined) return { routes }
|
||||
const { provider, model } = parentAgentOptionsForDelegation(exec.agent)
|
||||
if (provider === undefined || model === undefined) {
|
||||
throw new Error('cannot discover child LLM routes without a parent provider and model')
|
||||
}
|
||||
return { routes: [{ provider, model }] }
|
||||
return routes === undefined ? undefined : { routes }
|
||||
})
|
||||
}
|
||||
@@ -2,6 +2,13 @@
|
||||
import { LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
class DiscoveryAdapter extends LlmAdapter {
|
||||
async listModels(provider) {
|
||||
return [
|
||||
{ provider, id: 'healthy', name: 'Healthy' },
|
||||
{ provider, id: 'unauthorized', name: 'Unauthorized' },
|
||||
]
|
||||
}
|
||||
|
||||
async resolveModel(provider, model) {
|
||||
if (model === 'removed') throw new Error('model was removed')
|
||||
return { provider, id: model, name: 'Healthy' }
|
||||
|
||||
@@ -194,7 +194,7 @@ describe('list_subagent_models', () => {
|
||||
ctx.llm.registerAdapter(['alpha', 'beta'], new CatalogAdapter())
|
||||
|
||||
expect(text(await call(ctx, {}))).toBe('alpha — ALPHA API')
|
||||
expect(text(await call(ctx, { provider: 'alpha' }))).toBe('alpha/fast — Fast: Focused work.\nalpha/unlisted — Fast: Focused work.')
|
||||
expect(text(await call(ctx, { provider: 'alpha' }))).toBe('alpha/fast — Fast: Focused work.')
|
||||
expect(text(await call(ctx, { provider: 'alpha', model: 'unlisted' })))
|
||||
.toContain('alpha/unlisted — Fast')
|
||||
|
||||
@@ -216,62 +216,92 @@ describe('list_subagent_models', () => {
|
||||
expect(listModels).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('resolves an allowed model even when the advertised catalog is empty', async () => {
|
||||
const ctx = await setupListTool([{ provider: 'alpha', model: 'fast' }])
|
||||
ctx.llm.registerAdapter(['alpha'], new CatalogAdapter(true))
|
||||
it('returns an empty list when the catalog contains no authorized model', async () => {
|
||||
const ctx = await setupListTool([{ provider: 'alpha', model: 'unlisted' }])
|
||||
ctx.llm.registerAdapter(['alpha'], new CatalogAdapter())
|
||||
const result = await call(ctx, { provider: 'alpha' })
|
||||
expect(result.isError).toBe(false)
|
||||
expect(text(result)).toBe('alpha/fast — Fast: Focused work.')
|
||||
expect(text(result)).toBe('(no advertised models for alpha)')
|
||||
})
|
||||
|
||||
it.each([false, true])('reports each failed model alongside resolved models (all failed: %s)', async (allFailed) => {
|
||||
it('lists authorized catalog entries without resolving removed or unlisted models', async () => {
|
||||
const ctx = await setupListTool([
|
||||
{ provider: 'alpha', model: 'removed' },
|
||||
...allFailed ? [] : [{ provider: 'alpha', model: 'plain' }],
|
||||
{ provider: 'alpha', model: 'unavailable' },
|
||||
{ provider: 'alpha', model: 'plain' },
|
||||
])
|
||||
try {
|
||||
const adapter = new CatalogAdapter()
|
||||
vi.spyOn(adapter, 'resolveModel').mockImplementation(async (provider, model) => {
|
||||
if (model === 'removed') throw new Error('model was removed')
|
||||
// oxlint-disable-next-line typescript/prefer-promise-reject-errors -- Exercise non-Error rejections from an external adapter.
|
||||
if (model === 'unavailable') return Promise.reject('metadata unavailable')
|
||||
return { provider, id: model, name: 'Plain' }
|
||||
})
|
||||
const resolveModel = vi.spyOn(adapter, 'resolveModel').mockRejectedValue(new Error('lookup unavailable'))
|
||||
ctx.llm.registerAdapter(['alpha'], adapter)
|
||||
|
||||
const result = await call(ctx, { provider: 'alpha' })
|
||||
expect(result.isError).toBe(false)
|
||||
expect(text(result)).toBe([
|
||||
'alpha/removed — Error: model was removed',
|
||||
...allFailed ? [] : ['alpha/plain — Plain'],
|
||||
'alpha/unavailable — Error: metadata unavailable',
|
||||
].join('\n'))
|
||||
expect(text(result)).toBe('alpha/plain — Plain')
|
||||
expect(resolveModel).not.toHaveBeenCalled()
|
||||
|
||||
const exact = await call(ctx, { provider: 'alpha', model: 'removed' })
|
||||
expect(exact.isError).toBe(true)
|
||||
expect(text(exact)).toContain('model was removed')
|
||||
expect(text(exact)).toContain('lookup unavailable')
|
||||
} finally {
|
||||
await ctx.fiber.dispose()
|
||||
}
|
||||
})
|
||||
|
||||
it.each([false, true])('does not render cancellation as a model error (adapter rejects: %s)', async (rejects) => {
|
||||
const ctx = await setupListTool([{ provider: 'alpha', model: 'plain' }])
|
||||
it('rejects a failed catalog lookup', async () => {
|
||||
const ctx = await setupListTool()
|
||||
try {
|
||||
const adapter = new CatalogAdapter()
|
||||
vi.spyOn(adapter, 'listModels').mockRejectedValue(new Error('catalog unavailable'))
|
||||
ctx.llm.registerAdapter(['alpha'], adapter)
|
||||
const result = await call(ctx, { provider: 'alpha' })
|
||||
expect(result.isError).toBe(true)
|
||||
expect(text(result)).toContain('catalog unavailable')
|
||||
} finally {
|
||||
await ctx.fiber.dispose()
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects cancellation after a catalog lookup', async () => {
|
||||
const ctx = await setupListTool()
|
||||
try {
|
||||
const controller = new AbortController()
|
||||
const adapter = new CatalogAdapter()
|
||||
vi.spyOn(adapter, 'resolveModel').mockImplementation(async (provider, model) => {
|
||||
vi.spyOn(adapter, 'listModels').mockImplementation(async () => {
|
||||
controller.abort(new Error('discovery cancelled'))
|
||||
if (rejects) throw new Error('lookup interrupted')
|
||||
return { provider, id: model, name: 'Plain' }
|
||||
return []
|
||||
})
|
||||
ctx.llm.registerAdapter(['alpha'], adapter)
|
||||
|
||||
const result = await call(ctx, { provider: 'alpha' }, controller.signal)
|
||||
expect(result.isError).toBe(true)
|
||||
expect(text(result)).toContain('discovery cancelled')
|
||||
expect(text(result)).not.toContain('alpha/plain —')
|
||||
} finally {
|
||||
await ctx.fiber.dispose()
|
||||
}
|
||||
})
|
||||
|
||||
it.each([false, true])('resolves only the current parent model when selection is disabled (empty catalog: %s)', async (empty) => {
|
||||
const ctx = await setup({ provider: 'mock', parentAgentOptions: { provider: 'alpha', model: 'unlisted' } })
|
||||
const adapter = new CatalogAdapter(empty)
|
||||
const listModels = vi.spyOn(adapter, 'listModels')
|
||||
ctx.llm.registerAdapter(['alpha'], adapter)
|
||||
const result = await ctx.tools.execute({
|
||||
signal: testToolSignal, callId: ToolCallId('parent-only-discovery'),
|
||||
name: 'list_subagent_models', arguments: { provider: 'alpha' }, agent: modelSelectionSetupAgent(ctx),
|
||||
})
|
||||
expect(result.isError).toBe(false)
|
||||
expect(text(result)).toBe('alpha/unlisted — Fast: Focused work.')
|
||||
expect(listModels).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('requires a calling Agent when a discovery registration supplies no policy', async () => {
|
||||
const ctx = new Context()
|
||||
try {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRuntime)
|
||||
registerListSubagentModels(ctx, () => undefined)
|
||||
const result = await call(ctx, {})
|
||||
expect(result.isError).toBe(true)
|
||||
expect(text(result)).toContain('requires a calling agent')
|
||||
} finally {
|
||||
await ctx.fiber.dispose()
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
{"type":"session/title","data":{"title":"List the models available for","messageSeqs":[9],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_discovery","name":"list_subagent_models","arguments":"{\"provider\":\"discovery-fixture\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":100,"outputTokens":10},"stream":[{"type":"chunk","time":1791788384782,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"chunk","time":1791788384782,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_discovery","name":"list_subagent_models","arguments":"{\"provider\":\"discovery-fixture\"}"}}},{"type":"chunk","time":1791788384782,"chunk":{"type":"usage","usage":{"inputTokens":100,"outputTokens":10}}},{"type":"chunk","time":1791788384782,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_discovery","name":"list_subagent_models","arguments":"{\"provider\":\"discovery-fixture\"}"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_discovery"},"toolCallId":"call_discovery","content":[{"type":"text","text":"discovery-fixture/removed — Error: model was removed\ndiscovery-fixture/healthy — Healthy"}],"isError":false,"id":"{{message:5}}"}},"sourceEventSeqs":[15],"surfaceOp":"append"}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_discovery"},"toolCallId":"call_discovery","content":[{"type":"text","text":"discovery-fixture/healthy — Healthy"}],"isError":false,"id":"{{message:5}}"}},"sourceEventSeqs":[15],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":100,"outputTokens":10},"stream":[{"type":"chunk","time":1791788384799,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"chunk","time":1791788384800,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}},{"type":"chunk","time":1791788384800,"chunk":{"type":"usage","usage":{"inputTokens":100,"outputTokens":10}}},{"type":"chunk","time":1791788384800,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
|
||||
|
||||
Reference in new issue
Block a user