@gitkraken/ai-provider-tools 0.0.0-stage → 0.1.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 (44) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/LICENSE +19 -0
  3. package/README.md +64 -2
  4. package/dist/config.d.ts +56 -0
  5. package/dist/config.js +1 -0
  6. package/dist/create-model.d.ts +15 -0
  7. package/dist/create-model.js +1 -0
  8. package/dist/errors.d.ts +65 -0
  9. package/dist/errors.js +1 -0
  10. package/dist/generators/changelog.d.ts +18 -0
  11. package/dist/generators/changelog.js +1 -0
  12. package/dist/generators/commit-message.d.ts +15 -0
  13. package/dist/generators/commit-message.js +1 -0
  14. package/dist/generators/explain-changes.d.ts +15 -0
  15. package/dist/generators/explain-changes.js +1 -0
  16. package/dist/generators/index.d.ts +5 -0
  17. package/dist/generators/index.js +1 -0
  18. package/dist/generators/pull-request.d.ts +15 -0
  19. package/dist/generators/pull-request.js +1 -0
  20. package/dist/index.d.ts +11 -0
  21. package/dist/index.js +1 -0
  22. package/dist/mask-api-key.d.ts +8 -0
  23. package/dist/mask-api-key.js +1 -0
  24. package/dist/models.d.ts +20 -0
  25. package/dist/models.js +1 -0
  26. package/dist/ports/model.d.ts +50 -0
  27. package/dist/ports/model.js +1 -0
  28. package/dist/prompts.d.ts +14 -0
  29. package/dist/prompts.js +1 -0
  30. package/dist/providers/byok.d.ts +12 -0
  31. package/dist/providers/byok.js +1 -0
  32. package/dist/providers/gitkraken-errors.d.ts +21 -0
  33. package/dist/providers/gitkraken-errors.js +1 -0
  34. package/dist/providers/gitkraken-stream.d.ts +29 -0
  35. package/dist/providers/gitkraken-stream.js +3 -0
  36. package/dist/providers/gitkraken-wire.d.ts +156 -0
  37. package/dist/providers/gitkraken-wire.js +1 -0
  38. package/dist/providers/gitkraken.d.ts +48 -0
  39. package/dist/providers/gitkraken.js +1 -0
  40. package/dist/run-prompt-completion.d.ts +30 -0
  41. package/dist/run-prompt-completion.js +1 -0
  42. package/dist/xml.d.ts +14 -0
  43. package/dist/xml.js +1 -0
  44. package/package.json +44 -3
package/CHANGELOG.md ADDED
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/) and this project adheres to [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.1] - 2026-10-02
10
+
11
+ ### Added
12
+
13
+ - Initial release of `@gitkraken/ai-provider-tools`: `createModel` (a GitKraken-hosted or BYOK Anthropic/OpenAI/OpenRouter/Ollama text-generation port, assignable to both `@gitkraken/shared-tools`' `AiModelPort` and `@gitkraken/conflict-tools`' `ConflictModelPort`), `listModels`, `getPromptTemplate`, `runPromptCompletion`, `AiError` (including a `truncated_output` code porting Go's `TruncatedOutputError`/`AllowTruncatedOutput`, via `ModelGenerateParams`' `allowTruncatedOutput` option), and `maskApiKey`, ported from gkcli's `internal/ai` package (gitkraken/gkcli#1726).
14
+ - `runPromptCompletion(config, action, variables, options?)` is public: it runs any server-side prompt template by its task action name (a plain `string`; there is no `PromptAction` union).
15
+ - `./generators` subpath export with `generateCommitMessage`, `generatePullRequest`, `explainChanges`, `generateChangelog` and their result types and parsers; these are not re-exported from the package root.
16
+ - `config.telemetry` now emits `ai.generate` on success and reports thrown errors via `.exception`, from both the hosted and BYOK code paths.
17
+ - `ModelGenerateResult` carries optional `finishReason` (the new exported `AiFinishReason`), `rawFinishReason` and `model`, from both the hosted and BYOK providers.
18
+ - `runPromptCompletion` resolves to the new exported `PromptCompletionResult` (`text`, `usage?`, `finishReason?`, `rawFinishReason?`, `model?`, and on the hosted provider `task?` with the task `id`, `status` and `provider`) instead of a bare string, and each generator result carries the completion's `usage`.
19
+ - `AiError` exposes `status`, `providerErrorCode` and `providerErrorType`, the HTTP status and the raw `code`/`type` of the provider's error body.
20
+ - `ChatCompleteRequest` is exported and types `tools` as `AiToolDefinition[]`.
21
+
22
+ ### Changed
23
+
24
+ - Hosted chat requests no longer follow redirects, and malformed successful task, model-list,
25
+ prompt-template or non-streamed chat responses now fail with classified `AiError`s. Prompt
26
+ template 4xx failures other than 401/429 are non-retryable `invalid_request` errors.
27
+ - Hosted chat and task requests preserve an `AbortError` raised while obtaining the token.
28
+ - XML result parsing ignores comments and treats CDATA contents as literal text.
29
+ - Renamed the package from `@gitkraken/ai-tools` to `@gitkraken/ai-provider-tools` (directory `packages/ai-provider`) and `AiToolsConfig` to `AiProviderConfig`; the old name was never published.
30
+ - `AiModel`'s `providerName`, `modelName`, `maxInputTokens`, `maxOutputTokens` and `consumptionRateLabel` are optional and copied from the backend response without zero-value defaults, and `AiCreditsExhaustedDetails`' `used` and `limit` are optional when an explicit exhaustion code carries no counts.
31
+ - `generateCommitMessage` keeps the blank lines in `description` instead of collapsing them, and `explainChanges` trims its `title` and `body` like the other generators.
32
+ - Hosted POST requests send `Content-Type: application/json`.
33
+ - OpenRouter requests send `HTTP-Referer: https://www.gitkraken.com/` and take `X-Title` from `config.client.name` (`GitKraken` when omitted).
34
+ - 401 error messages no longer mention a CLI command.
35
+ - `AiToolDefinition`, `AiToolCall` and `ToolAwareMessage` are now re-exported from `@gitkraken/shared-tools`, and `ToolAwareGenerateParams` extends the shared shape with `allowTruncatedOutput`, replacing the hand-synced copy of conflict-tools' types.
package/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Core Tools License
2
+
3
+ Copyright (c) 2025-2026 Axosoft, LLC dba GitKraken ("GitKraken")
4
+
5
+ This software and associated documentation files (the "Software") may be compiled as part of GitKraken software products, including the gitkraken/vscode-gitlens open source project (collectively, the "GitKraken Products"), to the extent the Software is a required component of the GitKraken Products; provided, however, that the Software and its functionality may only be used if you (and any entity that you represent) have agreed to, and are in compliance with, the GitKraken End User License Agreement, available at https://gitkraken.com/eula (the "EULA"), or other agreement governing the use of the Software, as agreed by you and GitKraken, and otherwise have a valid subscription for the correct number of user seats for the applicable version of the Software (e.g., GitLens Free+, GitLens Pro, GitLens Teams, and GitLens Enterprise).
6
+
7
+ The Software is licensed, not sold. This license only gives you some rights to use the Software. GitKraken reserves all other rights. Unless applicable law gives you more rights despite this limitation, you may use the Software only as expressly permitted in this license. In doing so, you must comply with any technical limitations in the Software that only allow you to use it in certain ways. You may not (i) work around any technical limitations in the Software, (ii) reverse engineer, decompile or disassemble the Software, or otherwise attempt to derive the source code for the Software, except and to the extent required by third party licensing terms governing use of certain open source components that may be included with the Software, (iii) remove, minimize, block or modify any notices of GitKraken or its suppliers in the Software, (iv) use the software in any way that is against the law, (v) host, share, publish, rent or lease the Software; (vi) distribute the Software as a stand-alone or integrated offering or combine it with any of your applications for others to use, (vii) use the Software other than in connection with validly licensed GitKraken Products or (viii) use any portion of the Software to create software with the same or similar functionality.
8
+
9
+ You agree that GitKraken and/or its licensors (as applicable) retain all right, title and interest in and to the Software and all modifications and/or patches thereto. You are not granted any other rights beyond what is expressly stated herein. Except as set forth above, it is forbidden to copy, merge, publish, distribute, sublicense, modify and/or sell the Software.
10
+
11
+ If you give feedback about the Software to GitKraken, you give to GitKraken, without charge, the right to use, share and commercialize your feedback in any way and for any purpose. You will not give feedback that is subject to a license that requires GitKraken to license its software or documentation to third parties because we include your feedback in them. These rights survive this License.
12
+
13
+ The full text of this Core Tools License shall be included in all copies or substantial portions of the Software.
14
+
15
+ This License and the EULA, if applicable, are the entire agreement for the Software. You must comply with all domestic and international export laws and regulations that apply to the Software, which include restrictions on destinations, end users and end use. This License is governed and construed by the laws of the State of Arizona, United States without regard to any choice or conflict of law principles or rules.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL GITKRAKEN, THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18
+
19
+ For all third party components incorporated into the Software, those components are licensed under the original license provided by the owner of the applicable component.
package/README.md CHANGED
@@ -1,3 +1,65 @@
1
- # Temporary Holding Version
1
+ # @gitkraken/ai-provider-tools
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ AI text generation for GitKraken client SDKs: the GitKraken-hosted backend and BYOK
4
+ providers (Anthropic, OpenAI, OpenRouter, Ollama) behind one model port.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ pnpm add @gitkraken/ai-provider-tools
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ```ts
15
+ import { createModel } from '@gitkraken/ai-provider-tools';
16
+ import { generateCommitMessage } from '@gitkraken/ai-provider-tools/generators';
17
+
18
+ const config = {
19
+ provider: 'gitkraken' as const,
20
+ getToken: async () => authStore.accessToken,
21
+ organizationId: currentOrg.id,
22
+ };
23
+
24
+ const model = createModel(config);
25
+ const { text } = await model.generate({ messages: [{ role: 'user', content: 'hi' }] });
26
+
27
+ const { message, description } = await generateCommitMessage(config, diff);
28
+ ```
29
+
30
+ `runPromptCompletion(config, action, variables)` (root export) runs any server-side prompt template by its task action name and returns `{ text, usage?, finishReason?, rawFinishReason?, model? }`; the `./generators` subpath wraps it for commit messages, pull requests, change explanations and changelogs, each result carrying the completion's `usage` when the provider reports it.
31
+
32
+ A BYOK provider looks the same, minus the GitKraken-only fields:
33
+
34
+ ```ts
35
+ const model = createModel({ provider: 'anthropic', apiKey, model: 'claude-sonnet-4-5' });
36
+ ```
37
+
38
+ ## Model port
39
+
40
+ `createModel`'s result is a `Model` whose `generate` accepts either shared-tools'
41
+ `AiGenerateParams` or a tool-aware superset of it (system/messages/tools/maxTokens/
42
+ temperature/signal), and is assignable to both `@gitkraken/shared-tools`' `AiModelPort`
43
+ and `@gitkraken/conflict-tools`' `ConflictModelPort` — see `ARCHITECTURE.md` and
44
+ `src/model-port.type.test.ts`.
45
+
46
+ `generate` resolves to `{ text, toolCalls?, usage?, finishReason?, rawFinishReason?, model? }`.
47
+ `finishReason` is provider-neutral (`stop`, `length`, `tool-calls`, `content-filter`,
48
+ `error`, `other`), `rawFinishReason` is the provider's exact value, and `model` is the model
49
+ id the response reports.
50
+
51
+ ## Errors
52
+
53
+ Every operation throws `AiError` on failure:
54
+ `{ code, message, retryable, status?, providerErrorCode?, providerErrorType?, details? }`.
55
+ `status` is the HTTP status, and `providerErrorCode`/`providerErrorType` are the raw
56
+ `code`/`type` of the provider's error body. `code` is one of `credits_exhausted`, `not_logged_in`, `rate_limited`,
57
+ `invalid_request`, `provider_error`, `truncated_output`. User aborts are rethrown
58
+ unchanged rather than wrapped. See `ARCHITECTURE.md` for the mapping.
59
+
60
+ ## What the host owns
61
+
62
+ `getToken`, `fetch`, `request` (an `@gitkraken/api-tools` `ApiRequest`), `getCreditUsage`,
63
+ `onCreditUsage`, and `telemetry` are all injected. This package never reads
64
+ `process.env`, writes through a `StoragePort`, or caches a model list — see
65
+ `ARCHITECTURE.md`.
@@ -0,0 +1,56 @@
1
+ import { ApiRequest } from "@gitkraken/api-tools";
2
+ import { AiCreditUsage, GetAiCreditUsage, TelemetryPort } from "@gitkraken/shared-tools";
3
+
4
+ //#region src/config.d.ts
5
+ type AiProviderId = 'gitkraken' | 'anthropic' | 'openai' | 'openrouter' | 'ollama';
6
+ /**
7
+ * Configuration shared by `createModel`, `listModels`, `getPromptTemplate` and
8
+ * the four `generate*` functions. Only the fields a given provider/operation
9
+ * needs are read; the rest are ignored. Hosted `generate` calls send
10
+ * `GK-Action: compose-changes`.
11
+ */
12
+ interface AiProviderConfig {
13
+ provider: AiProviderId;
14
+ /** Model id. Falls back to the provider's own default when omitted. */
15
+ model?: string;
16
+ /** Required for BYOK providers other than Ollama. Ignored for `gitkraken`. */
17
+ apiKey?: string;
18
+ /**
19
+ * Overrides the provider's default endpoint. For `gitkraken`, this is the
20
+ * `v1/ai-tasks` API origin (default `https://api.gitkraken.dev/`), and it
21
+ * also controls where the prompt template is fetched from. For BYOK
22
+ * providers, this is the provider's own endpoint only — the prompt template
23
+ * is fetched from the `gitkraken` API origin, overridable via
24
+ * `gitkrakenApiBaseUrl`.
25
+ */
26
+ baseUrl?: string;
27
+ /** GitKraken API origin used for prompt templates when provider is BYOK; defaults to https://api.gitkraken.dev/. */
28
+ gitkrakenApiBaseUrl?: string;
29
+ /** Supplies a fresh bearer token per request. Used for `gitkraken` requests, and for BYOK providers' prompt-template fetch. */
30
+ getToken?: () => Promise<string>;
31
+ /**
32
+ * `gitkraken` only: the `GK-Org-Id` header value. The hosted chat-completions
33
+ * route rejects requests without it (HTTP 403).
34
+ */
35
+ organizationId?: string;
36
+ /** `gitkraken`: sent as the `Client-Name`/`Client-Version` headers. `openrouter`: `name` also becomes OpenRouter's `X-Title` (default `GitKraken`). */
37
+ client?: {
38
+ name: string;
39
+ version: string;
40
+ };
41
+ /**
42
+ * Raw fetch, used for the `gitkraken` provider's streamed chat/completions
43
+ * call and injected into the BYOK AI SDK providers so the host controls the
44
+ * network. Defaults to `globalThis.fetch`.
45
+ */
46
+ fetch?: typeof globalThis.fetch;
47
+ /** Used for the `gitkraken` non-streamed task route, and for BYOK providers' prompt-template fetch. Defaults to a `globalThis.fetch`-backed requester. */
48
+ request?: ApiRequest;
49
+ /** Looked up when a `gitkraken` request fails with 402/403/429 and no explicit credits code (D10). */
50
+ getCreditUsage?: GetAiCreditUsage;
51
+ /** Called with the credit usage reported alongside a successful `gitkraken` response, if any. The host decides whether/how to warn. */
52
+ onCreditUsage?: (usage: AiCreditUsage) => void;
53
+ telemetry?: TelemetryPort;
54
+ }
55
+ //#endregion
56
+ export { AiProviderConfig, AiProviderId };
package/dist/config.js ADDED
@@ -0,0 +1 @@
1
+ export{};
@@ -0,0 +1,15 @@
1
+ import { AiProviderConfig } from "./config.js";
2
+ import { Model } from "./ports/model.js";
3
+
4
+ //#region src/create-model.d.ts
5
+ /**
6
+ * Creates a text-generation `Model` for the configured provider: the
7
+ * GitKraken-hosted backend, or a BYOK provider (Anthropic, OpenAI, OpenRouter,
8
+ * Ollama) via the Vercel AI SDK. The result is assignable to both
9
+ * `@gitkraken/shared-tools`' `AiModelPort` and `@gitkraken/conflict-tools`'
10
+ * `ConflictModelPort` — see `model-port.type.test.ts`. Hosted `generate` calls
11
+ * send `GK-Action: compose-changes`.
12
+ */
13
+ declare function createModel(config: AiProviderConfig): Model;
14
+ //#endregion
15
+ export { createModel };
@@ -0,0 +1 @@
1
+ import{createByokModel as e}from"./providers/byok.js";import{createGitKrakenModel as t}from"./providers/gitkraken.js";function n(n){return n.provider===`gitkraken`?t(n):e(n)}export{n as createModel};
@@ -0,0 +1,65 @@
1
+ //#region src/errors.d.ts
2
+ /**
3
+ * Stable, machine-readable error codes. See D10 in the gkcli#1726 handoff
4
+ * plan. `truncated_output` is not in D10's table; it ports Go's
5
+ * `TruncatedOutputError` (a generic `ExpectedError` there, with no dedicated
6
+ * Go code) as its own code so a host can special-case it, e.g. by retrying
7
+ * with less input.
8
+ */
9
+ type AiErrorCode = 'credits_exhausted' | 'not_logged_in' | 'rate_limited' | 'invalid_request' | 'provider_error' | 'truncated_output';
10
+ /** The credit snapshot attached to a `credits_exhausted` error's `details`. */
11
+ interface AiCreditsExhaustedDetails {
12
+ /** Absent when the explicit exhaustion code carried no usable count. */
13
+ used?: number;
14
+ /** Absent when the explicit exhaustion code carried no usable count. */
15
+ limit?: number;
16
+ resetsOn?: string;
17
+ organizationId?: string;
18
+ }
19
+ interface AiErrorOptions {
20
+ retryable?: boolean;
21
+ details?: unknown;
22
+ cause?: unknown;
23
+ status?: number;
24
+ providerErrorCode?: string;
25
+ providerErrorType?: string;
26
+ }
27
+ /** A classified AI request failure. `retryable` defaults per `code` unless overridden. */
28
+ declare class AiError extends Error {
29
+ readonly code: AiErrorCode;
30
+ readonly retryable: boolean;
31
+ readonly details?: unknown;
32
+ /** The HTTP status of the failed response, when the failure came from one. */
33
+ readonly status?: number;
34
+ /** The raw `code` field of the provider's error body, when it sent one. */
35
+ readonly providerErrorCode?: string;
36
+ /** The raw `type` field of the provider's error body, when it sent one. */
37
+ readonly providerErrorType?: string;
38
+ constructor(code: AiErrorCode, message: string, options?: AiErrorOptions);
39
+ }
40
+ /**
41
+ * Reads the provider's own `code`/`type` from an error body (a JSON string or
42
+ * an already-parsed object), preferring the nested `error` object over the
43
+ * top level.
44
+ */
45
+ declare function wireErrorFields(body: unknown): {
46
+ providerErrorCode?: string;
47
+ providerErrorType?: string;
48
+ };
49
+ /**
50
+ * Reports whether a provider's terminal reason means the output hit the
51
+ * token limit. Ports Go's `providers.IsTruncatedFinishReason`: OpenAI-
52
+ * compatible providers and Ollama say `"length"`, Anthropic says
53
+ * `"max_tokens"`. The Vercel AI SDK already normalizes every BYOK provider's
54
+ * raw reason to its own `"length"`, so `byok.ts` only ever passes that value
55
+ * here; the `"max_tokens"` case is kept so a caller checking a provider's raw
56
+ * (non-normalized) reason string gets the same answer Go would.
57
+ */
58
+ declare function isTruncatedFinishReason(reason: string): boolean;
59
+ /** Ports Go's `providers.TruncatedOutputError`. */
60
+ declare function truncatedOutputError(): AiError;
61
+ declare function isAbortError(error: unknown): boolean;
62
+ /** Passes aborts and `AiError`s through unchanged; wraps anything else as a retryable `provider_error`. */
63
+ declare function toAiError(error: unknown, context: string): unknown;
64
+ //#endregion
65
+ export { AiCreditsExhaustedDetails, AiError, AiErrorCode, isAbortError, isTruncatedFinishReason, toAiError, truncatedOutputError, wireErrorFields };
package/dist/errors.js ADDED
@@ -0,0 +1 @@
1
+ function e(e){return e===`rate_limited`||e===`provider_error`}var t=class extends Error{code;retryable;details;status;providerErrorCode;providerErrorType;constructor(t,n,r){super(n,r?.cause===void 0?void 0:{cause:r.cause}),this.name=`AiError`,this.code=t,this.retryable=r?.retryable??e(t),this.details=r?.details,r?.status!==void 0&&(this.status=r.status),r?.providerErrorCode!==void 0&&(this.providerErrorCode=r.providerErrorCode),r?.providerErrorType!==void 0&&(this.providerErrorType=r.providerErrorType)}};function n(e,t){let n=e[t];return typeof n==`string`?n:void 0}function r(e){let t=e;if(typeof e==`string`)try{t=JSON.parse(e)}catch{t={}}if(typeof t!=`object`||!t)return{};let r=t,i=r.error!==null&&typeof r.error==`object`?r.error:void 0,a=(i&&n(i,`code`))??n(r,`code`),o=(i&&n(i,`type`))??n(r,`type`);return{...a===void 0?{}:{providerErrorCode:a},...o===void 0?{}:{providerErrorType:o}}}function i(e){return e===`length`||e===`max_tokens`}function a(){return new t(`truncated_output`,`AI provider returned an incomplete response because it hit the output token limit.`)}function o(e){return e instanceof Error&&e.name===`AbortError`}function s(e,n){return e instanceof t||o(e)?e:new t(`provider_error`,`${n}: ${e instanceof Error?e.message:String(e)}`,{cause:e})}export{t as AiError,o as isAbortError,i as isTruncatedFinishReason,s as toAiError,a as truncatedOutputError,r as wireErrorFields};
@@ -0,0 +1,18 @@
1
+ import { AiProviderConfig } from "../config.js";
2
+ import { AiTokenUsage } from "@gitkraken/shared-tools";
3
+
4
+ //#region src/generators/changelog.d.ts
5
+ interface ChangelogResult {
6
+ body: string;
7
+ usage?: AiTokenUsage;
8
+ }
9
+ /** Ports Go's `GenerateChangelog` tail: the completion is used verbatim, not XML-parsed. */
10
+ declare function parseChangelogResult(content: string): ChangelogResult;
11
+ /**
12
+ * Ports Go's `GenerateChangelog`. A changelog is used verbatim, so a
13
+ * clipped-but-usable answer beats an error: this is the only generator that
14
+ * passes `allowTruncatedOutput: true`.
15
+ */
16
+ declare function generateChangelog(config: AiProviderConfig, commits: string[]): Promise<ChangelogResult>;
17
+ //#endregion
18
+ export { ChangelogResult, generateChangelog, parseChangelogResult };
@@ -0,0 +1 @@
1
+ import{runPromptCompletion as e}from"../run-prompt-completion.js";function t(e){return{body:e.trim()}}async function n(n,r){let{text:i,usage:a}=await e(n,`generate-changelog`,{data:JSON.stringify(r)},{allowTruncatedOutput:!0});return{...t(i),...a?{usage:a}:{}}}export{n as generateChangelog,t as parseChangelogResult};
@@ -0,0 +1,15 @@
1
+ import { AiProviderConfig } from "../config.js";
2
+ import { AiTokenUsage } from "@gitkraken/shared-tools";
3
+
4
+ //#region src/generators/commit-message.d.ts
5
+ interface CommitMessageResult {
6
+ message: string;
7
+ description: string;
8
+ usage?: AiTokenUsage;
9
+ }
10
+ /** Ports Go's `parseCommitMessageResponse`. Both fields are trimmed; blank lines inside `description` are kept. */
11
+ declare function parseCommitMessageResult(content: string): CommitMessageResult;
12
+ /** Ports Go's `GenerateCommitMessage`. A clipped completion is unusable XML, so it fails loudly (Go: `allowTruncated=false`). */
13
+ declare function generateCommitMessage(config: AiProviderConfig, diff: string): Promise<CommitMessageResult>;
14
+ //#endregion
15
+ export { CommitMessageResult, generateCommitMessage, parseCommitMessageResult };
@@ -0,0 +1 @@
1
+ import{runPromptCompletion as e}from"../run-prompt-completion.js";import{parseXmlResult as t}from"../xml.js";function n(e){let{summary:n,body:r}=t(e);return{message:n.trim(),description:r.trim()}}async function r(t,r){let{text:i,usage:a}=await e(t,`generate-commitMessage`,{diff:r});return{...n(i),...a?{usage:a}:{}}}export{r as generateCommitMessage,n as parseCommitMessageResult};
@@ -0,0 +1,15 @@
1
+ import { AiProviderConfig } from "../config.js";
2
+ import { AiTokenUsage } from "@gitkraken/shared-tools";
3
+
4
+ //#region src/generators/explain-changes.d.ts
5
+ interface ExplainChangesResult {
6
+ title: string;
7
+ body: string;
8
+ usage?: AiTokenUsage;
9
+ }
10
+ /** Ports the XML parsing inline in Go's `ExplainChanges`. */
11
+ declare function parseExplainChangesResult(content: string): ExplainChangesResult;
12
+ /** Ports Go's `ExplainChanges` (Go: `allowTruncated=false`). */
13
+ declare function explainChanges(config: AiProviderConfig, commitMessage: string, diff: string): Promise<ExplainChangesResult>;
14
+ //#endregion
15
+ export { ExplainChangesResult, explainChanges, parseExplainChangesResult };
@@ -0,0 +1 @@
1
+ import{runPromptCompletion as e}from"../run-prompt-completion.js";import{parseXmlResult as t}from"../xml.js";function n(e){let{summary:n,body:r}=t(e);return{title:n.trim(),body:r.trim()}}async function r(t,r,i){let{text:a,usage:o}=await e(t,`explain-changes`,{diff:i,message:r});return{...n(a),...o?{usage:o}:{}}}export{r as explainChanges,n as parseExplainChangesResult};
@@ -0,0 +1,5 @@
1
+ import { ChangelogResult, generateChangelog, parseChangelogResult } from "./changelog.js";
2
+ import { CommitMessageResult, generateCommitMessage, parseCommitMessageResult } from "./commit-message.js";
3
+ import { ExplainChangesResult, explainChanges, parseExplainChangesResult } from "./explain-changes.js";
4
+ import { PullRequestResult, generatePullRequest, parsePullRequestResult } from "./pull-request.js";
5
+ export { type ChangelogResult, type CommitMessageResult, type ExplainChangesResult, type PullRequestResult, explainChanges, generateChangelog, generateCommitMessage, generatePullRequest, parseChangelogResult, parseCommitMessageResult, parseExplainChangesResult, parsePullRequestResult };
@@ -0,0 +1 @@
1
+ import{generateChangelog as e,parseChangelogResult as t}from"./changelog.js";import{generateCommitMessage as n,parseCommitMessageResult as r}from"./commit-message.js";import{explainChanges as i,parseExplainChangesResult as a}from"./explain-changes.js";import{generatePullRequest as o,parsePullRequestResult as s}from"./pull-request.js";export{i as explainChanges,e as generateChangelog,n as generateCommitMessage,o as generatePullRequest,t as parseChangelogResult,r as parseCommitMessageResult,a as parseExplainChangesResult,s as parsePullRequestResult};
@@ -0,0 +1,15 @@
1
+ import { AiProviderConfig } from "../config.js";
2
+ import { AiTokenUsage } from "@gitkraken/shared-tools";
3
+
4
+ //#region src/generators/pull-request.d.ts
5
+ interface PullRequestResult {
6
+ title: string;
7
+ body: string;
8
+ usage?: AiTokenUsage;
9
+ }
10
+ /** Ports the XML parsing inline in Go's `GeneratePullRequestInfo`. */
11
+ declare function parsePullRequestResult(content: string): PullRequestResult;
12
+ /** Ports Go's `GeneratePullRequestInfo` (Go: `allowTruncated=false`). */
13
+ declare function generatePullRequest(config: AiProviderConfig, diff: string): Promise<PullRequestResult>;
14
+ //#endregion
15
+ export { PullRequestResult, generatePullRequest, parsePullRequestResult };
@@ -0,0 +1 @@
1
+ import{runPromptCompletion as e}from"../run-prompt-completion.js";import{parseXmlResult as t}from"../xml.js";function n(e){let{summary:n,body:r}=t(e);return{title:n.trim(),body:r.trim()}}async function r(t,r){let{text:i,usage:a}=await e(t,`generate-create-pullRequest`,{diff:r});return{...n(i),...a?{usage:a}:{}}}export{r as generatePullRequest,n as parsePullRequestResult};
@@ -0,0 +1,11 @@
1
+ import { AiProviderConfig, AiProviderId } from "./config.js";
2
+ import { AiFinishReason, AiToolCall, AiToolDefinition, Model, ModelGenerateParams, ModelGenerateResult, ToolAwareGenerateParams, ToolAwareMessage } from "./ports/model.js";
3
+ import { createModel } from "./create-model.js";
4
+ import { AiCreditsExhaustedDetails, AiError, AiErrorCode } from "./errors.js";
5
+ import { maskApiKey } from "./mask-api-key.js";
6
+ import { AiModel } from "./models.js";
7
+ import { getPromptTemplate } from "./prompts.js";
8
+ import { AiTaskInfo, ChatCompleteRequest, chatComplete, listModels } from "./providers/gitkraken.js";
9
+ import { PromptCompletionResult, RunPromptCompletionOptions, runPromptCompletion } from "./run-prompt-completion.js";
10
+ import { XmlResultContent, parseXmlResult } from "./xml.js";
11
+ export { type AiCreditsExhaustedDetails, AiError, type AiErrorCode, type AiFinishReason, type AiModel, type AiProviderConfig, type AiProviderId, type AiTaskInfo, type AiToolCall, type AiToolDefinition, type ChatCompleteRequest, type Model, type ModelGenerateParams, type ModelGenerateResult, type PromptCompletionResult, type RunPromptCompletionOptions, type ToolAwareGenerateParams, type ToolAwareMessage, type XmlResultContent, chatComplete, createModel, getPromptTemplate, listModels, maskApiKey, parseXmlResult, runPromptCompletion };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{AiError as e}from"./errors.js";import{chatComplete as t,listModels as n}from"./providers/gitkraken.js";import{createModel as r}from"./create-model.js";import{maskApiKey as i}from"./mask-api-key.js";import{getPromptTemplate as a}from"./prompts.js";import{runPromptCompletion as o}from"./run-prompt-completion.js";import{parseXmlResult as s}from"./xml.js";export{e as AiError,t as chatComplete,r as createModel,a as getPromptTemplate,n as listModels,i as maskApiKey,s as parseXmlResult,o as runPromptCompletion};
@@ -0,0 +1,8 @@
1
+ //#region src/mask-api-key.d.ts
2
+ /**
3
+ * Redacts an API key to its last four characters, e.g. `sk-secret1234` ->
4
+ * `****1234`. A direct port of Go's `internal/actions/ai.MaskAPIKey`.
5
+ */
6
+ declare function maskApiKey(key: string): string;
7
+ //#endregion
8
+ export { maskApiKey };
@@ -0,0 +1 @@
1
+ function e(e){return e.length<=4?`****`:`****${e.slice(-4)}`}export{e as maskApiKey};
@@ -0,0 +1,20 @@
1
+ //#region src/models.d.ts
2
+ /**
3
+ * One model the GitKraken AI backend offers for a task type. `modelId` is the
4
+ * `provider:model` string `createModel`'s `model` field accepts. Optional
5
+ * fields are copied from the backend's response and are absent when it omits
6
+ * them.
7
+ */
8
+ interface AiModel {
9
+ providerId: string;
10
+ providerName?: string;
11
+ modelId: string;
12
+ modelName?: string;
13
+ maxInputTokens?: number;
14
+ maxOutputTokens?: number;
15
+ preferred: boolean;
16
+ /** The backend's "average token credit consumption rate" display label (e.g. `"4.5x"`). */
17
+ consumptionRateLabel?: string;
18
+ }
19
+ //#endregion
20
+ export { AiModel };
package/dist/models.js ADDED
@@ -0,0 +1 @@
1
+ export{};
@@ -0,0 +1,50 @@
1
+ import { AiGenerateParams, AiTokenUsage, AiToolCall, AiToolCall as AiToolCall$1, AiToolDefinition, ToolAwareGenerateParams as ToolAwareGenerateParams$1, ToolAwareMessage } from "@gitkraken/shared-tools";
2
+
3
+ //#region src/ports/model.d.ts
4
+ /**
5
+ * shared-tools' canonical {@link SharedToolAwareGenerateParams} (which conflict-tools'
6
+ * `ConflictModelParams` aliases) plus `allowTruncatedOutput`, kept out of the shared shape so
7
+ * conflict-tools' port is untouched.
8
+ */
9
+ interface ToolAwareGenerateParams extends ToolAwareGenerateParams$1 {
10
+ /**
11
+ * When false (the default), a completion cut short by the provider's
12
+ * output-token limit throws `AiError('truncated_output', ...)` instead of
13
+ * returning the partial text. Only BYOK providers honor this: the
14
+ * `gitkraken` provider's chat/completions route always errors on truncation.
15
+ */
16
+ allowTruncatedOutput?: boolean;
17
+ }
18
+ /**
19
+ * The union `Model.generate` accepts. A function typed to accept this union is
20
+ * assignable to both `AiModelPort.generate` (which requires `AiGenerateParams`)
21
+ * and `ConflictModelPort.generate` (which requires `ConflictModelParams`, the
22
+ * an alias of shared-tools' `ToolAwareGenerateParams`), because each of those
23
+ * narrower types is itself assignable to this union. `model-port.type.test.ts`
24
+ * checks this assignability directly against the real conflict-tools types.
25
+ */
26
+ type ModelGenerateParams = AiGenerateParams | ToolAwareGenerateParams;
27
+ /** Provider-neutral reason a completion stopped. `rawFinishReason` carries the provider's exact value. */
28
+ type AiFinishReason = 'stop' | 'length' | 'tool-calls' | 'content-filter' | 'error' | 'other';
29
+ /**
30
+ * `text` is always a string (possibly empty, e.g. a tool-calls-only turn)
31
+ * because `AiModelPort.generate` requires it; the optional fields make this
32
+ * assignable to `ConflictModelPort`'s result shape too.
33
+ */
34
+ interface ModelGenerateResult {
35
+ text: string;
36
+ toolCalls?: AiToolCall$1[];
37
+ usage?: AiTokenUsage;
38
+ /** Provider-neutral reason the completion stopped. */
39
+ finishReason?: AiFinishReason;
40
+ /** The exact finish reason the provider reported, before normalization. */
41
+ rawFinishReason?: string;
42
+ /** The model id the provider's response reports, which may be more specific than the requested one. */
43
+ model?: string;
44
+ }
45
+ /** The port `createModel` returns. See {@link ModelGenerateParams}. */
46
+ interface Model {
47
+ generate(params: ModelGenerateParams): Promise<ModelGenerateResult>;
48
+ }
49
+ //#endregion
50
+ export { AiFinishReason, type AiToolCall, type AiToolDefinition, Model, ModelGenerateParams, ModelGenerateResult, ToolAwareGenerateParams, type ToolAwareMessage };
@@ -0,0 +1 @@
1
+ export{};
@@ -0,0 +1,14 @@
1
+ import { AiProviderConfig } from "./config.js";
2
+
3
+ //#region src/prompts.d.ts
4
+ /**
5
+ * Fetches `v1/ai-tasks/templates/message-prompt/:action` (`action` is the task route name, e.g. `generate-commitMessage`) and substitutes
6
+ * the first `${name}` occurrence for each name the template declares in `variables` (a variable
7
+ * with no matching entry in `variables` substitutes the empty string, and an
8
+ * entry with no matching `${name}` placeholder is dropped — both mirror Go's
9
+ * `strings.Replace(..., 1)` loop in `GetPrompt` (`internal/ai/prompts.go`): only the first occurrence of
10
+ * each placeholder is replaced, and the value is inserted literally).
11
+ */
12
+ declare function getPromptTemplate(config: AiProviderConfig, action: string, variables: Record<string, string>): Promise<string>;
13
+ //#endregion
14
+ export { getPromptTemplate };
@@ -0,0 +1 @@
1
+ import{AiError as e,toAiError as t,wireErrorFields as n}from"./errors.js";import{HttpError as r,Transport as i}from"@gitkraken/api-tools";const a=`https://api.gitkraken.dev/`;async function o(o,s,c){if(!o.getToken)throw new e(`not_logged_in`,"getPromptTemplate requires `getToken` to be configured");let l=o.provider===`gitkraken`?o.baseUrl??a:o.gitkrakenApiBaseUrl??a,u=new i({baseUrl:l.endsWith(`/`)?l:`${l}/`,getToken:o.getToken,request:o.request}),d;try{let t=(await u.request({method:`GET`,path:`v1/ai-tasks/templates/message-prompt/${s}`})).data;if(typeof t==`object`&&t&&!Array.isArray(t)&&`error`in t&&t.error!=null)throw new e(`provider_error`,`prompt template request returned an error response`,{...n(t)});if(typeof t!=`object`||!t||Array.isArray(t))throw new e(`provider_error`,`prompt template request returned an invalid response`);let r=t.template,i=t.variables;if(typeof r!=`string`||!Array.isArray(i)||!i.every(e=>typeof e==`string`))throw new e(`provider_error`,`prompt template request returned an invalid response`);d={template:r,variables:i}}catch(i){if(i instanceof r){let t={status:i.status,cause:i,...n(i.body)};throw i.status===401?new e(`not_logged_in`,`GitKraken rejected the prompt template request (HTTP 401): the GitKraken token is missing, expired or invalid.`,t):i.status===429?new e(`rate_limited`,`prompt template request was rate limited`,t):i.status>=400&&i.status<500?new e(`invalid_request`,`prompt template request was rejected: ${i.message}`,t):new e(`provider_error`,`failed to fetch the prompt template: ${i.message}`,t)}throw t(i,`failed to fetch the prompt template`)}let f=d.template??``;for(let e of d.variables??[])f=f.replace(`\${${e}}`,()=>c[e]??``);return f}export{o as getPromptTemplate};
@@ -0,0 +1,12 @@
1
+ import { AiProviderConfig } from "../config.js";
2
+ import { Model, ModelGenerateResult } from "../ports/model.js";
3
+
4
+ //#region src/providers/byok.d.ts
5
+ /** Creates the `Model` port for a BYOK provider (`anthropic`, `openai`, `openrouter`, `ollama`) via the Vercel AI SDK. */
6
+ declare function createByokModel(config: AiProviderConfig): Model;
7
+ /** BYOK task-style single-turn completion, used by `generateCommitMessage` and friends. There is no separate task route for BYOK providers: Go's `Complete` ignores `Kind` for them, so this is the same chat call `generate` makes. */
8
+ declare function byokComplete(config: AiProviderConfig, prompt: string, options?: {
9
+ allowTruncatedOutput?: boolean;
10
+ }): Promise<Omit<ModelGenerateResult, 'toolCalls'>>;
11
+ //#endregion
12
+ export { byokComplete, createByokModel };
@@ -0,0 +1 @@
1
+ import{AiError as e,isAbortError as t,isTruncatedFinishReason as n,truncatedOutputError as r,wireErrorFields as i}from"../errors.js";import{createAnthropic as a}from"@ai-sdk/anthropic";import{createOpenAI as o}from"@ai-sdk/openai";import{createOpenRouter as s}from"@openrouter/ai-sdk-provider";import{generateText as c,jsonSchema as l,stepCountIs as u}from"ai";import{createOllama as d}from"ollama-ai-provider-v2";const f={anthropic:`claude-sonnet-4-5`,openai:`gpt-5`,openrouter:`anthropic/claude-sonnet-4.5`,ollama:`llama3.1`};function p(e){return Object.hasOwn(f,e)}function m(t){let n=t.provider;if(!p(n))throw new e(`invalid_request`,`${n} is not a BYOK provider`);let r=t.fetch??globalThis.fetch,i=t.model||f[n];if(n!==`ollama`&&!t.apiKey)throw new e(`invalid_request`,`the ${n} provider requires an API key`);switch(n){case`anthropic`:return a({apiKey:t.apiKey,baseURL:t.baseUrl,fetch:r})(i);case`openai`:return o({apiKey:t.apiKey,baseURL:t.baseUrl,fetch:r}).chat(i);case`openrouter`:return s({apiKey:t.apiKey,baseURL:t.baseUrl,fetch:r,headers:{"HTTP-Referer":`https://www.gitkraken.com/`,"X-Title":t.client?.name??`GitKraken`}})(i);case`ollama`:return d({baseURL:`${(t.baseUrl??`http://localhost:11434`).replace(/\/$/,``)}/api`,fetch:r})(i)}}function h(e){return e.map(e=>{if(e.role===`tool`)return{role:`tool`,content:[{type:`tool-result`,toolCallId:e.toolCallId,toolName:e.toolName,output:{type:`text`,value:e.content}}]};let t=e.toolCalls??[];return e.role===`assistant`&&t.length>0?{role:`assistant`,content:[...e.content?[{type:`text`,text:e.content}]:[],...t.map(e=>({type:`tool-call`,toolCallId:e.id,toolName:e.name,input:e.args}))]}:{role:e.role,content:e.content??``}})}function g(e){if(!e||e.length===0)return;let t={};for(let n of e)t[n.name]={description:n.description,inputSchema:l(n.parameters)};return t}function _(n){if(n instanceof e||t(n))return n;let r=n instanceof Error?n.message:String(n),a=n?.statusCode,o={cause:n,...a===void 0?{}:{status:a},...i(n?.responseBody)};return a===401||a===403?new e(`not_logged_in`,`AI provider rejected the request: ${r}`,o):a===429?new e(`rate_limited`,`AI request was rate limited: ${r}`,o):a!=null&&a>=400&&a<500?new e(`invalid_request`,r,o):new e(`provider_error`,r,{...o,retryable:!0})}function v(e){let t=m(e);return{async generate(i){let a=i.messages,o=`tools`in i?i.tools:void 0,s=`allowTruncatedOutput`in i?!!i.allowTruncatedOutput:!1,l=i.maxTokens??(e.provider===`anthropic`?8192:void 0);try{let d=await c({model:t,system:i.system,messages:h(a),tools:g(o),stopWhen:u(1),maxRetries:0,maxOutputTokens:l,temperature:i.temperature,abortSignal:i.signal});if(!s&&n(d.finishReason))throw r();return e.telemetry?.event(`ai.generate`,{provider:e.provider}),{text:d.text,finishReason:d.finishReason,...d.rawFinishReason===void 0?{}:{rawFinishReason:d.rawFinishReason},...d.response?.modelId?{model:d.response.modelId}:{},...d.toolCalls.length>0?{toolCalls:d.toolCalls.map(e=>({id:e.toolCallId,name:e.toolName,args:e.input??{}}))}:{},...d.usage&&(d.usage.inputTokens||d.usage.outputTokens)?{usage:{inputTokens:d.usage.inputTokens??0,outputTokens:d.usage.outputTokens??0,...d.usage.inputTokenDetails?.cacheReadTokens?{cacheReadTokens:d.usage.inputTokenDetails.cacheReadTokens}:{},...d.usage.inputTokenDetails?.cacheWriteTokens?{cacheWriteTokens:d.usage.inputTokenDetails.cacheWriteTokens}:{},...d.usage.outputTokenDetails?.reasoningTokens?{reasoningTokens:d.usage.outputTokenDetails.reasoningTokens}:{}}}:{}}}catch(t){let n=_(t);throw e.telemetry?.exception(n,{provider:e.provider}),n}}}}const y=new WeakMap;async function b(e,t,n){let r=y.get(e);r||(r=v(e),y.set(e,r));let{toolCalls:i,...a}=await r.generate({messages:[{role:`user`,content:t}],allowTruncatedOutput:n?.allowTruncatedOutput});return a}export{b as byokComplete,v as createByokModel};
@@ -0,0 +1,21 @@
1
+ import { AiProviderConfig } from "../config.js";
2
+ import { AiCreditsExhaustedDetails, AiError } from "../errors.js";
3
+
4
+ //#region src/providers/gitkraken-errors.d.ts
5
+ /** Parses `body` as JSON and looks for an explicit credits-exhaustion code anywhere in the tree. */
6
+ declare function creditsExhaustedFromBody(body: string): AiCreditsExhaustedDetails | undefined;
7
+ /**
8
+ * Confirms exhaustion from `getCreditUsage` when an otherwise-unclassified
9
+ * quota-shaped response fails. Mirrors Go's `creditsExhaustedError` usage
10
+ * fallback: only 402/403/429 probe, and only `Limit>0 && Used>=Limit` counts
11
+ * as confirmed exhaustion.
12
+ */
13
+ declare function probeCreditsExhausted(status: number, config: AiProviderConfig): Promise<AiCreditsExhaustedDetails | undefined>;
14
+ /**
15
+ * Classifies a non-2xx response from a `gitkraken`-provider request into an
16
+ * `AiError`, checking for credits exhaustion first (explicit body code, then
17
+ * the usage fallback) before falling back to a generic status-based mapping.
18
+ */
19
+ declare function classifyGitKrakenHttpError(status: number, body: string, config: AiProviderConfig): Promise<AiError>;
20
+ //#endregion
21
+ export { classifyGitKrakenHttpError, creditsExhaustedFromBody, probeCreditsExhausted };
@@ -0,0 +1 @@
1
+ import{AiError as e,wireErrorFields as t}from"../errors.js";const n=new Set([`credits_exhausted`,`credit_exhausted`,`credit_limit_exceeded`,`ai_credits_exhausted`,`insufficient_credits`]);function r(e){return typeof e==`string`&&n.has(e.trim().toLowerCase())}function i(e){if(Array.isArray(e)){for(let t of e){let e=i(t);if(e)return e}return}if(typeof e==`object`&&e){let t=e,n=a(t);if(n)return n;for(let e of Object.values(t)){let t=i(e);if(t)return t}}}function a(e){if(!Object.entries(e).some(([e,t])=>[`code`,`errorcode`,`type`].includes(e.toLowerCase().replaceAll(`_`,``))&&r(t)))return;let t={};for(let[n,r]of Object.entries(e)){let e=n.toLowerCase().replaceAll(`_`,``);e===`used`&&o(r)?t.used=Number(r):e===`limit`&&o(r)?t.limit=Number(r):e===`organizationid`&&typeof r==`string`?t.organizationId=r:e===`resetson`&&typeof r==`string`&&!Number.isNaN(Date.parse(r))&&(t.resetsOn=new Date(r).toISOString())}return t}function o(e){return typeof e==`number`||typeof e==`string`&&e.trim()!==``&&!Number.isNaN(Number(e))}function s(e){try{return i(JSON.parse(e))}catch{return}}const c=new Set([402,403,429]);async function l(e,t){if(!c.has(e)||!t.getCreditUsage)return;let n=new AbortController,r=setTimeout(()=>n.abort(),1e4);try{let e=await t.getCreditUsage(n.signal);return!e||e.limit<=0||e.used<e.limit?void 0:{used:e.used,limit:e.limit,resetsOn:e.resetsOn,organizationId:e.organizationId}}catch{return}finally{clearTimeout(r)}}async function u(n,r,i){let a={status:n,...t(r)},o=s(r);if(o)return new e(`credits_exhausted`,`GitKraken AI credits exhausted`,{...a,details:o});let c=await l(n,i);return c?new e(`credits_exhausted`,`GitKraken AI credits exhausted`,{...a,details:c}):n===401?new e(`not_logged_in`,`GitKraken AI service rejected the request (HTTP 401): the GitKraken token is missing, expired or invalid.`,a):n===429?new e(`rate_limited`,`GitKraken AI service returned HTTP ${n}: ${d(r)}`,a):n>=400&&n<500?new e(`invalid_request`,`GitKraken AI service returned HTTP ${n}: ${d(r)}`,a):new e(`provider_error`,`GitKraken AI service returned HTTP ${n}: ${d(r)}`,{...a,retryable:!0})}function d(e,t=400){return e.length>t?`${e.slice(0,t)}…`:e}export{u as classifyGitKrakenHttpError,s as creditsExhaustedFromBody,l as probeCreditsExhausted};
@@ -0,0 +1,29 @@
1
+ import { AiFinishReason } from "../ports/model.js";
2
+
3
+ //#region src/providers/gitkraken-stream.d.ts
4
+ interface AggregatedChatResult {
5
+ model?: string;
6
+ content: string;
7
+ toolCalls: Array<{
8
+ id: string;
9
+ name: string;
10
+ args: Record<string, unknown>;
11
+ }>;
12
+ finishReason: string;
13
+ promptTokens: number;
14
+ completionTokens: number;
15
+ totalTokens: number;
16
+ gk?: {
17
+ used?: number;
18
+ limit?: number;
19
+ resetsOn?: string;
20
+ };
21
+ }
22
+ declare function normalizeFinishReason(raw: string): AiFinishReason;
23
+ /**
24
+ * Parses an OpenAI-compatible SSE body (or a non-SSE JSON fallback body) into
25
+ * one aggregated result. A direct port of Go's `parseOpenAICompatibleStream`.
26
+ */
27
+ declare function parseOpenAICompatibleStream(body: string): AggregatedChatResult;
28
+ //#endregion
29
+ export { AggregatedChatResult, normalizeFinishReason, parseOpenAICompatibleStream };
@@ -0,0 +1,3 @@
1
+ import{AiError as e,isTruncatedFinishReason as t,truncatedOutputError as n}from"../errors.js";import{decodeToolArguments as r}from"./gitkraken-wire.js";const i=new Set([`credits_exhausted`,`credit_exhausted`,`credit_limit_exceeded`,`ai_credits_exhausted`,`insufficient_credits`]);function a(e){switch(e){case`stop`:case`end_turn`:case`stop_sequence`:return`stop`;case`length`:case`max_tokens`:return`length`;case`tool_calls`:case`tool_use`:case`function_call`:return`tool-calls`;case`content_filter`:return`content-filter`;case`error`:return`error`;default:return`other`}}function o(t){let n=(t.code??``).trim().toLowerCase(),r=(t.type??``).trim().toLowerCase(),a={...t.code?{providerErrorCode:t.code}:{},...t.type?{providerErrorType:t.type}:{}};if(i.has(n)||i.has(r))return new e(`credits_exhausted`,`GitKraken AI credits exhausted`,a);let o=t.message||`the AI service stream ended unexpectedly`;return r===`rate_limit_error`?new e(`rate_limited`,`AI request was rate limited: ${o}. Wait a moment and retry.`,a):r===`invalid_request_error`?new e(`invalid_request`,`AI request was rejected: ${o}`,a):new e(`provider_error`,`AI request failed: ${o}. Retry the request.`,a)}function s(t){try{return JSON.parse(t)}catch(t){throw new e(`provider_error`,`AI service returned a malformed response`,{cause:t})}}function c(i){let a=new Map,c=``,m=``,h=``,g=!1,_=!1,v=!1,y,b=0,x=0,S=0,C;for(let e of i.split(`
2
+ `)){let t=e.trim();if(t===``||t.startsWith(`:`))continue;if(!t.startsWith(`data:`)){m+=(m.length>0?`
3
+ `:``)+t;continue}g=!0;let n=t.slice(5).trim();if(n===``)continue;if(n===`[DONE]`){_=!0;break}let r=s(n);if(r.error?.message||r.error?.code||r.error?.type)throw o(r.error);r.model&&(y=r.model),r.usage&&((r.usage.prompt_tokens||r.usage.completion_tokens||r.usage.total_tokens)&&(b=r.usage.prompt_tokens??0,x=r.usage.completion_tokens??0,S=r.usage.total_tokens??0),r.usage.gk?.limit&&(C={used:r.usage.gk.used,limit:r.usage.gk.limit,resetsOn:r.usage.gk.resetsOn}));for(let e of r.choices??[])if((e.index??0)===0){v=!0,e.delta?.content!=null&&(c+=e.delta.content);for(let t of u(e)){let e=a.get(t.index)??{id:``,name:``,args:``};t.id&&(e.id=t.id),e.name+=t.name??``,e.args+=t.args??``,a.set(t.index,e)}e.finish_reason&&(h=e.finish_reason)}}if(!g&&m.length>0){let e=s(m);if(!f(e))throw p();if(e.error!=null){let t=f(e.error)?e.error:{};throw o({...typeof t.message==`string`?{message:t.message}:{},...typeof t.type==`string`?{type:t.type}:{},...typeof t.code==`string`?{code:t.code}:{}})}return l(d(e))}if(!v)throw new e(`provider_error`,`AI service returned no streamed choices`);if(t(h))throw n();if(h===``)throw _?new e(`provider_error`,`AI service returned an incomplete streamed chat completion (the stream ended without a finish reason). Retry the request.`):new e(`provider_error`,`AI service stream ended before the completion marker; response may be incomplete. Retry the request.`);if(c===``&&a.size===0)throw new e(`provider_error`,`AI service returned an empty streamed chat completion. Retry the request.`);let w=[...a.entries()].sort(([e],[t])=>e-t).map(([,e])=>({id:e.id,name:e.name,args:r(e.args)}));return{model:y,content:c,toolCalls:w,finishReason:h,promptTokens:b,completionTokens:x,totalTokens:S,gk:C}}function l(r){if(t(r.finishReason))throw n();if(r.finishReason===``)throw new e(`provider_error`,`AI service returned an incomplete chat completion without a finish reason. Retry the request.`);if(r.content===``&&r.toolCalls.length===0)throw new e(`provider_error`,`AI service returned an empty chat completion. Retry the request.`);return r}function u(e){let t=e.delta?.tool_calls;return t?t.map((e,t)=>({index:e.index??t,id:e.id,name:e.function?.name,args:e.function?.arguments})):[]}function d(e){let t=e.choices?.[0],n=Array.isArray(t?.message?.tool_calls)&&t.message.tool_calls.length>0;if(!t||typeof t.finish_reason!=`string`||t.finish_reason===``||!t.message||t.message.content!==void 0&&t.message.content!==null&&typeof t.message.content!=`string`||t.message.content==null&&!n||t.message.tool_calls!==void 0&&(!Array.isArray(t.message.tool_calls)||t.message.tool_calls.some(e=>!e||typeof e!=`object`||!e.function||typeof e.function.name!=`string`||e.function.arguments!==void 0&&typeof e.function.arguments!=`string`)))throw p();let i=(t?.message?.tool_calls??[]).map(e=>({id:e.id??``,name:e.function?.name??``,args:r(e.function?.arguments)}));return{model:e.model,content:t?.message?.content??``,toolCalls:i,finishReason:t?.finish_reason??``,promptTokens:e.usage?.prompt_tokens??0,completionTokens:e.usage?.completion_tokens??0,totalTokens:e.usage?.total_tokens??0,gk:e.usage?.gk?{used:e.usage.gk.used,limit:e.usage.gk.limit,resetsOn:e.usage.gk.resetsOn}:void 0}}function f(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function p(){return new e(`provider_error`,`AI service returned an invalid non-streamed chat completion`)}export{a as normalizeFinishReason,c as parseOpenAICompatibleStream};
@@ -0,0 +1,156 @@
1
+ import { AiToolDefinition, ToolAwareMessage } from "../ports/model.js";
2
+
3
+ //#region src/providers/gitkraken-wire.d.ts
4
+ /** `v1/ai-tasks/chat/completions` request body. Mirrors Go's `request.ChatCompletionRequest`. */
5
+ interface ChatCompletionWireRequest {
6
+ model?: string;
7
+ messages: ChatCompletionWireMessage[];
8
+ max_tokens?: number;
9
+ stream: true;
10
+ stream_options: {
11
+ include_usage: true;
12
+ };
13
+ temperature: number;
14
+ tools?: ChatCompletionWireTool[];
15
+ }
16
+ interface ChatCompletionWireMessage {
17
+ role: string;
18
+ content?: string;
19
+ tool_call_id?: string;
20
+ tool_calls?: ChatCompletionWireToolCall[];
21
+ }
22
+ interface ChatCompletionWireTool {
23
+ type: 'function';
24
+ function: {
25
+ name: string;
26
+ description?: string;
27
+ parameters?: Record<string, unknown>;
28
+ };
29
+ }
30
+ interface ChatCompletionWireToolCall {
31
+ id?: string;
32
+ type?: 'function';
33
+ function: {
34
+ name?: string;
35
+ arguments?: string;
36
+ };
37
+ }
38
+ interface GkCreditUsageWire {
39
+ resetsOn?: string;
40
+ used?: number;
41
+ limit?: number;
42
+ }
43
+ interface ChatCompletionWireResponse {
44
+ error?: {
45
+ message?: string;
46
+ type?: string;
47
+ code?: string;
48
+ };
49
+ id?: string;
50
+ object?: string;
51
+ created?: number;
52
+ model?: string;
53
+ choices?: Array<{
54
+ index?: number;
55
+ message?: {
56
+ role?: string;
57
+ content?: string | null;
58
+ tool_calls?: ChatCompletionWireToolCall[];
59
+ };
60
+ finish_reason?: string;
61
+ }>;
62
+ usage?: {
63
+ model?: string;
64
+ gk?: GkCreditUsageWire;
65
+ prompt_tokens?: number;
66
+ completion_tokens?: number;
67
+ total_tokens?: number;
68
+ };
69
+ }
70
+ /** `v1/ai-tasks` (task route) request body. Mirrors Go's `request.ChatAITasksRequest`. */
71
+ interface TaskWireRequest {
72
+ taskType: 'message-prompt';
73
+ action: string;
74
+ messages: [{
75
+ content: string;
76
+ }];
77
+ model?: string;
78
+ }
79
+ interface TaskWireResponse {
80
+ error?: {
81
+ message?: string;
82
+ code?: string;
83
+ };
84
+ data?: {
85
+ id?: string;
86
+ status?: string;
87
+ provider?: string;
88
+ result?: {
89
+ content?: string;
90
+ role?: string;
91
+ };
92
+ usage?: {
93
+ gk?: GkCreditUsageWire;
94
+ modelId?: string;
95
+ promptTokens?: number;
96
+ completionTokens?: number;
97
+ totalTokens?: number;
98
+ };
99
+ };
100
+ }
101
+ /** One `data:` line of the SSE stream. Mirrors Go's `response.ChatCompletionStreamResponse`. */
102
+ interface ChatCompletionStreamChunk {
103
+ error?: {
104
+ message?: string;
105
+ type?: string;
106
+ code?: string;
107
+ };
108
+ id?: string;
109
+ object?: string;
110
+ created?: number;
111
+ model?: string;
112
+ choices?: Array<{
113
+ index?: number;
114
+ delta?: {
115
+ role?: string;
116
+ content?: string | null;
117
+ tool_calls?: Array<ChatCompletionWireToolCall & {
118
+ index?: number;
119
+ }>;
120
+ };
121
+ finish_reason?: string;
122
+ }>;
123
+ usage?: {
124
+ model?: string;
125
+ gk?: GkCreditUsageWire;
126
+ prompt_tokens?: number;
127
+ completion_tokens?: number;
128
+ total_tokens?: number;
129
+ };
130
+ }
131
+ interface ModelsWireResponse {
132
+ data?: Array<{
133
+ providerId?: string;
134
+ providerName?: string;
135
+ modelId?: string;
136
+ modelName?: string;
137
+ maxInputTokens?: number;
138
+ maxOutputTokens?: number;
139
+ preferred?: boolean;
140
+ consumptionRateLabel?: string;
141
+ }>;
142
+ }
143
+ /**
144
+ * Renders one wire message's `content`, omitting it (rather than sending
145
+ * `""`) for an assistant turn that carries only tool calls. Mirrors Go's
146
+ * `providers.WireContent`: some gateways translate `"content": ""` into an
147
+ * empty text block that the upstream rejects.
148
+ */
149
+ declare function wireContent(content: string | undefined, hasToolCalls: boolean): string | undefined;
150
+ declare function encodeToolArguments(args: Record<string, unknown> | undefined): string;
151
+ declare function decodeToolArguments(raw: string | undefined): Record<string, unknown>;
152
+ /** Maps port-level messages to the GitKraken chat/completions wire shape. */
153
+ declare function toWireMessages(messages: ToolAwareMessage[]): ChatCompletionWireMessage[];
154
+ declare function toWireTools(tools: AiToolDefinition[] | undefined): ChatCompletionWireTool[] | undefined;
155
+ //#endregion
156
+ export { ChatCompletionStreamChunk, ChatCompletionWireMessage, ChatCompletionWireRequest, ChatCompletionWireResponse, ChatCompletionWireTool, ChatCompletionWireToolCall, GkCreditUsageWire, ModelsWireResponse, TaskWireRequest, TaskWireResponse, decodeToolArguments, encodeToolArguments, toWireMessages, toWireTools, wireContent };
@@ -0,0 +1 @@
1
+ function e(e,t){if(!((e??``)===``&&t))return e??``}function t(e){return!e||Object.keys(e).length===0?`{}`:JSON.stringify(e)}function n(e){if(!e)return{};let t=JSON.parse(e);return typeof t!=`object`||!t?{}:t}function r(n){return n.map(n=>{if(n.role===`tool`)return{role:`tool`,content:n.content,tool_call_id:n.toolCallId};let r=n.role===`assistant`?n.toolCalls??[]:[],i={role:n.role,content:e(n.content,r.length>0)};return r.length>0&&(i.tool_calls=r.map(e=>({id:e.id,type:`function`,function:{name:e.name,arguments:t(e.args)}}))),i})}function i(e){if(!(!e||e.length===0))return e.map(e=>({type:`function`,function:{name:e.name,description:e.description,parameters:e.parameters}}))}export{n as decodeToolArguments,t as encodeToolArguments,r as toWireMessages,i as toWireTools,e as wireContent};
@@ -0,0 +1,48 @@
1
+ import { AiProviderConfig } from "../config.js";
2
+ import { AiToolDefinition, Model, ModelGenerateResult, ToolAwareMessage } from "../ports/model.js";
3
+ import { AiModel } from "../models.js";
4
+
5
+ //#region src/providers/gitkraken.d.ts
6
+ /** The hosted task record behind a `gitkraken` task-route completion, with raw API values. */
7
+ interface AiTaskInfo {
8
+ id: string;
9
+ /** e.g. `completed`. */
10
+ status?: string;
11
+ /** The backend that served the task, e.g. `gemini`. */
12
+ provider?: string;
13
+ }
14
+ interface ChatCompleteRequest {
15
+ /** Sent as the `GK-Action` header. Defaults to `'compose-changes'`. */
16
+ action?: string;
17
+ model?: string;
18
+ /** Sent as the `GK-Conversation-ID` header. */
19
+ conversationId?: string;
20
+ system?: string;
21
+ messages: ToolAwareMessage[];
22
+ tools?: AiToolDefinition[];
23
+ maxTokens?: number;
24
+ temperature?: number;
25
+ signal?: AbortSignal;
26
+ }
27
+ /** POSTs `v1/ai-tasks/chat/completions` (streamed) and aggregates the SSE response. */
28
+ declare function chatComplete(config: AiProviderConfig, request: ChatCompleteRequest): Promise<ModelGenerateResult>;
29
+ /** Creates the `Model` port for `provider: 'gitkraken'`. `generate` always uses the chat/completions route (KindChat in Go). */
30
+ declare function createGitKrakenModel(config: AiProviderConfig): Model;
31
+ /**
32
+ * POSTs `v1/ai-tasks` (the non-streamed task route) with a single rendered
33
+ * prompt and returns the raw completion content and the token usage the route
34
+ * reports. Used by `generateCommitMessage` and friends, which parse the
35
+ * XML-wrapped content themselves.
36
+ */
37
+ declare function taskComplete(config: AiProviderConfig, options: {
38
+ action: string;
39
+ prompt: string;
40
+ model?: string;
41
+ signal?: AbortSignal;
42
+ }): Promise<Pick<ModelGenerateResult, 'text' | 'usage' | 'model'> & {
43
+ task?: AiTaskInfo;
44
+ }>;
45
+ /** GETs `v1/ai-tasks/providers/:taskType`. Defaults `taskType` to `message-prompt`. */
46
+ declare function listModels(config: AiProviderConfig, taskType?: string): Promise<AiModel[]>;
47
+ //#endregion
48
+ export { AiTaskInfo, ChatCompleteRequest, chatComplete, createGitKrakenModel, listModels, taskComplete };
@@ -0,0 +1 @@
1
+ import{AiError as e,isAbortError as t,toAiError as n,wireErrorFields as r}from"../errors.js";import{classifyGitKrakenHttpError as i}from"./gitkraken-errors.js";import{toWireMessages as a,toWireTools as o,wireContent as s}from"./gitkraken-wire.js";import{normalizeFinishReason as c,parseOpenAICompatibleStream as l}from"./gitkraken-stream.js";import{defaultRequest as u}from"@gitkraken/api-tools";const d=`message-prompt`;function f(e,t=``){let n=e.baseUrl??`https://api.gitkraken.dev/`,r=n.endsWith(`/`)?n:`${n}/`;return new URL(`v1/ai-tasks${t}`,r).toString()}async function p(n){if(!n.getToken)throw new e(`not_logged_in`,"the gitkraken provider requires `getToken` to be configured");try{return await n.getToken()}catch(n){throw t(n)?n:new e(`not_logged_in`,`failed to obtain a GitKraken token`,{cause:n})}}async function m(e,t){let n={Authorization:`Bearer ${await p(e)}`};return e.organizationId&&(n[`GK-Org-Id`]=e.organizationId),t&&(n[`GK-Action`]=t),e.client&&(n[`Client-Name`]=e.client.name,n[`Client-Version`]=e.client.version),n}function h(e){return typeof e==`string`?e:e==null?``:JSON.stringify(e)}function g(t){if(typeof t==`string`){if(t.length===0)return{};try{return JSON.parse(t)}catch(t){throw new e(`provider_error`,`AI service returned a malformed response`,{cause:t})}}return t??{}}function _(e,t){!t||!e.onCreditUsage||t.limit==null||t.limit<=0||t.used==null||e.onCreditUsage({used:t.used,limit:t.limit,resetsOn:t.resetsOn&&!Number.isNaN(Date.parse(t.resetsOn))?new Date(t.resetsOn).toISOString():t.resetsOn,organizationId:e.organizationId})}async function v(e,t){try{let n=a(t.messages);t.system&&n.unshift({role:`system`,content:s(t.system,!1)});let r={model:t.model||void 0,messages:n,max_tokens:t.maxTokens||void 0,stream:!0,stream_options:{include_usage:!0},temperature:t.temperature??0,tools:o(t.tools)},u=await m(e,t.action||`compose-changes`);u[`Content-Type`]=`application/json`,u.Accept=`text/event-stream`,t.conversationId&&(u[`GK-Conversation-ID`]=t.conversationId);let d=await(e.fetch??globalThis.fetch)(f(e,`/chat/completions`),{method:`POST`,redirect:`manual`,headers:u,body:JSON.stringify(r),signal:t.signal}),p=await d.text();if(!d.ok)throw await i(d.status,p,e);let h=l(p);return _(e,h.gk),e.telemetry?.event(`ai.generate`,{provider:`gitkraken`,kind:`chat`}),{text:h.content,toolCalls:h.toolCalls.length>0?h.toolCalls:void 0,usage:h.promptTokens||h.completionTokens||h.totalTokens?{inputTokens:h.promptTokens,outputTokens:h.completionTokens}:void 0,finishReason:c(h.finishReason),rawFinishReason:h.finishReason,...h.model?{model:h.model}:{}}}catch(t){throw e.telemetry?.exception(t,{provider:`gitkraken`,kind:`chat`}),n(t,`AI request failed`)}}function y(e){return{async generate(t){let n=t.messages,r=`tools`in t?t.tools:void 0;return v(e,{model:e.model,system:t.system,messages:n,tools:r,maxTokens:t.maxTokens,temperature:t.temperature,signal:t.signal})}}}async function b(t,a){try{let n=t.request??u,o={taskType:d,action:a.action,messages:[{content:a.prompt}],model:(a.model??t.model)||void 0},s=await n({method:`POST`,url:f(t),headers:{...await m(t,a.action),"Content-Type":`application/json`},body:o,signal:a.signal});if(!(s.status>=200&&s.status<300))throw await i(s.status,h(s.body),t);let c=g(s.body);if(!S(c))throw C(`task`);let l=c.error;if(l!=null){let t=r(c);throw new e(`provider_error`,`AI request failed: ${S(l)&&typeof l.message==`string`?l.message:`unknown provider error`}`,t)}let p=c.data;if(!S(p))throw C(`task`);if(p.status===`error`||p.status===`failed`)throw new e(`provider_error`,`AI task failed: ${String(p.status)}`,{...r(p),...typeof p.code==`string`?{providerErrorCode:p.code}:{},...typeof p.type==`string`?{providerErrorType:p.type}:{}});if(!S(p.result)||typeof p.result.content!=`string`)throw C(`task`);let v=p.result.content,y=p,b=y.usage;return _(t,b?.gk),t.telemetry?.event(`ai.generate`,{provider:`gitkraken`,kind:`task`}),{text:v,...b?.modelId?{model:b.modelId}:{},...y.id?{task:{id:y.id,...y.status?{status:y.status}:{},...y.provider?{provider:y.provider}:{}}}:{},...b?.promptTokens||b?.completionTokens?{usage:{inputTokens:b.promptTokens??0,outputTokens:b.completionTokens??0}}:{}}}catch(e){throw t.telemetry?.exception(e,{provider:`gitkraken`,kind:`task`}),n(e,`AI request failed`)}}async function x(t,a){let o=t.request??u,s;try{let e=await o({method:`GET`,url:f(t,`/providers/${a||d}`),headers:await m(t,void 0)});if(!(e.status>=200&&e.status<300))throw await i(e.status,h(e.body),t);s=g(e.body)}catch(e){throw n(e,`failed to list AI models`)}if(!S(s))throw C(`model list`);if(s.error!=null)throw new e(`provider_error`,`AI model list request returned an error response`,r(s));if(!Array.isArray(s.data))throw C(`model list`);return s.data.map(e=>{if(!S(e)||typeof e.providerId!=`string`||!e.providerId||typeof e.modelId!=`string`||!e.modelId||e.providerName!==void 0&&typeof e.providerName!=`string`||e.modelName!==void 0&&typeof e.modelName!=`string`||e.consumptionRateLabel!==void 0&&typeof e.consumptionRateLabel!=`string`||e.maxInputTokens!==void 0&&typeof e.maxInputTokens!=`number`||e.maxOutputTokens!==void 0&&typeof e.maxOutputTokens!=`number`||e.preferred!==void 0&&typeof e.preferred!=`boolean`)throw C(`model list`);let t=e;return{providerId:t.providerId,providerName:t.providerName,modelId:t.modelId,modelName:t.modelName,maxInputTokens:t.maxInputTokens,maxOutputTokens:t.maxOutputTokens,preferred:t.preferred??!1,consumptionRateLabel:t.consumptionRateLabel}})}function S(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function C(t){return new e(`provider_error`,`AI service returned an invalid ${t} response`)}export{v as chatComplete,y as createGitKrakenModel,x as listModels,b as taskComplete};
@@ -0,0 +1,30 @@
1
+ import { AiProviderConfig } from "./config.js";
2
+ import { ModelGenerateResult } from "./ports/model.js";
3
+ import { AiTaskInfo } from "./providers/gitkraken.js";
4
+
5
+ //#region src/run-prompt-completion.d.ts
6
+ interface RunPromptCompletionOptions {
7
+ /** Return a clipped completion instead of throwing `truncated_output`. Only honoured by BYOK providers; defaults to `false`. */
8
+ allowTruncatedOutput?: boolean;
9
+ }
10
+ /**
11
+ * The completion text plus whatever the provider reported alongside it. The
12
+ * hosted task route reports the model id, token usage and its task record;
13
+ * BYOK providers report the finish reason, model id and token usage.
14
+ */
15
+ type PromptCompletionResult = Pick<ModelGenerateResult, 'text' | 'usage' | 'finishReason' | 'rawFinishReason' | 'model'> & {
16
+ /** `gitkraken` only. */task?: AiTaskInfo;
17
+ };
18
+ /**
19
+ * Runs a single-turn task prompt: fetches and substitutes the server-side
20
+ * template for `action` (the `GK-Action`/template route name, e.g.
21
+ * `generate-commitMessage`), then completes it. For `gitkraken` this hits the
22
+ * non-streamed task route (`v1/ai-tasks`, `GK-Action` header), whose
23
+ * response carries no finish reason, so truncation is undetectable there. For
24
+ * a BYOK provider there is no separate task route, so it is the same
25
+ * single-message call `generate` makes, and `allowTruncatedOutput` applies
26
+ * exactly as it does there. Ported from gkcli's `aiService.runPromptCompletion`.
27
+ */
28
+ declare function runPromptCompletion(config: AiProviderConfig, action: string, variables: Record<string, string>, options?: RunPromptCompletionOptions): Promise<PromptCompletionResult>;
29
+ //#endregion
30
+ export { PromptCompletionResult, RunPromptCompletionOptions, runPromptCompletion };
@@ -0,0 +1 @@
1
+ import{byokComplete as e}from"./providers/byok.js";import{taskComplete as t}from"./providers/gitkraken.js";import{getPromptTemplate as n}from"./prompts.js";async function r(r,i,a,o){let s=await n(r,i,a);return r.provider===`gitkraken`?t(r,{action:i,prompt:s}):e(r,s,{allowTruncatedOutput:o?.allowTruncatedOutput??!1})}export{r as runPromptCompletion};
package/dist/xml.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ //#region src/xml.d.ts
2
+ /**
3
+ * A direct port of Go's `toJSON` + `response.XMLResultContent`: reads the
4
+ * `<summary>`/`<body>` elements out of a prompt completion that is XML
5
+ * fragments, not a full document (Go wraps it in a synthetic `<Content>`
6
+ * root before unmarshalling).
7
+ */
8
+ interface XmlResultContent {
9
+ summary: string;
10
+ body: string;
11
+ }
12
+ declare function parseXmlResult(content: string): XmlResultContent;
13
+ //#endregion
14
+ export { XmlResultContent, parseXmlResult };
package/dist/xml.js ADDED
@@ -0,0 +1 @@
1
+ const e={lt:`<`,gt:`>`,amp:`&`,quot:`"`,apos:`'`};function t(t){return t.replace(/&(lt|gt|amp|quot|apos|#\d+|#x[0-9a-fA-F]+);/g,(t,r)=>r in e?e[r]:r.startsWith(`#x`)?n(Number.parseInt(r.slice(2),16),t):r.startsWith(`#`)?n(Number.parseInt(r.slice(1),10),t):t)}function n(e,t){return e<=1114111?String.fromCodePoint(e):t}function r(e,n){let r=e.match(/<!--[\s\S]*?-->|<!\[CDATA\[[\s\S]*?\]\]>|<[^>]*>|[^<]+|</g)??[],i=RegExp(`^<\\/?${n}(?:\\s[^>]*)?\\s*/?>$`),a=0,o=!1,s=``;for(let e of r)if(!e.startsWith(`<!--`)){if(e.startsWith(`<![CDATA[`)){o&&(s+=e.slice(9,-3));continue}if(e.startsWith(`<`)){if(!i.test(e)){o&&a>0&&(s+=e);continue}let t=e.startsWith(`</`),n=e.endsWith(`/>`);if(!o){!t&&!n&&(o=!0,a=1);continue}if(t){if(--a,a===0)return s;s+=e}else n||(a+=1),s+=e;continue}o&&a>0&&(s+=t(e))}return``}function i(e){return{summary:r(e,`summary`),body:r(e,`body`)}}export{i as parseXmlResult};
package/package.json CHANGED
@@ -1,6 +1,47 @@
1
1
  {
2
2
  "name": "@gitkraken/ai-provider-tools",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.1",
4
+ "description": "AI text generation for GitKraken client SDKs: GitKraken-hosted and BYOK (Anthropic, OpenAI, OpenRouter, Ollama) providers behind one model port.",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "author": "Axosoft, LLC dba GitKraken",
7
+ "engines": {
8
+ "node": ">= 22"
9
+ },
10
+ "type": "module",
11
+ "sideEffects": false,
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "./generators": {
18
+ "types": "./dist/generators/index.d.ts",
19
+ "default": "./dist/generators/index.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "CHANGELOG.md"
25
+ ],
26
+ "dependencies": {
27
+ "@ai-sdk/anthropic": "^3.0.64",
28
+ "@ai-sdk/openai": "^3.0.48",
29
+ "@openrouter/ai-sdk-provider": "^2.3.3",
30
+ "ai": "^6.0.138",
31
+ "ollama-ai-provider-v2": "^3.5.0",
32
+ "@gitkraken/api-tools": "^0.3.0",
33
+ "@gitkraken/shared-tools": "^0.3.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^22.0.0",
37
+ "typescript": "^6.0.2",
38
+ "vitest": "^4.1.1",
39
+ "@gitkraken/conflict-tools": "0.6.0"
40
+ },
41
+ "scripts": {
42
+ "build": "tsdown --config-loader=unrun",
43
+ "build:dev": "tsdown --config tsdown.dev.config.ts --config-loader=unrun",
44
+ "typecheck": "tsc --noEmit",
45
+ "test": "vitest run"
46
+ }
6
47
  }