@oh-my-pi/pi-ai 16.1.23 → 16.2.1

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/types/auth-broker/wire-schemas.d.ts +5 -0
  3. package/dist/types/index.d.ts +2 -0
  4. package/dist/types/providers/anthropic-messages-server-schema.d.ts +31 -0
  5. package/dist/types/providers/gitlab-duo-workflow.d.ts +254 -0
  6. package/dist/types/registry/gitlab-duo-workflow.d.ts +10 -0
  7. package/dist/types/registry/gitlab-duo.d.ts +1 -1
  8. package/dist/types/registry/oauth/gitlab-duo-workflow.d.ts +6 -0
  9. package/dist/types/registry/registry.d.ts +9 -1
  10. package/dist/types/stream.d.ts +8 -0
  11. package/dist/types/types.d.ts +12 -0
  12. package/dist/types/usage/ollama.d.ts +5 -0
  13. package/dist/types/usage/openai-codex-base-url.d.ts +1 -0
  14. package/dist/types/usage/openai-codex.d.ts +0 -1
  15. package/dist/types/usage.d.ts +33 -0
  16. package/dist/types/utils/thinking-loop.d.ts +46 -0
  17. package/package.json +4 -4
  18. package/src/api-registry.ts +1 -0
  19. package/src/auth-broker/wire-schemas.ts +7 -0
  20. package/src/auth-storage.ts +15 -2
  21. package/src/dialect/anthropic.md +2 -2
  22. package/src/dialect/anthropic.ts +1 -1
  23. package/src/dialect/deepseek.md +2 -1
  24. package/src/dialect/deepseek.ts +1 -1
  25. package/src/dialect/gemini.md +2 -1
  26. package/src/dialect/gemma.md +2 -1
  27. package/src/dialect/glm.md +2 -2
  28. package/src/dialect/harmony.md +2 -1
  29. package/src/dialect/harmony.ts +1 -1
  30. package/src/dialect/hermes.md +2 -1
  31. package/src/dialect/hermes.ts +1 -1
  32. package/src/dialect/kimi.md +2 -1
  33. package/src/dialect/kimi.ts +1 -1
  34. package/src/dialect/minimax.md +2 -2
  35. package/src/dialect/pi.md +1 -1
  36. package/src/dialect/qwen3.md +2 -1
  37. package/src/dialect/qwen3.ts +1 -1
  38. package/src/dialect/xml.md +2 -2
  39. package/src/index.ts +2 -0
  40. package/src/providers/amazon-bedrock.ts +8 -2
  41. package/src/providers/anthropic-messages-server-schema.ts +6 -1
  42. package/src/providers/anthropic.ts +2 -1
  43. package/src/providers/cursor.ts +7 -2
  44. package/src/providers/devin.ts +1 -2
  45. package/src/providers/gitlab-duo-workflow-chatml-note.md +1 -0
  46. package/src/providers/gitlab-duo-workflow.ts +3038 -0
  47. package/src/providers/ollama.ts +1 -2
  48. package/src/providers/openai-codex-responses.ts +1 -1
  49. package/src/providers/openai-completions.ts +8 -2
  50. package/src/providers/openai-shared.ts +8 -2
  51. package/src/registry/gitlab-duo-workflow.ts +20 -0
  52. package/src/registry/gitlab-duo.ts +1 -1
  53. package/src/registry/oauth/gitlab-duo-workflow.ts +134 -0
  54. package/src/registry/registry.ts +2 -0
  55. package/src/stream.ts +505 -16
  56. package/src/types.ts +13 -0
  57. package/src/usage/ollama.ts +41 -0
  58. package/src/usage/openai-codex-base-url.ts +15 -0
  59. package/src/usage/openai-codex-reset.ts +1 -1
  60. package/src/usage/openai-codex.ts +30 -15
  61. package/src/usage.ts +26 -0
  62. package/src/utils/thinking-loop.ts +89 -14
  63. package/dist/types/utils/json-parse.d.ts +0 -48
  64. package/src/utils/json-parse.ts +0 -558
package/CHANGELOG.md CHANGED
@@ -2,6 +2,41 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [16.2.0] - 2026-06-27
6
+
7
+ ### Breaking Changes
8
+
9
+ - Removed the `@oh-my-pi/pi-ai/utils/json-parse` module. The JSON repair and parsing helpers (`repairJson`, `parseJsonWithRepair`, `parseStreamingJson`, `parseStreamingJsonThrottled`) have been moved to `@oh-my-pi/pi-utils` to be shared across utilities.
10
+
11
+ ### Added
12
+
13
+ - Added the GitLab Duo Agent provider (`gitlab-duo-agent`) and built-in implementation, renaming the existing AI Gateway proxy provider to "GitLab Duo Non-Agentic" (`gitlab-duo`).
14
+ - Added GitLab Duo Workflow provider support, featuring OAuth login via the official VS Code OAuth application, automatic project discovery, and automatic session-time namespace Duo settings enablement.
15
+ - Added runaway detection for Gemini models to interrupt streams stuck in excessive planning steps.
16
+ - Added a per-provider in-flight request limiter for LLM streams, shared across local OMP processes and configurable via `maxInFlightRequests`.
17
+ - Added a `credits` field to `UsageResetCredits` to display when banked rate-limit resets expire, with support for OpenAI Codex usage details.
18
+
19
+ ### Changed
20
+
21
+ - Optimized GitLab Duo Agent and Workflow providers to use an inline custom "ambient" flow with MCP-only agent privileges, registering MCP tools under their bare names.
22
+ - Improved GitLab Duo Agent context management and auto-compaction by lowering the soft overflow threshold to 1 MB and stripping redundant bytes (such as tool-call UUIDs and escaped JSON) from the goal transcript.
23
+ - Enhanced GitLab Duo Agent prompt engineering to render replayed tool calls as past-tense records, reducing model confusion and preventing the model from mimicking historical markers.
24
+ - Added caching for discovered GitLab Duo Agent root namespaces per account to avoid redundant discovery requests.
25
+
26
+ ### Fixed
27
+
28
+ - Fixed various GitLab Duo Agent and Workflow stability issues, including infinite tool-call loops, connection hangs on half-open WebSockets, and unhandled step-limit or generic server-side failures.
29
+ - Improved GitLab Duo Workflow routing, namespace resolution, and project-path handling, ensuring correct numeric ID resolution and support for self-managed GitLab relative install base paths.
30
+ - Fixed GitLab Duo Workflow checkpoint streaming to correctly map reasoning entries to thinking blocks, preserve tool boundaries, and accurately report token usage.
31
+ - Fixed `AuthStorage.login` to only synthesize manual-code paste prompts for paste-code providers, preventing terminal-blocking races on loopback OAuth flows.
32
+ - Fixed llama.cpp compatibility by downgrading named forced `tool_choice` objects to the string `"required"` in the chat-completions encoder.
33
+ - Fixed `omp usage` omitting Ollama and Ollama Cloud accounts by registering placeholder usage providers.
34
+ - Fixed Gemini reasoning-runaway detection to expose a dedicated thought-summary header guard to interrupt streams stuck in planning loops.
35
+
36
+ ### Removed
37
+
38
+ - Removed legacy GitLab Duo Workflow `chat` and `software_development` flow paths and the non-MCP action bridge in favor of the inline custom `ambient` flow.
39
+
5
40
  ## [16.1.23] - 2026-06-26
6
41
 
7
42
  ### Added
@@ -331,6 +331,11 @@ export declare const usageResponseSchema: import("arktype/internal/variants/obje
331
331
  }[];
332
332
  resetCredits?: {
333
333
  availableCount: number;
334
+ credits?: {
335
+ grantedAt?: string | undefined;
336
+ expiresAt?: string | undefined;
337
+ status?: string | undefined;
338
+ }[] | undefined;
334
339
  } | undefined;
335
340
  notes?: string[] | undefined;
336
341
  metadata?: {
@@ -13,6 +13,7 @@ export * from "./providers/anthropic-client";
13
13
  export * from "./providers/azure-openai-responses";
14
14
  export type * from "./providers/cursor";
15
15
  export * from "./providers/gitlab-duo";
16
+ export * from "./providers/gitlab-duo-workflow";
16
17
  export type * from "./providers/google";
17
18
  export type * from "./providers/google-gemini-cli";
18
19
  export type * from "./providers/google-vertex";
@@ -34,6 +35,7 @@ export * from "./usage/github-copilot";
34
35
  export * from "./usage/google-antigravity";
35
36
  export * from "./usage/kimi";
36
37
  export * from "./usage/minimax-code";
38
+ export * from "./usage/ollama";
37
39
  export * from "./usage/openai-codex";
38
40
  export * from "./usage/openai-codex-reset";
39
41
  export * from "./usage/opencode-go";
@@ -103,6 +103,17 @@ export declare const userMessageSchema: import("arktype/internal/variants/object
103
103
  type: string;
104
104
  })[];
105
105
  }, {}>;
106
+ export declare const systemMessageSchema: import("arktype/internal/variants/object.ts").ObjectType<{
107
+ role: "system";
108
+ content: string | {
109
+ type: "text";
110
+ text: string;
111
+ cache_control?: {
112
+ type: "ephemeral";
113
+ ttl?: "1h" | "5m" | undefined;
114
+ } | undefined;
115
+ }[];
116
+ }, {}>;
106
117
  export declare const assistantMessageSchema: import("arktype/internal/variants/object.ts").ObjectType<{
107
118
  role: "assistant";
108
119
  content: string | ({
@@ -204,6 +215,16 @@ export declare const messageSchema: import("arktype/internal/variants/object.ts"
204
215
  } | {
205
216
  type: string;
206
217
  })[];
218
+ } | {
219
+ role: "system";
220
+ content: string | {
221
+ type: "text";
222
+ text: string;
223
+ cache_control?: {
224
+ type: "ephemeral";
225
+ ttl?: "1h" | "5m" | undefined;
226
+ } | undefined;
227
+ }[];
207
228
  } | {
208
229
  role: "assistant";
209
230
  content: string | ({
@@ -344,6 +365,16 @@ export declare const anthropicMessagesRequestSchema: import("arktype/internal/va
344
365
  } | {
345
366
  type: string;
346
367
  })[];
368
+ } | {
369
+ role: "system";
370
+ content: string | {
371
+ type: "text";
372
+ text: string;
373
+ cache_control?: {
374
+ type: "ephemeral";
375
+ ttl?: "1h" | "5m" | undefined;
376
+ } | undefined;
377
+ }[];
347
378
  } | {
348
379
  role: "assistant";
349
380
  content: string | ({
@@ -0,0 +1,254 @@
1
+ import { type GitLabDuoWorkflowNamespaceSelection } from "@oh-my-pi/pi-catalog/discovery/gitlab-duo-workflow";
2
+ import type { AssistantMessage, Context, FetchImpl, Model, ProviderSessionState, StreamFunction, StreamOptions, Tool, ToolChoice, ToolResultMessage } from "../types";
3
+ import { AssistantMessageEventStream } from "../utils/event-stream";
4
+ export declare const GITLAB_DUO_WORKFLOW_PROVIDER_ID = "gitlab-duo-agent";
5
+ export declare const GITLAB_DUO_WORKFLOW_API = "gitlab-duo-agent";
6
+ export declare const GITLAB_DUO_WORKFLOW_DEFINITION = "ambient";
7
+ export type GitLabDuoWorkflowDefinition = "ambient" | (string & {});
8
+ export declare const GITLAB_DUO_WORKFLOW_CLIENT_CAPABILITIES: readonly ["incremental_streaming", "read_file_chunked", "shell_command", "command_timeout", "tool_call_approval"];
9
+ export interface GitLabMcpToolArgs {
10
+ name?: string;
11
+ tool_name?: string;
12
+ toolName?: string;
13
+ providerIdentifier?: string;
14
+ provider_identifier?: string;
15
+ toolCallId?: string;
16
+ tool_call_id?: string;
17
+ args?: Record<string, unknown> | string;
18
+ arguments?: Record<string, unknown> | string;
19
+ }
20
+ export interface GitLabPlainTextResponse {
21
+ response?: string;
22
+ error?: string;
23
+ }
24
+ export type PlainTextResponse = GitLabPlainTextResponse;
25
+ export interface GitLabDuoWorkflowOptions extends StreamOptions {
26
+ rootNamespaceId?: string;
27
+ namespaceId?: string;
28
+ projectId?: string;
29
+ projectPath?: string;
30
+ workflowDefinition?: GitLabDuoWorkflowDefinition;
31
+ workflowId?: string;
32
+ workflowToken?: string;
33
+ cwd?: string;
34
+ webSocketFactory?: GitLabDuoWorkflowWebSocketFactory;
35
+ /** Idle WebSocket deadline (ms) before aborting and resuming; defaults to {@link GITLAB_DUO_WORKFLOW_IDLE_TIMEOUT_MS}. */
36
+ idleTimeoutMs?: number;
37
+ /**
38
+ * Tool-choice override forwarded from the stream layer. Only `"none"` is
39
+ * acted on: a side-request (e.g. handoff) keeps tool definitions in the cache
40
+ * prefix but disables tool use, so the provider must not advertise them to Duo.
41
+ */
42
+ toolChoice?: ToolChoice;
43
+ }
44
+ export interface GitLabDuoWorkflowWebSocketLike {
45
+ readyState?: number;
46
+ binaryType?: string;
47
+ onopen: ((event: Event) => void) | null;
48
+ onmessage: ((event: MessageEvent) => void) | null;
49
+ onerror: ((event: Event) => void) | null;
50
+ onclose: ((event: CloseEvent) => void) | null;
51
+ send(data: string): void;
52
+ close(code?: number, reason?: string): void;
53
+ }
54
+ export interface GitLabDuoWorkflowWebSocketFactoryOptions {
55
+ headers: Record<string, string>;
56
+ protocols?: string[];
57
+ }
58
+ export type GitLabDuoWorkflowWebSocketFactory = (url: string, options: GitLabDuoWorkflowWebSocketFactoryOptions) => GitLabDuoWorkflowWebSocketLike;
59
+ export interface GitLabDirectAccessResponse {
60
+ token?: string;
61
+ access_token?: string;
62
+ jwt?: string;
63
+ workflow_token?: string;
64
+ duo_workflow_access_token?: string;
65
+ duo_workflow_service?: {
66
+ token?: string;
67
+ base_url?: string;
68
+ headers?: Record<string, string>;
69
+ };
70
+ gitlab_rails?: {
71
+ token?: string;
72
+ };
73
+ [key: string]: unknown;
74
+ }
75
+ interface GitLabDuoWorkflowCreateBodyOptions {
76
+ projectId?: string;
77
+ goal?: string;
78
+ workflowDefinition?: GitLabDuoWorkflowDefinition;
79
+ }
80
+ interface GitLabDuoWorkflowStartMetadataOptions {
81
+ projectId?: string;
82
+ projectPath?: string;
83
+ namespaceId?: string;
84
+ rootNamespaceId?: string;
85
+ workflowDefinition?: GitLabDuoWorkflowDefinition;
86
+ inlineFlow?: boolean;
87
+ }
88
+ export interface GitLabMcpToolDefinition {
89
+ name: string;
90
+ originalToolName: string;
91
+ serverName: string;
92
+ description: string;
93
+ inputSchema: string;
94
+ isApproved: boolean;
95
+ }
96
+ export interface GitLabDuoWorkflowAdditionalContextItem {
97
+ id: string;
98
+ category: "agent_user_environment" | "user_rule";
99
+ content: string;
100
+ metadata: {
101
+ title: string;
102
+ enabled: boolean;
103
+ subType: "snippet";
104
+ icon: string;
105
+ secondaryText: string;
106
+ subTypeLabel: string;
107
+ };
108
+ }
109
+ export interface GitLabDuoWorkflowStartRequest {
110
+ workflowID: string;
111
+ clientVersion: "1.0";
112
+ workflowDefinition: GitLabDuoWorkflowDefinition;
113
+ goal: string;
114
+ workflowMetadata: string;
115
+ additional_context: readonly GitLabDuoWorkflowAdditionalContextItem[];
116
+ approval?: {
117
+ approval?: Record<string, never>;
118
+ rejection?: {
119
+ message?: string;
120
+ };
121
+ };
122
+ clientCapabilities: readonly (typeof GITLAB_DUO_WORKFLOW_CLIENT_CAPABILITIES)[number][];
123
+ mcpTools: GitLabMcpToolDefinition[];
124
+ preapproved_tools: string[];
125
+ flowConfigSchemaVersion?: "v1";
126
+ flowConfigId?: string;
127
+ flowVersion?: string;
128
+ flowConfig?: GitLabDuoWorkflowInlineFlowConfig;
129
+ }
130
+ export interface GitLabDuoWorkflowInlineFlowComponent {
131
+ name: string;
132
+ type: "AgentComponent";
133
+ prompt_id: string;
134
+ toolset: string[];
135
+ inputs: {
136
+ from: string;
137
+ as: string;
138
+ }[];
139
+ ui_log_events: string[];
140
+ }
141
+ export interface GitLabDuoWorkflowInlineFlowPrompt {
142
+ name: string;
143
+ prompt_id: string;
144
+ unit_primitives: string[];
145
+ prompt_template: {
146
+ system: string;
147
+ user: string;
148
+ placeholder: string;
149
+ };
150
+ }
151
+ export interface GitLabDuoWorkflowInlineFlowConfig {
152
+ version: "v1";
153
+ environment: "ambient";
154
+ flow: {
155
+ entry_point: string;
156
+ };
157
+ components: GitLabDuoWorkflowInlineFlowComponent[];
158
+ routers: {
159
+ from: string;
160
+ to: string;
161
+ }[];
162
+ prompts: GitLabDuoWorkflowInlineFlowPrompt[];
163
+ }
164
+ export interface GitLabDuoWorkflowActionResponse {
165
+ actionResponse: {
166
+ requestID: string;
167
+ plainTextResponse?: GitLabPlainTextResponse;
168
+ };
169
+ }
170
+ interface GitLabDuoWorkflowActionDescriptor {
171
+ requestID: string;
172
+ name: string;
173
+ args: unknown;
174
+ }
175
+ export interface GitLabDuoWorkflowActiveSession {
176
+ workflowId: string;
177
+ startPayload: GitLabDuoWorkflowStartRequest;
178
+ ws: GitLabDuoWorkflowWebSocketLike;
179
+ stop?: () => void;
180
+ pendingActions?: GitLabDuoWorkflowActionDescriptor[];
181
+ checkpointAgentContentByKey?: Record<string, string>;
182
+ checkpointAgentContentSignatures?: Record<string, true>;
183
+ paused?: boolean;
184
+ pauseBuffer?: unknown[];
185
+ lastToolBoundaryContentLength?: number;
186
+ }
187
+ export interface GitLabDuoWorkflowProviderSessionState extends ProviderSessionState {
188
+ active?: GitLabDuoWorkflowActiveSession;
189
+ }
190
+ export interface GitLabDuoWorkflowStreamState {
191
+ stream: AssistantMessageEventStream;
192
+ output: AssistantMessage;
193
+ activeTextIndex?: number;
194
+ activeThinkingIndex?: number;
195
+ activeCheckpointMessageKey?: string;
196
+ started: boolean;
197
+ checkpointAgentContentByKey?: Record<string, string>;
198
+ checkpointAgentContentSignatures?: Record<string, true>;
199
+ pauseRequested?: boolean;
200
+ stepLimitRequested?: boolean;
201
+ retryableErrorRequested?: boolean;
202
+ lastCheckpointContentLength?: number;
203
+ stalledRequested?: boolean;
204
+ providerSessionState?: GitLabDuoWorkflowProviderSessionState;
205
+ lastApprovalStatus?: string;
206
+ goalOverflowMessage?: string;
207
+ }
208
+ type GitLabDuoWorkflowSocketResult = "closed" | "terminal" | "approval" | "action" | "pause" | "timeout" | "step_limit" | "retryable_error" | "stalled";
209
+ export interface GitLabAvailableModel {
210
+ name?: string | null;
211
+ ref?: string | null;
212
+ }
213
+ export interface GitLabAvailableModelsPayload {
214
+ pinnedModel?: GitLabAvailableModel | null;
215
+ selectedModel?: GitLabAvailableModel | null;
216
+ defaultModel?: GitLabAvailableModel | null;
217
+ selectableModels?: GitLabAvailableModel[] | null;
218
+ }
219
+ export declare const streamGitLabDuoWorkflow: StreamFunction<"gitlab-duo-agent">;
220
+ export declare function buildGitLabDuoWorkflowDirectAccessBody(rootNamespaceId: string, projectId?: string, workflowDefinition?: GitLabDuoWorkflowDefinition): Record<string, string>;
221
+ export declare function buildGitLabDuoWorkflowCreateBody(namespaceId?: string, options?: GitLabDuoWorkflowCreateBodyOptions): Record<string, string | boolean | string[] | number[]>;
222
+ export declare function buildGitLabDuoWorkflowStopBody(): Record<string, string>;
223
+ export declare function buildGitLabDuoWorkflowWebSocketUrl(baseUrl: string, options?: {
224
+ projectId?: string;
225
+ namespaceId?: string;
226
+ rootNamespaceId?: string;
227
+ selectedModelIdentifier?: string;
228
+ workflowDefinition?: GitLabDuoWorkflowDefinition;
229
+ serviceEndpoint?: boolean;
230
+ }): string;
231
+ export declare function buildGitLabDuoWorkflowWebSocketHeaders(options: {
232
+ token: string;
233
+ baseUrl?: string;
234
+ projectId?: string;
235
+ namespaceId?: string;
236
+ rootNamespaceId?: string;
237
+ extraHeaders?: Record<string, string>;
238
+ }): Record<string, string>;
239
+ export declare function buildGitLabDuoWorkflowStartRequest(workflowId: string, model: Model<"gitlab-duo-agent">, context: Context, tools?: Tool[] | undefined, availableModels?: GitLabAvailableModelsPayload | null, metadataOptions?: GitLabDuoWorkflowStartMetadataOptions): GitLabDuoWorkflowStartRequest;
240
+ export declare function buildGitLabDuoWorkflowInlineFlowConfig(systemPrompt: string): GitLabDuoWorkflowInlineFlowConfig;
241
+ export declare function buildGitLabDuoWorkflowClientAdditionalContext(): GitLabDuoWorkflowAdditionalContextItem[];
242
+ export declare function buildGitLabDuoWorkflowMcpTools(tools: Tool[] | undefined): GitLabMcpToolDefinition[];
243
+ export declare function selectGitLabDuoWorkflowModelRef(selectedModel: string, availableModels?: GitLabAvailableModelsPayload | null): string;
244
+ export declare function buildGitLabPlainTextFromToolResult(toolResult: ToolResultMessage): GitLabPlainTextResponse;
245
+ export declare function gitLabDuoWorkflowErrorText(error: unknown): string;
246
+ export declare function buildGitLabDuoWorkflowSettingsBody(): Record<string, unknown>;
247
+ export declare function runGitLabDuoWorkflowSocket(ws: GitLabDuoWorkflowWebSocketLike, startPayload: GitLabDuoWorkflowStartRequest, state: GitLabDuoWorkflowStreamState, options: GitLabDuoWorkflowOptions, resumeResponse?: GitLabDuoWorkflowActionResponse | readonly GitLabDuoWorkflowActionResponse[], replayMessages?: readonly unknown[]): Promise<GitLabDuoWorkflowSocketResult>;
248
+ export declare function buildGitLabDuoWorkflowApprovalStartRequest(startPayload: GitLabDuoWorkflowStartRequest): GitLabDuoWorkflowStartRequest;
249
+ export declare function describeGitLabDuoWorkflowSocketEvent(event: unknown): string;
250
+ export declare function traceGitLabDuoWorkflow(event: string, data?: Record<string, unknown>): void;
251
+ export declare function extractGitLabWorkflowToken(payload: GitLabDirectAccessResponse): string | undefined;
252
+ export declare function resolveGitLabDuoWorkflowNamespaceSelection(model: Model<"gitlab-duo-agent">, options: GitLabDuoWorkflowOptions, apiKey: string, baseUrl: string, fetchImpl: FetchImpl): Promise<GitLabDuoWorkflowNamespaceSelection>;
253
+ export declare function resolveGitLabDuoWorkflowRootNamespaceId(model: Model<"gitlab-duo-agent">, options: GitLabDuoWorkflowOptions, apiKey: string, baseUrl: string, fetchImpl: FetchImpl): Promise<string>;
254
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { OAuthCredentials, OAuthLoginCallbacks } from "./oauth/types";
2
+ export declare const gitLabDuoWorkflowProvider: {
3
+ readonly id: "gitlab-duo-agent";
4
+ readonly name: "GitLab Duo Agent";
5
+ readonly envKeys: "GITLAB_TOKEN";
6
+ readonly login: (cb: OAuthLoginCallbacks) => Promise<OAuthCredentials>;
7
+ readonly refreshToken: (credentials: OAuthCredentials) => Promise<OAuthCredentials>;
8
+ readonly callbackPort: 8080;
9
+ readonly pasteCodeFlow: true;
10
+ };
@@ -1,7 +1,7 @@
1
1
  import type { OAuthCredentials, OAuthLoginCallbacks } from "./oauth/types";
2
2
  export declare const gitlabDuoProvider: {
3
3
  readonly id: "gitlab-duo";
4
- readonly name: "GitLab Duo";
4
+ readonly name: "GitLab Duo Non-Agentic";
5
5
  readonly login: (cb: OAuthLoginCallbacks) => Promise<OAuthCredentials>;
6
6
  readonly refreshToken: (credentials: OAuthCredentials) => Promise<OAuthCredentials>;
7
7
  readonly callbackPort: 8080;
@@ -0,0 +1,6 @@
1
+ import type { FetchImpl } from "../../types";
2
+ import type { OAuthCredentials, OAuthLoginCallbacks } from "./types";
3
+ export declare const GITLAB_DUO_WORKFLOW_OAUTH_CLIENT_ID = "36f2a70cddeb5a0889d4fd8295c241b7e9848e89cf9e599d0eed2d8e5350fbf5";
4
+ export declare const GITLAB_DUO_WORKFLOW_OAUTH_REDIRECT_URI = "vscode://gitlab.gitlab-workflow/authentication";
5
+ export declare function loginGitLabDuoWorkflow(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials>;
6
+ export declare function refreshGitLabDuoWorkflowToken(credentials: OAuthCredentials, fetchImpl?: FetchImpl): Promise<OAuthCredentials>;
@@ -71,7 +71,15 @@ declare const ALL: ({
71
71
  readonly refreshToken: (credentials: import("./oauth").OAuthCredentials) => Promise<import("./oauth").OAuthCredentials>;
72
72
  } | {
73
73
  readonly id: "gitlab-duo";
74
- readonly name: "GitLab Duo";
74
+ readonly name: "GitLab Duo Non-Agentic";
75
+ readonly login: (cb: import("./oauth").OAuthLoginCallbacks) => Promise<import("./oauth").OAuthCredentials>;
76
+ readonly refreshToken: (credentials: import("./oauth").OAuthCredentials) => Promise<import("./oauth").OAuthCredentials>;
77
+ readonly callbackPort: 8080;
78
+ readonly pasteCodeFlow: true;
79
+ } | {
80
+ readonly id: "gitlab-duo-agent";
81
+ readonly name: "GitLab Duo Agent";
82
+ readonly envKeys: "GITLAB_TOKEN";
75
83
  readonly login: (cb: import("./oauth").OAuthLoginCallbacks) => Promise<import("./oauth").OAuthCredentials>;
76
84
  readonly refreshToken: (credentials: import("./oauth").OAuthCredentials) => Promise<import("./oauth").OAuthCredentials>;
77
85
  readonly callbackPort: 8080;
@@ -5,6 +5,14 @@ import type { GoogleGeminiCliOptions } from "./providers/google-gemini-cli";
5
5
  import type { GoogleVertexOptions } from "./providers/google-vertex";
6
6
  import type { Api, AssistantMessage, Context, Model, OptionsForApi, SimpleStreamOptions, ToolChoice } from "./types";
7
7
  import { AssistantMessageEventStream } from "./utils/event-stream";
8
+ export declare function configureProviderMaxInFlightRequests(limits: Record<string, number> | undefined): void;
9
+ export declare const __providerInFlightForTesting: {
10
+ setRoot(root: string | undefined): void;
11
+ providerDir(provider: string): string;
12
+ lockDir(provider: string): string;
13
+ captureStaleLockRelease(provider: string): Promise<(() => Promise<void>) | null>;
14
+ captureLockDirRelease(provider: string): Promise<(() => Promise<void>) | null>;
15
+ };
8
16
  /**
9
17
  * Get API key for provider from known environment variables, e.g. OPENAI_API_KEY.
10
18
  *
@@ -12,6 +12,7 @@ import type { StopDetails } from "./providers/anthropic-wire";
12
12
  import type { AzureOpenAIResponsesOptions } from "./providers/azure-openai-responses";
13
13
  import type { CursorOptions } from "./providers/cursor";
14
14
  import type { DevinOptions } from "./providers/devin";
15
+ import type { GitLabDuoWorkflowOptions } from "./providers/gitlab-duo-workflow";
15
16
  import type { GoogleOptions } from "./providers/google";
16
17
  import type { GoogleGeminiCliOptions } from "./providers/google-gemini-cli";
17
18
  import type { GoogleVertexOptions } from "./providers/google-vertex";
@@ -47,6 +48,7 @@ export interface ApiOptionsMap {
47
48
  "google-vertex": GoogleVertexOptions;
48
49
  "ollama-chat": OllamaChatOptions;
49
50
  "cursor-agent": CursorOptions;
51
+ "gitlab-duo-agent": GitLabDuoWorkflowOptions;
50
52
  "devin-agent": DevinOptions;
51
53
  }
52
54
  export type OptionsForApi<TApi extends Api> = StreamOptions | (TApi extends keyof ApiOptionsMap ? ApiOptionsMap[TApi] : never);
@@ -201,6 +203,14 @@ export interface StreamOptions {
201
203
  * Providers can use this to persist transport/session state between turns.
202
204
  */
203
205
  providerSessionState?: Map<string, ProviderSessionState>;
206
+ /**
207
+ * Optional per-provider concurrent request cap for LLM stream calls. Keys are
208
+ * provider ids (`model.provider`); positive numeric values cap in-flight
209
+ * requests across local OMP processes that share the same config root. Omitted
210
+ * providers are unlimited. Non-chat provider APIs that bypass stream helpers
211
+ * are not covered.
212
+ */
213
+ maxInFlightRequests?: Record<string, number>;
204
214
  /**
205
215
  * Optional callback for inspecting or replacing provider payloads before sending.
206
216
  * Return undefined to keep the payload unchanged.
@@ -261,6 +271,8 @@ export interface StreamOptions {
261
271
  * channel) silently ignore the override.
262
272
  */
263
273
  fetch?: FetchImpl;
274
+ /** Current session working directory for providers that need workspace-scoped discovery. */
275
+ cwd?: string;
264
276
  /** Cursor exec/MCP tool handlers (cursor-agent only). */
265
277
  execHandlers?: CursorExecHandlers;
266
278
  }
@@ -0,0 +1,5 @@
1
+ import type { UsageProvider } from "../usage";
2
+ /** Registers Ollama accounts with usage views even though no quota endpoint is exposed. */
3
+ export declare const ollamaUsageProvider: UsageProvider;
4
+ /** Registers Ollama Cloud accounts with usage views until a quota endpoint is available. */
5
+ export declare const ollamaCloudUsageProvider: UsageProvider;
@@ -0,0 +1 @@
1
+ export declare function normalizeCodexBaseUrl(baseUrl?: string): string;
@@ -1,4 +1,3 @@
1
1
  import type { CredentialRankingStrategy, UsageProvider } from "../usage";
2
- export declare function normalizeCodexBaseUrl(baseUrl?: string): string;
3
2
  export declare const openaiCodexUsageProvider: UsageProvider;
4
3
  export declare const codexRankingStrategy: CredentialRankingStrategy;
@@ -50,6 +50,22 @@ export interface UsageLimit {
50
50
  status?: UsageStatus;
51
51
  notes?: string[];
52
52
  }
53
+ /**
54
+ * Per-credit detail for a saved/banked rate-limit reset.
55
+ *
56
+ * Populated when the provider's listing endpoint returns individual credit
57
+ * metadata (e.g. OpenAI Codex `wham/rate-limit-reset-credits`). Callers that
58
+ * only need the count can ignore this; display layers use `expiresAt` to show
59
+ * when banked resets expire ([#3339](https://github.com/can1357/oh-my-pi/issues/3339)).
60
+ */
61
+ export interface UsageResetCreditDetail {
62
+ /** ISO timestamp when the credit was granted. */
63
+ grantedAt?: string;
64
+ /** ISO timestamp when the credit expires and can no longer be redeemed. */
65
+ expiresAt?: string;
66
+ /** Backend status, e.g. `available`, `redeemed`. */
67
+ status?: string;
68
+ }
53
69
  /**
54
70
  * Saved/banked rate-limit resets an account can redeem on demand.
55
71
  *
@@ -60,6 +76,8 @@ export interface UsageLimit {
60
76
  export interface UsageResetCredits {
61
77
  /** Number of resets available to redeem right now. */
62
78
  availableCount: number;
79
+ /** Individual credit details (expiry dates, etc.) when the provider exposes them. */
80
+ credits?: UsageResetCreditDetail[];
63
81
  }
64
82
  /** Aggregated usage report for a provider. */
65
83
  export interface UsageReport {
@@ -188,8 +206,18 @@ export declare const usageLimitSchema: import("arktype/internal/variants/object.
188
206
  status?: "exhausted" | "ok" | "unknown" | "warning" | undefined;
189
207
  notes?: string[] | undefined;
190
208
  }, {}>;
209
+ export declare const usageResetCreditDetailSchema: import("arktype/internal/variants/object.ts").ObjectType<{
210
+ grantedAt?: string | undefined;
211
+ expiresAt?: string | undefined;
212
+ status?: string | undefined;
213
+ }, {}>;
191
214
  export declare const usageResetCreditsSchema: import("arktype/internal/variants/object.ts").ObjectType<{
192
215
  availableCount: number;
216
+ credits?: {
217
+ grantedAt?: string | undefined;
218
+ expiresAt?: string | undefined;
219
+ status?: string | undefined;
220
+ }[] | undefined;
193
221
  }, {}>;
194
222
  export declare const usageReportSchema: import("arktype/internal/variants/object.ts").ObjectType<{
195
223
  provider: string;
@@ -226,6 +254,11 @@ export declare const usageReportSchema: import("arktype/internal/variants/object
226
254
  }[];
227
255
  resetCredits?: {
228
256
  availableCount: number;
257
+ credits?: {
258
+ grantedAt?: string | undefined;
259
+ expiresAt?: string | undefined;
260
+ status?: string | undefined;
261
+ }[] | undefined;
229
262
  } | undefined;
230
263
  notes?: string[] | undefined;
231
264
  metadata?: {
@@ -4,6 +4,15 @@ import { AssistantMessageEventStream } from "./event-stream";
4
4
  * message also carries "stream stall" so the session + transport retry
5
5
  * classifiers treat it as a transient (retryable) stop without bespoke rules. */
6
6
  export declare const THINKING_LOOP_ERROR_MARKER = "Thinking loop detected";
7
+ /**
8
+ * True when `model` is a Gemini model whose native thinking stream surfaces the
9
+ * "thought summary" titles this module's header guard counts.
10
+ *
11
+ * OpenAI-compat transports can serve Gemini under an arbitrary provider/id, so they
12
+ * carry the explicit `compat.enableGeminiThinkingLoopGuard` flag; direct Gemini
13
+ * transports carry a clearly shaped id/provider, so a string match is sufficient.
14
+ */
15
+ export declare function isGeminiThinkingModel(model: Model<Api>): boolean;
7
16
  /**
8
17
  * True when `model` should be guarded for thinking/response loops (Gemini & DeepSeek).
9
18
  *
@@ -27,6 +36,43 @@ export declare class ThinkingLoopDetector {
27
36
  * which may be the one that completes a duplicate cluster — is not dropped. */
28
37
  flush(): string | null;
29
38
  }
39
+ /**
40
+ * Consecutive Gemini thought-summary headers in one uninterrupted reasoning
41
+ * stream that trips the tool-call reminder. Gemini occasionally narrates a long
42
+ * chain of titled summaries ("Examining Result Handling", "Refining Result
43
+ * Rendering", …) without ever calling a tool, burning the whole budget on
44
+ * planning; at this many distinct titles it has almost certainly stalled. This
45
+ * is the over-planning shape {@link ThinkingLoopDetector} misses — those titles
46
+ * are stripped before its similarity analysis precisely because their wording
47
+ * keeps changing, so a genuinely-distinct planning runaway never trips it.
48
+ */
49
+ export declare const GEMINI_HEADER_RUNAWAY_THRESHOLD = 10;
50
+ /**
51
+ * True when a single trimmed line is a Gemini reasoning-summary title: a markdown
52
+ * ATX heading (`## …`) or a whole-line bold / bold-italic run (`**Title**`,
53
+ * `***Title***`). Inline emphasis inside prose never matches — the bold run must
54
+ * span the entire line. Mirrors the title shapes {@link ThinkingLoopDetector}
55
+ * strips before similarity analysis.
56
+ */
57
+ export declare function isReasoningSummaryHeader(line: string): boolean;
58
+ /**
59
+ * Counts consecutive Gemini reasoning-summary headers across a streamed thinking
60
+ * block. {@link push} returns true exactly once — when the running header count
61
+ * first reaches {@link GEMINI_HEADER_RUNAWAY_THRESHOLD} — and the caller then
62
+ * interrupts the stream and reminds the model to issue a tool call. Paragraph
63
+ * lines between titles do NOT reset the run (Gemini emits header + paragraph per
64
+ * thought, so the run IS the number of summaries); leaving the reasoning channel
65
+ * does, via {@link reset} on a new thinking block / prose / tool call.
66
+ */
67
+ export declare class GeminiHeaderRunDetector {
68
+ #private;
69
+ /** Feed a thinking delta. Returns true the first time the run hits the threshold. */
70
+ push(delta: string): boolean;
71
+ /** Number of summary titles counted in the current run (for the reminder/log). */
72
+ get count(): number;
73
+ /** Re-arm for a fresh reasoning block: clears the buffer, count, and latch. */
74
+ reset(): void;
75
+ }
30
76
  /**
31
77
  * Wrap a provider stream with the loop guard. `controller` is the guard's own
32
78
  * abort handle: aborting it (after wiring it into the provider's signal via
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-ai",
4
- "version": "16.1.23",
4
+ "version": "16.2.1",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -38,9 +38,9 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@bufbuild/protobuf": "^2.12.0",
41
- "@oh-my-pi/pi-catalog": "16.1.23",
42
- "@oh-my-pi/pi-utils": "16.1.23",
43
- "@oh-my-pi/pi-wire": "16.1.23",
41
+ "@oh-my-pi/pi-catalog": "16.2.1",
42
+ "@oh-my-pi/pi-utils": "16.2.1",
43
+ "@oh-my-pi/pi-wire": "16.2.1",
44
44
  "arktype": "^2.2.0",
45
45
  "zod": "^4"
46
46
  },
@@ -27,6 +27,7 @@ const BUILTIN_API_IDS = [
27
27
  "google-vertex",
28
28
  "ollama-chat",
29
29
  "cursor-agent",
30
+ "gitlab-duo-agent",
30
31
  "devin-agent",
31
32
  ] as const satisfies readonly KnownApi[];
32
33