@llm4ts/core 2.35.4 → 2.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ConnectorConfig.d.ts +12 -0
- package/dist/ConnectorConfig.d.ts.map +1 -1
- package/dist/ConnectorConfig.js +18 -4
- package/dist/ConnectorConfig.js.map +1 -1
- package/dist/Models.d.ts +29 -0
- package/dist/Models.d.ts.map +1 -1
- package/dist/Models.js +28 -2
- package/dist/Models.js.map +1 -1
- package/dist/providers/AnthropicModels.d.ts +36 -3
- package/dist/providers/AnthropicModels.d.ts.map +1 -1
- package/dist/providers/AnthropicModels.js +35 -6
- package/dist/providers/AnthropicModels.js.map +1 -1
- package/dist/providers/AnthropicProvider.d.ts +18 -2
- package/dist/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/providers/AnthropicProvider.js +89 -20
- package/dist/providers/AnthropicProvider.js.map +1 -1
- package/dist/providers/ClaudeCliConnector.d.ts.map +1 -1
- package/dist/providers/ClaudeCliConnector.js +14 -3
- package/dist/providers/ClaudeCliConnector.js.map +1 -1
- package/dist/providers/CliSupport.d.ts +6 -1
- package/dist/providers/CliSupport.d.ts.map +1 -1
- package/dist/providers/CliSupport.js +5 -0
- package/dist/providers/CliSupport.js.map +1 -1
- package/dist/providers/CodexConnector.d.ts.map +1 -1
- package/dist/providers/CodexConnector.js +17 -3
- package/dist/providers/CodexConnector.js.map +1 -1
- package/dist/providers/GeminiCliProvider.d.ts +2 -0
- package/dist/providers/GeminiCliProvider.d.ts.map +1 -1
- package/dist/providers/GeminiCliProvider.js +10 -2
- package/dist/providers/GeminiCliProvider.js.map +1 -1
- package/dist/providers/OpenAIModels.d.ts +2 -0
- package/dist/providers/OpenAIModels.d.ts.map +1 -1
- package/dist/providers/OpenAIModels.js +2 -0
- package/dist/providers/OpenAIModels.js.map +1 -1
- package/dist/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/providers/OpenAIProvider.js +6 -3
- package/dist/providers/OpenAIProvider.js.map +1 -1
- package/dist/providers/PiConnector.d.ts.map +1 -1
- package/dist/providers/PiConnector.js +9 -2
- package/dist/providers/PiConnector.js.map +1 -1
- package/package.json +1 -1
- package/src/ConnectorConfig.ts +18 -4
- package/src/Models.ts +37 -1
- package/src/providers/AnthropicModels.ts +41 -6
- package/src/providers/AnthropicProvider.ts +136 -39
- package/src/providers/ClaudeCliConnector.ts +15 -3
- package/src/providers/CliSupport.ts +8 -1
- package/src/providers/CodexConnector.ts +17 -2
- package/src/providers/GeminiCliProvider.ts +10 -2
- package/src/providers/OpenAIModels.ts +2 -0
- package/src/providers/OpenAIProvider.ts +6 -2
- package/src/providers/PiConnector.ts +9 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect"
|
|
2
2
|
import * as Redacted from "effect/Redacted"
|
|
3
|
+
import * as Ref from "effect/Ref"
|
|
3
4
|
import * as Schema from "effect/Schema"
|
|
4
5
|
import * as Stream from "effect/Stream"
|
|
5
6
|
import { makeApiConnector, type ApiConnectorShape } from "../Connector.ts"
|
|
@@ -7,8 +8,10 @@ import { AuthenticationError, ConfigError, ParseError, type LlmError } from "../
|
|
|
7
8
|
import type { HttpClientShape } from "../HttpClient.ts"
|
|
8
9
|
import type { StructuredResult } from "../LlmService.ts"
|
|
9
10
|
import {
|
|
11
|
+
ConnectorCapabilities,
|
|
10
12
|
ConnectorIds,
|
|
11
13
|
LlmChunk,
|
|
14
|
+
TokenUsage,
|
|
12
15
|
ToolCall,
|
|
13
16
|
ToolCallResponse,
|
|
14
17
|
type JsonSchema,
|
|
@@ -20,14 +23,18 @@ import { collect } from "../Streaming.ts"
|
|
|
20
23
|
import { parseFromText } from "../StructuredOutput.ts"
|
|
21
24
|
import type { AnthropicContentBlockFull } from "./AnthropicModels.ts"
|
|
22
25
|
import {
|
|
26
|
+
AnthropicCacheControl,
|
|
23
27
|
AnthropicMessage,
|
|
28
|
+
AnthropicOutputConfig,
|
|
24
29
|
AnthropicRequest,
|
|
25
30
|
AnthropicRequestWithTools,
|
|
26
31
|
AnthropicResponse,
|
|
27
32
|
AnthropicResponseWithTools,
|
|
28
33
|
AnthropicStreamChunk,
|
|
34
|
+
AnthropicTextBlock,
|
|
29
35
|
AnthropicTool,
|
|
30
|
-
AnthropicToolInputSchema
|
|
36
|
+
AnthropicToolInputSchema,
|
|
37
|
+
AnthropicUsage
|
|
31
38
|
} from "./AnthropicModels.ts"
|
|
32
39
|
|
|
33
40
|
interface AnthropicRequiredConfig {
|
|
@@ -84,20 +91,76 @@ export const anthropicHistory = (messages: ReadonlyArray<Message>): AnthropicHis
|
|
|
84
91
|
)
|
|
85
92
|
})
|
|
86
93
|
|
|
94
|
+
const ephemeral = AnthropicCacheControl.make({ type: "ephemeral" })
|
|
95
|
+
|
|
96
|
+
const cachedBlock = (text: string): AnthropicTextBlock =>
|
|
97
|
+
AnthropicTextBlock.make({ type: "text", text, cache_control: ephemeral })
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Prompt-cache breakpoints (ADR 0029): one on the system block, one on the
|
|
101
|
+
* last message. The API caches the prefix up to each marker and looks the
|
|
102
|
+
* next request up at earlier markers too, so a chat whose history only
|
|
103
|
+
* grows at the end reads every earlier turn from the cache.
|
|
104
|
+
*/
|
|
105
|
+
export const withCacheMarkers = (
|
|
106
|
+
messages: ReadonlyArray<AnthropicMessage>,
|
|
107
|
+
system: string | undefined
|
|
108
|
+
): {
|
|
109
|
+
readonly messages: ReadonlyArray<AnthropicMessage>
|
|
110
|
+
readonly system: string | ReadonlyArray<AnthropicTextBlock> | undefined
|
|
111
|
+
} => {
|
|
112
|
+
const last = messages[messages.length - 1]
|
|
113
|
+
const marked =
|
|
114
|
+
last === undefined || typeof last.content !== "string"
|
|
115
|
+
? messages
|
|
116
|
+
: [
|
|
117
|
+
...messages.slice(0, -1),
|
|
118
|
+
AnthropicMessage.make({ role: last.role, content: [cachedBlock(last.content)] })
|
|
119
|
+
]
|
|
120
|
+
return {
|
|
121
|
+
messages: marked,
|
|
122
|
+
system: system === undefined || system.length === 0 ? system : [cachedBlock(system)]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
87
126
|
export const buildAnthropicRequest = (
|
|
88
127
|
config: LlmConfig,
|
|
89
128
|
messages: ReadonlyArray<AnthropicMessage>,
|
|
90
129
|
stream: boolean,
|
|
91
130
|
system?: string
|
|
92
|
-
): AnthropicRequest =>
|
|
93
|
-
|
|
131
|
+
): AnthropicRequest => {
|
|
132
|
+
const cached =
|
|
133
|
+
config.promptCache === false ? { messages, system } : withCacheMarkers(messages, system)
|
|
134
|
+
return AnthropicRequest.make({
|
|
94
135
|
model: config.model,
|
|
95
136
|
max_tokens: config.maxTokens ?? 4096,
|
|
96
|
-
messages,
|
|
137
|
+
messages: cached.messages,
|
|
97
138
|
stream,
|
|
98
139
|
...(config.temperature === undefined ? {} : { temperature: config.temperature }),
|
|
99
|
-
...(system === undefined ? {} : { system })
|
|
140
|
+
...(cached.system === undefined ? {} : { system: cached.system }),
|
|
141
|
+
...(config.effort === undefined
|
|
142
|
+
? {}
|
|
143
|
+
: { output_config: AnthropicOutputConfig.make({ effort: config.effort }) })
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The API's usage as llm4ts counts it: cache reads and writes are prompt
|
|
149
|
+
* tokens the request paid for (at their own rates), `cached` is what was
|
|
150
|
+
* read from the cache.
|
|
151
|
+
*/
|
|
152
|
+
export const anthropicTokenUsage = (usage: AnthropicUsage): TokenUsage => {
|
|
153
|
+
const read = usage.cache_read_input_tokens ?? 0
|
|
154
|
+
const written = usage.cache_creation_input_tokens ?? 0
|
|
155
|
+
const prompt = (usage.input_tokens ?? 0) + read + written
|
|
156
|
+
const completion = usage.output_tokens ?? 0
|
|
157
|
+
return TokenUsage.make({
|
|
158
|
+
prompt,
|
|
159
|
+
completion,
|
|
160
|
+
total: prompt + completion,
|
|
161
|
+
...(read > 0 ? { cached: read } : {})
|
|
100
162
|
})
|
|
163
|
+
}
|
|
101
164
|
|
|
102
165
|
export const buildAnthropicToolRequest = (
|
|
103
166
|
config: LlmConfig,
|
|
@@ -123,7 +186,10 @@ export const buildAnthropicToolRequest = (
|
|
|
123
186
|
})
|
|
124
187
|
})
|
|
125
188
|
),
|
|
126
|
-
...(config.temperature === undefined ? {} : { temperature: config.temperature })
|
|
189
|
+
...(config.temperature === undefined ? {} : { temperature: config.temperature }),
|
|
190
|
+
...(config.effort === undefined
|
|
191
|
+
? {}
|
|
192
|
+
: { output_config: AnthropicOutputConfig.make({ effort: config.effort }) })
|
|
127
193
|
})
|
|
128
194
|
|
|
129
195
|
const contentFromResponse = (
|
|
@@ -195,46 +261,76 @@ export const makeAnthropicProvider = (
|
|
|
195
261
|
Stream.unwrap(
|
|
196
262
|
Effect.map(requiredConfig, ({ apiKey, baseUrl }) => {
|
|
197
263
|
const request = buildAnthropicRequest(config, messages, true, system)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
264
|
+
// Usage arrives in two events: `message_start` carries the input
|
|
265
|
+
// (and cache) counts, `message_delta` the output so far. The final
|
|
266
|
+
// chunk carries them together, so the seat is measured, not estimated.
|
|
267
|
+
const usage = Stream.unwrap(
|
|
268
|
+
Effect.map(Ref.make<AnthropicUsage | undefined>(undefined), (started) =>
|
|
269
|
+
httpClient
|
|
270
|
+
.postJsonStreamSSE(
|
|
271
|
+
`${baseUrl}/messages`,
|
|
272
|
+
JSON.stringify(request),
|
|
273
|
+
authHeaders(apiKey),
|
|
274
|
+
config.timeout
|
|
275
|
+
)
|
|
276
|
+
.pipe(
|
|
277
|
+
Stream.mapEffect(decodeStreamChunk),
|
|
278
|
+
Stream.mapEffect((chunk) =>
|
|
279
|
+
chunk.type === "message_start" && chunk.message?.usage !== undefined
|
|
280
|
+
? Effect.as(Ref.set(started, chunk.message.usage), chunk)
|
|
281
|
+
: Effect.succeed(chunk)
|
|
282
|
+
),
|
|
283
|
+
Stream.flatMap((chunk) => {
|
|
284
|
+
if (chunk.type === "content_block_delta") {
|
|
285
|
+
const delta = chunk.delta?.text ?? ""
|
|
286
|
+
return delta.length === 0
|
|
287
|
+
? Stream.empty
|
|
288
|
+
: Stream.succeed(
|
|
289
|
+
LlmChunk.make({
|
|
290
|
+
delta,
|
|
291
|
+
metadata: {
|
|
292
|
+
provider: "anthropic",
|
|
293
|
+
model: config.model
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
const finishReason =
|
|
299
|
+
chunk.type === "message_delta"
|
|
300
|
+
? (chunk.delta?.stop_reason ?? undefined)
|
|
301
|
+
: undefined
|
|
302
|
+
if (finishReason === undefined) {
|
|
303
|
+
return Stream.empty
|
|
304
|
+
}
|
|
305
|
+
return Stream.fromEffect(
|
|
306
|
+
Effect.map(Ref.get(started), (input) => {
|
|
307
|
+
const combined =
|
|
308
|
+
input === undefined && chunk.usage === undefined
|
|
309
|
+
? undefined
|
|
310
|
+
: anthropicTokenUsage(
|
|
311
|
+
AnthropicUsage.make({
|
|
312
|
+
...(input ?? {}),
|
|
313
|
+
...(chunk.usage?.output_tokens === undefined
|
|
314
|
+
? {}
|
|
315
|
+
: { output_tokens: chunk.usage.output_tokens })
|
|
316
|
+
})
|
|
317
|
+
)
|
|
318
|
+
return LlmChunk.make({
|
|
319
|
+
delta: "",
|
|
320
|
+
finishReason,
|
|
321
|
+
...(combined === undefined ? {} : { usage: combined }),
|
|
215
322
|
metadata: {
|
|
216
323
|
provider: "anthropic",
|
|
217
324
|
model: config.model
|
|
218
325
|
}
|
|
219
326
|
})
|
|
220
|
-
)
|
|
221
|
-
}
|
|
222
|
-
const finishReason =
|
|
223
|
-
chunk.type === "message_delta" ? (chunk.delta?.stop_reason ?? undefined) : undefined
|
|
224
|
-
return finishReason === undefined
|
|
225
|
-
? Stream.empty
|
|
226
|
-
: Stream.succeed(
|
|
227
|
-
LlmChunk.make({
|
|
228
|
-
delta: "",
|
|
229
|
-
finishReason,
|
|
230
|
-
metadata: {
|
|
231
|
-
provider: "anthropic",
|
|
232
|
-
model: config.model
|
|
233
|
-
}
|
|
234
327
|
})
|
|
235
328
|
)
|
|
236
|
-
|
|
329
|
+
})
|
|
330
|
+
)
|
|
237
331
|
)
|
|
332
|
+
)
|
|
333
|
+
return usage
|
|
238
334
|
})
|
|
239
335
|
)
|
|
240
336
|
|
|
@@ -315,6 +411,7 @@ export const makeAnthropicProvider = (
|
|
|
315
411
|
},
|
|
316
412
|
executeWithTools,
|
|
317
413
|
executeStructuredWithUsage,
|
|
318
|
-
isAvailable
|
|
414
|
+
isAvailable,
|
|
415
|
+
capabilities: ConnectorCapabilities.make({ readOnlyEnforcement: "enforced", effort: "mapped" })
|
|
319
416
|
})
|
|
320
417
|
}
|
|
@@ -30,7 +30,8 @@ import {
|
|
|
30
30
|
toolResultChunk,
|
|
31
31
|
toolResultText,
|
|
32
32
|
usageEventChunk,
|
|
33
|
-
type JsonValue
|
|
33
|
+
type JsonValue,
|
|
34
|
+
effortWord
|
|
34
35
|
} from "./CliSupport.ts"
|
|
35
36
|
|
|
36
37
|
/**
|
|
@@ -51,7 +52,16 @@ export const claudeCliExtraArgs = (config: CliConnectorConfig): ReadonlyArray<st
|
|
|
51
52
|
...config.flags
|
|
52
53
|
}
|
|
53
54
|
: config.flags
|
|
54
|
-
return [
|
|
55
|
+
return [
|
|
56
|
+
...optionalModelArgs(config.model),
|
|
57
|
+
// `--effort` is Claude Code's own vocabulary: low, medium, high, max.
|
|
58
|
+
...(config.effort === undefined ? [] : ["--effort", effortWord(config.effort)]),
|
|
59
|
+
// `--bare` (Claude Code 2.1.81+) skips hooks, skills, plugins, MCP,
|
|
60
|
+
// auto memory and CLAUDE.md discovery: the documented headless mode for
|
|
61
|
+
// a repository the run does not trust (ADR 0029).
|
|
62
|
+
...(config.isolated ? ["--bare"] : []),
|
|
63
|
+
...sortedFlagArgs(effectiveFlags)
|
|
64
|
+
]
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
export const claudeCliInitModel = (line: string): string | undefined => {
|
|
@@ -256,7 +266,9 @@ export const makeClaudeCliConnector = (
|
|
|
256
266
|
askUser: true,
|
|
257
267
|
approval: true,
|
|
258
268
|
resumableSessions: true,
|
|
259
|
-
readOnlyEnforcement: "enforced"
|
|
269
|
+
readOnlyEnforcement: "enforced",
|
|
270
|
+
effort: "mapped",
|
|
271
|
+
isolatedHeadless: "enforced"
|
|
260
272
|
}),
|
|
261
273
|
buildArgv: (prompt, _context) => ["claude", "--print", ...extraArgs, prompt],
|
|
262
274
|
buildInteractiveArgv: (_context) => ["claude", ...extraArgs],
|
|
@@ -5,7 +5,7 @@ import * as Ref from "effect/Ref"
|
|
|
5
5
|
import * as Schema from "effect/Schema"
|
|
6
6
|
import * as Stream from "effect/Stream"
|
|
7
7
|
import { ProviderError, type LlmError } from "../Errors.ts"
|
|
8
|
-
import { LlmChunk, TokenUsage } from "../Models.ts"
|
|
8
|
+
import { LlmChunk, TokenUsage, type Effort } from "../Models.ts"
|
|
9
9
|
import { classifyUsageLimit } from "../UsageLimits.ts"
|
|
10
10
|
|
|
11
11
|
export type JsonValue = typeof Schema.Json.Type
|
|
@@ -68,6 +68,13 @@ export const sortedFlagArgs = (flags: Readonly<Record<string, string>>): Readonl
|
|
|
68
68
|
export const optionalModelArgs = (model: string | undefined): ReadonlyArray<string> =>
|
|
69
69
|
model === undefined ? [] : ["--model", model]
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* The harness's word for an llm4ts effort (ADR 0029): the same four words,
|
|
73
|
+
* except `max` on a harness whose top level is `xhigh`.
|
|
74
|
+
*/
|
|
75
|
+
export const effortWord = (effort: Effort, top: "max" | "xhigh" = "max"): string =>
|
|
76
|
+
effort === "max" ? top : effort
|
|
77
|
+
|
|
71
78
|
export const toolEventChunk = (name: string, input: JsonValue | undefined, id?: string): LlmChunk =>
|
|
72
79
|
LlmChunk.make({
|
|
73
80
|
delta: "",
|
|
@@ -17,6 +17,7 @@ import { collect } from "../Streaming.ts"
|
|
|
17
17
|
import type { TemporaryFilesShape } from "../TemporaryFiles.ts"
|
|
18
18
|
import { parseFromText, withSchemaHint } from "../StructuredOutput.ts"
|
|
19
19
|
import {
|
|
20
|
+
effortWord,
|
|
20
21
|
failClassifiedCliError,
|
|
21
22
|
isJsonRecord,
|
|
22
23
|
jsonField,
|
|
@@ -39,7 +40,19 @@ export const codexExtraArgs = (config: CliConnectorConfig): ReadonlyArray<string
|
|
|
39
40
|
sandbox: "read-only"
|
|
40
41
|
}
|
|
41
42
|
: config.flags
|
|
42
|
-
return [
|
|
43
|
+
return [
|
|
44
|
+
...optionalModelArgs(config.model),
|
|
45
|
+
// Codex reads its effort from config: `-c model_reasoning_effort="…"`,
|
|
46
|
+
// top level `xhigh`.
|
|
47
|
+
...(config.effort === undefined
|
|
48
|
+
? []
|
|
49
|
+
: ["-c", `model_reasoning_effort="${effortWord(config.effort, "xhigh")}"`]),
|
|
50
|
+
// Isolation is partial (ADR 0029): the project's `.codex/config.toml`
|
|
51
|
+
// is skipped unless the user trusted that path, and this turns off the
|
|
52
|
+
// project's AGENTS.md; MCP servers from the user's own config still load.
|
|
53
|
+
...(config.isolated ? ["-c", "project_doc_max_bytes=0"] : []),
|
|
54
|
+
...sortedFlagArgs(effectiveFlags)
|
|
55
|
+
]
|
|
43
56
|
}
|
|
44
57
|
|
|
45
58
|
export const codexSchemaEnforceable = (schema: JsonSchema): boolean => {
|
|
@@ -224,7 +237,9 @@ export const makeCodexConnector = (
|
|
|
224
237
|
// `sandbox: read-only` is an OS-level sandbox — a real capability gate.
|
|
225
238
|
capabilities: ConnectorCapabilities.make({
|
|
226
239
|
interactiveSessions: true,
|
|
227
|
-
readOnlyEnforcement: "enforced"
|
|
240
|
+
readOnlyEnforcement: "enforced",
|
|
241
|
+
effort: "mapped",
|
|
242
|
+
isolatedHeadless: "partial"
|
|
228
243
|
}),
|
|
229
244
|
buildArgv: (prompt, _context) => ["codex", "exec", ...extraArgs, prompt],
|
|
230
245
|
buildInteractiveArgv: (_context) => ["codex", ...extraArgs],
|
|
@@ -122,6 +122,8 @@ export class GeminiCliExecutionContext extends Schema.Class<GeminiCliExecutionCo
|
|
|
122
122
|
sandbox: Schema.optionalKey(GeminiSandbox),
|
|
123
123
|
turnLimit: Schema.optionalKey(Schema.Int),
|
|
124
124
|
readOnly: Schema.Boolean.pipe(Schema.withConstructorDefault(Effect.succeed(false))),
|
|
125
|
+
/** No project extensions (`-e none`); settings and GEMINI.md are still read (ADR 0029). */
|
|
126
|
+
isolated: Schema.Boolean.pipe(Schema.withConstructorDefault(Effect.succeed(false))),
|
|
125
127
|
/** The seat's own environment (a roster `env`), under llm4ts's own keys. */
|
|
126
128
|
envVars: Schema.Record(Schema.String, Schema.String).pipe(
|
|
127
129
|
Schema.withConstructorDefault(Effect.succeed<Readonly<Record<string, string>>>({}))
|
|
@@ -135,6 +137,7 @@ export const geminiCliExecutionContextFrom = (
|
|
|
135
137
|
...(config.workingDir === undefined ? {} : { cwd: config.workingDir }),
|
|
136
138
|
...(config.turnLimit === undefined ? {} : { turnLimit: config.turnLimit }),
|
|
137
139
|
readOnly: config.readOnly,
|
|
140
|
+
isolated: config.isolated,
|
|
138
141
|
envVars: config.envVars,
|
|
139
142
|
...(config.sandbox === undefined
|
|
140
143
|
? {}
|
|
@@ -226,7 +229,11 @@ export const buildGeminiArgs = (
|
|
|
226
229
|
outputFormat,
|
|
227
230
|
...directories,
|
|
228
231
|
...(adminPolicyPath === undefined ? [] : ["--admin-policy", adminPolicyPath]),
|
|
229
|
-
...(context.sandbox === undefined ? [] : ["-s"])
|
|
232
|
+
...(context.sandbox === undefined ? [] : ["-s"]),
|
|
233
|
+
// Partial isolation: no extensions; `.gemini/settings.json` and
|
|
234
|
+
// GEMINI.md have no off switch, and the ADR 0027 harness policy above
|
|
235
|
+
// already keeps the model out of llm4ts's own packages.
|
|
236
|
+
...(context.isolated ? ["-e", "none"] : [])
|
|
230
237
|
]
|
|
231
238
|
}
|
|
232
239
|
|
|
@@ -964,7 +971,8 @@ export const makeGeminiCliProvider = (
|
|
|
964
971
|
// every seat that runs.
|
|
965
972
|
capabilities: ConnectorCapabilities.make({
|
|
966
973
|
interactiveSessions: true,
|
|
967
|
-
readOnlyEnforcement: "enforced"
|
|
974
|
+
readOnlyEnforcement: "enforced",
|
|
975
|
+
isolatedHeadless: "partial"
|
|
968
976
|
})
|
|
969
977
|
})
|
|
970
978
|
|
|
@@ -44,6 +44,8 @@ export class OpenAIChatCompletionRequest extends Schema.Class<OpenAIChatCompleti
|
|
|
44
44
|
stream: Schema.optionalKey(Schema.Boolean),
|
|
45
45
|
response_format: Schema.optionalKey(OpenAIResponseFormat),
|
|
46
46
|
chat_template_kwargs: Schema.optionalKey(OpenAIChatTemplateKwargs),
|
|
47
|
+
/** Reasoning effort (ADR 0029): low, medium, high, xhigh; sent only when asked. */
|
|
48
|
+
reasoning_effort: Schema.optionalKey(Schema.String),
|
|
47
49
|
logprobs: Schema.optionalKey(Schema.Boolean),
|
|
48
50
|
top_logprobs: Schema.optionalKey(Schema.Int)
|
|
49
51
|
}) {}
|
|
@@ -7,6 +7,7 @@ import { AuthenticationError, ConfigError, ParseError, type LlmError } from "../
|
|
|
7
7
|
import type { HttpClientShape } from "../HttpClient.ts"
|
|
8
8
|
import type { StructuredResult } from "../LlmService.ts"
|
|
9
9
|
import {
|
|
10
|
+
ConnectorCapabilities,
|
|
10
11
|
ConnectorIds,
|
|
11
12
|
LlmChunk,
|
|
12
13
|
ToolCall,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
type ToolDefinition
|
|
19
20
|
} from "../Models.ts"
|
|
20
21
|
import { parseFromText } from "../StructuredOutput.ts"
|
|
22
|
+
import { effortWord } from "./CliSupport.ts"
|
|
21
23
|
import {
|
|
22
24
|
OpenAIChatChunk,
|
|
23
25
|
OpenAIChatCompletionRequest,
|
|
@@ -74,7 +76,8 @@ export const buildOpenAIChatRequest = (
|
|
|
74
76
|
temperature: config.temperature ?? 0.7,
|
|
75
77
|
stream,
|
|
76
78
|
...(config.maxTokens === undefined ? {} : { max_tokens: config.maxTokens }),
|
|
77
|
-
...(responseFormat === undefined ? {} : { response_format: responseFormat })
|
|
79
|
+
...(responseFormat === undefined ? {} : { response_format: responseFormat }),
|
|
80
|
+
...(config.effort === undefined ? {} : { reasoning_effort: effortWord(config.effort, "xhigh") })
|
|
78
81
|
})
|
|
79
82
|
|
|
80
83
|
export const buildOpenAIToolRequest = (
|
|
@@ -326,6 +329,7 @@ export const makeOpenAIProvider = (
|
|
|
326
329
|
executeStreamWithHistory: (messages) => executeStreamRequest(openAIHistoryMessages(messages)),
|
|
327
330
|
executeWithTools,
|
|
328
331
|
executeStructuredWithUsage,
|
|
329
|
-
isAvailable
|
|
332
|
+
isAvailable,
|
|
333
|
+
capabilities: ConnectorCapabilities.make({ readOnlyEnforcement: "enforced", effort: "mapped" })
|
|
330
334
|
})
|
|
331
335
|
}
|
|
@@ -6,6 +6,7 @@ import { ProviderError } from "../Errors.ts"
|
|
|
6
6
|
import { ConnectorCapabilities, ConnectorIds, LlmChunk, TokenUsage } from "../Models.ts"
|
|
7
7
|
import type { ProcessExecutorShape } from "../ProcessExecutor.ts"
|
|
8
8
|
import {
|
|
9
|
+
effortWord,
|
|
9
10
|
cumulativeUsage,
|
|
10
11
|
failClassifiedCliError,
|
|
11
12
|
jsonBooleanField,
|
|
@@ -29,6 +30,11 @@ export const piExtraArgs = (config: CliConnectorConfig): ReadonlyArray<string> =
|
|
|
29
30
|
// session, unsafe for a one-shot complete() call with no tool-loop
|
|
30
31
|
// continuation (see the field's doc comment on CliConnectorConfig).
|
|
31
32
|
...(config.noTools ? ["--no-tools"] : config.readOnly ? ["--tools", "read"] : []),
|
|
33
|
+
// `--thinking` is pi's effort flag (off, minimal, low, medium, high, xhigh).
|
|
34
|
+
...(config.effort === undefined ? [] : ["--thinking", effortWord(config.effort, "xhigh")]),
|
|
35
|
+
// Isolation is partial (ADR 0029): project extensions and skills stay
|
|
36
|
+
// out; pi still reads AGENTS.md from the working directory and its parents.
|
|
37
|
+
...(config.isolated ? ["--no-extensions", "--no-skills"] : []),
|
|
32
38
|
...sortedFlagArgs(config.flags)
|
|
33
39
|
]
|
|
34
40
|
|
|
@@ -182,7 +188,9 @@ export const makePiConnector = (
|
|
|
182
188
|
// capability removal, the same mechanism class as claude's `--tools`.
|
|
183
189
|
capabilities: ConnectorCapabilities.make({
|
|
184
190
|
interactiveSessions: true,
|
|
185
|
-
readOnlyEnforcement: "enforced"
|
|
191
|
+
readOnlyEnforcement: "enforced",
|
|
192
|
+
effort: "mapped",
|
|
193
|
+
isolatedHeadless: "partial"
|
|
186
194
|
}),
|
|
187
195
|
buildArgv: (prompt, _context) => ["pi", "-p", ...extraArgs, prompt],
|
|
188
196
|
buildInteractiveArgv: (_context) => ["pi", ...extraArgs],
|