Skip to content

Commit febaf8e

Browse files
committed
update(api): align framework surface
1 parent 30f4ad0 commit febaf8e

11 files changed

Lines changed: 76 additions & 3 deletions

File tree

Sources/AgentRunKit/Core/Context/SubAgentContext.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
protocol CurrentDepthProviding {
1+
/// Surfaces a current sub-agent recursion depth for custom depth-aware contexts.
2+
public protocol CurrentDepthProviding {
23
var currentDepth: Int { get }
34
}
45

Sources/AgentRunKit/Documentation.docc/Articles/DefiningTools.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ let searchTool = try Tool<SearchParams, String, EmptyContext>(
125125
| Property | Default | Description |
126126
|---|---|---|
127127
| `isConcurrencySafe` | `false` | Whether the tool can safely run concurrently with other tools. ``Agent`` honors this: unsafe tools form exclusive barriers in the execution schedule. |
128+
| `isReadOnly` | `false` | Whether the tool only reads state without side effects. This is advisory metadata for callers and approval policy. |
128129
| `maxResultCharacters` | `nil` | Per-tool override for ``AgentConfiguration/maxToolResultCharacters``. When set, this limit governs instead of the global default. |
129130
| `strict` | `nil` | Whether the provider should enforce strict JSON Schema adherence on the tool's arguments. Preserved on first-party OpenAI Chat and Responses function tools where supported; unsupported providers reject strict schemas instead of dropping the request. |
130131

@@ -134,7 +135,7 @@ When ``Agent`` executes sibling tool calls, it groups contiguous `isConcurrencyS
134135

135136
## Per-Tool Timeout
136137

137-
Override ``AgentConfiguration/toolTimeout`` for a specific tool by passing `toolTimeout: Duration?` to ``Tool/init(name:description:isConcurrencySafe:maxResultCharacters:strict:toolTimeout:executor:)``. `nil` (the default) inherits the agent's configured timeout. Set an explicit `Duration` to apply a per-tool ceiling:
138+
Override ``AgentConfiguration/toolTimeout`` for a specific tool by passing `toolTimeout: Duration?` to ``Tool/init(name:description:isConcurrencySafe:isReadOnly:maxResultCharacters:strict:toolTimeout:executor:)``. `nil` (the default) inherits the agent's configured timeout. Set an explicit `Duration` to apply a per-tool ceiling:
138139

139140
```swift
140141
let deepPoll = try Tool<PollParams, PollResult, AppContext>(

Sources/AgentRunKit/Documentation.docc/Articles/SubAgents.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ When a sub-agent executes, ``SubAgentTool`` calls `descending()` to increment th
9696

9797
**Tool timeout.** `toolTimeout` overrides the parent agent's default tool timeout for this sub-agent invocation. `nil` (the default) inherits the parent's ``AgentConfiguration/toolTimeout``.
9898

99-
**Tool metadata.** `SubAgentTool` also exposes `isConcurrencySafe` and `maxResultCharacters`, matching `Tool`. ``Agent`` honors `isConcurrencySafe` for scheduling: sibling sub-agents default to sequential execution and must opt in to concurrent execution.
99+
**Tool metadata.** `SubAgentTool` also exposes `isConcurrencySafe`, `isReadOnly`, and `maxResultCharacters`, matching `Tool`. ``Agent`` honors `isConcurrencySafe` for scheduling: sibling sub-agents default to sequential execution and must opt in to concurrent execution. `isReadOnly` is advisory metadata for callers and approval policy.
100100

101101
## Streaming Propagation
102102

Sources/AgentRunKit/LLM/Providers/Responses/ResponsesAPIClient.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,4 +353,6 @@ extension ResponsesAPIClient {
353353
public extension ResponsesAPIClient {
354354
nonisolated static let openAIBaseURL =
355355
URL(validStaticString: "https://api.openai.com/v1")
356+
nonisolated static let chatGPTBaseURL =
357+
URL(validStaticString: "https://chatgpt.com/backend-api/codex")
356358
}

Sources/AgentRunKit/MCP/MCPClient.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,22 @@ public actor MCPClient {
5151
self.toolCallTimeout = toolCallTimeout
5252
}
5353

54+
#if os(macOS)
55+
public init(configuration: MCPServerConfiguration) {
56+
self.init(
57+
serverName: configuration.name,
58+
transport: StdioMCPTransport(
59+
command: configuration.command,
60+
arguments: configuration.arguments,
61+
environment: configuration.environment,
62+
workingDirectory: configuration.workingDirectory.map { URL(fileURLWithPath: $0) }
63+
),
64+
initializationTimeout: configuration.initializationTimeout,
65+
toolCallTimeout: configuration.toolCallTimeout
66+
)
67+
}
68+
#endif
69+
5470
func connectAndInitialize() async throws {
5571
guard case .created = state else {
5672
throw MCPError.connectionFailed("MCPClient is in state \(state), expected .created")

Sources/AgentRunKit/Tools/AnyTool.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ public protocol AnyTool<Context>: Sendable {
1313
/// Whether this tool can safely execute concurrently with other tools.
1414
var isConcurrencySafe: Bool { get }
1515

16+
/// Whether this tool only reads state without producing side effects.
17+
var isReadOnly: Bool { get }
18+
1619
/// Per-tool override for the maximum tool result length before truncation, or `nil` to use the global default.
1720
var maxResultCharacters: Int? { get }
1821

@@ -30,6 +33,10 @@ public extension AnyTool {
3033
false
3134
}
3235

36+
var isReadOnly: Bool {
37+
false
38+
}
39+
3340
var maxResultCharacters: Int? {
3441
nil
3542
}

Sources/AgentRunKit/Tools/Tool.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public struct Tool<P: Codable & SchemaProviding & Sendable, O: Codable & Sendabl
1010
public let description: String
1111
public let parametersSchema: JSONSchema
1212
public let isConcurrencySafe: Bool
13+
public let isReadOnly: Bool
1314
public let maxResultCharacters: Int?
1415
public let strict: Bool?
1516
public let toolTimeout: Duration?
@@ -19,6 +20,7 @@ public struct Tool<P: Codable & SchemaProviding & Sendable, O: Codable & Sendabl
1920
name: String,
2021
description: String,
2122
isConcurrencySafe: Bool = false,
23+
isReadOnly: Bool = false,
2224
maxResultCharacters: Int? = nil,
2325
strict: Bool? = nil,
2426
toolTimeout: Duration? = nil,
@@ -34,6 +36,7 @@ public struct Tool<P: Codable & SchemaProviding & Sendable, O: Codable & Sendabl
3436
self.name = name
3537
self.description = description
3638
self.isConcurrencySafe = isConcurrencySafe
39+
self.isReadOnly = isReadOnly
3740
self.maxResultCharacters = maxResultCharacters
3841
self.strict = strict
3942
self.toolTimeout = toolTimeout

Tests/AgentRunKitTests/LLM/Responses/ResponsesAPIClientTests.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ struct ResponsesRequestSerializationTests {
1313
)
1414
}
1515

16+
@Test
17+
func baseURLConstantsExposeOpenAIAndChatGPTEndpoints() {
18+
#expect(ResponsesAPIClient.openAIBaseURL.absoluteString == "https://api.openai.com/v1")
19+
#expect(ResponsesAPIClient.chatGPTBaseURL.absoluteString == "https://chatgpt.com/backend-api/codex")
20+
}
21+
1622
@Test
1723
func userMessageMapsToInputItem() async throws {
1824
let client = makeClient()

Tests/AgentRunKitTests/MCP/MCPClientTests.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ struct MCPClientTests {
4545
await client.shutdown()
4646
}
4747

48+
#if os(macOS)
49+
@Test
50+
func configurationInitializerPreservesServerName() {
51+
let client = MCPClient(configuration: MCPServerConfiguration(name: "configured", command: "/bin/test"))
52+
#expect(client.serverName == "configured")
53+
}
54+
#endif
55+
4856
@Test
4957
func initializeVersionMismatch() async throws {
5058
let transport = ScriptedMCPTransport(responses: [

Tests/AgentRunKitTests/Tools/SubAgentToolMetadataTests.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ struct SubAgentToolMetadataTests {
2222
)
2323

2424
#expect(tool.isConcurrencySafe == false)
25+
#expect(tool.isReadOnly == false)
2526
#expect(tool.maxResultCharacters == nil)
2627
#expect(tool.toolTimeout == nil)
2728
}
@@ -34,12 +35,34 @@ struct SubAgentToolMetadataTests {
3435
description: "Research tool",
3536
agent: childAgent,
3637
isConcurrencySafe: true,
38+
isReadOnly: true,
3739
maxResultCharacters: 500,
3840
toolTimeout: .seconds(5),
3941
messageBuilder: { $0.query }
4042
)
4143

4244
#expect(tool.isConcurrencySafe == true)
45+
#expect(tool.isReadOnly == true)
46+
#expect(tool.maxResultCharacters == 500)
47+
#expect(tool.toolTimeout == .seconds(5))
48+
}
49+
50+
@Test
51+
func factoryPreservesMetadataOverrides() throws {
52+
let childAgent = Agent<SubAgentContext<EmptyContext>>(client: MockLLMClient(responses: []), tools: [])
53+
let tool = try subAgentTool(
54+
name: "research",
55+
description: "Research tool",
56+
agent: childAgent,
57+
isConcurrencySafe: true,
58+
isReadOnly: true,
59+
maxResultCharacters: 500,
60+
toolTimeout: .seconds(5),
61+
messageBuilder: { (params: MetadataQueryParams) in params.query }
62+
)
63+
64+
#expect(tool.isConcurrencySafe == true)
65+
#expect(tool.isReadOnly == true)
4366
#expect(tool.maxResultCharacters == 500)
4467
#expect(tool.toolTimeout == .seconds(5))
4568
}

0 commit comments

Comments
 (0)