@andershaf/testopencode 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # @andershaf/testopencode
2
+
3
+ OpenCode custom provider for **CDF project AI** (`/api/v1/projects/{project}/ai/chat/completions`).
4
+
5
+ - Strips fields CDF rejects (`max_tokens`, `tool_choice`, `stream_options`, tool `parameters.$schema`, …).
6
+ - Normalizes **CDF camelCase** responses to **OpenAI-style snake_case** for the AI SDK.
7
+
8
+ Derived from [cognitedata/atlascode PR #1](https://github.com/cognitedata/atlascode/pull/1).
9
+
10
+ ## OpenCode `opencode.json`
11
+
12
+ ```json
13
+ {
14
+ "provider": {
15
+ "cdf": {
16
+ "name": "CDF",
17
+ "npm": "@andershaf/testopencode",
18
+ "models": {
19
+ "azure/gpt-5.1": {
20
+ "name": "GPT-5.1",
21
+ "id": "azure/gpt-5.1",
22
+ "tool_call": true
23
+ }
24
+ },
25
+ "options": {
26
+ "cluster": "api",
27
+ "project": "your-project",
28
+ "token": "{env:CDF_TOKEN}",
29
+ "headers": {
30
+ "cdf-version": "20230101-alpha"
31
+ }
32
+ }
33
+ }
34
+ },
35
+ "model": "cdf/azure/gpt-5.1"
36
+ }
37
+ ```
38
+
39
+ ## Publish
40
+
41
+ ```bash
42
+ cd packages/testopencode
43
+ npm install
44
+ npm run build
45
+ npm publish --access public
46
+ ```
47
+
48
+ Use an npm account with access to the `@andershaf` scope.
49
+
50
+ ## Develop
51
+
52
+ ```bash
53
+ npm run build
54
+ ```
@@ -0,0 +1,8 @@
1
+ import type { CdfProviderOptions } from "./types.js";
2
+ export declare function sanitizeRequestBody(body: Record<string, any>): {
3
+ [x: string]: any;
4
+ };
5
+ export declare function normalizeCdfPayload(value: any): any;
6
+ export declare function createCdfProvider(options: CdfProviderOptions): import("@ai-sdk/openai-compatible").OpenAICompatibleProvider<string, string, string, string>;
7
+ export default createCdfProvider;
8
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAsCpD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;;EAsC5D;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,GAAG,GAAG,GAAG,CAenD;AAgGD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,gGAoC5D;AAED,eAAe,iBAAiB,CAAA"}
@@ -0,0 +1,186 @@
1
+ /**
2
+ * CDF Atlas-style OpenCode provider: sanitize outbound bodies for strict CDF AI,
3
+ * normalize camelCase responses for OpenCode / AI SDK.
4
+ *
5
+ * Based on cognitedata/atlascode (PR #1) + cdf-version header merge.
6
+ * @see https://github.com/cognitedata/atlascode/pull/1
7
+ */
8
+ import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
9
+ const CDF_TO_OPENAI_KEY_MAP = {
10
+ acceptedPredictionTokens: "accepted_prediction_tokens",
11
+ cachedTokens: "cached_tokens",
12
+ completionTokens: "completion_tokens",
13
+ completionTokensDetails: "completion_tokens_details",
14
+ extraContent: "extra_content",
15
+ finishReason: "finish_reason",
16
+ promptTokens: "prompt_tokens",
17
+ promptTokensDetails: "prompt_tokens_details",
18
+ reasoningContent: "reasoning_content",
19
+ reasoningTokens: "reasoning_tokens",
20
+ rejectedPredictionTokens: "rejected_prediction_tokens",
21
+ thoughtSignature: "thought_signature",
22
+ toolCalls: "tool_calls",
23
+ totalTokens: "total_tokens",
24
+ };
25
+ const CDF_MISSING_TOKEN_MESSAGE = "CDF provider is missing an access token. Set CDF_TOKEN in your environment and restart OpenCode, or configure provider.options.token / an Authorization: Bearer ... header for the cdf provider.";
26
+ function stripUnsupportedSchemaFields(value) {
27
+ if (Array.isArray(value)) {
28
+ return value.map(stripUnsupportedSchemaFields);
29
+ }
30
+ if (value && typeof value === "object") {
31
+ const entries = Object.entries(value)
32
+ .filter(([key]) => key !== "$schema" && key !== "ref")
33
+ .map(([key, nested]) => [key, stripUnsupportedSchemaFields(nested)]);
34
+ return Object.fromEntries(entries);
35
+ }
36
+ return value;
37
+ }
38
+ export function sanitizeRequestBody(body) {
39
+ const { max_tokens, stream_options, tool_choice, reasoningSummary, reasoning_effort, top_p, verbosity, ...rest } = body;
40
+ if (rest.tools) {
41
+ rest.tools = rest.tools.map((tool) => {
42
+ if (!tool.function?.parameters)
43
+ return tool;
44
+ return {
45
+ ...tool,
46
+ function: {
47
+ ...tool.function,
48
+ parameters: stripUnsupportedSchemaFields(tool.function.parameters),
49
+ },
50
+ };
51
+ });
52
+ }
53
+ if (rest.messages) {
54
+ rest.messages = rest.messages.map(({ cache_control, reasoning_content, ...msg }) => {
55
+ if (Array.isArray(msg.content)) {
56
+ msg.content = msg.content.map(({ cache_control: _, ...part }) => part);
57
+ }
58
+ if (Array.isArray(msg.tool_calls)) {
59
+ msg.tool_calls = msg.tool_calls.map(({ cache_control: _, ...toolCall }) => toolCall);
60
+ }
61
+ return msg;
62
+ });
63
+ }
64
+ return rest;
65
+ }
66
+ export function normalizeCdfPayload(value) {
67
+ if (Array.isArray(value)) {
68
+ return value.map(normalizeCdfPayload);
69
+ }
70
+ if (value && typeof value === "object") {
71
+ return Object.fromEntries(Object.entries(value).map(([key, nested]) => [
72
+ CDF_TO_OPENAI_KEY_MAP[key] ?? key,
73
+ normalizeCdfPayload(nested),
74
+ ]));
75
+ }
76
+ return value;
77
+ }
78
+ function copyResponseHeaders(response, contentType) {
79
+ const headers = new Headers(response.headers);
80
+ headers.delete("content-length");
81
+ if (contentType)
82
+ headers.set("content-type", contentType);
83
+ return headers;
84
+ }
85
+ function transformJsonlToEventStream(stream) {
86
+ const encoder = new TextEncoder();
87
+ let buffer = "";
88
+ return stream
89
+ .pipeThrough(new TextDecoderStream())
90
+ .pipeThrough(new TransformStream({
91
+ transform(chunk, controller) {
92
+ buffer += chunk;
93
+ const lines = buffer.split(/\r?\n/);
94
+ buffer = lines.pop() ?? "";
95
+ for (const line of lines) {
96
+ const trimmed = line.trim();
97
+ if (!trimmed)
98
+ continue;
99
+ const normalized = normalizeCdfPayload(JSON.parse(trimmed));
100
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(normalized)}\n\n`));
101
+ }
102
+ },
103
+ flush(controller) {
104
+ const trimmed = buffer.trim();
105
+ if (!trimmed)
106
+ return;
107
+ const normalized = normalizeCdfPayload(JSON.parse(trimmed));
108
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(normalized)}\n\n`));
109
+ },
110
+ }));
111
+ }
112
+ async function normalizeCdfResponse(response) {
113
+ if (!response.ok)
114
+ return response;
115
+ const contentType = response.headers.get("content-type") ?? "";
116
+ if (contentType.includes("application/jsonl") && response.body) {
117
+ return new Response(transformJsonlToEventStream(response.body), {
118
+ status: response.status,
119
+ statusText: response.statusText,
120
+ headers: copyResponseHeaders(response, "text/event-stream"),
121
+ });
122
+ }
123
+ if (!contentType.includes("application/json")) {
124
+ return response;
125
+ }
126
+ const text = await response.text();
127
+ const normalized = normalizeCdfPayload(JSON.parse(text));
128
+ return new Response(JSON.stringify(normalized), {
129
+ status: response.status,
130
+ statusText: response.statusText,
131
+ headers: copyResponseHeaders(response, "application/json"),
132
+ });
133
+ }
134
+ function getBearerToken(headers) {
135
+ if (!headers)
136
+ return undefined;
137
+ const value = new Headers(headers).get("Authorization");
138
+ if (!value)
139
+ return undefined;
140
+ const match = /^Bearer\s+(.+)$/i.exec(value);
141
+ return match?.[1];
142
+ }
143
+ function createMissingTokenResponse() {
144
+ return new Response(JSON.stringify({
145
+ error: {
146
+ message: CDF_MISSING_TOKEN_MESSAGE,
147
+ code: "missing_token",
148
+ },
149
+ }), {
150
+ status: 401,
151
+ headers: {
152
+ "content-type": "application/json",
153
+ },
154
+ });
155
+ }
156
+ export function createCdfProvider(options) {
157
+ const { cluster, project, baseURL, name = "cdf", token, headers: extraHeaders } = options;
158
+ const staticToken = token ?? getBearerToken(extraHeaders);
159
+ const resolvedBaseURL = baseURL ?? `https://${cluster}.cognitedata.com/api/v1/projects/${project}/ai`;
160
+ return createOpenAICompatible({
161
+ name,
162
+ apiKey: "cdf",
163
+ baseURL: resolvedBaseURL,
164
+ fetch: (async (input, init) => {
165
+ if (!staticToken) {
166
+ return createMissingTokenResponse();
167
+ }
168
+ const headers = new Headers(init?.headers);
169
+ if (extraHeaders) {
170
+ new Headers(extraHeaders).forEach((value, key) => {
171
+ if (key.toLowerCase() !== "authorization") {
172
+ headers.set(key, value);
173
+ }
174
+ });
175
+ }
176
+ headers.set("Authorization", `Bearer ${staticToken}`);
177
+ let body = init?.body;
178
+ if (typeof body === "string") {
179
+ body = JSON.stringify(sanitizeRequestBody(JSON.parse(body)));
180
+ }
181
+ const response = await globalThis.fetch(input, { ...init, headers, body });
182
+ return normalizeCdfResponse(response);
183
+ }),
184
+ });
185
+ }
186
+ export default createCdfProvider;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Based on cognitedata/atlascode (PR #1).
3
+ * @see https://github.com/cognitedata/atlascode/pull/1
4
+ */
5
+ export interface CdfProviderOptions {
6
+ /** CDF cluster (e.g. "api", "westeurope-1"). Required unless baseURL is set. */
7
+ cluster: string;
8
+ /** CDF project name. Required unless baseURL is set. */
9
+ project: string;
10
+ /** OAuth access token for CDF API calls. */
11
+ token?: string;
12
+ /**
13
+ * Extra headers (e.g. cdf-version). If Authorization is set, bearer is used as token.
14
+ */
15
+ headers?: HeadersInit;
16
+ /**
17
+ * Chat API base URL.
18
+ * @default https://${cluster}.cognitedata.com/api/v1/projects/${project}/ai
19
+ */
20
+ baseURL?: string;
21
+ /** Provider display name in OpenCode. @default "cdf" */
22
+ name?: string;
23
+ defaultModel?: string;
24
+ }
25
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,gFAAgF;IAChF,OAAO,EAAE,MAAM,CAAA;IAEf,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAA;IAEf,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAA;IAEd;;OAEG;IACH,OAAO,CAAC,EAAE,WAAW,CAAA;IAErB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB,wDAAwD;IACxD,IAAI,CAAC,EAAE,MAAM,CAAA;IAEb,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@andershaf/testopencode",
3
+ "version": "0.1.0",
4
+ "description": "OpenCode provider for CDF project AI chat (atlascode-style request sanitize + response normalize)",
5
+ "type": "module",
6
+ "main": "./dist/provider.js",
7
+ "types": "./dist/provider.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/provider.d.ts",
11
+ "import": "./dist/provider.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "keywords": [
22
+ "opencode",
23
+ "cognite",
24
+ "cdf",
25
+ "ai-sdk"
26
+ ],
27
+ "license": "Apache-2.0",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/cognitedata/atlascode.git"
31
+ },
32
+ "dependencies": {
33
+ "@ai-sdk/openai-compatible": "^1.0.34",
34
+ "@ai-sdk/provider-utils": "^3.0.22"
35
+ },
36
+ "peerDependencies": {
37
+ "@ai-sdk/provider": "^2.0.0",
38
+ "zod": "^3.25.76 || ^4.1.8"
39
+ },
40
+ "devDependencies": {
41
+ "typescript": "^5.8.0"
42
+ }
43
+ }